1
0
Fork 0
mirror of https://github.com/vanitasvitae/Smack.git synced 2024-11-26 14:02:06 +01:00

Compare commits

...

14 commits

Author SHA1 Message Date
Florian Schmaus
bc503c7475
Merge pull request #401 from adiaholic/xep-0156
Add support for HTTP lookup method through XEP-0156
2020-06-25 18:22:02 +02:00
Aditya Borikar
dcb66eef59 Add support for HTTP lookup method through xep-0156 2020-06-25 21:10:43 +05:30
Florian Schmaus
a4e4fbeee1 Smack 4.4.0-alpha6-SNAPSHOT 2020-06-19 22:15:16 +02:00
Florian Schmaus
b8a575f4b0 Smack 4.4.0-alpha5 2020-06-19 21:47:11 +02:00
Florian Schmaus
8752605c74 Bump MiniDNS version to 0.4.0-alpha6 2020-06-19 21:15:26 +02:00
Florian Schmaus
63f133e68b Set 'running' to true prior starting the reader/writer threads
To ensure the thread starting the reader/writer threads sees them
running and eventually waits until the 'running' boolean is reset to
'false' upon connection termination.
2020-06-17 21:56:45 +02:00
Florian Schmaus
c384849532 Set 'running' to false before calling notifyConnectionError()
Since the current variant of notifyConnectionError() does not execute
most of its work in a new thread, especially since instantShutdown()
is called in the invoking thread, we have to mark the connections
reader or writer threads as no longer running prior them invoking
notifyConnectionError(). Otherwise they will end up waiting for
themselves to terminate.
2020-06-17 21:56:45 +02:00
Florian Schmaus
884ee327e1 Remove writerException in XMPPTCPConnection
This delay mechanism is no longer necessary.
2020-06-17 21:56:45 +02:00
Florian Schmaus
a05b464032 Do not use waitForConditionOrConnectionException() in XMPPTCPConnection
Since at this point, there will potentially be an active connection
exception, which would cause the call to return immediately.
2020-06-17 21:56:45 +02:00
Florian Schmaus
ddc39030d7 Rename waitForCondition() to waitForConditionOrConnectionException()
To make it clear that this will either return if the condition is
true *or* if a connection exception happened.

Also introduce waitFor(), which is deliberately not named
waitForCondition() because it carries a different semantic.
2020-06-17 21:56:45 +02:00
Florian Schmaus
f9292a23fb [tcp] Add and improve log of reader/writer thread termination 2020-06-17 21:56:45 +02:00
Florian Schmaus
018cba7f4f [tcp] Log XmlStringBuilder NPEs and the causing class 2020-06-16 21:44:04 +02:00
Florian Schmaus
5bd247c3e6
Merge pull request #400 from vanitasvitae/java8fullnoomemo
Remove smack-java8-full dependency on smack-omemo-signal
2020-06-16 14:21:29 +02:00
8a0371bcea
Remove smack-java8-full dependency on smack-omemo-signal 2020-06-16 13:58:27 +02:00
10 changed files with 294 additions and 25 deletions

View file

@ -123,7 +123,7 @@ allprojects {
// - https://issues.apache.org/jira/browse/MNG-6232
// - https://issues.igniterealtime.org/browse/SMACK-858
jxmppVersion = '0.7.0-alpha6'
miniDnsVersion = '0.4.0-alpha5'
miniDnsVersion = '0.4.0-alpha6'
smackMinAndroidSdk = 19
junitVersion = '5.6.2'
commonsIoVersion = '2.6'

View file

@ -14,6 +14,7 @@ Currently supported XEPs of Smack (all sub-projects)
| Name | XEP | Version | Description |
|---------------------------------------------|--------------------------------------------------------|-----------|----------------------------------------------------------------------------------------------------------|
| Discovering Alternative XMPP Connection Methods | [XEP-0156](https://xmpp.org/extensions/xep-0156.html) | 1.3.0 | Defines ways to discover alternative connection methods. |
| Nonzas | [XEP-0360](https://xmpp.org/extensions/xep-0360.html) | n/a | Defines the term "Nonza", describing every top level stream element that is not a Stanza. |
Currently supported XEPs of smack-tcp

View file

@ -688,7 +688,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
}
/**
* We use an extra object for {@link #notifyWaitingThreads()} and {@link #waitForCondition(Supplier)}, because all state
* We use an extra object for {@link #notifyWaitingThreads()} and {@link #waitForConditionOrConnectionException(Supplier)}, because all state
* changing methods of the connection are synchronized using the connection instance as monitor. If we now would
* also use the connection instance for the internal process to wait for a condition, the {@link Object#wait()}
* would leave the monitor when it waites, which would allow for another potential call to a state changing function
@ -702,10 +702,10 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
}
}
protected final boolean waitForCondition(Supplier<Boolean> condition) throws InterruptedException {
protected final boolean waitFor(Supplier<Boolean> condition) throws InterruptedException {
final long deadline = System.currentTimeMillis() + getReplyTimeout();
synchronized (internalMonitor) {
while (!condition.get().booleanValue() && !hasCurrentConnectionException()) {
while (!condition.get().booleanValue()) {
final long now = System.currentTimeMillis();
if (now >= deadline) {
return false;
@ -716,15 +716,19 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
return true;
}
protected final void waitForCondition(Supplier<Boolean> condition, String waitFor) throws InterruptedException, NoResponseException {
boolean success = waitForCondition(condition);
protected final boolean waitForConditionOrConnectionException(Supplier<Boolean> condition) throws InterruptedException {
return waitFor(() -> condition.get().booleanValue() || hasCurrentConnectionException());
}
protected final void waitForConditionOrConnectionException(Supplier<Boolean> condition, String waitFor) throws InterruptedException, NoResponseException {
boolean success = waitForConditionOrConnectionException(condition);
if (!success) {
throw NoResponseException.newWith(this, waitFor);
}
}
protected final void waitForConditionOrThrowConnectionException(Supplier<Boolean> condition, String waitFor) throws InterruptedException, SmackException, XMPPException {
waitForCondition(condition, waitFor);
waitForConditionOrConnectionException(condition, waitFor);
if (hasCurrentConnectionException()) {
throwCurrentConnectionException();
}

View file

@ -0,0 +1,176 @@
/**
*
* Copyright 2020 Aditya Borikar.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.altconnections;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;
import org.jxmpp.jid.DomainBareJid;
/**
* The HTTP lookup method uses web host metadata to list the URIs of alternative connection methods.
*
* <p>In order to obtain host-meta XRD element from the host in the form of an <i>InputStream</i>,
* use {@link #getXrdStream(DomainBareJid)} method. To obtain endpoints for Bosh or Websocket
* connection endpoints from host, use {@link LinkRelation#BOSH} and {@link LinkRelation#WEBSOCKET}
* respectively with the {@link #lookup(DomainBareJid, LinkRelation)} method. In case one is looking
* for endpoints described by other than BOSH or Websocket LinkRelation, use the more flexible
* {@link #lookup(DomainBareJid, String)} method.</p>
* Example:<br>
* <pre>
* {@code
* DomainBareJid xmppServerAddress = JidCreate.domainBareFrom("example.org");
* List<URI> endpoints = HttpLookupMethod.lookup(xmppServiceAddress, LinkRelation.WEBSOCKET);
* }
* </pre>
* @see <a href="https://xmpp.org/extensions/xep-0156.html#http">
* HTTP Lookup Method from XEP-0156.
* </a>
*/
public final class HttpLookupMethod {
private static final String XRD_NAMESPACE = "http://docs.oasis-open.org/ns/xri/xrd-1.0";
/**
* Specifies a link relation for the selected type of connection.
*/
public enum LinkRelation {
/**
* Selects link relation attribute as "urn:xmpp:alt-connections:xbosh".
*/
BOSH("urn:xmpp:alt-connections:xbosh"),
/**
* Selects link relation attribute as "urn:xmpp:alt-connections:websocket".
*/
WEBSOCKET("urn:xmpp:alt-connections:websocket");
private final String attribute;
LinkRelation(String relAttribute) {
this.attribute = relAttribute;
}
}
/**
* Get remote endpoints for the given LinkRelation from host.
*
* @param xmppServiceAddress address of host
* @param relation LinkRelation as a string specifying type of connection
* @return list of endpoints
* @throws IOException exception due to input/output operations
* @throws XmlPullParserException exception encountered during XML parsing
* @throws URISyntaxException exception to indicate that a string could not be parsed as a URI reference
*/
public static List<URI> lookup(DomainBareJid xmppServiceAddress, String relation) throws IOException, XmlPullParserException, URISyntaxException {
try (InputStream inputStream = getXrdStream(xmppServiceAddress)) {
XmlPullParser xmlPullParser = PacketParserUtils.getParserFor(inputStream);
List<URI> endpoints = parseXrdLinkReferencesFor(xmlPullParser, relation);
return endpoints;
}
}
/**
* Get remote endpoints for the given LinkRelation from host.
*
* @param xmppServiceAddress address of host
* @param relation {@link LinkRelation} specifying type of connection
* @return list of endpoints
* @throws IOException exception due to input/output operations
* @throws XmlPullParserException exception encountered during XML parsing
* @throws URISyntaxException exception to indicate that a string could not be parsed as a URI reference
*/
public static List<URI> lookup(DomainBareJid xmppServiceAddress, LinkRelation relation) throws IOException, XmlPullParserException, URISyntaxException {
return lookup(xmppServiceAddress, relation.attribute);
}
/**
* Constructs a HTTP connection with the host specified by the DomainBareJid
* and retrieves XRD element in the form of an InputStream. The method will
* throw a {@link FileNotFoundException} if host-meta isn't published.
*
* @param xmppServiceAddress address of host
* @return InputStream containing XRD element
* @throws IOException exception due to input/output operations
*/
public static InputStream getXrdStream(DomainBareJid xmppServiceAddress) throws IOException {
final String metadataUrl = "https://" + xmppServiceAddress + "/.well-known/host-meta";
final URL putUrl = new URL(metadataUrl);
final URLConnection urlConnection = putUrl.openConnection();
return urlConnection.getInputStream();
}
/**
* Get remote endpoints for the provided LinkRelation from provided XmlPullParser.
*
* @param parser XmlPullParser that contains LinkRelations
* @param relation type of endpoints specified by the given LinkRelation
* @return list of endpoints
* @throws IOException exception due to input/output operations
* @throws XmlPullParserException exception encountered during XML parsing
* @throws URISyntaxException exception to indicate that a string could not be parsed as a URI reference
*/
public static List<URI> parseXrdLinkReferencesFor(XmlPullParser parser, String relation) throws IOException, XmlPullParserException, URISyntaxException {
List<URI> uriList = new ArrayList<>();
int initialDepth = parser.getDepth();
loop: while (true) {
XmlPullParser.TagEvent tag = parser.nextTag();
switch (tag) {
case START_ELEMENT:
String name = parser.getName();
String namespace = parser.getNamespace();
String rel = parser.getAttributeValue("rel");
if (!namespace.equals(XRD_NAMESPACE) || !name.equals("Link") || !rel.equals(relation)) {
continue loop;
}
String endpointUri = parser.getAttributeValue("href");
URI uri = new URI(endpointUri);
uriList.add(uri);
break;
case END_ELEMENT:
if (parser.getDepth() == initialDepth) {
break loop;
}
break;
}
}
return uriList;
}
/**
* Get remote endpoints for the provided LinkRelation from provided XmlPullParser.
*
* @param parser XmlPullParser that contains LinkRelations
* @param relation type of endpoints specified by the given LinkRelation
* @return list of endpoints
* @throws IOException exception due to input/output operations
* @throws XmlPullParserException exception encountered during XML parsing
* @throws URISyntaxException exception to indicate that a string could not be parsed as a URI reference
*/
public static List<URI> parseXrdLinkReferencesFor(XmlPullParser parser, LinkRelation relation) throws IOException, XmlPullParserException, URISyntaxException {
return parseXrdLinkReferencesFor(parser, relation.attribute);
}
}

View file

@ -0,0 +1,22 @@
/**
*
* Copyright 2020 Aditya Borikar.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Smack's API for <a href="https://xmpp.org/extensions/xep-0156.html"> XEP-0156: Discovering Alternative XMPP Connection Methods</a>.
* <br>
* XEP is partially supported as HTTP lookup is supported but DNS lookup isn't.
*/
package org.jivesoftware.smack.altconnections;

View file

@ -0,0 +1,57 @@
/**
*
* Copyright 2020 Aditya Borikar.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.altconnections;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import org.jivesoftware.smack.altconnections.HttpLookupMethod.LinkRelation;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.xml.XmlPullParserException;
import org.junit.jupiter.api.Test;
import org.jxmpp.stringprep.XmppStringprepException;
public class HttpLookupMethodTest {
private static final String XRD_XML = "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>" +
"<Link rel='urn:xmpp:alt-connections:xbosh' href='https://igniterealtime.org:443/http-bind/'/>" +
"<Link rel='urn:xmpp:alt-connections:websocket' href='wss://xmpp.igniterealtime.org:7483/ws/'/>" +
"<Link rel='urn:xmpp:alt-connections:websocket' href='ws://xmpp.igniterealtime.org:7070/ws/'/>" +
"</XRD>";
@Test
public void parseXrdLinkReferencesForWebsockets() throws XmppStringprepException, IOException, XmlPullParserException, URISyntaxException {
List<URI> endpoints = new ArrayList<>();
endpoints.add(new URI("wss://xmpp.igniterealtime.org:7483/ws/"));
endpoints.add(new URI("ws://xmpp.igniterealtime.org:7070/ws/"));
List<URI> expectedEndpoints = HttpLookupMethod.parseXrdLinkReferencesFor(PacketParserUtils.getParserFor(XRD_XML), LinkRelation.WEBSOCKET);
assertEquals(expectedEndpoints, endpoints);
}
@Test
public void parseXrdLinkReferencesForBosh() throws URISyntaxException, IOException, XmlPullParserException {
List<URI> endpoints = new ArrayList<>();
endpoints.add(new URI("https://igniterealtime.org:443/http-bind/"));
List<URI> expectedEndpoints = HttpLookupMethod.parseXrdLinkReferencesFor(PacketParserUtils.getParserFor(XRD_XML), LinkRelation.BOSH);
assertEquals(expectedEndpoints, endpoints);
}
}

View file

@ -9,7 +9,6 @@ dependencies {
api project(':smack-java7')
api project(':smack-legacy')
api project(':smack-omemo')
api project(':smack-omemo-signal')
api project(':smack-openpgp')
api project(':smack-resolver-minidns')
api project(':smack-resolver-minidns-dox')

View file

@ -17,6 +17,7 @@ dependencies {
// smack-java*-full and since we may want to use parts of sinttest
// in the REPL, we simply depend sinttest.
api project(':smack-integration-test')
api project(':smack-omemo-signal')
compile "org.scala-lang:scala-library:$scalaVersion"
compile "com.lihaoyi:ammonite_$scalaVersion:1.3.2"

View file

@ -394,7 +394,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
if (isSmResumptionPossible()) {
smResumedSyncPoint = SyncPointState.request_sent;
sendNonza(new Resume(clientHandledStanzasCount, smSessionId));
waitForCondition(() -> smResumedSyncPoint == SyncPointState.successful || smResumptionFailed != null, "resume previous stream");
waitForConditionOrConnectionException(() -> smResumedSyncPoint == SyncPointState.successful || smResumptionFailed != null, "resume previous stream");
if (smResumedSyncPoint == SyncPointState.successful) {
// We successfully resumed the stream, be done here
afterSuccessfulLogin(true);
@ -500,27 +500,30 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
if (!packetWriter.done()) {
// First shutdown the writer, this will result in a closing stream element getting send to
// the server
LOGGER.finer("PacketWriter shutdown()");
LOGGER.finer(packetWriter.threadName + " shutdown()");
packetWriter.shutdown(instant);
LOGGER.finer("PacketWriter has been shut down");
LOGGER.finer(packetWriter.threadName + " shutdown() returned");
if (!instant) {
waitForClosingStreamTagFromServer();
}
}
LOGGER.finer("PacketReader shutdown()");
LOGGER.finer(packetReader.threadName + " shutdown()");
packetReader.shutdown();
LOGGER.finer("PacketReader has been shut down");
LOGGER.finer(packetReader.threadName + " shutdown() returned");
CloseableUtil.maybeClose(socket, LOGGER);
setWasAuthenticated();
try {
boolean readerAndWriterThreadsTermianted = waitForCondition(() -> !packetWriter.running && !packetReader.running);
boolean readerAndWriterThreadsTermianted = waitFor(() -> !packetWriter.running && !packetReader.running);
if (!readerAndWriterThreadsTermianted) {
LOGGER.severe("Reader and writer threads did not terminate");
LOGGER.severe("Reader and/or writer threads did not terminate timely. Writer running: "
+ packetWriter.running + ", Reader running: " + packetReader.running);
} else {
LOGGER.fine("Reader and writer threads terminated");
}
} catch (InterruptedException e) {
LOGGER.log(Level.FINE, "Interrupted while waiting for reader and writer threads to terminate", e);
@ -909,11 +912,11 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
void init() {
done = false;
running = true;
Async.go(new Runnable() {
@Override
public void run() {
LOGGER.finer(threadName + " start");
running = true;
try {
parsePackets();
} finally {
@ -1126,6 +1129,9 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
// The exception can be ignored if the the connection is 'done'
// or if the it was caused because the socket got closed.
if (!done) {
// Set running to false since this thread will exit here and notifyConnectionError() will wait until
// the reader and writer thread's 'running' value is false.
running = false;
// Close the connection and notify connection listeners of the
// error.
notifyConnectionError(e);
@ -1178,11 +1184,11 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
}
queue.start();
running = true;
Async.go(new Runnable() {
@Override
public void run() {
LOGGER.finer(threadName + " start");
running = true;
try {
writePackets();
} finally {
@ -1269,7 +1275,6 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
}
private void writePackets() {
Exception writerException = null;
try {
// Write out packets from the queue.
while (!done()) {
@ -1318,7 +1323,12 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
CharSequence elementXml = element.toXML(outgoingStreamXmlEnvironment);
if (elementXml instanceof XmlStringBuilder) {
((XmlStringBuilder) elementXml).write(writer, outgoingStreamXmlEnvironment);
try {
((XmlStringBuilder) elementXml).write(writer, outgoingStreamXmlEnvironment);
} catch (NullPointerException npe) {
LOGGER.log(Level.FINE, "NPE in XmlStringBuilder of " + element.getClass() + ": " + element, npe);
throw npe;
}
}
else {
writer.write(elementXml.toString());
@ -1374,15 +1384,14 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
// The exception can be ignored if the the connection is 'done'
// or if the it was caused because the socket got closed
if (!(done() || queue.isShutdown())) {
writerException = e;
// Set running to false since this thread will exit here and notifyConnectionError() will wait until
// the reader and writer thread's 'running' value is false.
running = false;
notifyConnectionError(e);
} else {
LOGGER.log(Level.FINE, "Ignoring Exception in writePackets()", e);
}
}
// Delay notifyConnectionError after shutdownDone has been reported in the finally block.
if (writerException != null) {
notifyConnectionError(writerException);
}
}
private void drainWriterQueueToUnacknowledgedStanzas() {

View file

@ -1 +1 @@
4.4.0-alpha5-SNAPSHOT
4.4.0-alpha6-SNAPSHOT