From 8a0371bceaf5799d3919bec963b05ec6a6726d9f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 16 Jun 2020 13:58:27 +0200 Subject: [PATCH 01/12] Remove smack-java8-full dependency on smack-omemo-signal --- smack-java8-full/build.gradle | 1 - smack-repl/build.gradle | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/smack-java8-full/build.gradle b/smack-java8-full/build.gradle index 5ba426c3b..8586a0005 100644 --- a/smack-java8-full/build.gradle +++ b/smack-java8-full/build.gradle @@ -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') diff --git a/smack-repl/build.gradle b/smack-repl/build.gradle index 2ba95bcc4..c1ed72fdc 100644 --- a/smack-repl/build.gradle +++ b/smack-repl/build.gradle @@ -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" From 018cba7f4f27adbe10f1c6cc7063edb62615f20b Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Mon, 15 Jun 2020 17:52:28 +0200 Subject: [PATCH 02/12] [tcp] Log XmlStringBuilder NPEs and the causing class --- .../java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java index ded929681..c5328798a 100644 --- a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java +++ b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java @@ -1318,7 +1318,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()); From f9292a23fbdec8612e9e2554a2068cf6b1e86dc5 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Tue, 16 Jun 2020 22:59:26 +0200 Subject: [PATCH 03/12] [tcp] Add and improve log of reader/writer thread termination --- .../jivesoftware/smack/tcp/XMPPTCPConnection.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java index c5328798a..99c6d2953 100644 --- a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java +++ b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java @@ -500,18 +500,18 @@ 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); @@ -520,7 +520,10 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { try { boolean readerAndWriterThreadsTermianted = waitForCondition(() -> !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); From ddc39030d77d8319be613c60fbf88492276f4030 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Wed, 17 Jun 2020 20:27:02 +0200 Subject: [PATCH 04/12] 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. --- .../smack/AbstractXMPPConnection.java | 16 ++++++++++------ .../smack/tcp/XMPPTCPConnection.java | 4 ++-- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java b/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java index 6e90e35b2..bbb104fe8 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java @@ -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 condition) throws InterruptedException { + protected final boolean waitFor(Supplier 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 condition, String waitFor) throws InterruptedException, NoResponseException { - boolean success = waitForCondition(condition); + protected final boolean waitForConditionOrConnectionException(Supplier condition) throws InterruptedException { + return waitFor(() -> condition.get().booleanValue() || hasCurrentConnectionException()); + } + + protected final void waitForConditionOrConnectionException(Supplier condition, String waitFor) throws InterruptedException, NoResponseException { + boolean success = waitForConditionOrConnectionException(condition); if (!success) { throw NoResponseException.newWith(this, waitFor); } } protected final void waitForConditionOrThrowConnectionException(Supplier condition, String waitFor) throws InterruptedException, SmackException, XMPPException { - waitForCondition(condition, waitFor); + waitForConditionOrConnectionException(condition, waitFor); if (hasCurrentConnectionException()) { throwCurrentConnectionException(); } diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java index 99c6d2953..91bedc8e4 100644 --- a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java +++ b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java @@ -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); @@ -518,7 +518,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { setWasAuthenticated(); try { - boolean readerAndWriterThreadsTermianted = waitForCondition(() -> !packetWriter.running && !packetReader.running); + boolean readerAndWriterThreadsTermianted = waitForConditionOrConnectionException(() -> !packetWriter.running && !packetReader.running); if (!readerAndWriterThreadsTermianted) { LOGGER.severe("Reader and/or writer threads did not terminate timely. Writer running: " + packetWriter.running + ", Reader running: " + packetReader.running); From a05b46403225059efbc4d2f17a806058a8509e6d Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Wed, 17 Jun 2020 20:29:21 +0200 Subject: [PATCH 05/12] 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. --- .../main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java index 91bedc8e4..669130420 100644 --- a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java +++ b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java @@ -518,7 +518,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { setWasAuthenticated(); try { - boolean readerAndWriterThreadsTermianted = waitForConditionOrConnectionException(() -> !packetWriter.running && !packetReader.running); + boolean readerAndWriterThreadsTermianted = waitFor(() -> !packetWriter.running && !packetReader.running); if (!readerAndWriterThreadsTermianted) { LOGGER.severe("Reader and/or writer threads did not terminate timely. Writer running: " + packetWriter.running + ", Reader running: " + packetReader.running); From 884ee327e1521f410e29b27f318c57f4784b61f0 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Wed, 17 Jun 2020 20:31:28 +0200 Subject: [PATCH 06/12] Remove writerException in XMPPTCPConnection This delay mechanism is no longer necessary. --- .../java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java index 669130420..558bacdf0 100644 --- a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java +++ b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java @@ -1272,7 +1272,6 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { } private void writePackets() { - Exception writerException = null; try { // Write out packets from the queue. while (!done()) { @@ -1382,15 +1381,11 @@ 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; + 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() { From c3848495328bf6ebaf7de1adfe0e5c8685afa1f3 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Wed, 17 Jun 2020 20:34:36 +0200 Subject: [PATCH 07/12] 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. --- .../java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java index 558bacdf0..30785fdbc 100644 --- a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java +++ b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java @@ -1129,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); @@ -1381,6 +1384,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() || queue.isShutdown())) { + // 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); From 63f133e68bab8e861ea2fcc18464c50349c34dc3 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Wed, 17 Jun 2020 21:55:24 +0200 Subject: [PATCH 08/12] 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. --- .../java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java index 30785fdbc..ad86ce420 100644 --- a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java +++ b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java @@ -912,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 { @@ -1184,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 { From 8752605c74d14f7aed52c12d5400797005281c23 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Fri, 19 Jun 2020 21:15:26 +0200 Subject: [PATCH 09/12] Bump MiniDNS version to 0.4.0-alpha6 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 0bdce4c74..2e1270a4c 100644 --- a/build.gradle +++ b/build.gradle @@ -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' From b8a575f4b0a7520ba63a0c5ad7618f52fdcd6088 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Fri, 19 Jun 2020 21:47:11 +0200 Subject: [PATCH 10/12] Smack 4.4.0-alpha5 --- version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version b/version index c4b97782d..cec28b6dc 100644 --- a/version +++ b/version @@ -1 +1 @@ -4.4.0-alpha5-SNAPSHOT +4.4.0-alpha5 From a4e4fbeee1bbcfa92e3835e82f9999cac6b273f5 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Fri, 19 Jun 2020 22:15:16 +0200 Subject: [PATCH 11/12] Smack 4.4.0-alpha6-SNAPSHOT --- version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version b/version index cec28b6dc..c6de5a7d9 100644 --- a/version +++ b/version @@ -1 +1 @@ -4.4.0-alpha5 +4.4.0-alpha6-SNAPSHOT From dcb66eef592bf3959a3aaafae0802e0b35500e2d Mon Sep 17 00:00:00 2001 From: Aditya Borikar Date: Fri, 19 Jun 2020 05:12:03 +0530 Subject: [PATCH 12/12] Add support for HTTP lookup method through xep-0156 --- documentation/extensions/index.md | 1 + .../altconnections/HttpLookupMethod.java | 176 ++++++++++++++++++ .../smack/altconnections/package-info.java | 22 +++ .../altconnections/HttpLookupMethodTest.java | 57 ++++++ 4 files changed, 256 insertions(+) create mode 100644 smack-core/src/main/java/org/jivesoftware/smack/altconnections/HttpLookupMethod.java create mode 100644 smack-core/src/main/java/org/jivesoftware/smack/altconnections/package-info.java create mode 100644 smack-core/src/test/java/org/jivesoftware/smack/altconnections/HttpLookupMethodTest.java diff --git a/documentation/extensions/index.md b/documentation/extensions/index.md index 0223d1554..78a2bbc57 100644 --- a/documentation/extensions/index.md +++ b/documentation/extensions/index.md @@ -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 diff --git a/smack-core/src/main/java/org/jivesoftware/smack/altconnections/HttpLookupMethod.java b/smack-core/src/main/java/org/jivesoftware/smack/altconnections/HttpLookupMethod.java new file mode 100644 index 000000000..75a4cf263 --- /dev/null +++ b/smack-core/src/main/java/org/jivesoftware/smack/altconnections/HttpLookupMethod.java @@ -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. + * + *

In order to obtain host-meta XRD element from the host in the form of an InputStream, + * 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.

+ * Example:
+ *
+ * {@code
+ * DomainBareJid xmppServerAddress = JidCreate.domainBareFrom("example.org");
+ * List endpoints = HttpLookupMethod.lookup(xmppServiceAddress, LinkRelation.WEBSOCKET);
+ * }
+ * 
+ * @see + * HTTP Lookup Method from XEP-0156. + * + */ +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 lookup(DomainBareJid xmppServiceAddress, String relation) throws IOException, XmlPullParserException, URISyntaxException { + try (InputStream inputStream = getXrdStream(xmppServiceAddress)) { + XmlPullParser xmlPullParser = PacketParserUtils.getParserFor(inputStream); + List 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 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 parseXrdLinkReferencesFor(XmlPullParser parser, String relation) throws IOException, XmlPullParserException, URISyntaxException { + List 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 parseXrdLinkReferencesFor(XmlPullParser parser, LinkRelation relation) throws IOException, XmlPullParserException, URISyntaxException { + return parseXrdLinkReferencesFor(parser, relation.attribute); + } +} diff --git a/smack-core/src/main/java/org/jivesoftware/smack/altconnections/package-info.java b/smack-core/src/main/java/org/jivesoftware/smack/altconnections/package-info.java new file mode 100644 index 000000000..5d9955232 --- /dev/null +++ b/smack-core/src/main/java/org/jivesoftware/smack/altconnections/package-info.java @@ -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 XEP-0156: Discovering Alternative XMPP Connection Methods. + *
+ * XEP is partially supported as HTTP lookup is supported but DNS lookup isn't. + */ +package org.jivesoftware.smack.altconnections; diff --git a/smack-core/src/test/java/org/jivesoftware/smack/altconnections/HttpLookupMethodTest.java b/smack-core/src/test/java/org/jivesoftware/smack/altconnections/HttpLookupMethodTest.java new file mode 100644 index 000000000..efa503ac1 --- /dev/null +++ b/smack-core/src/test/java/org/jivesoftware/smack/altconnections/HttpLookupMethodTest.java @@ -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 = "" + + "" + + "" + + "" + + ""; + + @Test + public void parseXrdLinkReferencesForWebsockets() throws XmppStringprepException, IOException, XmlPullParserException, URISyntaxException { + List endpoints = new ArrayList<>(); + endpoints.add(new URI("wss://xmpp.igniterealtime.org:7483/ws/")); + endpoints.add(new URI("ws://xmpp.igniterealtime.org:7070/ws/")); + List expectedEndpoints = HttpLookupMethod.parseXrdLinkReferencesFor(PacketParserUtils.getParserFor(XRD_XML), LinkRelation.WEBSOCKET); + assertEquals(expectedEndpoints, endpoints); + } + + @Test + public void parseXrdLinkReferencesForBosh() throws URISyntaxException, IOException, XmlPullParserException { + List endpoints = new ArrayList<>(); + endpoints.add(new URI("https://igniterealtime.org:443/http-bind/")); + List expectedEndpoints = HttpLookupMethod.parseXrdLinkReferencesFor(PacketParserUtils.getParserFor(XRD_XML), LinkRelation.BOSH); + assertEquals(expectedEndpoints, endpoints); + } +}