From 64fb47c98b590660233049a97423aeebe9d133b6 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sat, 25 Jul 2020 13:55:35 +0200 Subject: [PATCH 01/29] Fix typo in StateDescriptor method: s/Inferiorty/Inferiority/ --- .../main/java/org/jivesoftware/smack/fsm/StateDescriptor.java | 2 +- .../java/org/jivesoftware/smack/sm/StreamManagementModule.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/smack-core/src/main/java/org/jivesoftware/smack/fsm/StateDescriptor.java b/smack-core/src/main/java/org/jivesoftware/smack/fsm/StateDescriptor.java index aec14b18e..dcff96b67 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/fsm/StateDescriptor.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/fsm/StateDescriptor.java @@ -133,7 +133,7 @@ public abstract class StateDescriptor { addAndCheckNonExistent(precedenceOver, subordinate); } - protected void declareInferiortyTo(Class superior) { + protected void declareInferiorityTo(Class superior) { addAndCheckNonExistent(inferiorTo, superior); } diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/sm/StreamManagementModule.java b/smack-tcp/src/main/java/org/jivesoftware/smack/sm/StreamManagementModule.java index 58be31a34..1a6c78a93 100644 --- a/smack-tcp/src/main/java/org/jivesoftware/smack/sm/StreamManagementModule.java +++ b/smack-tcp/src/main/java/org/jivesoftware/smack/sm/StreamManagementModule.java @@ -91,7 +91,7 @@ public class StreamManagementModule extends ModularXmppClientToServerConnectionM addPredeccessor(AuthenticatedButUnboundStateDescriptor.class); addSuccessor(AuthenticatedAndResourceBoundStateDescriptor.class); declarePrecedenceOver(ResourceBindingStateDescriptor.class); - declareInferiortyTo(CompressionStateDescriptor.class); + declareInferiorityTo(CompressionStateDescriptor.class); } @Override From ec80d5287bccbeb5c6e7f45b3226f2a39a3a3952 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sat, 25 Jul 2020 14:27:45 +0200 Subject: [PATCH 02/29] [core] Add String-based StateDescriptor precedence/inferiority declaration methods --- .../smack/fsm/StateDescriptor.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/smack-core/src/main/java/org/jivesoftware/smack/fsm/StateDescriptor.java b/smack-core/src/main/java/org/jivesoftware/smack/fsm/StateDescriptor.java index dcff96b67..8f399480e 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/fsm/StateDescriptor.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/fsm/StateDescriptor.java @@ -22,12 +22,16 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection; import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal; public abstract class StateDescriptor { + private static final Logger LOGGER = Logger.getLogger(StateDescriptor.class.getName()); + public enum Property { multiVisitState, finalState, @@ -133,10 +137,35 @@ public abstract class StateDescriptor { addAndCheckNonExistent(precedenceOver, subordinate); } + protected void declarePrecedenceOver(String subordinate) { + addAndCheckNonExistent(precedenceOver, subordinate); + } + protected void declareInferiorityTo(Class superior) { addAndCheckNonExistent(inferiorTo, superior); } + protected void declareInferiorityTo(String superior) { + addAndCheckNonExistent(inferiorTo, superior); + } + + private static void addAndCheckNonExistent(Set> set, String clazzName) { + Class clazz; + try { + clazz = Class.forName(clazzName); + } catch (ClassNotFoundException e) { + // The state descriptor class is not in classpath, which probably means that the smack module is not loaded + // into the classpath. Hence we can silently ignore that. + LOGGER.log(Level.FINEST, "Ignoring unknown state descriptor '" + clazzName + "'", e); + return; + } + if (!StateDescriptor.class.isAssignableFrom(clazz)) { + throw new IllegalArgumentException(clazz + " is no state descriptor class"); + } + Class stateDescriptorClass = clazz.asSubclass(StateDescriptor.class); + addAndCheckNonExistent(set, stateDescriptorClass); + } + private static void addAndCheckNonExistent(Set set, E e) { boolean newElement = set.add(e); if (!newElement) { From dd631048a3bce3385e9f898e9d42946dd5bcce40 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 28 Jul 2020 19:00:04 +0200 Subject: [PATCH 03/29] NoEndpointDiscoveredException: s/Not/No --- .../src/main/java/org/jivesoftware/smack/SmackException.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smack-core/src/main/java/org/jivesoftware/smack/SmackException.java b/smack-core/src/main/java/org/jivesoftware/smack/SmackException.java index 3d2c0b851..ea34d243b 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/SmackException.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/SmackException.java @@ -364,7 +364,7 @@ public abstract class SmackException extends Exception { if (lookupFailures.isEmpty()) { sb.append("No endpoint lookup finished within the timeout"); } else { - sb.append("Not endpoints could be discovered due the following lookup failures: "); + sb.append("No endpoints could be discovered due the following lookup failures: "); StringUtils.appendTo(lookupFailures, sb); } From 3c54d5ffcd11f5de775bd4ae40e9d6df0c8e63d3 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Thu, 6 Aug 2020 12:04:14 +0200 Subject: [PATCH 04/29] [build] Use ranged version [1.0.0, 1.0.999] for jXMPP and MiniDNS This also means that we need to lookup the resovled versions of those artifacts in order to provide the javadoc link. --- build.gradle | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index 9e4d6651f..47e8f24f6 100644 --- a/build.gradle +++ b/build.gradle @@ -122,8 +122,8 @@ allprojects { // See also: // - https://issues.apache.org/jira/browse/MNG-6232 // - https://issues.igniterealtime.org/browse/SMACK-858 - jxmppVersion = '1.0.0' - miniDnsVersion = '1.0.0' + jxmppVersion = '[1.0.0, 1.0.999]' + miniDnsVersion = '[1.0.0, 1.0.999]' smackMinAndroidSdk = 19 junitVersion = '5.6.2' commonsIoVersion = '2.6' @@ -305,6 +305,13 @@ configure (junit4Projects) { } } +// We need to evaluate the child projects first because +// - javadocAll needs the smack-core child to have already resolved +// the jXMPP/MiniDNS dependencies, so that we can the resovled +// version to link to those project's javadoc. +// - We use the child's project description as description for the +// Maven POM. +evaluationDependsOnChildren() task javadocAll(type: Javadoc) { source javadocAllProjects.collect {project -> project.sourceSets.main.allJava.findAll { @@ -319,13 +326,15 @@ task javadocAll(type: Javadoc) { classpath = files(subprojects.collect {project -> project.sourceSets.main.compileClasspath}) classpath += files(androidBootClasspath) + def staticJxmppVersion = getResolvedVersion('org.jxmpp:jxmpp-core') + def staticMiniDnsVersion = getResolvedVersion('org.minidns:minidns-core') options { linkSource = true use = true links = [ "https://docs.oracle.com/javase/${javaMajor}/docs/api/", - "https://jxmpp.org/releases/$jxmppVersion/javadoc/", - "https://minidns.org/releases/$miniDnsVersion/javadoc/", + "https://jxmpp.org/releases/${staticJxmppVersion}/javadoc/", + "https://minidns.org/releases/${staticMiniDnsVersion}/javadoc/", ] as String[] overview = "$projectDir/resources/javadoc-overview.html" } @@ -408,7 +417,6 @@ description = """\ Smack ${version} ${oneLineDesc}.""" -evaluationDependsOnChildren() subprojects { apply plugin: 'maven-publish' apply plugin: 'signing' @@ -740,3 +748,24 @@ def readVersionFile() { } versionFile.text.trim() } + +def getResolvedVersion(queriedProject = 'smack-core', component) { + def configuration = project(queriedProject) + .configurations + .compileClasspath + + def artifact = configuration + .resolvedConfiguration + .resolvedArtifacts + .findAll { + // 'it' is of type ResolvedArtifact, 'id' of + // Component*Artifcat*Identifier, and we check the + // ComponentIdentifier. + it.id.getComponentIdentifier() instanceof org.gradle.api.artifacts.component.ModuleComponentIdentifier + } + .find { + it.id.getComponentIdentifier().toString().startsWith(component + ':') + } + + artifact.getModuleVersion().getId().getVersion() +} From 1a2a613112712580bedbf4374fba84dfe0f44517 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Thu, 6 Aug 2020 10:28:07 +0200 Subject: [PATCH 05/29] Set 'connected' to 'true' as early as possible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We previously only set 'connected' after connectInternal() returned. This could lead to notifyConnectionError() ignoring stream error exceptions, e.g. when establishing TLS which happens also in connectInternal(), because 'connected' was still 'false'. 2020-08-06 13:08:06.265 19830-20423/org.atalk.android D/SMACK: SENT (0): 2020-08-06 13:08:06.333 19830-20424/org.atalk.android D/SMACK: RECV (0): ?xml version='1.0'?> Too many (20) failed authentications from this IP address (::ffff:42.60.7.13). The address will be unblocked at 05:15:34 06.08.2020 UTC 2020-08-06 13:08:06.346 19830-20424/org.atalk.android I/aTalk: [241896] org.jivesoftware.smack.AbstractXMPPConnection.notifyConnectionError() Connection was already disconnected when attempting to handle org.jivesoftware.smack.XMPPException$StreamErrorException: policy-violation You can read more about the meaning of this stream error at http://xmpp.org/rfcs/rfc6120.html#streams-error-conditions Too many (20) failed authentications from this IP address (::ffff:42.60.7.13). The address will be unblocked at 05:15:34 06.08.2020 UTC org.jivesoftware.smack.XMPPException$StreamErrorException: policy-violation You can read more about the meaning of this stream error at http://xmpp.org/rfcs/rfc6120.html#streams-error-conditions Too many (20) failed authentications from this IP address (::ffff:42.60.7.13). The address will be unblocked at 05:15:34 06.08.2020 UTC at org.jivesoftware.smack.tcp.XMPPTCPConnection$PacketReader.parsePackets(XMPPTCPConnection.java:966) at org.jivesoftware.smack.tcp.XMPPTCPConnection$PacketReader.access$700(XMPPTCPConnection.java:898) at org.jivesoftware.smack.tcp.XMPPTCPConnection$PacketReader$1.run(XMPPTCPConnection.java:921) at java.lang.Thread.run(Thread.java:919) Which eventually leads to a NoResponseException org.jivesoftware.smack.SmackException$NoResponseException: No response received within reply timeout. Timeout was 30000ms (~30s). While waiting for establishing TLS [XMPPTCPConnection[not-authenticated] (4)] We now set 'connected' to 'true' as soon as the transport (e.g. TCP, BOSH, …) is connected. While this is in other ways also sensible, it also allows notifyConnectionError() to handle exceptions in the early connection stage. Thanks to Eng Chong Meng for reporting this. --- .../org/jivesoftware/smack/AbstractXMPPConnection.java | 8 ++++++-- .../smack/c2s/ModularXmppClientToServerConnection.java | 1 + .../ModularXmppClientToServerConnectionInternal.java | 6 ++++++ .../org/jivesoftware/smack/tcp/XMPPTCPConnection.java | 2 ++ 4 files changed, 15 insertions(+), 2 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 bd3d47554..530bf7b11 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java @@ -522,6 +522,9 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { closingStreamReceived = false; streamId = null; + // The connection should not be connected nor marked as such prior calling connectInternal(). + assert !connected; + try { // Perform the actual connection to the XMPP service connectInternal(); @@ -537,8 +540,9 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { throw e; } - // Make note of the fact that we're now connected. - connected = true; + // If connectInternal() did not throw, then this connection must now be marked as connected. + assert connected; + callConnectionConnectedListener(); return this; diff --git a/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java b/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java index f62caf8ee..f12180ce7 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java @@ -217,6 +217,7 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne @Override public void setTransport(XmppClientToServerTransport xmppTransport) { ModularXmppClientToServerConnection.this.activeTransport = xmppTransport; + ModularXmppClientToServerConnection.this.connected = true; } }; diff --git a/smack-core/src/main/java/org/jivesoftware/smack/c2s/internal/ModularXmppClientToServerConnectionInternal.java b/smack-core/src/main/java/org/jivesoftware/smack/c2s/internal/ModularXmppClientToServerConnectionInternal.java index 81a485771..db41feafb 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/c2s/internal/ModularXmppClientToServerConnectionInternal.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/c2s/internal/ModularXmppClientToServerConnectionInternal.java @@ -116,5 +116,11 @@ public abstract class ModularXmppClientToServerConnectionInternal { public abstract void setCompressionEnabled(boolean compressionEnabled); + /** + * Set the active transport (TCP, BOSH, WebSocket, …) to be used for the XMPP connection. Also marks the connection + * as connected. + * + * @param xmppTransport the active transport. + */ public abstract void setTransport(XmppClientToServerTransport xmppTransport); } 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 2dbb44a04..4e5ff2974 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 @@ -836,6 +836,8 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { // there is an error establishing the connection connectUsingConfiguration(); + connected = true; + // We connected successfully to the servers TCP port initConnection(); From ac788592a6ab7f5198ac4faa9c5074786b8cf317 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Thu, 6 Aug 2020 18:17:04 +0200 Subject: [PATCH 06/29] =?UTF-8?q?waitForCondition()=20=E2=86=92=20waitForC?= =?UTF-8?q?onditionOrThrowConnectionException()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The method was already renamed, but not in ModularXmppClientToServerConnectionInternal. --- .../smack/c2s/ModularXmppClientToServerConnection.java | 2 +- .../internal/ModularXmppClientToServerConnectionInternal.java | 2 +- .../java/org/jivesoftware/smack/tcp/XmppTcpTransportModule.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java b/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java index f62caf8ee..b099d49d3 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java @@ -199,7 +199,7 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne } @Override - public void waitForCondition(Supplier condition, String waitFor) + public void waitForConditionOrThrowConnectionException(Supplier condition, String waitFor) throws InterruptedException, SmackException, XMPPException { ModularXmppClientToServerConnection.this.waitForConditionOrThrowConnectionException(condition, waitFor); } diff --git a/smack-core/src/main/java/org/jivesoftware/smack/c2s/internal/ModularXmppClientToServerConnectionInternal.java b/smack-core/src/main/java/org/jivesoftware/smack/c2s/internal/ModularXmppClientToServerConnectionInternal.java index 81a485771..79c0d3d8c 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/c2s/internal/ModularXmppClientToServerConnectionInternal.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/c2s/internal/ModularXmppClientToServerConnectionInternal.java @@ -110,7 +110,7 @@ public abstract class ModularXmppClientToServerConnectionInternal { public abstract void asyncGo(Runnable runnable); - public abstract void waitForCondition(Supplier condition, String waitFor) throws InterruptedException, SmackException, XMPPException; + public abstract void waitForConditionOrThrowConnectionException(Supplier condition, String waitFor) throws InterruptedException, SmackException, XMPPException; public abstract void notifyWaitingThreads(); diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XmppTcpTransportModule.java b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XmppTcpTransportModule.java index 60ba9fd41..946eb4a1a 100644 --- a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XmppTcpTransportModule.java +++ b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XmppTcpTransportModule.java @@ -1165,7 +1165,7 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM } private void waitForHandshakeFinished() throws InterruptedException, CertificateException, SSLException, SmackException, XMPPException { - connectionInternal.waitForCondition(() -> isHandshakeFinished(), "TLS handshake to finish"); + connectionInternal.waitForConditionOrThrowConnectionException(() -> isHandshakeFinished(), "TLS handshake to finish"); if (handshakeStatus == TlsHandshakeStatus.failed) { throw handshakeException; From 7796b367cc779a001a42c7313bbdaf5f50c325b6 Mon Sep 17 00:00:00 2001 From: Aditya Borikar Date: Fri, 24 Jul 2020 06:30:53 +0530 Subject: [PATCH 07/29] Position parser at START_ELEMENT before parsing This PR aims to provide parseXrdLinkReferencesFor() method the ability to parse forward to the first START_ELEMENT tag.The HttpLookupMethodTest tests the HttpLookupMethod class by parsing String. This makes use of PacketParserUtils.getParserFor(String), which already does forward winding to reach START_ELEMENT. However when fetching endpoints from a remote host meta data, PacketParserUtils.getParserFor(InputStream) is used which doesn't do winding in any form. And thus, even though HttpLookupMethodTest tests pass, this implementation would crash while parsing remote host-meta. --- .../smack/altconnections/HttpLookupMethod.java | 2 ++ .../jivesoftware/smack/util/PacketParserUtils.java | 9 +-------- .../java/org/jivesoftware/smack/util/ParserUtils.java | 11 +++++++++++ 3 files changed, 14 insertions(+), 8 deletions(-) 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 index 75a4cf263..e30530be7 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/altconnections/HttpLookupMethod.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/altconnections/HttpLookupMethod.java @@ -27,6 +27,7 @@ import java.util.ArrayList; import java.util.List; import org.jivesoftware.smack.util.PacketParserUtils; +import org.jivesoftware.smack.util.ParserUtils; import org.jivesoftware.smack.xml.XmlPullParser; import org.jivesoftware.smack.xml.XmlPullParserException; @@ -132,6 +133,7 @@ public final class HttpLookupMethod { * @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 { + ParserUtils.forwardToStartElement(parser); List uriList = new ArrayList<>(); int initialDepth = parser.getDepth(); diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/PacketParserUtils.java b/smack-core/src/main/java/org/jivesoftware/smack/util/PacketParserUtils.java index 70861812b..67249b559 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/PacketParserUtils.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/PacketParserUtils.java @@ -82,14 +82,7 @@ public class PacketParserUtils { public static XmlPullParser getParserFor(Reader reader) throws XmlPullParserException, IOException { XmlPullParser parser = SmackXmlParser.newXmlParser(reader); - // Wind the parser forward to the first start tag - XmlPullParser.Event event = parser.getEventType(); - while (event != XmlPullParser.Event.START_ELEMENT) { - if (event == XmlPullParser.Event.END_DOCUMENT) { - throw new IllegalArgumentException("Document contains no start tag"); - } - event = parser.next(); - } + ParserUtils.forwardToStartElement(parser); return parser; } diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/ParserUtils.java b/smack-core/src/main/java/org/jivesoftware/smack/util/ParserUtils.java index 3486b372d..4b038cf8f 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/ParserUtils.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/ParserUtils.java @@ -63,6 +63,17 @@ public class ParserUtils { assert parser.getEventType() == XmlPullParser.Event.END_ELEMENT; } + public static void forwardToStartElement(XmlPullParser parser) throws XmlPullParserException, IOException { + // Wind the parser forward to the first start tag + XmlPullParser.Event event = parser.getEventType(); + while (event != XmlPullParser.Event.START_ELEMENT) { + if (event == XmlPullParser.Event.END_DOCUMENT) { + throw new IllegalArgumentException("Document contains no start tag"); + } + event = parser.next(); + } + } + public static void forwardToEndTagOfDepth(XmlPullParser parser, int depth) throws XmlPullParserException, IOException { XmlPullParser.Event event = parser.getEventType(); From b61426c8d037d903610cb293313d5d1f585b59d2 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Fri, 7 Aug 2020 21:25:20 +0200 Subject: [PATCH 08/29] Smack 4.4.0-beta1 --- version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version b/version index c6de5a7d9..c7398ea67 100644 --- a/version +++ b/version @@ -1 +1 @@ -4.4.0-alpha6-SNAPSHOT +4.4.0-beta1 From 89c5895ab334770f826b00714fb27406b536d5a9 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Fri, 7 Aug 2020 22:06:32 +0200 Subject: [PATCH 09/29] Smack 4.4.0-beta2-SNAPSHOT --- version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version b/version index c7398ea67..093319f00 100644 --- a/version +++ b/version @@ -1 +1 @@ -4.4.0-beta1 +4.4.0-beta2-SNAPSHOT From 11e38f9ba5ceb8938a01b20322bb79165a568748 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Fri, 7 Aug 2020 22:07:00 +0200 Subject: [PATCH 10/29] Smack 4.5.0-alpha1-SNAPSHOT --- version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version b/version index c6de5a7d9..07b8ab2a4 100644 --- a/version +++ b/version @@ -1 +1 @@ -4.4.0-alpha6-SNAPSHOT +4.5.0-alpha1-SNAPSHOT From c9cf4f15419be5a20bb7a046facfb664b0141f38 Mon Sep 17 00:00:00 2001 From: Aditya Borikar Date: Sat, 8 Aug 2020 20:14:39 +0530 Subject: [PATCH 11/29] XmlEnvironment: Use correct method to obatain effective namespace. --- .../main/java/org/jivesoftware/smack/packet/XmlEnvironment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smack-core/src/main/java/org/jivesoftware/smack/packet/XmlEnvironment.java b/smack-core/src/main/java/org/jivesoftware/smack/packet/XmlEnvironment.java index defc09868..cf4e8f9fd 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/packet/XmlEnvironment.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/packet/XmlEnvironment.java @@ -71,7 +71,7 @@ public class XmlEnvironment { } public String getEffectiveNamespaceOrUse(String namespace) { - String effectiveNamespace = getEffectiveLanguage(); + String effectiveNamespace = getEffectiveNamespace(); if (StringUtils.isNullOrEmpty(effectiveNamespace)) { return namespace; } From 45e865757a7242ea4942855213b53a9670c06f9c Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sat, 15 Aug 2020 10:06:37 +0200 Subject: [PATCH 12/29] [gradle] Use 'projectDirFile' File instance in getGitCommit() The previous construct def projectDir = dotGit.getParentFile() was a little bit strange (and its declaration shadows the projectDir String property). --- build.gradle | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 47e8f24f6..ab804a0d1 100644 --- a/build.gradle +++ b/build.gradle @@ -686,19 +686,19 @@ task sinttestAll { } def getGitCommit() { - def dotGit = new File("$projectDir/.git") + def projectDirFile = new File("$projectDir") + def dotGit = new File(projectDirFile, ".git") if (!dotGit.isDirectory()) return 'non-git build' - def projectDir = dotGit.getParentFile() def cmd = 'git describe --always --tags --dirty=+' - def proc = cmd.execute(null, projectDir) + def proc = cmd.execute(null, projectDirFile) proc.waitForOrKill(10 * 1000) def gitCommit = proc.text.trim() assert !gitCommit.isEmpty() def srCmd = 'git symbolic-ref --short HEAD' - def srProc = srCmd.execute(null, projectDir) + def srProc = srCmd.execute(null, projectDirFile) srProc.waitForOrKill(10 * 1000) if (srProc.exitValue() == 0) { // Only add the information if the git command was From 317e391da554e0a09e210829ffc933e170572794 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sat, 15 Aug 2020 14:03:57 +0200 Subject: [PATCH 13/29] Create smack-streammanagement project and move o.j.smack.sm code there --- settings.gradle | 1 + smack-streammanagement/build.gradle | 8 +++++++ .../org/jivesoftware/smack/sm/SMUtils.java | 0 .../smack/sm/StreamManagementException.java | 0 .../smack/sm/StreamManagementModule.java | 0 .../sm/StreamManagementModuleDescriptor.java | 0 .../jivesoftware/smack/sm/package-info.java | 0 .../smack/sm/packet/StreamManagement.java | 0 .../smack/sm/packet/package-info.java | 0 .../smack/sm/predicates/AfterXStanzas.java | 0 .../smack/sm/predicates/ForEveryMessage.java | 0 .../smack/sm/predicates/ForEveryStanza.java | 0 .../ForMatchingPredicateOrAfterXStanzas.java | 0 .../smack/sm/predicates/Predicate.java | 0 .../sm/predicates/ShortcutPredicates.java | 0 .../smack/sm/predicates/package-info.java | 0 .../sm/provider/ParseStreamManagement.java | 0 ...StreamManagementStreamFeatureProvider.java | 0 .../smack/sm/provider/package-info.java | 0 .../provider/ParseStreamManagementTest.java | 0 smack-tcp/build.gradle | 1 + .../{ => tcp}/OnceForThisStanza.java | 4 ++-- .../smack/sm/predicates/tcp/package-info.java | 21 +++++++++++++++++++ 23 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 smack-streammanagement/build.gradle rename {smack-tcp => smack-streammanagement}/src/main/java/org/jivesoftware/smack/sm/SMUtils.java (100%) rename {smack-tcp => smack-streammanagement}/src/main/java/org/jivesoftware/smack/sm/StreamManagementException.java (100%) rename {smack-tcp => smack-streammanagement}/src/main/java/org/jivesoftware/smack/sm/StreamManagementModule.java (100%) rename {smack-tcp => smack-streammanagement}/src/main/java/org/jivesoftware/smack/sm/StreamManagementModuleDescriptor.java (100%) rename {smack-tcp => smack-streammanagement}/src/main/java/org/jivesoftware/smack/sm/package-info.java (100%) rename {smack-tcp => smack-streammanagement}/src/main/java/org/jivesoftware/smack/sm/packet/StreamManagement.java (100%) rename {smack-tcp => smack-streammanagement}/src/main/java/org/jivesoftware/smack/sm/packet/package-info.java (100%) rename {smack-tcp => smack-streammanagement}/src/main/java/org/jivesoftware/smack/sm/predicates/AfterXStanzas.java (100%) rename {smack-tcp => smack-streammanagement}/src/main/java/org/jivesoftware/smack/sm/predicates/ForEveryMessage.java (100%) rename {smack-tcp => smack-streammanagement}/src/main/java/org/jivesoftware/smack/sm/predicates/ForEveryStanza.java (100%) rename {smack-tcp => smack-streammanagement}/src/main/java/org/jivesoftware/smack/sm/predicates/ForMatchingPredicateOrAfterXStanzas.java (100%) rename {smack-tcp => smack-streammanagement}/src/main/java/org/jivesoftware/smack/sm/predicates/Predicate.java (100%) rename {smack-tcp => smack-streammanagement}/src/main/java/org/jivesoftware/smack/sm/predicates/ShortcutPredicates.java (100%) rename {smack-tcp => smack-streammanagement}/src/main/java/org/jivesoftware/smack/sm/predicates/package-info.java (100%) rename {smack-tcp => smack-streammanagement}/src/main/java/org/jivesoftware/smack/sm/provider/ParseStreamManagement.java (100%) rename {smack-tcp => smack-streammanagement}/src/main/java/org/jivesoftware/smack/sm/provider/StreamManagementStreamFeatureProvider.java (100%) rename {smack-tcp => smack-streammanagement}/src/main/java/org/jivesoftware/smack/sm/provider/package-info.java (100%) rename {smack-tcp => smack-streammanagement}/src/test/java/org/jivesoftware/smack/sm/provider/ParseStreamManagementTest.java (100%) rename smack-tcp/src/main/java/org/jivesoftware/smack/sm/predicates/{ => tcp}/OnceForThisStanza.java (95%) create mode 100644 smack-tcp/src/main/java/org/jivesoftware/smack/sm/predicates/tcp/package-info.java diff --git a/settings.gradle b/settings.gradle index 37bbbb004..875a6e88c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -16,6 +16,7 @@ include 'smack-core', 'smack-resolver-javax', 'smack-sasl-javax', 'smack-sasl-provided', + 'smack-streammanagement', 'smack-legacy', 'smack-jingle-old', 'smack-bosh', diff --git a/smack-streammanagement/build.gradle b/smack-streammanagement/build.gradle new file mode 100644 index 000000000..c06c423d9 --- /dev/null +++ b/smack-streammanagement/build.gradle @@ -0,0 +1,8 @@ +description = """\ +Smack support for XMPP Stream Management (XEP-0198).""" + +dependencies { + api project(':smack-core') + + testFixturesApi(testFixtures(project(":smack-core"))) +} diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/sm/SMUtils.java b/smack-streammanagement/src/main/java/org/jivesoftware/smack/sm/SMUtils.java similarity index 100% rename from smack-tcp/src/main/java/org/jivesoftware/smack/sm/SMUtils.java rename to smack-streammanagement/src/main/java/org/jivesoftware/smack/sm/SMUtils.java diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/sm/StreamManagementException.java b/smack-streammanagement/src/main/java/org/jivesoftware/smack/sm/StreamManagementException.java similarity index 100% rename from smack-tcp/src/main/java/org/jivesoftware/smack/sm/StreamManagementException.java rename to smack-streammanagement/src/main/java/org/jivesoftware/smack/sm/StreamManagementException.java diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/sm/StreamManagementModule.java b/smack-streammanagement/src/main/java/org/jivesoftware/smack/sm/StreamManagementModule.java similarity index 100% rename from smack-tcp/src/main/java/org/jivesoftware/smack/sm/StreamManagementModule.java rename to smack-streammanagement/src/main/java/org/jivesoftware/smack/sm/StreamManagementModule.java diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/sm/StreamManagementModuleDescriptor.java b/smack-streammanagement/src/main/java/org/jivesoftware/smack/sm/StreamManagementModuleDescriptor.java similarity index 100% rename from smack-tcp/src/main/java/org/jivesoftware/smack/sm/StreamManagementModuleDescriptor.java rename to smack-streammanagement/src/main/java/org/jivesoftware/smack/sm/StreamManagementModuleDescriptor.java diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/sm/package-info.java b/smack-streammanagement/src/main/java/org/jivesoftware/smack/sm/package-info.java similarity index 100% rename from smack-tcp/src/main/java/org/jivesoftware/smack/sm/package-info.java rename to smack-streammanagement/src/main/java/org/jivesoftware/smack/sm/package-info.java diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/sm/packet/StreamManagement.java b/smack-streammanagement/src/main/java/org/jivesoftware/smack/sm/packet/StreamManagement.java similarity index 100% rename from smack-tcp/src/main/java/org/jivesoftware/smack/sm/packet/StreamManagement.java rename to smack-streammanagement/src/main/java/org/jivesoftware/smack/sm/packet/StreamManagement.java diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/sm/packet/package-info.java b/smack-streammanagement/src/main/java/org/jivesoftware/smack/sm/packet/package-info.java similarity index 100% rename from smack-tcp/src/main/java/org/jivesoftware/smack/sm/packet/package-info.java rename to smack-streammanagement/src/main/java/org/jivesoftware/smack/sm/packet/package-info.java diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/sm/predicates/AfterXStanzas.java b/smack-streammanagement/src/main/java/org/jivesoftware/smack/sm/predicates/AfterXStanzas.java similarity index 100% rename from smack-tcp/src/main/java/org/jivesoftware/smack/sm/predicates/AfterXStanzas.java rename to smack-streammanagement/src/main/java/org/jivesoftware/smack/sm/predicates/AfterXStanzas.java diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/sm/predicates/ForEveryMessage.java b/smack-streammanagement/src/main/java/org/jivesoftware/smack/sm/predicates/ForEveryMessage.java similarity index 100% rename from smack-tcp/src/main/java/org/jivesoftware/smack/sm/predicates/ForEveryMessage.java rename to smack-streammanagement/src/main/java/org/jivesoftware/smack/sm/predicates/ForEveryMessage.java diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/sm/predicates/ForEveryStanza.java b/smack-streammanagement/src/main/java/org/jivesoftware/smack/sm/predicates/ForEveryStanza.java similarity index 100% rename from smack-tcp/src/main/java/org/jivesoftware/smack/sm/predicates/ForEveryStanza.java rename to smack-streammanagement/src/main/java/org/jivesoftware/smack/sm/predicates/ForEveryStanza.java diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/sm/predicates/ForMatchingPredicateOrAfterXStanzas.java b/smack-streammanagement/src/main/java/org/jivesoftware/smack/sm/predicates/ForMatchingPredicateOrAfterXStanzas.java similarity index 100% rename from smack-tcp/src/main/java/org/jivesoftware/smack/sm/predicates/ForMatchingPredicateOrAfterXStanzas.java rename to smack-streammanagement/src/main/java/org/jivesoftware/smack/sm/predicates/ForMatchingPredicateOrAfterXStanzas.java diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/sm/predicates/Predicate.java b/smack-streammanagement/src/main/java/org/jivesoftware/smack/sm/predicates/Predicate.java similarity index 100% rename from smack-tcp/src/main/java/org/jivesoftware/smack/sm/predicates/Predicate.java rename to smack-streammanagement/src/main/java/org/jivesoftware/smack/sm/predicates/Predicate.java diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/sm/predicates/ShortcutPredicates.java b/smack-streammanagement/src/main/java/org/jivesoftware/smack/sm/predicates/ShortcutPredicates.java similarity index 100% rename from smack-tcp/src/main/java/org/jivesoftware/smack/sm/predicates/ShortcutPredicates.java rename to smack-streammanagement/src/main/java/org/jivesoftware/smack/sm/predicates/ShortcutPredicates.java diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/sm/predicates/package-info.java b/smack-streammanagement/src/main/java/org/jivesoftware/smack/sm/predicates/package-info.java similarity index 100% rename from smack-tcp/src/main/java/org/jivesoftware/smack/sm/predicates/package-info.java rename to smack-streammanagement/src/main/java/org/jivesoftware/smack/sm/predicates/package-info.java diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/sm/provider/ParseStreamManagement.java b/smack-streammanagement/src/main/java/org/jivesoftware/smack/sm/provider/ParseStreamManagement.java similarity index 100% rename from smack-tcp/src/main/java/org/jivesoftware/smack/sm/provider/ParseStreamManagement.java rename to smack-streammanagement/src/main/java/org/jivesoftware/smack/sm/provider/ParseStreamManagement.java diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/sm/provider/StreamManagementStreamFeatureProvider.java b/smack-streammanagement/src/main/java/org/jivesoftware/smack/sm/provider/StreamManagementStreamFeatureProvider.java similarity index 100% rename from smack-tcp/src/main/java/org/jivesoftware/smack/sm/provider/StreamManagementStreamFeatureProvider.java rename to smack-streammanagement/src/main/java/org/jivesoftware/smack/sm/provider/StreamManagementStreamFeatureProvider.java diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/sm/provider/package-info.java b/smack-streammanagement/src/main/java/org/jivesoftware/smack/sm/provider/package-info.java similarity index 100% rename from smack-tcp/src/main/java/org/jivesoftware/smack/sm/provider/package-info.java rename to smack-streammanagement/src/main/java/org/jivesoftware/smack/sm/provider/package-info.java diff --git a/smack-tcp/src/test/java/org/jivesoftware/smack/sm/provider/ParseStreamManagementTest.java b/smack-streammanagement/src/test/java/org/jivesoftware/smack/sm/provider/ParseStreamManagementTest.java similarity index 100% rename from smack-tcp/src/test/java/org/jivesoftware/smack/sm/provider/ParseStreamManagementTest.java rename to smack-streammanagement/src/test/java/org/jivesoftware/smack/sm/provider/ParseStreamManagementTest.java diff --git a/smack-tcp/build.gradle b/smack-tcp/build.gradle index 74ec76eb8..60b4b2691 100644 --- a/smack-tcp/build.gradle +++ b/smack-tcp/build.gradle @@ -3,6 +3,7 @@ Smack for standard XMPP connections over TCP.""" dependencies { compile project(':smack-core') + api project(':smack-streammanagement') testFixturesApi(testFixtures(project(":smack-core"))) } diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/sm/predicates/OnceForThisStanza.java b/smack-tcp/src/main/java/org/jivesoftware/smack/sm/predicates/tcp/OnceForThisStanza.java similarity index 95% rename from smack-tcp/src/main/java/org/jivesoftware/smack/sm/predicates/OnceForThisStanza.java rename to smack-tcp/src/main/java/org/jivesoftware/smack/sm/predicates/tcp/OnceForThisStanza.java index 94c9e6863..5c6ab7fe8 100644 --- a/smack-tcp/src/main/java/org/jivesoftware/smack/sm/predicates/OnceForThisStanza.java +++ b/smack-tcp/src/main/java/org/jivesoftware/smack/sm/predicates/tcp/OnceForThisStanza.java @@ -1,6 +1,6 @@ /** * - * Copyright © 2014 Florian Schmaus + * Copyright © 2014-2020 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.jivesoftware.smack.sm.predicates; +package org.jivesoftware.smack.sm.predicates.tcp; import org.jivesoftware.smack.filter.StanzaFilter; import org.jivesoftware.smack.packet.Stanza; diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/sm/predicates/tcp/package-info.java b/smack-tcp/src/main/java/org/jivesoftware/smack/sm/predicates/tcp/package-info.java new file mode 100644 index 000000000..15e022848 --- /dev/null +++ b/smack-tcp/src/main/java/org/jivesoftware/smack/sm/predicates/tcp/package-info.java @@ -0,0 +1,21 @@ +/** + * + * Copyright 2020 Florian Schmaus + * + * 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. + */ + +/** + * XMPPTCPConnection Stream Managment Predicates. + */ +package org.jivesoftware.smack.sm.predicates.tcp; From 49ebe8c58719ce2fb7a830595d6c17f0b4ac7f75 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Mon, 17 Aug 2020 21:56:26 +0200 Subject: [PATCH 14/29] [tcp] Drop Stream Management state on clean shutdown We previously only set the SM session ID to zero, but that is not enough. On a clean shutdown, i.e. where we send a close tag, we also have to nullify the unacknowledgedStanzas queue. --- .../smack/tcp/XMPPTCPConnection.java | 19 ++++++++++++++----- 1 file changed, 14 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 4e5ff2974..2d283d802 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 @@ -540,13 +540,22 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { // If we are able to resume the stream, then don't set // connected/authenticated/usingTLS to false since we like behave like we are still // connected (e.g. sendStanza should not throw a NotConnectedException). - if (isSmResumptionPossible() && instant) { - disconnectedButResumeable = true; + if (instant) { + disconnectedButResumeable = isSmResumptionPossible(); + if (!disconnectedButResumeable) { + // Reset the stream management session id to null, since the stream is no longer resumable. Note that we + // keep the unacknowledgedStanzas queue, because we want to resend them when we are reconnected. + smSessionId = null; + } } else { disconnectedButResumeable = false; - // Reset the stream management session id to null, since if the stream is cleanly closed, i.e. sending a closing - // stream tag, there is no longer a stream to resume. - smSessionId = null; + + // Drop the stream management state if this is not an instant shutdown. We send + // a close tag and now the stream management state is no longer valid. + // This also prevents that we will potentially (re-)send any unavailable presence we + // may have send, because it got put into the unacknowledged queue and was not acknowledged before the + // connection terminated. + dropSmState(); // Note that we deliberately do not reset authenticatedConnectionInitiallyEstablishedTimestamp here, so that the // information is available in the connectionClosedOnError() listeners. } From 9fcc97836bf5bb8fb788dc44675bf4e5f50e6f25 Mon Sep 17 00:00:00 2001 From: Aditya Borikar Date: Tue, 18 Aug 2020 10:35:22 +0530 Subject: [PATCH 15/29] Introduce AbstractStreamOpen and AbstractStreamClose - Inherit StreamOpen and StreamClose from AbstractStream classes --- .../smack/packet/AbstractStreamClose.java | 20 +++++ .../smack/packet/AbstractStreamOpen.java | 79 +++++++++++++++++++ .../smack/packet/StreamClose.java | 2 +- .../jivesoftware/smack/packet/StreamOpen.java | 52 +----------- 4 files changed, 102 insertions(+), 51 deletions(-) create mode 100644 smack-core/src/main/java/org/jivesoftware/smack/packet/AbstractStreamClose.java create mode 100644 smack-core/src/main/java/org/jivesoftware/smack/packet/AbstractStreamOpen.java diff --git a/smack-core/src/main/java/org/jivesoftware/smack/packet/AbstractStreamClose.java b/smack-core/src/main/java/org/jivesoftware/smack/packet/AbstractStreamClose.java new file mode 100644 index 000000000..c938c7e9e --- /dev/null +++ b/smack-core/src/main/java/org/jivesoftware/smack/packet/AbstractStreamClose.java @@ -0,0 +1,20 @@ +/** + * + * 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.packet; + +public abstract class AbstractStreamClose implements Nonza { +} diff --git a/smack-core/src/main/java/org/jivesoftware/smack/packet/AbstractStreamOpen.java b/smack-core/src/main/java/org/jivesoftware/smack/packet/AbstractStreamOpen.java new file mode 100644 index 000000000..36f0dc760 --- /dev/null +++ b/smack-core/src/main/java/org/jivesoftware/smack/packet/AbstractStreamOpen.java @@ -0,0 +1,79 @@ +/** + * + * 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.packet; + +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.packet.StreamOpen.StreamContentNamespace; +import org.jivesoftware.smack.util.StringUtils; + +/** + * AbstractStreamOpen is actually a {@link TopLevelStreamElement}, however we + * implement {@link Nonza} here. This is because, {@link XMPPConnection} doesn't + * yet support sending {@link TopLevelStreamElement} directly and the same can only + * be achieved through {@link XMPPConnection#sendNonza(Nonza)}. + */ +public abstract class AbstractStreamOpen implements Nonza { + public static final String CLIENT_NAMESPACE = "jabber:client"; + public static final String SERVER_NAMESPACE = "jabber:server"; + + /** + * RFC 6120 § 4.7.5. + */ + public static final String VERSION = "1.0"; + + /** + * RFC 6120 § 4.7.1. + */ + protected final String from; + + /** + * RFC 6120 § 4.7.2. + */ + protected final String to; + + /** + * RFC 6120 § 4.7.3. + */ + protected final String id; + + /** + * RFC 6120 § 4.7.4. + */ + protected final String lang; + + /** + * RFC 6120 § 4.8.2. + */ + protected final String contentNamespace; + + public AbstractStreamOpen(CharSequence to, CharSequence from, String id, String lang, StreamContentNamespace ns) { + this.to = StringUtils.maybeToString(to); + this.from = StringUtils.maybeToString(from); + this.id = id; + this.lang = lang; + switch (ns) { + case client: + this.contentNamespace = CLIENT_NAMESPACE; + break; + case server: + this.contentNamespace = SERVER_NAMESPACE; + break; + default: + throw new IllegalStateException(); + } + } +} diff --git a/smack-core/src/main/java/org/jivesoftware/smack/packet/StreamClose.java b/smack-core/src/main/java/org/jivesoftware/smack/packet/StreamClose.java index 377e6c616..fd284a2e5 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/packet/StreamClose.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/packet/StreamClose.java @@ -16,7 +16,7 @@ */ package org.jivesoftware.smack.packet; -public final class StreamClose implements Nonza { +public final class StreamClose extends AbstractStreamClose { public static final StreamClose INSTANCE = new StreamClose(); diff --git a/smack-core/src/main/java/org/jivesoftware/smack/packet/StreamOpen.java b/smack-core/src/main/java/org/jivesoftware/smack/packet/StreamOpen.java index 6ef368b63..f4b59db4b 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/packet/StreamOpen.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/packet/StreamOpen.java @@ -17,49 +17,14 @@ package org.jivesoftware.smack.packet; -import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.XmlStringBuilder; /** * The stream open tag. */ -public class StreamOpen implements Nonza { - +public final class StreamOpen extends AbstractStreamOpen { public static final String ELEMENT = "stream:stream"; - public static final String CLIENT_NAMESPACE = "jabber:client"; - public static final String SERVER_NAMESPACE = "jabber:server"; - - /** - * RFC 6120 § 4.7.5. - */ - public static final String VERSION = "1.0"; - - /** - * RFC 6120 § 4.7.1. - */ - private final String from; - - /** - * RFC 6120 § 4.7.2. - */ - private final String to; - - /** - * RFC 6120 § 4.7.3. - */ - private final String id; - - /** - * RFC 6120 § 4.7.4. - */ - private final String lang; - - /** - * RFC 6120 § 4.8.2. - */ - private final String contentNamespace; - public StreamOpen(CharSequence to) { this(to, null, null, null, StreamContentNamespace.client); } @@ -69,20 +34,7 @@ public class StreamOpen implements Nonza { } public StreamOpen(CharSequence to, CharSequence from, String id, String lang, StreamContentNamespace ns) { - this.to = StringUtils.maybeToString(to); - this.from = StringUtils.maybeToString(from); - this.id = id; - this.lang = lang; - switch (ns) { - case client: - this.contentNamespace = CLIENT_NAMESPACE; - break; - case server: - this.contentNamespace = SERVER_NAMESPACE; - break; - default: - throw new IllegalStateException(); - } + super(to, from, id, lang, ns); } @Override From 0e49adff1d4d88359c3a0c2c2d60efdfc31677e8 Mon Sep 17 00:00:00 2001 From: Aditya Borikar Date: Tue, 18 Aug 2020 11:12:56 +0530 Subject: [PATCH 16/29] Introduce StreamOpenAndCloseFactory for modular architecture --- .../smack/AbstractXMPPConnection.java | 3 +++ .../ModularXmppClientToServerConnection.java | 21 ++++++++++++++- .../smack/c2s/StreamOpenAndCloseFactory.java | 26 +++++++++++++++++++ .../c2s/XmppClientToServerTransport.java | 2 ++ .../smack/tcp/XmppTcpTransportModule.java | 18 +++++++++++++ 5 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 smack-core/src/main/java/org/jivesoftware/smack/c2s/StreamOpenAndCloseFactory.java 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 530bf7b11..fa76f780d 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java @@ -2243,7 +2243,10 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { StreamOpen streamOpen = new StreamOpen(to, from, id, config.getXmlLang(), StreamOpen.StreamContentNamespace.client); sendNonza(streamOpen); + updateOutgoingStreamXmlEnvironmentOnStreamOpen(streamOpen); + } + protected void updateOutgoingStreamXmlEnvironmentOnStreamOpen(StreamOpen streamOpen) { XmlEnvironment.Builder xmlEnvironmentBuilder = XmlEnvironment.builder(); xmlEnvironmentBuilder.with(streamOpen); outgoingStreamXmlEnvironment = xmlEnvironmentBuilder.build(); diff --git a/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java b/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java index 5bc020c46..25351cc45 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java @@ -68,6 +68,7 @@ import org.jivesoftware.smack.packet.Presence; import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smack.packet.StreamClose; import org.jivesoftware.smack.packet.StreamError; +import org.jivesoftware.smack.packet.StreamOpen; import org.jivesoftware.smack.packet.TopLevelStreamElement; import org.jivesoftware.smack.packet.XmlEnvironment; import org.jivesoftware.smack.parsing.SmackParsingException; @@ -81,7 +82,9 @@ import org.jivesoftware.smack.util.Supplier; import org.jivesoftware.smack.xml.XmlPullParser; import org.jivesoftware.smack.xml.XmlPullParserException; +import org.jxmpp.jid.DomainBareJid; import org.jxmpp.jid.parts.Resourcepart; +import org.jxmpp.util.XmppStringUtils; public final class ModularXmppClientToServerConnection extends AbstractXMPPConnection { @@ -560,10 +563,26 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne protected void newStreamOpenWaitForFeaturesSequence(String waitFor) throws InterruptedException, SmackException, XMPPException { prepareToWaitForFeaturesReceived(); - sendStreamOpen(); + + // Create StreamOpen from StreamOpenAndCloseFactory via underlying transport. + StreamOpenAndCloseFactory streamOpenAndCloseFactory = activeTransport.getStreamOpenAndCloseFactory(); + CharSequence from = null; + CharSequence localpart = connectionInternal.connection.getConfiguration().getUsername(); + DomainBareJid xmppServiceDomain = getXMPPServiceDomain(); + if (localpart != null) { + from = XmppStringUtils.completeJidFrom(localpart, xmppServiceDomain); + } + StreamOpen streamOpen = streamOpenAndCloseFactory.createStreamOpen(xmppServiceDomain, from, getStreamId(), getConfiguration().getXmlLang()); + sendStreamOpen(streamOpen); + waitForFeaturesReceived(waitFor); } + private void sendStreamOpen(StreamOpen streamOpen) throws NotConnectedException, InterruptedException { + sendNonza(streamOpen); + updateOutgoingStreamXmlEnvironmentOnStreamOpen(streamOpen); + } + public static class DisconnectedStateDescriptor extends StateDescriptor { protected DisconnectedStateDescriptor() { super(DisconnectedState.class, StateDescriptor.Property.finalState); diff --git a/smack-core/src/main/java/org/jivesoftware/smack/c2s/StreamOpenAndCloseFactory.java b/smack-core/src/main/java/org/jivesoftware/smack/c2s/StreamOpenAndCloseFactory.java new file mode 100644 index 000000000..250fedfce --- /dev/null +++ b/smack-core/src/main/java/org/jivesoftware/smack/c2s/StreamOpenAndCloseFactory.java @@ -0,0 +1,26 @@ +/** + * + * 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.c2s; + +import org.jivesoftware.smack.packet.StreamClose; +import org.jivesoftware.smack.packet.StreamOpen; + +public interface StreamOpenAndCloseFactory { + StreamOpen createStreamOpen(CharSequence to, CharSequence from, String id, String lang); + + StreamClose createStreamClose(); +} diff --git a/smack-core/src/main/java/org/jivesoftware/smack/c2s/XmppClientToServerTransport.java b/smack-core/src/main/java/org/jivesoftware/smack/c2s/XmppClientToServerTransport.java index d68440fc3..4bedb51b6 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/c2s/XmppClientToServerTransport.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/c2s/XmppClientToServerTransport.java @@ -58,6 +58,8 @@ public abstract class XmppClientToServerTransport { return getSslSession() != null; } + public abstract StreamOpenAndCloseFactory getStreamOpenAndCloseFactory(); + public abstract Stats getStats(); public abstract static class Stats { diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XmppTcpTransportModule.java b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XmppTcpTransportModule.java index 946eb4a1a..ca003ca8d 100644 --- a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XmppTcpTransportModule.java +++ b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XmppTcpTransportModule.java @@ -58,6 +58,7 @@ import org.jivesoftware.smack.XmppInputOutputFilter; import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.ConnectedButUnauthenticatedStateDescriptor; import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.LookupRemoteConnectionEndpointsStateDescriptor; import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionModule; +import org.jivesoftware.smack.c2s.StreamOpenAndCloseFactory; import org.jivesoftware.smack.c2s.XmppClientToServerTransport; import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal; import org.jivesoftware.smack.c2s.internal.WalkStateGraphContext; @@ -68,6 +69,7 @@ import org.jivesoftware.smack.fsm.StateTransitionResult; import org.jivesoftware.smack.internal.SmackTlsContext; import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smack.packet.StartTls; +import org.jivesoftware.smack.packet.StreamClose; import org.jivesoftware.smack.packet.StreamOpen; import org.jivesoftware.smack.packet.TlsFailure; import org.jivesoftware.smack.packet.TlsProceed; @@ -580,6 +582,22 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM super(connectionInternal); } + @Override + public StreamOpenAndCloseFactory getStreamOpenAndCloseFactory() { + return new StreamOpenAndCloseFactory() { + @Override + public StreamOpen createStreamOpen(CharSequence to, CharSequence from, String id, String lang) { + String xmlLang = connectionInternal.connection.getConfiguration().getXmlLang(); + StreamOpen streamOpen = new StreamOpen(to, from, id, xmlLang, StreamOpen.StreamContentNamespace.client); + return streamOpen; + } + @Override + public StreamClose createStreamClose() { + return StreamClose.INSTANCE; + } + }; + } + @Override protected void resetDiscoveredConnectionEndpoints() { discoveredTcpEndpoints = null; From db385e6595d02b95ce0c221e718780a8ee59fc89 Mon Sep 17 00:00:00 2001 From: Aditya Borikar Date: Wed, 19 Aug 2020 11:32:50 +0530 Subject: [PATCH 17/29] Make ModularXmppClientToServerConnectionConfiguration.addModule() public This commit will allow users to plug their module descriptors inside modular architecture. --- .../c2s/ModularXmppClientToServerConnectionConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnectionConfiguration.java b/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnectionConfiguration.java index a3bf16911..f67ab5a11 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnectionConfiguration.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnectionConfiguration.java @@ -96,7 +96,7 @@ public final class ModularXmppClientToServerConnectionConfiguration extends Conn return new ModularXmppClientToServerConnectionConfiguration(this); } - void addModule(ModularXmppClientToServerConnectionModuleDescriptor connectionModule) { + public void addModule(ModularXmppClientToServerConnectionModuleDescriptor connectionModule) { Class moduleDescriptorClass = connectionModule.getClass(); if (modulesDescriptors.containsKey(moduleDescriptorClass)) { throw new IllegalArgumentException("A connection module for " + moduleDescriptorClass + " is already configured"); From 648a1cfab1f69f9b00070182d55142d3d0f35965 Mon Sep 17 00:00:00 2001 From: Aditya Borikar Date: Wed, 19 Aug 2020 11:07:30 +0530 Subject: [PATCH 18/29] Use AbstractStreamOpen instead of StreamOpen to open stream Before the existence of AbstractStreamOpen, StreamOpen sufficed our need during sending an open stream element. Since the intention behind introducing AbstractStreamOpen is to allow underlying transports provide transport specific opening streams, these changes will further support the cause. This commit will allow us to send transport specific open element which should be inherited from AbstractStreamOpen. --- .../org/jivesoftware/smack/AbstractXMPPConnection.java | 3 ++- .../smack/c2s/ModularXmppClientToServerConnection.java | 6 +++--- .../jivesoftware/smack/c2s/StreamOpenAndCloseFactory.java | 8 ++++---- .../org/jivesoftware/smack/packet/XmlEnvironment.java | 2 +- 4 files changed, 10 insertions(+), 9 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 fa76f780d..377d53c0c 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java @@ -70,6 +70,7 @@ import org.jivesoftware.smack.filter.StanzaFilter; import org.jivesoftware.smack.filter.StanzaIdFilter; import org.jivesoftware.smack.internal.SmackTlsContext; import org.jivesoftware.smack.iqrequest.IQRequestHandler; +import org.jivesoftware.smack.packet.AbstractStreamOpen; import org.jivesoftware.smack.packet.Bind; import org.jivesoftware.smack.packet.ErrorIQ; import org.jivesoftware.smack.packet.ExtensionElement; @@ -2246,7 +2247,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { updateOutgoingStreamXmlEnvironmentOnStreamOpen(streamOpen); } - protected void updateOutgoingStreamXmlEnvironmentOnStreamOpen(StreamOpen streamOpen) { + protected void updateOutgoingStreamXmlEnvironmentOnStreamOpen(AbstractStreamOpen streamOpen) { XmlEnvironment.Builder xmlEnvironmentBuilder = XmlEnvironment.builder(); xmlEnvironmentBuilder.with(streamOpen); outgoingStreamXmlEnvironment = xmlEnvironmentBuilder.build(); diff --git a/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java b/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java index 25351cc45..e404795db 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java @@ -61,6 +61,7 @@ import org.jivesoftware.smack.fsm.StateTransitionResult; import org.jivesoftware.smack.fsm.StateTransitionResult.AttemptResult; import org.jivesoftware.smack.internal.AbstractStats; import org.jivesoftware.smack.internal.SmackTlsContext; +import org.jivesoftware.smack.packet.AbstractStreamOpen; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Nonza; @@ -68,7 +69,6 @@ import org.jivesoftware.smack.packet.Presence; import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smack.packet.StreamClose; import org.jivesoftware.smack.packet.StreamError; -import org.jivesoftware.smack.packet.StreamOpen; import org.jivesoftware.smack.packet.TopLevelStreamElement; import org.jivesoftware.smack.packet.XmlEnvironment; import org.jivesoftware.smack.parsing.SmackParsingException; @@ -572,13 +572,13 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne if (localpart != null) { from = XmppStringUtils.completeJidFrom(localpart, xmppServiceDomain); } - StreamOpen streamOpen = streamOpenAndCloseFactory.createStreamOpen(xmppServiceDomain, from, getStreamId(), getConfiguration().getXmlLang()); + AbstractStreamOpen streamOpen = streamOpenAndCloseFactory.createStreamOpen(xmppServiceDomain, from, getStreamId(), getConfiguration().getXmlLang()); sendStreamOpen(streamOpen); waitForFeaturesReceived(waitFor); } - private void sendStreamOpen(StreamOpen streamOpen) throws NotConnectedException, InterruptedException { + private void sendStreamOpen(AbstractStreamOpen streamOpen) throws NotConnectedException, InterruptedException { sendNonza(streamOpen); updateOutgoingStreamXmlEnvironmentOnStreamOpen(streamOpen); } diff --git a/smack-core/src/main/java/org/jivesoftware/smack/c2s/StreamOpenAndCloseFactory.java b/smack-core/src/main/java/org/jivesoftware/smack/c2s/StreamOpenAndCloseFactory.java index 250fedfce..4a15467d2 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/c2s/StreamOpenAndCloseFactory.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/c2s/StreamOpenAndCloseFactory.java @@ -16,11 +16,11 @@ */ package org.jivesoftware.smack.c2s; -import org.jivesoftware.smack.packet.StreamClose; -import org.jivesoftware.smack.packet.StreamOpen; +import org.jivesoftware.smack.packet.AbstractStreamClose; +import org.jivesoftware.smack.packet.AbstractStreamOpen; public interface StreamOpenAndCloseFactory { - StreamOpen createStreamOpen(CharSequence to, CharSequence from, String id, String lang); + AbstractStreamOpen createStreamOpen(CharSequence to, CharSequence from, String id, String lang); - StreamClose createStreamClose(); + AbstractStreamClose createStreamClose(); } diff --git a/smack-core/src/main/java/org/jivesoftware/smack/packet/XmlEnvironment.java b/smack-core/src/main/java/org/jivesoftware/smack/packet/XmlEnvironment.java index cf4e8f9fd..2fdc21333 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/packet/XmlEnvironment.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/packet/XmlEnvironment.java @@ -162,7 +162,7 @@ public class XmlEnvironment { return this; } - public Builder with(StreamOpen streamOpen) { + public Builder with(AbstractStreamOpen streamOpen) { withNamespace(streamOpen.getNamespace()); withLanguage(streamOpen.getLanguage()); return this; From 0bb0884512023df92b68f73b3427881ac20c683b Mon Sep 17 00:00:00 2001 From: Aditya Borikar Date: Sun, 23 Aug 2020 13:25:18 +0530 Subject: [PATCH 19/29] XMPPTCPConnection: Add missing `to` in comment --- .../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 4e5ff2974..62420a1a6 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 @@ -538,7 +538,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { } // If we are able to resume the stream, then don't set - // connected/authenticated/usingTLS to false since we like behave like we are still + // connected/authenticated/usingTLS to false since we like to behave like we are still // connected (e.g. sendStanza should not throw a NotConnectedException). if (isSmResumptionPossible() && instant) { disconnectedButResumeable = true; From d00656493a5daafe779abe27177cc903ac53ffae Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Wed, 26 Aug 2020 11:34:57 +0200 Subject: [PATCH 20/29] [jingle] Use correct XmlStringBuilder constructor in JingleReason --- .../org/jivesoftware/smackx/jingle/element/JingleReason.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/JingleReason.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/JingleReason.java index 91c909aba..a7f1364a4 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/JingleReason.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/JingleReason.java @@ -1,6 +1,6 @@ /** * - * Copyright 2017-2019 Florian Schmaus + * Copyright 2017-2020 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -151,7 +151,7 @@ public class JingleReason implements FullyQualifiedElement { @Override public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) { - XmlStringBuilder xml = new XmlStringBuilder(this); + XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace); xml.rightAngleBracket(); xml.openElement(reason.asString); From 2a9671ca93b46ad250338051b77df9aa3a306adc Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Wed, 26 Aug 2020 11:35:22 +0200 Subject: [PATCH 21/29] [core] Change type of XmlStringBuilder cosntructor to FullyQualifiedElement There is no reason we should do this only for ExtensionElements, this behavior is sane for every FulllyQualifiedElement. --- .../java/org/jivesoftware/smack/util/XmlStringBuilder.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/XmlStringBuilder.java b/smack-core/src/main/java/org/jivesoftware/smack/util/XmlStringBuilder.java index c483db913..50e27e072 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/XmlStringBuilder.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/XmlStringBuilder.java @@ -1,6 +1,6 @@ /** * - * Copyright 2014-2019 Florian Schmaus + * Copyright 2014-2020 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,6 @@ import java.util.Date; import java.util.List; import org.jivesoftware.smack.packet.Element; -import org.jivesoftware.smack.packet.ExtensionElement; import org.jivesoftware.smack.packet.FullyQualifiedElement; import org.jivesoftware.smack.packet.NamedElement; import org.jivesoftware.smack.packet.XmlEnvironment; @@ -43,7 +42,7 @@ public class XmlStringBuilder implements Appendable, CharSequence, Element { effectiveXmlEnvironment = null; } - public XmlStringBuilder(ExtensionElement pe) { + public XmlStringBuilder(FullyQualifiedElement pe) { this(pe, null); } From e6a60213b6e2e86ea9dfacbb61e97e76508668ac Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Wed, 26 Aug 2020 11:36:55 +0200 Subject: [PATCH 22/29] [core] Add convenience constructor to AbstractStreamOpen Most of the times when we construct a stream-open-like element, we want the jabber:client namespace. Hence add a constructor that does select the namespace implicitly. --- .../org/jivesoftware/smack/packet/AbstractStreamOpen.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/smack-core/src/main/java/org/jivesoftware/smack/packet/AbstractStreamOpen.java b/smack-core/src/main/java/org/jivesoftware/smack/packet/AbstractStreamOpen.java index 36f0dc760..1b6a3d1e0 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/packet/AbstractStreamOpen.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/packet/AbstractStreamOpen.java @@ -1,6 +1,6 @@ /** * - * Copyright 2020 Aditya Borikar + * Copyright 2020 Florian Schmaus, Aditya Borikar * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -60,6 +60,10 @@ public abstract class AbstractStreamOpen implements Nonza { */ protected final String contentNamespace; + public AbstractStreamOpen(CharSequence to, CharSequence from, String id, String lang) { + this(to, from, id, lang, StreamContentNamespace.client); + } + public AbstractStreamOpen(CharSequence to, CharSequence from, String id, String lang, StreamContentNamespace ns) { this.to = StringUtils.maybeToString(to); this.from = StringUtils.maybeToString(from); From f892ba136901f416aad6587bc2f806c7694944c4 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Wed, 26 Aug 2020 11:38:24 +0200 Subject: [PATCH 23/29] [core] Get stream-open-like element from transport When sending a stream-open-like element, it depends on the actual used transport which element is send. For example, RFC6120-style TCP uses , whereas the Websocket binding for XMPP uses . --- .../org/jivesoftware/smack/AbstractXMPPConnection.java | 9 +++++++-- .../smack/c2s/ModularXmppClientToServerConnection.java | 6 ++++++ .../java/org/jivesoftware/smack/packet/StreamOpen.java | 6 +++++- 3 files changed, 18 insertions(+), 3 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 377d53c0c..a421d6cd1 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java @@ -2229,7 +2229,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { } } - protected void sendStreamOpen() throws NotConnectedException, InterruptedException { + protected final void sendStreamOpen() throws NotConnectedException, InterruptedException { // If possible, provide the receiving entity of the stream open tag, i.e. the server, as much information as // possible. The 'to' attribute is *always* available. The 'from' attribute if set by the user and no external // mechanism is used to determine the local entity (user). And the 'id' attribute is available after the first @@ -2241,12 +2241,17 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { from = XmppStringUtils.completeJidFrom(localpart, to); } String id = getStreamId(); + String lang = config.getXmlLang(); - StreamOpen streamOpen = new StreamOpen(to, from, id, config.getXmlLang(), StreamOpen.StreamContentNamespace.client); + AbstractStreamOpen streamOpen = getStreamOpen(to, from, id, lang); sendNonza(streamOpen); updateOutgoingStreamXmlEnvironmentOnStreamOpen(streamOpen); } + protected AbstractStreamOpen getStreamOpen(CharSequence to, CharSequence from, String id, String lang) { + return new StreamOpen(to, from, id, lang); + } + protected void updateOutgoingStreamXmlEnvironmentOnStreamOpen(AbstractStreamOpen streamOpen) { XmlEnvironment.Builder xmlEnvironmentBuilder = XmlEnvironment.builder(); xmlEnvironmentBuilder.with(streamOpen); diff --git a/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java b/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java index e404795db..191dd32e4 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java @@ -560,6 +560,12 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne waitForConditionOrThrowConnectionException(() -> featuresReceived, waitFor); } + @Override + protected AbstractStreamOpen getStreamOpen(CharSequence to, CharSequence from, String id, String lang) { + StreamOpenAndCloseFactory streamOpenAndCloseFactory = activeTransport.getStreamOpenAndCloseFactory(); + return streamOpenAndCloseFactory.createStreamOpen(to, from, id, lang); + } + protected void newStreamOpenWaitForFeaturesSequence(String waitFor) throws InterruptedException, SmackException, XMPPException { prepareToWaitForFeaturesReceived(); diff --git a/smack-core/src/main/java/org/jivesoftware/smack/packet/StreamOpen.java b/smack-core/src/main/java/org/jivesoftware/smack/packet/StreamOpen.java index f4b59db4b..959adc1fa 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/packet/StreamOpen.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/packet/StreamOpen.java @@ -1,6 +1,6 @@ /** * - * Copyright © 2014-2019 Florian Schmaus + * Copyright © 2014-2020 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,6 +33,10 @@ public final class StreamOpen extends AbstractStreamOpen { this(to, from, id, "en", StreamContentNamespace.client); } + public StreamOpen(CharSequence to, CharSequence from, String id, String lang) { + super(to, from, id, lang); + } + public StreamOpen(CharSequence to, CharSequence from, String id, String lang, StreamContentNamespace ns) { super(to, from, id, lang, ns); } From cf4c9725b7eed3edc6a5c835504eb4093f2d2008 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Fri, 28 Aug 2020 09:37:29 +0200 Subject: [PATCH 24/29] [core] Add ProviderManager.getExtensionProvider(QName) --- .../org/jivesoftware/smack/provider/ProviderManager.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/smack-core/src/main/java/org/jivesoftware/smack/provider/ProviderManager.java b/smack-core/src/main/java/org/jivesoftware/smack/provider/ProviderManager.java index 7527b330f..f9e2bb560 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/provider/ProviderManager.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/provider/ProviderManager.java @@ -243,7 +243,11 @@ public final class ProviderManager { */ public static ExtensionElementProvider getExtensionProvider(String elementName, String namespace) { QName key = getQName(elementName, namespace); - return extensionProviders.get(key); + return getExtensionProvider(key); + } + + public static ExtensionElementProvider getExtensionProvider(QName qname) { + return extensionProviders.get(qname); } /** From 1aab0b8aac197147f81b9c3731da847cbd3f152e Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Fri, 28 Aug 2020 09:47:09 +0200 Subject: [PATCH 25/29] [core] Add cache to XmppElementUtil.getQNameFor(Class) --- .../smack/util/XmppElementUtil.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/XmppElementUtil.java b/smack-core/src/main/java/org/jivesoftware/smack/util/XmppElementUtil.java index b78f62ab9..83d09cf21 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/XmppElementUtil.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/XmppElementUtil.java @@ -25,15 +25,26 @@ import javax.xml.namespace.QName; import org.jivesoftware.smack.packet.FullyQualifiedElement; +import org.jxmpp.util.cache.LruCache; + public class XmppElementUtil { + private static final LruCache, QName> CLASS_TO_QNAME_CACHE = new LruCache<>(512); + public static final Logger LOGGER = Logger.getLogger(XmppElementUtil.class.getName()); public static QName getQNameFor(Class fullyQualifiedElement) { + QName qname = CLASS_TO_QNAME_CACHE.get(fullyQualifiedElement); + if (qname != null) { + return qname; + } + try { Object qnameObject = fullyQualifiedElement.getField("QNAME").get(null); if (QName.class.isAssignableFrom(qnameObject.getClass())) { - return (QName) qnameObject; + qname = (QName) qnameObject; + CLASS_TO_QNAME_CACHE.put(fullyQualifiedElement, qname); + return qname; } LOGGER.warning("The QNAME field of " + fullyQualifiedElement + " is not of type QNAME."); } catch (NoSuchFieldException e) { @@ -52,7 +63,9 @@ public class XmppElementUtil { throw new IllegalArgumentException("The " + fullyQualifiedElement + " has no ELEMENT, NAMESPACE or QNAME member. Consider adding QNAME", e); } - return new QName(namespace, element); + qname = new QName(namespace, element); + CLASS_TO_QNAME_CACHE.put(fullyQualifiedElement, qname); + return qname; } public static List getElementsFrom( From 99297e5a765c23caaa69d1ab4a5c4b025b55f69a Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Fri, 28 Aug 2020 09:47:54 +0200 Subject: [PATCH 26/29] [mam] Improve MamResultExtension: use MessageView in from() and add QNAME --- .../smackx/mam/element/MamElements.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamElements.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamElements.java index 9fcff7b9c..2423b0c1e 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamElements.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamElements.java @@ -18,9 +18,11 @@ package org.jivesoftware.smackx.mam.element; import java.util.List; +import javax.xml.namespace.QName; + import org.jivesoftware.smack.packet.Element; import org.jivesoftware.smack.packet.ExtensionElement; -import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.MessageView; import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.XmlStringBuilder; @@ -54,6 +56,11 @@ public class MamElements { */ public static final String ELEMENT = "result"; + /** + * The qualified name of the MAM result extension element. + */ + public static final QName QNAME = new QName(NAMESPACE, ELEMENT); + /** * id of the result. */ @@ -139,8 +146,8 @@ public class MamElements { return xml; } - public static MamResultExtension from(Message message) { - return (MamResultExtension) message.getExtensionElement(ELEMENT, NAMESPACE); + public static MamResultExtension from(MessageView message) { + return message.getExtension(MamResultExtension.class); } } From 7ed29b9d5f41e9c8542ad47567e11ae1aaba0c66 Mon Sep 17 00:00:00 2001 From: adiaholic Date: Thu, 14 May 2020 18:05:37 +0530 Subject: [PATCH 27/29] Introduce websocket module into smack --- build.gradle | 3 + settings.gradle | 1 + .../ModularXmppClientToServerConnection.java | 16 +- ...rXmppClientToServerConnectionInternal.java | 4 + .../smack/packet/AbstractStreamOpen.java | 6 + ...RemoteConnectionEndpointLookupFailure.java | 14 + .../org.jivesoftware.smack/smack-config.xml | 1 + .../smack/inttest/XmppConnectionManager.java | 10 + smack-java8-full/build.gradle | 1 + .../src/test/resources/state-graph.dot | 4 +- .../smack/smackrepl/WebsocketConnection.java | 53 +++ smack-websocket/build.gradle | 10 + .../WebsocketConnectionAttemptState.java | 101 ++++++ .../smack/websocket/WebsocketException.java | 38 ++ .../smack/websocket/WebsocketInitializer.java | 28 ++ .../XmppWebsocketTransportModule.java | 325 ++++++++++++++++++ ...mppWebsocketTransportModuleDescriptor.java | 136 ++++++++ .../elements/AbstractWebsocketNonza.java | 47 +++ .../elements/WebsocketCloseElement.java | 49 +++ .../elements/WebsocketOpenElement.java | 54 +++ .../websocket/elements/package-info.java | 20 ++ .../implementations/AbstractWebsocket.java | 63 ++++ .../WebsocketImplProvider.java | 35 ++ .../okhttp/LoggingInterceptor.java | 90 +++++ .../okhttp/OkHttpWebsocket.java | 179 ++++++++++ .../implementations/okhttp/package-info.java | 17 + .../implementations/package-info.java | 20 ++ .../smack/websocket/package-info.java | 20 ++ .../WebsocketRemoteConnectionEndpoint.java | 85 +++++ ...bsocketRemoteConnectionEndpointLookup.java | 115 +++++++ .../smack/websocket/rce/package-info.java | 20 ++ .../WebsocketConnectionAttemptStateTest.java | 28 ++ .../websocket/WebsocketInitializerTest.java | 32 ++ .../XmppWebsocketTransportModuleTest.java | 124 +++++++ .../elements/WebsocketElementTest.java | 43 +++ .../AbstractWebsocketTest.java | 47 +++ .../implementations/ProviderTest.java | 61 ++++ ...WebsocketRemoteConnectionEndpointTest.java | 45 +++ .../org.mockito.plugins.MockMaker | 1 + 39 files changed, 1943 insertions(+), 3 deletions(-) create mode 100644 smack-repl/src/main/java/org/igniterealtime/smack/smackrepl/WebsocketConnection.java create mode 100644 smack-websocket/build.gradle create mode 100644 smack-websocket/src/main/java/org/jivesoftware/smack/websocket/WebsocketConnectionAttemptState.java create mode 100644 smack-websocket/src/main/java/org/jivesoftware/smack/websocket/WebsocketException.java create mode 100644 smack-websocket/src/main/java/org/jivesoftware/smack/websocket/WebsocketInitializer.java create mode 100644 smack-websocket/src/main/java/org/jivesoftware/smack/websocket/XmppWebsocketTransportModule.java create mode 100644 smack-websocket/src/main/java/org/jivesoftware/smack/websocket/XmppWebsocketTransportModuleDescriptor.java create mode 100644 smack-websocket/src/main/java/org/jivesoftware/smack/websocket/elements/AbstractWebsocketNonza.java create mode 100644 smack-websocket/src/main/java/org/jivesoftware/smack/websocket/elements/WebsocketCloseElement.java create mode 100644 smack-websocket/src/main/java/org/jivesoftware/smack/websocket/elements/WebsocketOpenElement.java create mode 100644 smack-websocket/src/main/java/org/jivesoftware/smack/websocket/elements/package-info.java create mode 100644 smack-websocket/src/main/java/org/jivesoftware/smack/websocket/implementations/AbstractWebsocket.java create mode 100644 smack-websocket/src/main/java/org/jivesoftware/smack/websocket/implementations/WebsocketImplProvider.java create mode 100644 smack-websocket/src/main/java/org/jivesoftware/smack/websocket/implementations/okhttp/LoggingInterceptor.java create mode 100644 smack-websocket/src/main/java/org/jivesoftware/smack/websocket/implementations/okhttp/OkHttpWebsocket.java create mode 100644 smack-websocket/src/main/java/org/jivesoftware/smack/websocket/implementations/okhttp/package-info.java create mode 100644 smack-websocket/src/main/java/org/jivesoftware/smack/websocket/implementations/package-info.java create mode 100644 smack-websocket/src/main/java/org/jivesoftware/smack/websocket/package-info.java create mode 100644 smack-websocket/src/main/java/org/jivesoftware/smack/websocket/rce/WebsocketRemoteConnectionEndpoint.java create mode 100644 smack-websocket/src/main/java/org/jivesoftware/smack/websocket/rce/WebsocketRemoteConnectionEndpointLookup.java create mode 100644 smack-websocket/src/main/java/org/jivesoftware/smack/websocket/rce/package-info.java create mode 100644 smack-websocket/src/test/java/org/jivesoftware/smack/websocket/WebsocketConnectionAttemptStateTest.java create mode 100644 smack-websocket/src/test/java/org/jivesoftware/smack/websocket/WebsocketInitializerTest.java create mode 100644 smack-websocket/src/test/java/org/jivesoftware/smack/websocket/XmppWebsocketTransportModuleTest.java create mode 100644 smack-websocket/src/test/java/org/jivesoftware/smack/websocket/elements/WebsocketElementTest.java create mode 100644 smack-websocket/src/test/java/org/jivesoftware/smack/websocket/implementations/AbstractWebsocketTest.java create mode 100644 smack-websocket/src/test/java/org/jivesoftware/smack/websocket/implementations/ProviderTest.java create mode 100644 smack-websocket/src/test/java/org/jivesoftware/smack/websocket/rce/WebsocketRemoteConnectionEndpointTest.java create mode 100644 smack-websocket/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker diff --git a/build.gradle b/build.gradle index ab804a0d1..49aa67092 100644 --- a/build.gradle +++ b/build.gradle @@ -288,6 +288,9 @@ tasks.withType(Javadoc) { // fixtures, and we want to have mockito also available in // test, so we use API here. testFixturesApi "org.mockito:mockito-core:3.3.3" + + // To mock final classes + testImplementation 'org.mockito:mockito-inline:3.3.3' testImplementation 'com.jamesmurty.utils:java-xmlbuilder:1.2' errorprone 'com.google.errorprone:error_prone_core:2.3.4' diff --git a/settings.gradle b/settings.gradle index 875a6e88c..460e5e403 100644 --- a/settings.gradle +++ b/settings.gradle @@ -30,6 +30,7 @@ include 'smack-core', 'smack-omemo-signal-integration-test', 'smack-repl', 'smack-openpgp', + 'smack-websocket', 'smack-xmlparser', 'smack-xmlparser-stax', 'smack-xmlparser-xpp3' diff --git a/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java b/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java index 191dd32e4..7f677871e 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java @@ -61,13 +61,13 @@ import org.jivesoftware.smack.fsm.StateTransitionResult; import org.jivesoftware.smack.fsm.StateTransitionResult.AttemptResult; import org.jivesoftware.smack.internal.AbstractStats; import org.jivesoftware.smack.internal.SmackTlsContext; +import org.jivesoftware.smack.packet.AbstractStreamClose; import org.jivesoftware.smack.packet.AbstractStreamOpen; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Nonza; import org.jivesoftware.smack.packet.Presence; import org.jivesoftware.smack.packet.Stanza; -import org.jivesoftware.smack.packet.StreamClose; import org.jivesoftware.smack.packet.StreamError; import org.jivesoftware.smack.packet.TopLevelStreamElement; import org.jivesoftware.smack.packet.XmlEnvironment; @@ -138,6 +138,11 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne ModularXmppClientToServerConnection.this.notifyConnectionError(e); } + @Override + public void setCurrentConnectionExceptionAndNotify(Exception exception) { + ModularXmppClientToServerConnection.this.setCurrentConnectionExceptionAndNotify(exception); + } + @Override public void onStreamOpen(XmlPullParser parser) { ModularXmppClientToServerConnection.this.onStreamOpen(parser); @@ -179,6 +184,11 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne return inputOutputFilters.listIterator(inputOutputFilters.size()); } + @Override + public void waitForFeaturesReceived(String waitFor) throws InterruptedException, SmackException, XMPPException { + ModularXmppClientToServerConnection.this.waitForFeaturesReceived(waitFor); + } + @Override public void newStreamOpenWaitForFeaturesSequence(String waitFor) throws InterruptedException, SmackException, XMPPException { @@ -930,7 +940,9 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) { closingStreamReceived = false; - boolean streamCloseIssued = outgoingElementsQueue.offerAndShutdown(StreamClose.INSTANCE); + StreamOpenAndCloseFactory openAndCloseFactory = activeTransport.getStreamOpenAndCloseFactory(); + AbstractStreamClose closeStreamElement = openAndCloseFactory.createStreamClose(); + boolean streamCloseIssued = outgoingElementsQueue.offerAndShutdown(closeStreamElement); if (streamCloseIssued) { activeTransport.notifyAboutNewOutgoingElements(); diff --git a/smack-core/src/main/java/org/jivesoftware/smack/c2s/internal/ModularXmppClientToServerConnectionInternal.java b/smack-core/src/main/java/org/jivesoftware/smack/c2s/internal/ModularXmppClientToServerConnectionInternal.java index 0462307c1..139f1194f 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/c2s/internal/ModularXmppClientToServerConnectionInternal.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/c2s/internal/ModularXmppClientToServerConnectionInternal.java @@ -85,6 +85,8 @@ public abstract class ModularXmppClientToServerConnectionInternal { public abstract void notifyConnectionError(Exception e); + public abstract void setCurrentConnectionExceptionAndNotify(Exception exception); + public abstract void onStreamOpen(XmlPullParser parser); public abstract void onStreamClosed(); @@ -99,6 +101,8 @@ public abstract class ModularXmppClientToServerConnectionInternal { public abstract ListIterator getXmppInputOutputFilterEndIterator(); + public abstract void waitForFeaturesReceived(String waitFor) throws InterruptedException, SmackException, XMPPException; + public abstract void newStreamOpenWaitForFeaturesSequence(String waitFor) throws InterruptedException, NoResponseException, NotConnectedException, SmackException, XMPPException; diff --git a/smack-core/src/main/java/org/jivesoftware/smack/packet/AbstractStreamOpen.java b/smack-core/src/main/java/org/jivesoftware/smack/packet/AbstractStreamOpen.java index 1b6a3d1e0..698e7888d 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/packet/AbstractStreamOpen.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/packet/AbstractStreamOpen.java @@ -19,6 +19,7 @@ package org.jivesoftware.smack.packet; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.packet.StreamOpen.StreamContentNamespace; import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smack.util.XmlStringBuilder; /** * AbstractStreamOpen is actually a {@link TopLevelStreamElement}, however we @@ -80,4 +81,9 @@ public abstract class AbstractStreamOpen implements Nonza { throw new IllegalStateException(); } } + + protected final void addCommonAttributes(XmlStringBuilder xml) { + xml.optAttribute("to", to); + xml.optAttribute("version", VERSION); + } } diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/rce/RemoteConnectionEndpointLookupFailure.java b/smack-core/src/main/java/org/jivesoftware/smack/util/rce/RemoteConnectionEndpointLookupFailure.java index 73ce8a4b0..7e92b13dc 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/rce/RemoteConnectionEndpointLookupFailure.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/rce/RemoteConnectionEndpointLookupFailure.java @@ -18,6 +18,7 @@ package org.jivesoftware.smack.util.rce; import org.jivesoftware.smack.util.ToStringUtil; +import org.jxmpp.jid.DomainBareJid; import org.minidns.dnsname.DnsName; public abstract class RemoteConnectionEndpointLookupFailure { @@ -67,4 +68,17 @@ public abstract class RemoteConnectionEndpointLookupFailure { return dnsName; } } + + public static class HttpLookupFailure extends RemoteConnectionEndpointLookupFailure { + private final DomainBareJid host; + + public HttpLookupFailure(DomainBareJid host, Exception exception) { + super("Http lookup exception for " + host, exception); + this.host = host; + } + + public DomainBareJid getHost() { + return host; + } + } } diff --git a/smack-core/src/main/resources/org.jivesoftware.smack/smack-config.xml b/smack-core/src/main/resources/org.jivesoftware.smack/smack-config.xml index e1be746e0..b80048942 100644 --- a/smack-core/src/main/resources/org.jivesoftware.smack/smack-config.xml +++ b/smack-core/src/main/resources/org.jivesoftware.smack/smack-config.xml @@ -20,6 +20,7 @@ org.jivesoftware.smack.android.AndroidSmackInitializer org.jivesoftware.smack.java7.Java7SmackInitializer org.jivesoftware.smack.im.SmackImInitializer + org.jivesoftware.smack.websocket.WebsocketInitializer org.jivesoftware.smackx.omemo.OmemoInitializer org.jivesoftware.smackx.ox.util.OpenPgpInitializer diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/XmppConnectionManager.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/XmppConnectionManager.java index 204562fd4..4f5e3d6c1 100644 --- a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/XmppConnectionManager.java +++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/XmppConnectionManager.java @@ -44,6 +44,7 @@ import org.jivesoftware.smack.tcp.XMPPTCPConnection; import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration; import org.jivesoftware.smack.util.MultiMap; import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smack.websocket.XmppWebsocketTransportModuleDescriptor; import org.jivesoftware.smackx.admin.ServiceAdministrationManager; import org.jivesoftware.smackx.iqregister.AccountManager; @@ -86,6 +87,15 @@ public class XmppConnectionManager { .applyExtraConfguration(cb -> cb.removeModule(CompressionModuleDescriptor.class)) .build() ); + addConnectionDescriptor( + XmppConnectionDescriptor.buildWith(ModularXmppClientToServerConnection.class, ModularXmppClientToServerConnectionConfiguration.class, ModularXmppClientToServerConnectionConfiguration.Builder.class) + .withNickname("modular-websocket") + .applyExtraConfguration(cb -> { + cb.removeAllModules(); + cb.addModule(XmppWebsocketTransportModuleDescriptor.class); + }) + .build() + ); } catch (NoSuchMethodException | SecurityException e) { throw new AssertionError(e); } diff --git a/smack-java8-full/build.gradle b/smack-java8-full/build.gradle index 8586a0005..5fbf1ddc8 100644 --- a/smack-java8-full/build.gradle +++ b/smack-java8-full/build.gradle @@ -12,6 +12,7 @@ dependencies { api project(':smack-openpgp') api project(':smack-resolver-minidns') api project(':smack-resolver-minidns-dox') + api project(':smack-websocket') api project(':smack-tcp') testImplementation 'com.google.guava:guava:28.2-jre' diff --git a/smack-java8-full/src/test/resources/state-graph.dot b/smack-java8-full/src/test/resources/state-graph.dot index 650f3bff6..8c41183a5 100644 --- a/smack-java8-full/src/test/resources/state-graph.dot +++ b/smack-java8-full/src/test/resources/state-graph.dot @@ -1,6 +1,6 @@ digraph { "Disconnected" -> "LookupRemoteConnectionEndpoints"; - "LookupRemoteConnectionEndpoints" -> "EstablishingTcpConnection"; + "LookupRemoteConnectionEndpoints" -> "EstablishingTcpConnection" [xlabel="1"]; "EstablishingTcpConnection" -> "EstablishTls (RFC 6120 § 5)" [xlabel="1"]; "EstablishTls (RFC 6120 § 5)" -> "ConnectedButUnauthenticated"; "ConnectedButUnauthenticated" -> "Bind2 (XEP-0386)" [xlabel="1"]; @@ -32,5 +32,7 @@ digraph { "ConnectedButUnauthenticated" -> "InstantShutdown" [xlabel="5"]; "ConnectedButUnauthenticated" [ style=filled ] "EstablishingTcpConnection" -> "ConnectedButUnauthenticated" [xlabel="2"]; + "LookupRemoteConnectionEndpoints" -> "EstablishingWebsocketConnection" [xlabel="2"]; + "EstablishingWebsocketConnection" -> "ConnectedButUnauthenticated"; "Disconnected" [ style=filled ] } diff --git a/smack-repl/src/main/java/org/igniterealtime/smack/smackrepl/WebsocketConnection.java b/smack-repl/src/main/java/org/igniterealtime/smack/smackrepl/WebsocketConnection.java new file mode 100644 index 000000000..035276fab --- /dev/null +++ b/smack-repl/src/main/java/org/igniterealtime/smack/smackrepl/WebsocketConnection.java @@ -0,0 +1,53 @@ +/** + * + * Copyright 2020 Aditya Borikar + * + * This file is part of smack-repl. + * + * smack-repl is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package org.igniterealtime.smack.smackrepl; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; + +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.XMPPException; + +import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection; +import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration; +import org.jivesoftware.smack.websocket.XmppWebsocketTransportModuleDescriptor; + +public class WebsocketConnection { + + public static void main(String[] args) throws SmackException, IOException, XMPPException, InterruptedException, URISyntaxException { + ModularXmppClientToServerConnectionConfiguration.Builder builder = ModularXmppClientToServerConnectionConfiguration.builder(); + builder.removeAllModules(); + builder.setXmppAddressAndPassword(args[0], args[1]); + + // Set a fallback uri into websocket transport descriptor and add this descriptor into connection builder. + XmppWebsocketTransportModuleDescriptor.Builder websocketBuilder = XmppWebsocketTransportModuleDescriptor.getBuilder(builder); + websocketBuilder.explicitlySetWebsocketEndpointAndDiscovery(new URI(args[2]), false); + builder.addModule(websocketBuilder.build()); + + ModularXmppClientToServerConnectionConfiguration config = builder.build(); + ModularXmppClientToServerConnection connection = new ModularXmppClientToServerConnection(config); + + connection.connect(); + connection.login(); + connection.disconnect(); + } +} diff --git a/smack-websocket/build.gradle b/smack-websocket/build.gradle new file mode 100644 index 000000000..43d205f36 --- /dev/null +++ b/smack-websocket/build.gradle @@ -0,0 +1,10 @@ +description = """\ +Smack for standard XMPP connections over Websockets.""" + +dependencies { + compile project(':smack-core') + + testFixturesApi(testFixtures(project(":smack-core"))) + + implementation("com.squareup.okhttp3:okhttp:4.6.0") +} diff --git a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/WebsocketConnectionAttemptState.java b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/WebsocketConnectionAttemptState.java new file mode 100644 index 000000000..ba683fc75 --- /dev/null +++ b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/WebsocketConnectionAttemptState.java @@ -0,0 +1,101 @@ +/** + * + * 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.websocket; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; + +import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal; +import org.jivesoftware.smack.websocket.XmppWebsocketTransportModule.EstablishingWebsocketConnectionState; +import org.jivesoftware.smack.websocket.implementations.AbstractWebsocket; +import org.jivesoftware.smack.websocket.implementations.WebsocketImplProvider; +import org.jivesoftware.smack.websocket.implementations.okhttp.OkHttpWebsocket; +import org.jivesoftware.smack.websocket.rce.WebsocketRemoteConnectionEndpoint; + +public final class WebsocketConnectionAttemptState { + private final ModularXmppClientToServerConnectionInternal connectionInternal; + private final XmppWebsocketTransportModule.XmppWebsocketTransport.DiscoveredWebsocketEndpoints discoveredEndpoints; + + private WebsocketRemoteConnectionEndpoint connectedEndpoint; + + WebsocketConnectionAttemptState(ModularXmppClientToServerConnectionInternal connectionInternal, + XmppWebsocketTransportModule.XmppWebsocketTransport.DiscoveredWebsocketEndpoints discoveredWebsocketEndpoints, + EstablishingWebsocketConnectionState establishingWebsocketConnectionState) { + assert discoveredWebsocketEndpoints != null; + this.connectionInternal = connectionInternal; + this.discoveredEndpoints = discoveredWebsocketEndpoints; + } + + /** + * Establish a websocket connection with one of the discoveredRemoteConnectionEndpoints.
+ * + * @return {@link AbstractWebsocket} with which connection is establised + * @throws InterruptedException if the calling thread was interrupted + * @throws WebsocketException if encounters a websocket exception + */ + AbstractWebsocket establishWebsocketConnection() throws InterruptedException, WebsocketException { + List endpoints = discoveredEndpoints.result.discoveredRemoteConnectionEndpoints; + + if (endpoints.isEmpty()) { + throw new WebsocketException(new Throwable("No Endpoints discovered to establish connection")); + } + + List connectionFailureList = new ArrayList<>(); + AbstractWebsocket websocket; + + try { + // Obtain desired websocket implementation by using WebsocketImplProvider + websocket = WebsocketImplProvider.getWebsocketImpl(OkHttpWebsocket.class, connectionInternal, discoveredEndpoints); + } catch (NoSuchMethodException | SecurityException | InstantiationException | + IllegalAccessException | IllegalArgumentException | InvocationTargetException exception) { + throw new WebsocketException(exception); + } + + // Keep iterating over available endpoints until a connection is establised or all endpoints are tried to create a connection with. + for (WebsocketRemoteConnectionEndpoint endpoint : endpoints) { + try { + websocket.connect(endpoint); + connectedEndpoint = endpoint; + break; + } catch (Throwable t) { + connectionFailureList.add(t); + + // If the number of entries in connectionFailureList is equal to the number of endpoints, + // it means that all endpoints have been tried and have been unsuccessful. + if (connectionFailureList.size() == endpoints.size()) { + WebsocketException websocketException = new WebsocketException(connectionFailureList); + throw new WebsocketException(websocketException); + } + } + } + + assert connectedEndpoint != null; + + // Return connected websocket when no failure occurs. + return websocket; + } + + /** + * Returns the connected websocket endpoint. + * + * @return connected websocket endpoint + */ + public WebsocketRemoteConnectionEndpoint getConnectedEndpoint() { + return connectedEndpoint; + } +} diff --git a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/WebsocketException.java b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/WebsocketException.java new file mode 100644 index 000000000..ba508001c --- /dev/null +++ b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/WebsocketException.java @@ -0,0 +1,38 @@ +/** + * + * 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.websocket; + +import java.util.Collections; +import java.util.List; + +public final class WebsocketException extends Exception { + private static final long serialVersionUID = 1L; + + private final List throwableList; + + public WebsocketException(List throwableList) { + this.throwableList = throwableList; + } + + public WebsocketException(Throwable throwable) { + this.throwableList = Collections.singletonList(throwable); + } + + public List getThrowableList() { + return throwableList; + } +} diff --git a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/WebsocketInitializer.java b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/WebsocketInitializer.java new file mode 100644 index 000000000..18b3b17b1 --- /dev/null +++ b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/WebsocketInitializer.java @@ -0,0 +1,28 @@ +/** + * + * 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.websocket; + +import org.jivesoftware.smack.SmackConfiguration; +import org.jivesoftware.smack.initializer.UrlInitializer; + +public final class WebsocketInitializer extends UrlInitializer { + + static { + SmackConfiguration.addModule(XmppWebsocketTransportModuleDescriptor.class); + } + +} diff --git a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/XmppWebsocketTransportModule.java b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/XmppWebsocketTransportModule.java new file mode 100644 index 000000000..77f16f27c --- /dev/null +++ b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/XmppWebsocketTransportModule.java @@ -0,0 +1,325 @@ +/** + * + * 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.websocket; + +import java.io.IOException; +import java.net.URI; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Queue; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.net.ssl.SSLSession; + +import org.jivesoftware.smack.AsyncButOrdered; +import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode; +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.SmackFuture; +import org.jivesoftware.smack.SmackFuture.InternalSmackFuture; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.ConnectedButUnauthenticatedStateDescriptor; +import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.LookupRemoteConnectionEndpointsStateDescriptor; +import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration; +import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionModule; +import org.jivesoftware.smack.c2s.StreamOpenAndCloseFactory; +import org.jivesoftware.smack.c2s.XmppClientToServerTransport; +import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal; +import org.jivesoftware.smack.c2s.internal.WalkStateGraphContext; +import org.jivesoftware.smack.fsm.State; +import org.jivesoftware.smack.fsm.StateDescriptor; +import org.jivesoftware.smack.fsm.StateTransitionResult; +import org.jivesoftware.smack.fsm.StateTransitionResult.AttemptResult; +import org.jivesoftware.smack.packet.AbstractStreamClose; +import org.jivesoftware.smack.packet.AbstractStreamOpen; +import org.jivesoftware.smack.packet.TopLevelStreamElement; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smack.util.rce.RemoteConnectionEndpointLookupFailure; +import org.jivesoftware.smack.websocket.XmppWebsocketTransportModule.XmppWebsocketTransport.DiscoveredWebsocketEndpoints; +import org.jivesoftware.smack.websocket.elements.WebsocketCloseElement; +import org.jivesoftware.smack.websocket.elements.WebsocketOpenElement; +import org.jivesoftware.smack.websocket.implementations.AbstractWebsocket; +import org.jivesoftware.smack.websocket.rce.WebsocketRemoteConnectionEndpoint; +import org.jivesoftware.smack.websocket.rce.WebsocketRemoteConnectionEndpointLookup; +import org.jivesoftware.smack.websocket.rce.WebsocketRemoteConnectionEndpointLookup.Result; + +import org.jxmpp.jid.DomainBareJid; +import org.jxmpp.jid.impl.JidCreate; +import org.jxmpp.stringprep.XmppStringprepException; + +/** + * The websocket transport module that goes with Smack's modular architecture. + */ +public final class XmppWebsocketTransportModule + extends ModularXmppClientToServerConnectionModule { + private final XmppWebsocketTransport websocketTransport; + + private AbstractWebsocket websocket; + + protected XmppWebsocketTransportModule(XmppWebsocketTransportModuleDescriptor moduleDescriptor, + ModularXmppClientToServerConnectionInternal connectionInternal) { + super(moduleDescriptor, connectionInternal); + + websocketTransport = new XmppWebsocketTransport(connectionInternal); + } + + @Override + protected XmppWebsocketTransport getTransport() { + return websocketTransport; + } + + static final class EstablishingWebsocketConnectionStateDescriptor extends StateDescriptor { + private EstablishingWebsocketConnectionStateDescriptor() { + super(XmppWebsocketTransportModule.EstablishingWebsocketConnectionState.class); + addPredeccessor(LookupRemoteConnectionEndpointsStateDescriptor.class); + addSuccessor(ConnectedButUnauthenticatedStateDescriptor.class); + + // This states preference to TCP transports over this Websocket transport implementation. + declareInferiorityTo("org.jivesoftware.smack.tcp.XmppTcpTransportModule$EstablishingTcpConnectionStateDescriptor"); + } + + @Override + protected State constructState(ModularXmppClientToServerConnectionInternal connectionInternal) { + XmppWebsocketTransportModule websocketTransportModule = connectionInternal.connection.getConnectionModuleFor( + XmppWebsocketTransportModuleDescriptor.class); + return websocketTransportModule.constructEstablishingWebsocketConnectionState(this, connectionInternal); + } + } + + final class EstablishingWebsocketConnectionState extends State { + protected EstablishingWebsocketConnectionState(StateDescriptor stateDescriptor, + ModularXmppClientToServerConnectionInternal connectionInternal) { + super(stateDescriptor, connectionInternal); + } + + @Override + public AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) + throws IOException, SmackException, InterruptedException, XMPPException { + WebsocketConnectionAttemptState connectionAttemptState = new WebsocketConnectionAttemptState( + connectionInternal, discoveredWebsocketEndpoints, this); + + try { + websocket = connectionAttemptState.establishWebsocketConnection(); + } catch (InterruptedException | WebsocketException e) { + StateTransitionResult.Failure failure = new StateTransitionResult.FailureCausedByException(e); + return failure; + } + + connectionInternal.setTransport(websocketTransport); + + WebsocketRemoteConnectionEndpoint connectedEndpoint = connectionAttemptState.getConnectedEndpoint(); + + // Construct a WebsocketConnectedResult using the connected endpoint. + return new WebsocketConnectedResult(connectedEndpoint); + } + } + + public EstablishingWebsocketConnectionState constructEstablishingWebsocketConnectionState( + EstablishingWebsocketConnectionStateDescriptor establishingWebsocketConnectionStateDescriptor, + ModularXmppClientToServerConnectionInternal connectionInternal) { + return new EstablishingWebsocketConnectionState(establishingWebsocketConnectionStateDescriptor, + connectionInternal); + } + + public static final class WebsocketConnectedResult extends StateTransitionResult.Success { + final WebsocketRemoteConnectionEndpoint connectedEndpoint; + + public WebsocketConnectedResult(WebsocketRemoteConnectionEndpoint connectedEndpoint) { + super("Websocket connection establised with endpoint: " + connectedEndpoint.getWebsocketEndpoint()); + this.connectedEndpoint = connectedEndpoint; + } + } + + private DiscoveredWebsocketEndpoints discoveredWebsocketEndpoints; + + /** + * Transport class for {@link ModularXmppClientToServerConnectionModule}'s websocket implementation. + */ + public final class XmppWebsocketTransport extends XmppClientToServerTransport { + + AsyncButOrdered> asyncButOrderedOutgoingElementsQueue; + + protected XmppWebsocketTransport(ModularXmppClientToServerConnectionInternal connectionInternal) { + super(connectionInternal); + asyncButOrderedOutgoingElementsQueue = new AsyncButOrdered>(); + } + + @Override + protected void resetDiscoveredConnectionEndpoints() { + discoveredWebsocketEndpoints = null; + } + + @Override + protected List> lookupConnectionEndpoints() { + // Assert that there are no stale discovered endpoints prior performing the lookup. + assert discoveredWebsocketEndpoints == null; + + InternalSmackFuture websocketEndpointsLookupFuture = new InternalSmackFuture<>(); + + connectionInternal.asyncGo(() -> { + + WebsocketRemoteConnectionEndpoint providedEndpoint = null; + + // Check if there is a websocket endpoint already configured. + URI uri = moduleDescriptor.getExplicitlyProvidedUri(); + if (uri != null) { + providedEndpoint = new WebsocketRemoteConnectionEndpoint(uri); + } + + if (!moduleDescriptor.isWebsocketEndpointDiscoveryEnabled()) { + // If discovery is disabled, assert that the provided endpoint isn't null. + assert providedEndpoint != null; + + SecurityMode mode = connectionInternal.connection.getConfiguration().getSecurityMode(); + if ((providedEndpoint.isSecureEndpoint() && + mode.equals(SecurityMode.disabled)) + || (!providedEndpoint.isSecureEndpoint() && + mode.equals(SecurityMode.required))) { + throw new IllegalStateException("Explicitly configured uri: " + providedEndpoint.getWebsocketEndpoint().toString() + + " does not comply with the configured security mode: " + mode); + } + + // Generate Result for explicitly configured endpoint. + Result manualResult = new Result(Arrays.asList(providedEndpoint), null); + + LookupConnectionEndpointsResult endpointsResult = new DiscoveredWebsocketEndpoints(manualResult); + + websocketEndpointsLookupFuture.setResult(endpointsResult); + } else { + DomainBareJid host = connectionInternal.connection.getXMPPServiceDomain(); + ModularXmppClientToServerConnectionConfiguration configuration = connectionInternal.connection.getConfiguration(); + SecurityMode mode = configuration.getSecurityMode(); + + // Fetch remote endpoints. + Result xep0156result = WebsocketRemoteConnectionEndpointLookup.lookup(host, mode); + + List discoveredEndpoints = xep0156result.discoveredRemoteConnectionEndpoints; + + // Generate result considering both manual and fetched endpoints. + Result finalResult = new Result(discoveredEndpoints, xep0156result.getLookupFailures()); + + LookupConnectionEndpointsResult endpointsResult = new DiscoveredWebsocketEndpoints(finalResult); + + websocketEndpointsLookupFuture.setResult(endpointsResult); + } + }); + + return Collections.singletonList(websocketEndpointsLookupFuture); + } + + @Override + protected void loadConnectionEndpoints(LookupConnectionEndpointsSuccess lookupConnectionEndpointsSuccess) { + discoveredWebsocketEndpoints = (DiscoveredWebsocketEndpoints) lookupConnectionEndpointsSuccess; + } + + @Override + protected void afterFiltersClosed() { + } + + @Override + protected void disconnect() { + websocket.disconnect(1000, "Websocket closed normally"); + } + + @Override + protected void notifyAboutNewOutgoingElements() { + Queue outgoingElementsQueue = connectionInternal.outgoingElementsQueue; + asyncButOrderedOutgoingElementsQueue.performAsyncButOrdered(outgoingElementsQueue, () -> { + // Once new outgoingElement is notified, send the top level stream element obtained by polling. + TopLevelStreamElement topLevelStreamElement = outgoingElementsQueue.poll(); + websocket.send(topLevelStreamElement); + }); + } + + @Override + public SSLSession getSslSession() { + return websocket.getSSLSession(); + } + + @Override + public boolean isTransportSecured() { + return websocket.isConnectionSecure(); + } + + @Override + public boolean isConnected() { + return websocket.isConnected(); + } + + @Override + public Stats getStats() { + return null; + } + + @Override + public StreamOpenAndCloseFactory getStreamOpenAndCloseFactory() { + return new StreamOpenAndCloseFactory() { + @Override + public AbstractStreamOpen createStreamOpen(CharSequence to, CharSequence from, String id, String lang) { + try { + return new WebsocketOpenElement(JidCreate.domainBareFrom(to)); + } catch (XmppStringprepException e) { + Logger.getAnonymousLogger().log(Level.WARNING, "Couldn't create OpenElement", e); + return null; + } + } + @Override + public AbstractStreamClose createStreamClose() { + return new WebsocketCloseElement(); + } + }; + } + + /** + * Contains {@link Result} for successfully discovered endpoints. + */ + public final class DiscoveredWebsocketEndpoints implements LookupConnectionEndpointsSuccess { + final WebsocketRemoteConnectionEndpointLookup.Result result; + + DiscoveredWebsocketEndpoints(Result result) { + assert result != null; + this.result = result; + } + + public WebsocketRemoteConnectionEndpointLookup.Result getResult() { + return result; + } + } + + /** + * Contains list of {@link RemoteConnectionEndpointLookupFailure} when no endpoint + * could be found during http lookup. + */ + final class WebsocketEndpointsDiscoveryFailed implements LookupConnectionEndpointsFailed { + final List lookupFailures; + + WebsocketEndpointsDiscoveryFailed( + WebsocketRemoteConnectionEndpointLookup.Result result) { + assert result != null; + lookupFailures = Collections.unmodifiableList(result.lookupFailures); + } + + @Override + public String toString() { + StringBuilder str = new StringBuilder(); + StringUtils.appendTo(lookupFailures, str); + return str.toString(); + } + } + } +} diff --git a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/XmppWebsocketTransportModuleDescriptor.java b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/XmppWebsocketTransportModuleDescriptor.java new file mode 100644 index 000000000..07a3e2e84 --- /dev/null +++ b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/XmppWebsocketTransportModuleDescriptor.java @@ -0,0 +1,136 @@ +/** + * + * 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.websocket; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.HashSet; +import java.util.Set; + +import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection; +import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration; +import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionModule; +import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionModuleDescriptor; +import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal; +import org.jivesoftware.smack.fsm.StateDescriptor; +import org.jivesoftware.smack.util.Objects; +import org.jivesoftware.smack.websocket.XmppWebsocketTransportModule.EstablishingWebsocketConnectionStateDescriptor; + +/** + * The descriptor class for {@link XmppWebsocketTransportModule}. + *
+ * To add {@link XmppWebsocketTransportModule} to {@link ModularXmppClientToServerConnection}, + * use {@link ModularXmppClientToServerConnectionConfiguration.Builder#addModule(ModularXmppClientToServerConnectionModuleDescriptor)}. + */ +public final class XmppWebsocketTransportModuleDescriptor extends ModularXmppClientToServerConnectionModuleDescriptor { + private boolean performWebsocketEndpointDiscovery; + private URI uri; + + public XmppWebsocketTransportModuleDescriptor(Builder builder) { + this.performWebsocketEndpointDiscovery = builder.performWebsocketEndpointDiscovery; + this.uri = builder.uri; + } + + /** + * Returns true if websocket endpoint discovery is true, returns false otherwise. + * @return boolean + */ + public boolean isWebsocketEndpointDiscoveryEnabled() { + return performWebsocketEndpointDiscovery; + } + + /** + * Returns explicitly configured websocket endpoint uri. + * @return uri + */ + public URI getExplicitlyProvidedUri() { + return uri; + } + + @Override + protected Set> getStateDescriptors() { + Set> res = new HashSet<>(); + res.add(EstablishingWebsocketConnectionStateDescriptor.class); + return res; + } + + @Override + protected ModularXmppClientToServerConnectionModule constructXmppConnectionModule( + ModularXmppClientToServerConnectionInternal connectionInternal) { + return new XmppWebsocketTransportModule(this, connectionInternal); + } + + /** + * Returns a new instance of {@link Builder}. + *
+ * @return Builder + * @param connectionConfigurationBuilder {@link ModularXmppClientToServerConnectionConfiguration.Builder}. + */ + public static Builder getBuilder( + ModularXmppClientToServerConnectionConfiguration.Builder connectionConfigurationBuilder) { + return new Builder(connectionConfigurationBuilder); + } + + /** + * Builder class for {@link XmppWebsocketTransportModuleDescriptor}. + *
+ * To obtain an instance of {@link XmppWebsocketTransportModuleDescriptor.Builder}, use {@link XmppWebsocketTransportModuleDescriptor#getBuilder(ModularXmppClientToServerConnectionConfiguration.Builder)} method. + *
+ * Use {@link Builder#explicitlySetWebsocketEndpoint(URI)} to configure the URI of an endpoint as a backup in case connection couldn't be established with endpoints through http lookup. + *
+ * Use {@link Builder#explicitlySetWebsocketEndpointAndDiscovery(URI, boolean)} to configure endpoint and disallow websocket endpoint discovery through http lookup. + * By default, {@link Builder#performWebsocketEndpointDiscovery} is set to true. + *
+ * Use {@link Builder#build()} to obtain {@link XmppWebsocketTransportModuleDescriptor}. + */ + public static final class Builder extends ModularXmppClientToServerConnectionModuleDescriptor.Builder { + private boolean performWebsocketEndpointDiscovery = true; + private URI uri; + + private Builder( + ModularXmppClientToServerConnectionConfiguration.Builder connectionConfigurationBuilder) { + super(connectionConfigurationBuilder); + } + + public Builder explicitlySetWebsocketEndpoint(URI endpoint) { + return explicitlySetWebsocketEndpointAndDiscovery(endpoint, true); + } + + public Builder explicitlySetWebsocketEndpointAndDiscovery(URI endpoint, boolean performWebsocketEndpointDiscovery) { + Objects.requireNonNull(endpoint, "Provided endpoint URI must not be null"); + this.uri = endpoint; + this.performWebsocketEndpointDiscovery = performWebsocketEndpointDiscovery; + return this; + } + + public Builder explicitlySetWebsocketEndpoint(CharSequence endpoint) throws URISyntaxException { + URI endpointUri = new URI(endpoint.toString()); + return explicitlySetWebsocketEndpointAndDiscovery(endpointUri, true); + } + + public Builder explicitlySetWebsocketEndpoint(CharSequence endpoint, boolean performWebsocketEndpointDiscovery) + throws URISyntaxException { + URI endpointUri = new URI(endpoint.toString()); + return explicitlySetWebsocketEndpointAndDiscovery(endpointUri, performWebsocketEndpointDiscovery); + } + + @Override + public ModularXmppClientToServerConnectionModuleDescriptor build() { + return new XmppWebsocketTransportModuleDescriptor(this); + } + } +} diff --git a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/elements/AbstractWebsocketNonza.java b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/elements/AbstractWebsocketNonza.java new file mode 100644 index 000000000..3a95124ae --- /dev/null +++ b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/elements/AbstractWebsocketNonza.java @@ -0,0 +1,47 @@ +/** + * + * 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.websocket.elements; + +import org.jivesoftware.smack.packet.Nonza; +import org.jivesoftware.smack.packet.XmlEnvironment; +import org.jivesoftware.smack.util.XmlStringBuilder; + +import org.jxmpp.jid.DomainBareJid; + +public abstract class AbstractWebsocketNonza implements Nonza { + public static final String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-framing"; + private static final String VERSION = "1.0"; + private final DomainBareJid to; + + public AbstractWebsocketNonza(DomainBareJid jid) { + this.to = jid; + } + + @Override + public String getNamespace() { + return NAMESPACE; + } + + @Override + public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) { + XmlStringBuilder xml = new XmlStringBuilder(this, xmlEnvironment); + xml.attribute("to", to.toString()); + xml.attribute("version", VERSION); + xml.closeEmptyElement(); + return xml; + } +} diff --git a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/elements/WebsocketCloseElement.java b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/elements/WebsocketCloseElement.java new file mode 100644 index 000000000..d455336c0 --- /dev/null +++ b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/elements/WebsocketCloseElement.java @@ -0,0 +1,49 @@ +/** + * + * 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.websocket.elements; + +import javax.xml.namespace.QName; + +import org.jivesoftware.smack.packet.AbstractStreamClose; +import org.jivesoftware.smack.packet.XmlEnvironment; +import org.jivesoftware.smack.util.XmlStringBuilder; + +public final class WebsocketCloseElement extends AbstractStreamClose { + public static final String ELEMENT = "close"; + public static final String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-framing"; + public static final QName QNAME = new QName(NAMESPACE, ELEMENT); + + public WebsocketCloseElement() { + } + + @Override + public String getElementName() { + return ELEMENT; + } + + @Override + public String getNamespace() { + return NAMESPACE; + } + + @Override + public CharSequence toXML(XmlEnvironment xmlEnvironment) { + XmlStringBuilder xml = new XmlStringBuilder(this); + xml.closeEmptyElement(); + return xml; + } +} diff --git a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/elements/WebsocketOpenElement.java b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/elements/WebsocketOpenElement.java new file mode 100644 index 000000000..5660018cf --- /dev/null +++ b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/elements/WebsocketOpenElement.java @@ -0,0 +1,54 @@ +/** + * + * 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.websocket.elements; + +import javax.xml.namespace.QName; + +import org.jivesoftware.smack.packet.AbstractStreamOpen; +import org.jivesoftware.smack.packet.StreamOpen.StreamContentNamespace; +import org.jivesoftware.smack.packet.XmlEnvironment; +import org.jivesoftware.smack.util.XmlStringBuilder; + +import org.jxmpp.jid.DomainBareJid; + +public final class WebsocketOpenElement extends AbstractStreamOpen { + public static final String ELEMENT = "open"; + public static final String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-framing"; + public static final QName QNAME = new QName(NAMESPACE, ELEMENT); + + public WebsocketOpenElement(DomainBareJid to) { + super(to, null, null, null, StreamContentNamespace.client); + } + + @Override + public String getElementName() { + return ELEMENT; + } + + @Override + public String getNamespace() { + return NAMESPACE; + } + + @Override + public CharSequence toXML(XmlEnvironment xmlEnvironment) { + XmlStringBuilder xml = new XmlStringBuilder(this); + addCommonAttributes(xml); + xml.closeEmptyElement(); + return xml; + } +} diff --git a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/elements/package-info.java b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/elements/package-info.java new file mode 100644 index 000000000..4b992c9c4 --- /dev/null +++ b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/elements/package-info.java @@ -0,0 +1,20 @@ +/** + * + * 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. + */ +/** + * This package contains Stanzas required to open and close stream. + */ +package org.jivesoftware.smack.websocket.elements; diff --git a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/implementations/AbstractWebsocket.java b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/implementations/AbstractWebsocket.java new file mode 100644 index 000000000..8341c2f1b --- /dev/null +++ b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/implementations/AbstractWebsocket.java @@ -0,0 +1,63 @@ +/** + * + * 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.websocket.implementations; + +import javax.net.ssl.SSLSession; + +import org.jivesoftware.smack.packet.TopLevelStreamElement; +import org.jivesoftware.smack.websocket.rce.WebsocketRemoteConnectionEndpoint; + +public abstract class AbstractWebsocket { + + protected enum WebsocketConnectionPhase { + openFrameSent, + exchangingTopLevelStreamElements + } + + protected static String getStreamFromOpenElement(String openElement) { + String streamElement = openElement.replaceFirst("\\A\\s*\\z", ">"); + return streamElement; + } + + protected static boolean isOpenElement(String text) { + if (text.startsWith("")) { + return true; + } + return false; + } + + public abstract void connect(WebsocketRemoteConnectionEndpoint endpoint) throws Throwable; + + public abstract void send(TopLevelStreamElement element); + + public abstract void disconnect(int code, String message); + + public abstract boolean isConnectionSecure(); + + public abstract SSLSession getSSLSession(); + + public abstract boolean isConnected(); +} diff --git a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/implementations/WebsocketImplProvider.java b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/implementations/WebsocketImplProvider.java new file mode 100644 index 000000000..b67ac236d --- /dev/null +++ b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/implementations/WebsocketImplProvider.java @@ -0,0 +1,35 @@ +/** + * + * 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.websocket.implementations; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal; +import org.jivesoftware.smack.util.Objects; +import org.jivesoftware.smack.websocket.XmppWebsocketTransportModule.XmppWebsocketTransport.DiscoveredWebsocketEndpoints; + +public final class WebsocketImplProvider { + + public static AbstractWebsocket getWebsocketImpl(Class websocketImpl, ModularXmppClientToServerConnectionInternal connectionInternal, DiscoveredWebsocketEndpoints discoveredWebsocketEndpoints) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { + Objects.requireNonNull(connectionInternal, "ConnectionInternal cannot be null"); + + // Creates an instance of the constructor for the desired websocket implementation. + Constructor constructor = websocketImpl.getConstructor(ModularXmppClientToServerConnectionInternal.class, DiscoveredWebsocketEndpoints.class); + return (AbstractWebsocket) constructor.newInstance(connectionInternal, discoveredWebsocketEndpoints); + } +} diff --git a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/implementations/okhttp/LoggingInterceptor.java b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/implementations/okhttp/LoggingInterceptor.java new file mode 100644 index 000000000..c76684b98 --- /dev/null +++ b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/implementations/okhttp/LoggingInterceptor.java @@ -0,0 +1,90 @@ +/** + * + * 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.websocket.implementations.okhttp; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.Iterator; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.jivesoftware.smack.debugger.SmackDebugger; + +import okhttp3.Headers; +import okhttp3.Response; + +import org.jxmpp.xml.splitter.Utf8ByteXmppXmlSplitter; +import org.jxmpp.xml.splitter.XmlPrettyPrinter; +import org.jxmpp.xml.splitter.XmppXmlSplitter; + +public final class LoggingInterceptor { + private static final Logger LOGGER = Logger.getAnonymousLogger(); + private static final int MAX_ELEMENT_SIZE = 64 * 1024; + private final SmackDebugger debugger; + private final Utf8ByteXmppXmlSplitter incomingTextSplitter; + private final Utf8ByteXmppXmlSplitter outgoingTextSplitter; + + public LoggingInterceptor(SmackDebugger smackDebugger) { + this.debugger = smackDebugger; + + XmlPrettyPrinter incomingTextPrinter = XmlPrettyPrinter.builder() + .setPrettyWriter(sb -> debugger.incomingStreamSink(sb)) + .setTabWidth(4) + .build(); + XmppXmlSplitter incomingXmlSplitter = new XmppXmlSplitter(MAX_ELEMENT_SIZE, null, + incomingTextPrinter); + incomingTextSplitter = new Utf8ByteXmppXmlSplitter(incomingXmlSplitter); + + XmlPrettyPrinter outgoingTextPrinter = XmlPrettyPrinter.builder() + .setPrettyWriter(sb -> debugger.outgoingStreamSink(sb)) + .setTabWidth(4) + .build(); + XmppXmlSplitter outgoingXmlSplitter = new XmppXmlSplitter(MAX_ELEMENT_SIZE, null, + outgoingTextPrinter); + outgoingTextSplitter = new Utf8ByteXmppXmlSplitter(outgoingXmlSplitter); + } + + // Open response received here isn't in the form of an Xml an so, there isn't much to format. + public void interceptOpenResponse(Response response) { + Headers headers = response.headers(); + Iterator iterator = headers.iterator(); + StringBuilder sb = new StringBuilder(); + sb.append("Received headers:"); + while (iterator.hasNext()) { + sb.append("\n\t" + iterator.next()); + } + debugger.incomingStreamSink(sb); + } + + public void interceptReceivedText(String text) { + try { + incomingTextSplitter.write(text.getBytes(Charset.defaultCharset())); + } catch (IOException e) { + // Connections shouldn't be terminated due to exceptions encountered during debugging. hence only log them. + LOGGER.log(Level.WARNING, "IOException encountered while parsing received text: " + text, e); + } + } + + public void interceptSentText(String text) { + try { + outgoingTextSplitter.write(text.getBytes(Charset.defaultCharset())); + } catch (IOException e) { + // Connections shouldn't be terminated due to exceptions encountered during debugging, hence only log them. + LOGGER.log(Level.WARNING, "IOException encountered while parsing outgoing text: " + text, e); + } + } +} diff --git a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/implementations/okhttp/OkHttpWebsocket.java b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/implementations/okhttp/OkHttpWebsocket.java new file mode 100644 index 000000000..0d898d849 --- /dev/null +++ b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/implementations/okhttp/OkHttpWebsocket.java @@ -0,0 +1,179 @@ +/** + * + * 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.websocket.implementations.okhttp; + +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.net.ssl.SSLSession; + +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal; +import org.jivesoftware.smack.packet.TopLevelStreamElement; +import org.jivesoftware.smack.util.PacketParserUtils; +import org.jivesoftware.smack.websocket.WebsocketException; +import org.jivesoftware.smack.websocket.XmppWebsocketTransportModule.XmppWebsocketTransport.DiscoveredWebsocketEndpoints; +import org.jivesoftware.smack.websocket.elements.WebsocketOpenElement; +import org.jivesoftware.smack.websocket.implementations.AbstractWebsocket; +import org.jivesoftware.smack.websocket.rce.WebsocketRemoteConnectionEndpoint; +import org.jivesoftware.smack.xml.XmlPullParserException; + +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.WebSocket; +import okhttp3.WebSocketListener; + +public final class OkHttpWebsocket extends AbstractWebsocket { + + private static final Logger LOGGER = Logger.getLogger(OkHttpWebsocket.class.getName()); + + private static OkHttpClient okHttpClient = null; + + private final ModularXmppClientToServerConnectionInternal connectionInternal; + private final LoggingInterceptor interceptor; + + private String openStreamHeader; + private WebSocket currentWebsocket; + private WebsocketConnectionPhase phase; + private WebsocketRemoteConnectionEndpoint connectedEndpoint; + + public OkHttpWebsocket(ModularXmppClientToServerConnectionInternal connectionInternal, + DiscoveredWebsocketEndpoints discoveredWebsocketEndpoints) { + this.connectionInternal = connectionInternal; + + if (okHttpClient == null) { + // Creates an instance of okHttp client. + OkHttpClient.Builder builder = new OkHttpClient.Builder(); + okHttpClient = builder.build(); + } + // Add some mechanism to enable and disable this interceptor. + if (connectionInternal.smackDebugger != null) { + interceptor = new LoggingInterceptor(connectionInternal.smackDebugger); + } else { + interceptor = null; + } + } + + @Override + public void connect(WebsocketRemoteConnectionEndpoint endpoint) throws InterruptedException, SmackException, XMPPException { + final String currentUri = endpoint.getWebsocketEndpoint().toString(); + Request request = new Request.Builder() + .url(currentUri) + .header("Sec-WebSocket-Protocol", "xmpp") + .build(); + + WebSocketListener listener = new WebSocketListener() { + + @Override + public void onOpen(WebSocket webSocket, Response response) { + LOGGER.log(Level.FINER, "Websocket is open"); + phase = WebsocketConnectionPhase.openFrameSent; + if (interceptor != null) { + interceptor.interceptOpenResponse(response); + } + send(new WebsocketOpenElement(connectionInternal.connection.getXMPPServiceDomain())); + } + + @Override + public void onMessage(WebSocket webSocket, String text) { + if (interceptor != null) { + interceptor.interceptReceivedText(text); + } + if (isCloseElement(text)) { + connectionInternal.onStreamClosed(); + return; + } + + String closingStream = "
"; + switch (phase) { + case openFrameSent: + if (isOpenElement(text)) { + // Converts the element received into element. + openStreamHeader = getStreamFromOpenElement(text); + phase = WebsocketConnectionPhase.exchangingTopLevelStreamElements; + + try { + connectionInternal.onStreamOpen(PacketParserUtils.getParserFor(openStreamHeader)); + } catch (XmlPullParserException | IOException e) { + LOGGER.log(Level.WARNING, "Exception caught:", e); + } + } else { + LOGGER.log(Level.WARNING, "Unexpected Frame received", text); + } + break; + case exchangingTopLevelStreamElements: + connectionInternal.parseAndProcessElement(openStreamHeader + text + closingStream); + break; + default: + LOGGER.log(Level.INFO, "Default text: " + text); + } + } + + @Override + public void onFailure(WebSocket webSocket, Throwable t, Response response) { + LOGGER.log(Level.INFO, "Exception caught", t); + WebsocketException websocketException = new WebsocketException(t); + if (connectionInternal.connection.isConnected()) { + connectionInternal.notifyConnectionError(websocketException); + } else { + connectionInternal.setCurrentConnectionExceptionAndNotify(websocketException); + } + } + }; + + // Creates an instance of websocket through okHttpClient. + currentWebsocket = okHttpClient.newWebSocket(request, listener); + + // Open a new stream and wait until features are received. + connectionInternal.waitForFeaturesReceived("Waiting to receive features"); + + connectedEndpoint = endpoint; + } + + @Override + public void send(TopLevelStreamElement element) { + String textToBeSent = element.toXML().toString(); + if (interceptor != null) { + interceptor.interceptSentText(textToBeSent); + } + currentWebsocket.send(textToBeSent); + } + + @Override + public void disconnect(int code, String message) { + currentWebsocket.close(code, message); + LOGGER.log(Level.INFO, "Websocket has been closed with message: " + message); + } + + @Override + public boolean isConnectionSecure() { + return connectedEndpoint.isSecureEndpoint(); + } + + @Override + public boolean isConnected() { + return connectedEndpoint == null ? false : true; + } + + @Override + public SSLSession getSSLSession() { + return null; + } +} diff --git a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/implementations/okhttp/package-info.java b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/implementations/okhttp/package-info.java new file mode 100644 index 000000000..c077b1214 --- /dev/null +++ b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/implementations/okhttp/package-info.java @@ -0,0 +1,17 @@ +/** + * + * 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.websocket.implementations.okhttp; diff --git a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/implementations/package-info.java b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/implementations/package-info.java new file mode 100644 index 000000000..4260faaaf --- /dev/null +++ b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/implementations/package-info.java @@ -0,0 +1,20 @@ +/** + * + * 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. + */ +/** + * This package contains websocket implementations to be plugged inside websocket transport. + */ +package org.jivesoftware.smack.websocket.implementations; diff --git a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/package-info.java b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/package-info.java new file mode 100644 index 000000000..1f3049bde --- /dev/null +++ b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/package-info.java @@ -0,0 +1,20 @@ +/** + * + * 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. + */ +/** + * Websocket related classes for Smack. + */ +package org.jivesoftware.smack.websocket; diff --git a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/rce/WebsocketRemoteConnectionEndpoint.java b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/rce/WebsocketRemoteConnectionEndpoint.java new file mode 100644 index 000000000..d8bb0dca3 --- /dev/null +++ b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/rce/WebsocketRemoteConnectionEndpoint.java @@ -0,0 +1,85 @@ +/** + * + * 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.websocket.rce; + +import java.net.InetAddress; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.UnknownHostException; +import java.util.Collection; +import java.util.Collections; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.jivesoftware.smack.datatypes.UInt16; +import org.jivesoftware.smack.util.rce.RemoteConnectionEndpoint; + +public final class WebsocketRemoteConnectionEndpoint implements RemoteConnectionEndpoint { + + private static final Logger LOGGER = Logger.getAnonymousLogger(); + + private final URI uri; + + public WebsocketRemoteConnectionEndpoint(String uri) throws URISyntaxException { + this(new URI(uri)); + } + + public WebsocketRemoteConnectionEndpoint(URI uri) { + this.uri = uri; + String scheme = uri.getScheme(); + if (!(scheme.equals("ws") || scheme.equals("wss"))) { + throw new IllegalArgumentException("Only allowed protocols are ws and wss"); + } + } + + public URI getWebsocketEndpoint() { + return uri; + } + + public boolean isSecureEndpoint() { + if (uri.getScheme().equals("wss")) { + return true; + } + return false; + } + + @Override + public CharSequence getHost() { + return uri.getHost(); + } + + @Override + public UInt16 getPort() { + return UInt16.from(uri.getPort()); + } + + @Override + public Collection getInetAddresses() { + try { + InetAddress address = InetAddress.getByName(getHost().toString()); + return Collections.singletonList(address); + } catch (UnknownHostException e) { + LOGGER.log(Level.INFO, "Unknown Host Exception ", e); + } + return null; + } + + @Override + public String getDescription() { + return null; + } +} diff --git a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/rce/WebsocketRemoteConnectionEndpointLookup.java b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/rce/WebsocketRemoteConnectionEndpointLookup.java new file mode 100644 index 000000000..16ffc7f83 --- /dev/null +++ b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/rce/WebsocketRemoteConnectionEndpointLookup.java @@ -0,0 +1,115 @@ +/** + * + * 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.websocket.rce; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode; +import org.jivesoftware.smack.altconnections.HttpLookupMethod; +import org.jivesoftware.smack.altconnections.HttpLookupMethod.LinkRelation; +import org.jivesoftware.smack.util.rce.RemoteConnectionEndpointLookupFailure; +import org.jivesoftware.smack.xml.XmlPullParserException; + +import org.jxmpp.jid.DomainBareJid; + +public final class WebsocketRemoteConnectionEndpointLookup { + + public static Result lookup(DomainBareJid domainBareJid, SecurityMode securityMode) { + List lookupFailures = new ArrayList<>(1); + List discoveredRemoteConnectionEndpoints = new ArrayList<>(); + + List rcUriList = null; + try { + // Look for remote connection endpoints by making use of http lookup method described inside XEP-0156. + rcUriList = HttpLookupMethod.lookup(domainBareJid, + LinkRelation.WEBSOCKET); + } catch (IOException | XmlPullParserException | URISyntaxException e) { + lookupFailures.add(new RemoteConnectionEndpointLookupFailure.HttpLookupFailure( + domainBareJid, e)); + return new Result(discoveredRemoteConnectionEndpoints, lookupFailures); + } + + if (rcUriList.isEmpty()) { + throw new IllegalStateException("No endpoints were found inside host-meta"); + } + + // Convert rcUriList to List + Iterator iterator = rcUriList.iterator(); + List rceList = new ArrayList<>(); + while (iterator.hasNext()) { + rceList.add(new WebsocketRemoteConnectionEndpoint(iterator.next())); + } + + switch (securityMode) { + case ifpossible: + // If security mode equals `if-possible`, give priority to secure endpoints over insecure endpoints. + + // Seprate secure and unsecure endpoints. + List secureEndpointsForSecurityModeIfPossible = new ArrayList<>(); + List insecureEndpointsForSecurityModeIfPossible = new ArrayList<>(); + for (WebsocketRemoteConnectionEndpoint uri : rceList) { + if (uri.isSecureEndpoint()) { + secureEndpointsForSecurityModeIfPossible.add(uri); + } else { + insecureEndpointsForSecurityModeIfPossible.add(uri); + } + } + discoveredRemoteConnectionEndpoints = secureEndpointsForSecurityModeIfPossible; + discoveredRemoteConnectionEndpoints.addAll(insecureEndpointsForSecurityModeIfPossible); + break; + case required: + case disabled: + /** + * If, SecurityMode equals to required, accept wss endpoints (secure endpoints) only or, + * if SecurityMode equals to disabled, accept ws endpoints (unsecure endpoints) only. + */ + for (WebsocketRemoteConnectionEndpoint uri : rceList) { + if ((securityMode.equals(SecurityMode.disabled) && !uri.isSecureEndpoint()) + || (securityMode.equals(SecurityMode.required) && uri.isSecureEndpoint())) { + discoveredRemoteConnectionEndpoints.add(uri); + } + } + break; + default: + } + return new Result(discoveredRemoteConnectionEndpoints, lookupFailures); + } + + public static final class Result { + public final List discoveredRemoteConnectionEndpoints; + public final List lookupFailures; + + public Result(List discoveredRemoteConnectionEndpoints, + List lookupFailures) { + this.discoveredRemoteConnectionEndpoints = discoveredRemoteConnectionEndpoints; + this.lookupFailures = lookupFailures; + } + + public List getDiscoveredRemoteConnectionEndpoints() { + return discoveredRemoteConnectionEndpoints; + } + + public List getLookupFailures() { + return lookupFailures; + } + } +} diff --git a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/rce/package-info.java b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/rce/package-info.java new file mode 100644 index 000000000..f7b0eb2e2 --- /dev/null +++ b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/rce/package-info.java @@ -0,0 +1,20 @@ +/** + * + * 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. + */ +/** + * This package contains websocket endpoint classes needed by the websocket transport. + */ +package org.jivesoftware.smack.websocket.rce; diff --git a/smack-websocket/src/test/java/org/jivesoftware/smack/websocket/WebsocketConnectionAttemptStateTest.java b/smack-websocket/src/test/java/org/jivesoftware/smack/websocket/WebsocketConnectionAttemptStateTest.java new file mode 100644 index 000000000..59cd0adc8 --- /dev/null +++ b/smack-websocket/src/test/java/org/jivesoftware/smack/websocket/WebsocketConnectionAttemptStateTest.java @@ -0,0 +1,28 @@ +/** + * + * 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.websocket; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +public class WebsocketConnectionAttemptStateTest { + @Test + public void constructorTest() { + assertThrows(AssertionError.class, () -> new WebsocketConnectionAttemptState(null, null, null)); + } +} diff --git a/smack-websocket/src/test/java/org/jivesoftware/smack/websocket/WebsocketInitializerTest.java b/smack-websocket/src/test/java/org/jivesoftware/smack/websocket/WebsocketInitializerTest.java new file mode 100644 index 000000000..ce41c594e --- /dev/null +++ b/smack-websocket/src/test/java/org/jivesoftware/smack/websocket/WebsocketInitializerTest.java @@ -0,0 +1,32 @@ +/** + * + * 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.websocket; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +public class WebsocketInitializerTest { + @Test + public void testExtensionInitializer() { + WebsocketInitializer initializer = new WebsocketInitializer(); + List exceptions = initializer.initialize(); + assertTrue(exceptions.size() == 0); + } +} diff --git a/smack-websocket/src/test/java/org/jivesoftware/smack/websocket/XmppWebsocketTransportModuleTest.java b/smack-websocket/src/test/java/org/jivesoftware/smack/websocket/XmppWebsocketTransportModuleTest.java new file mode 100644 index 000000000..ec745aa70 --- /dev/null +++ b/smack-websocket/src/test/java/org/jivesoftware/smack/websocket/XmppWebsocketTransportModuleTest.java @@ -0,0 +1,124 @@ +/** + * + * 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.websocket; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import static org.mockito.Mockito.mock; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.List; + +import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection; +import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration; +import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal; +import org.jivesoftware.smack.util.rce.RemoteConnectionEndpointLookupFailure; +import org.jivesoftware.smack.util.rce.RemoteConnectionEndpointLookupFailure.HttpLookupFailure; +import org.jivesoftware.smack.websocket.XmppWebsocketTransportModule.XmppWebsocketTransport.DiscoveredWebsocketEndpoints; +import org.jivesoftware.smack.websocket.XmppWebsocketTransportModule.XmppWebsocketTransport.WebsocketEndpointsDiscoveryFailed; +import org.jivesoftware.smack.websocket.rce.WebsocketRemoteConnectionEndpoint; +import org.jivesoftware.smack.websocket.rce.WebsocketRemoteConnectionEndpointLookup.Result; + +import org.junit.jupiter.api.Test; +import org.jxmpp.stringprep.XmppStringprepException; + +public class XmppWebsocketTransportModuleTest { + @Test + public void createWebsocketModuleConnectionInstanceTest() throws URISyntaxException, XmppStringprepException { + ModularXmppClientToServerConnectionConfiguration.Builder builder = ModularXmppClientToServerConnectionConfiguration + .builder(); + + builder.removeAllModules(); + builder.addModule(XmppWebsocketTransportModuleDescriptor.class); + builder.setXmppAddressAndPassword("user5@localhost.org", "user5"); + builder.setHost("localhost.org"); + + XmppWebsocketTransportModuleDescriptor.Builder websocketBuilder = XmppWebsocketTransportModuleDescriptor.getBuilder(builder); + websocketBuilder.explicitlySetWebsocketEndpointAndDiscovery(new URI("wss://localhost.org:7443/ws/"), false); + + ModularXmppClientToServerConnectionConfiguration config = builder.build(); + ModularXmppClientToServerConnection connection = new ModularXmppClientToServerConnection(config); + assertNotNull(connection); + } + + @Test + public void createDescriptorTest() throws URISyntaxException, XmppStringprepException { + XmppWebsocketTransportModuleDescriptor websocketTransportModuleDescriptor = getWebsocketDescriptor(); + assertNotNull(websocketTransportModuleDescriptor); + } + + @Test + public void websocketEndpointDiscoveryTest() throws URISyntaxException { + XmppWebsocketTransportModuleDescriptor websocketTransportModuleDescriptor = getWebsocketDescriptor(); + ModularXmppClientToServerConnectionInternal connectionInternal = mock(ModularXmppClientToServerConnectionInternal.class); + + XmppWebsocketTransportModule transportModule + = new XmppWebsocketTransportModule(websocketTransportModuleDescriptor, connectionInternal); + + XmppWebsocketTransportModule.XmppWebsocketTransport transport = transportModule.getTransport(); + + assertThrows(AssertionError.class, () -> transport.new DiscoveredWebsocketEndpoints(null)); + assertThrows(AssertionError.class, () -> transport.new WebsocketEndpointsDiscoveryFailed(null)); + + WebsocketRemoteConnectionEndpoint endpoint = new WebsocketRemoteConnectionEndpoint("wss://localhost.org:7443/ws/"); + + List discoveredRemoteConnectionEndpoints = new ArrayList<>(); + discoveredRemoteConnectionEndpoints.add(endpoint); + + HttpLookupFailure httpLookupFailure = new RemoteConnectionEndpointLookupFailure.HttpLookupFailure(null, null); + List failureList = new ArrayList<>(); + failureList.add(httpLookupFailure); + Result result = new Result(discoveredRemoteConnectionEndpoints, failureList); + + DiscoveredWebsocketEndpoints discoveredWebsocketEndpoints = transport.new DiscoveredWebsocketEndpoints(result); + assertNotNull(discoveredWebsocketEndpoints.getResult()); + + WebsocketEndpointsDiscoveryFailed endpointsDiscoveryFailed = transport.new WebsocketEndpointsDiscoveryFailed(result); + assertNotNull(endpointsDiscoveryFailed.toString()); + } + + @Test + public void websocketConnectedResultTest() throws URISyntaxException { + WebsocketRemoteConnectionEndpoint connectedEndpoint = new WebsocketRemoteConnectionEndpoint("wss://localhost.org:7443/ws/"); + assertNotNull(new XmppWebsocketTransportModule.WebsocketConnectedResult(connectedEndpoint)); + } + + @Test + public void lookupConnectionEndpointsTest() throws URISyntaxException { + XmppWebsocketTransportModuleDescriptor websocketTransportModuleDescriptor = getWebsocketDescriptor(); + ModularXmppClientToServerConnectionInternal connectionInternal = mock(ModularXmppClientToServerConnectionInternal.class); + + XmppWebsocketTransportModule transportModule + = new XmppWebsocketTransportModule(websocketTransportModuleDescriptor, connectionInternal); + + XmppWebsocketTransportModule.XmppWebsocketTransport transport = transportModule.getTransport(); + assertNotNull(transport.lookupConnectionEndpoints()); + + } + + private static XmppWebsocketTransportModuleDescriptor getWebsocketDescriptor() throws URISyntaxException { + ModularXmppClientToServerConnectionConfiguration.Builder builder = ModularXmppClientToServerConnectionConfiguration + .builder(); + + XmppWebsocketTransportModuleDescriptor.Builder websocketBuilder = XmppWebsocketTransportModuleDescriptor.getBuilder(builder); + websocketBuilder.explicitlySetWebsocketEndpointAndDiscovery(new URI("wss://localhost.org:7443/ws/"), false); + return (XmppWebsocketTransportModuleDescriptor) websocketBuilder.build(); + } +} diff --git a/smack-websocket/src/test/java/org/jivesoftware/smack/websocket/elements/WebsocketElementTest.java b/smack-websocket/src/test/java/org/jivesoftware/smack/websocket/elements/WebsocketElementTest.java new file mode 100644 index 000000000..7c00bbeac --- /dev/null +++ b/smack-websocket/src/test/java/org/jivesoftware/smack/websocket/elements/WebsocketElementTest.java @@ -0,0 +1,43 @@ +/** + * + * 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.websocket.elements; + +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlNotSimilar; +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; + +import org.junit.jupiter.api.Test; +import org.jxmpp.jid.impl.JidCreate; +import org.jxmpp.stringprep.XmppStringprepException; + +public class WebsocketElementTest { + private static final String OPEN_ELEMENT = ""; + private static final String CLOSE_ELEMENT = ""; + + @Test + public void websocketOpenElementTest() throws XmppStringprepException { + String openElementXml = new WebsocketOpenElement(JidCreate.domainBareFrom("foodomain.foo")).toXML().toString(); + assertXmlSimilar(OPEN_ELEMENT, openElementXml); + assertXmlNotSimilar(CLOSE_ELEMENT, new WebsocketOpenElement(JidCreate.domainBareFrom("foodomain.foo")).toXML()); + } + + @Test + public void websocketCloseElementTest() throws XmppStringprepException { + String closeElementXml = new WebsocketCloseElement().toXML().toString(); + assertXmlSimilar(CLOSE_ELEMENT, closeElementXml); + assertXmlNotSimilar(OPEN_ELEMENT, closeElementXml); + } +} diff --git a/smack-websocket/src/test/java/org/jivesoftware/smack/websocket/implementations/AbstractWebsocketTest.java b/smack-websocket/src/test/java/org/jivesoftware/smack/websocket/implementations/AbstractWebsocketTest.java new file mode 100644 index 000000000..3c71dadfa --- /dev/null +++ b/smack-websocket/src/test/java/org/jivesoftware/smack/websocket/implementations/AbstractWebsocketTest.java @@ -0,0 +1,47 @@ +/** + * + * 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.websocket.implementations; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +public final class AbstractWebsocketTest { + private static final String OPEN_ELEMENT = ""; + private static final String OPEN_STREAM = ""; + private static final String CLOSE_ELEMENT = ""; + + @Test + public void getStreamFromOpenElementTest() { + String generatedOpenStream = AbstractWebsocket.getStreamFromOpenElement(OPEN_ELEMENT); + assertEquals(generatedOpenStream, OPEN_STREAM); + } + + @Test + public void isOpenElementTest() { + assertTrue(AbstractWebsocket.isOpenElement(OPEN_ELEMENT)); + assertFalse(AbstractWebsocket.isOpenElement(OPEN_STREAM)); + } + + @Test + public void isCloseElementTest() { + assertTrue(AbstractWebsocket.isCloseElement(CLOSE_ELEMENT)); + assertFalse(AbstractWebsocket.isCloseElement(OPEN_STREAM)); + } +} diff --git a/smack-websocket/src/test/java/org/jivesoftware/smack/websocket/implementations/ProviderTest.java b/smack-websocket/src/test/java/org/jivesoftware/smack/websocket/implementations/ProviderTest.java new file mode 100644 index 000000000..842a3fe0e --- /dev/null +++ b/smack-websocket/src/test/java/org/jivesoftware/smack/websocket/implementations/ProviderTest.java @@ -0,0 +1,61 @@ +/** + * + * 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.websocket.implementations; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.lang.reflect.InvocationTargetException; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.List; + +import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal; +import org.jivesoftware.smack.websocket.XmppWebsocketTransportModule.XmppWebsocketTransport.DiscoveredWebsocketEndpoints; + +import org.jivesoftware.smack.websocket.implementations.okhttp.OkHttpWebsocket; +import org.jivesoftware.smack.websocket.rce.WebsocketRemoteConnectionEndpoint; +import org.jivesoftware.smack.websocket.rce.WebsocketRemoteConnectionEndpointLookup.Result; + +import org.junit.jupiter.api.Test; + +public class ProviderTest { + @Test + public void providerTest() { + assertThrows(IllegalArgumentException.class, () -> WebsocketImplProvider.getWebsocketImpl(OkHttpWebsocket.class, null, null)); + } + + @Test + public void getImplTest() throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, URISyntaxException { + WebsocketRemoteConnectionEndpoint endpoint = new WebsocketRemoteConnectionEndpoint("wss://localhost.org:7443/ws/"); + + List discoveredRemoteConnectionEndpoints = new ArrayList<>(); + discoveredRemoteConnectionEndpoints.add(endpoint); + + Result result = new Result(discoveredRemoteConnectionEndpoints, null); + + DiscoveredWebsocketEndpoints discoveredWebsocketEndpoints = mock(DiscoveredWebsocketEndpoints.class); + when(discoveredWebsocketEndpoints.getResult()).thenReturn(result); + + ModularXmppClientToServerConnectionInternal connectionInternal = mock(ModularXmppClientToServerConnectionInternal.class); + + assertNotNull(WebsocketImplProvider.getWebsocketImpl(OkHttpWebsocket.class, connectionInternal, discoveredWebsocketEndpoints)); + } +} diff --git a/smack-websocket/src/test/java/org/jivesoftware/smack/websocket/rce/WebsocketRemoteConnectionEndpointTest.java b/smack-websocket/src/test/java/org/jivesoftware/smack/websocket/rce/WebsocketRemoteConnectionEndpointTest.java new file mode 100644 index 000000000..534f99de2 --- /dev/null +++ b/smack-websocket/src/test/java/org/jivesoftware/smack/websocket/rce/WebsocketRemoteConnectionEndpointTest.java @@ -0,0 +1,45 @@ +/** + * + * 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.websocket.rce; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.net.URISyntaxException; + +import org.jivesoftware.smack.datatypes.UInt16; + +import org.junit.jupiter.api.Test; + +public class WebsocketRemoteConnectionEndpointTest { + @Test + public void endpointTest() throws URISyntaxException { + String endpointString = "ws://fooDomain.org:7070/ws/"; + WebsocketRemoteConnectionEndpoint endpoint = new WebsocketRemoteConnectionEndpoint(endpointString); + assertEquals("fooDomain.org", endpoint.getHost()); + assertEquals(UInt16.from(7070), endpoint.getPort()); + assertEquals(endpointString, endpoint.getWebsocketEndpoint().toString()); + } + + @Test + public void faultyEndpointTest() { + String faultyProtocolString = "wst://fooDomain.org:7070/ws/"; + assertThrows(IllegalArgumentException.class, () -> { + new WebsocketRemoteConnectionEndpoint(faultyProtocolString); + }); + } +} diff --git a/smack-websocket/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/smack-websocket/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 000000000..ca6ee9cea --- /dev/null +++ b/smack-websocket/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline \ No newline at end of file From d06e9499e86f8f7273d144e18a0953f76dd90b1d Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Fri, 28 Aug 2020 09:51:07 +0200 Subject: [PATCH 28/29] [core] Add XmppElementUtil.castOrThrow(ExtensionElement, Class) This method throws an IllegalStateException if the provided extension element is not of the expected type and hints users towards potential causes. --- .../smack/util/XmppElementUtil.java | 41 ++++++++++++++++--- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/XmppElementUtil.java b/smack-core/src/main/java/org/jivesoftware/smack/util/XmppElementUtil.java index 83d09cf21..1e4eabbc3 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/XmppElementUtil.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/XmppElementUtil.java @@ -23,7 +23,10 @@ import java.util.logging.Logger; import javax.xml.namespace.QName; +import org.jivesoftware.smack.packet.ExtensionElement; import org.jivesoftware.smack.packet.FullyQualifiedElement; +import org.jivesoftware.smack.packet.StandardExtensionElement; +import org.jivesoftware.smack.provider.ProviderManager; import org.jxmpp.util.cache.LruCache; @@ -68,21 +71,47 @@ public class XmppElementUtil { return qname; } - public static List getElementsFrom( - MultiMap elementMap, Class extensionElementClass) { + public static List getElementsFrom( + MultiMap elementMap, Class extensionElementClass) { QName qname = XmppElementUtil.getQNameFor(extensionElementClass); - List extensionElements = elementMap.getAll(qname); + List extensionElements = elementMap.getAll(qname); if (extensionElements.isEmpty()) { return Collections.emptyList(); } - List res = new ArrayList<>(extensionElements.size()); - for (E extensionElement : extensionElements) { - R e = extensionElementClass.cast(extensionElement); + List res = new ArrayList<>(extensionElements.size()); + for (ExtensionElement extensionElement : extensionElements) { + E e = castOrThrow(extensionElement, extensionElementClass); res.add(e); } return res; } + + public static E castOrThrow(ExtensionElement extensionElement, Class extensionElementClass) { + if (!extensionElementClass.isInstance(extensionElement)) { + final QName qname = getQNameFor(extensionElementClass); + + final String detailMessage; + if (extensionElement instanceof StandardExtensionElement) { + detailMessage = "because there is no according extension element provider registered with ProviderManager for " + + qname + + ". WARNING: This indicates a serious problem with your Smack setup, probably causing Smack not being able to properly initialize itself."; + } else { + Object provider = ProviderManager.getExtensionProvider(qname); + detailMessage = "because there is an inconsistency with the provider registered with ProviderManager: the active provider for " + + qname + " '" + provider.getClass() + + "' does not return instances of type " + extensionElementClass + + ", but instead returns instances of type " + extensionElement.getClass() + "."; + } + + String message = "Extension element is not of expected class '" + extensionElementClass.getName() + "', " + + detailMessage; + throw new IllegalStateException(message); + } + + return extensionElementClass.cast(extensionElement); + } + } From b09cd0605322d27367371f066d15275f5c0b05d5 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Fri, 28 Aug 2020 09:52:28 +0200 Subject: [PATCH 29/29] [core] Use XmppElementUtil.castOrThrow() in StanzaView.getExtension(Class) This means that users get now exceptions with helpful error messages instead of the dreaded ClassCastException, like java.lang.ClassCastException: org.jivesoftware.smack.packet.StandardExtensionElement cannot be cast to org.jivesoftware.smackx.mam.element.MamElements$MamResultExtension at when StanzaView.getExtension(Class) is used to retrieve the extension. --- .../main/java/org/jivesoftware/smack/packet/StanzaView.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/smack-core/src/main/java/org/jivesoftware/smack/packet/StanzaView.java b/smack-core/src/main/java/org/jivesoftware/smack/packet/StanzaView.java index 7745ecc2a..003ddbeef 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/packet/StanzaView.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/packet/StanzaView.java @@ -90,11 +90,12 @@ public interface StanzaView extends XmlLangElement { default E getExtension(Class extensionElementClass) { QName qname = XmppElementUtil.getQNameFor(extensionElementClass); ExtensionElement extensionElement = getExtension(qname); - if (!extensionElementClass.isInstance(extensionElement)) { + + if (extensionElement == null) { return null; } - return extensionElementClass.cast(extensionElement); + return XmppElementUtil.castOrThrow(extensionElement, extensionElementClass); } /**