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 e246210c0..5a2009db8 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2009 Jive Software, 2018-2020 Florian Schmaus.
+ * Copyright 2009 Jive Software, 2018-2021 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -2201,18 +2201,29 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
return SMACK_REACTOR.schedule(runnable, delay, unit, ScheduledAction.Kind.NonBlocking);
}
- protected void onStreamOpen(XmlPullParser parser) {
- // We found an opening stream.
- if ("jabber:client".equals(parser.getNamespace(null))) {
- streamId = parser.getAttributeValue("", "id");
- incomingStreamXmlEnvironment = XmlEnvironment.from(parser);
+ /**
+ * Must be called when a XMPP stream open tag is encountered. Sets values like the stream ID and the incoming stream
+ * XML environment.
+ *
+ * This method also returns a matching stream close tag. For example if the stream open is {@code }, then
+ * {@code } is returned. But if it is {@code }, then {@code } is returned.
+ * Or if it is {@code }, then {@code } is returned.
+ *
+ *
+ * @param parser an XML parser that is positioned at the start of the stream open.
+ * @return a String representing the corresponding stream end tag.
+ */
+ protected String onStreamOpen(XmlPullParser parser) {
+ assert StreamOpen.ETHERX_JABBER_STREAMS_NAMESPACE.equals(parser.getNamespace());
+ assert StreamOpen.UNPREFIXED_ELEMENT.equals(parser.getName());
- String reportedServerDomainString = parser.getAttributeValue("", "from");
- if (reportedServerDomainString == null) {
- // RFC 6120 § 4.7.1. makes no explicit statement whether or not 'from' in the stream open from the server
- // in c2s connections is required or not.
- return;
- }
+ streamId = parser.getAttributeValue("id");
+ incomingStreamXmlEnvironment = XmlEnvironment.from(parser);
+
+ String reportedServerDomainString = parser.getAttributeValue("from");
+ // RFC 6120 § 4.7.1. makes no explicit statement whether or not 'from' in the stream open from the server
+ // in c2s connections is required or not.
+ if (reportedServerDomainString != null) {
DomainBareJid reportedServerDomain;
try {
reportedServerDomain = JidCreate.domainBareFrom(reportedServerDomainString);
@@ -2226,6 +2237,12 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
+ "' as reported by server could not be transformed to a valid JID", e);
}
}
+
+ String prefix = parser.getPrefix();
+ if (StringUtils.isNotEmpty(prefix)) {
+ return "" + prefix + ":stream>";
+ }
+ return "";
}
protected final void sendStreamOpen() throws NotConnectedException, InterruptedException {
@@ -2233,7 +2250,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
// 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
// response from the server (see e.g. RFC 6120 § 9.1.1 Step 2.)
- CharSequence to = getXMPPServiceDomain();
+ DomainBareJid to = getXMPPServiceDomain();
CharSequence from = null;
CharSequence localpart = config.getUsername();
if (localpart != null) {
@@ -2247,7 +2264,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
updateOutgoingStreamXmlEnvironmentOnStreamOpen(streamOpen);
}
- protected AbstractStreamOpen getStreamOpen(CharSequence to, CharSequence from, String id, String lang) {
+ protected AbstractStreamOpen getStreamOpen(DomainBareJid to, CharSequence from, String id, String lang) {
return new StreamOpen(to, from, id, lang);
}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/SmackFuture.java b/smack-core/src/main/java/org/jivesoftware/smack/SmackFuture.java
index 08646728a..6cd744bf0 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/SmackFuture.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/SmackFuture.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2017-2020 Florian Schmaus
+ * Copyright 2017-2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -75,6 +75,10 @@ public abstract class SmackFuture implements Future,
@Override
public final synchronized boolean isDone() {
+ return result != null || exception != null || cancelled;
+ }
+
+ public final synchronized boolean wasSuccessful() {
return result != null;
}
@@ -162,6 +166,10 @@ public abstract class SmackFuture implements Future,
return result;
}
+ public E getExceptionIfAvailable() {
+ return exception;
+ }
+
protected final synchronized void maybeInvokeCallbacks() {
if (cancelled) {
return;
@@ -326,6 +334,11 @@ public abstract class SmackFuture implements Future,
return future;
}
+ public static boolean await(Collection extends SmackFuture, ?>> futures, long timeout)
+ throws InterruptedException {
+ return await(futures, timeout, TimeUnit.MILLISECONDS);
+ }
+
public static boolean await(Collection extends SmackFuture, ?>> futures, long timeout, TimeUnit unit) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(futures.size());
for (SmackFuture, ?> future : futures) {
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 7f677871e..e7a2db3aa 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
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2018-2020 Florian Schmaus
+ * Copyright 2018-2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -139,13 +139,8 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
}
@Override
- public void setCurrentConnectionExceptionAndNotify(Exception exception) {
- ModularXmppClientToServerConnection.this.setCurrentConnectionExceptionAndNotify(exception);
- }
-
- @Override
- public void onStreamOpen(XmlPullParser parser) {
- ModularXmppClientToServerConnection.this.onStreamOpen(parser);
+ public String onStreamOpen(XmlPullParser parser) {
+ return ModularXmppClientToServerConnection.this.onStreamOpen(parser);
}
@Override
@@ -571,7 +566,7 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
}
@Override
- protected AbstractStreamOpen getStreamOpen(CharSequence to, CharSequence from, String id, String lang) {
+ protected AbstractStreamOpen getStreamOpen(DomainBareJid to, CharSequence from, String id, String lang) {
StreamOpenAndCloseFactory streamOpenAndCloseFactory = activeTransport.getStreamOpenAndCloseFactory();
return streamOpenAndCloseFactory.createStreamOpen(to, from, id, lang);
}
@@ -720,6 +715,11 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
throw SmackException.NoEndpointsDiscoveredException.from(lookupFailures);
}
+ if (!lookupFailures.isEmpty()) {
+ // TODO: Put those non-fatal lookup failures into a sink of the connection so that the user is able to
+ // be aware of them.
+ }
+
// Even though the outgoing elements queue is unrelated to the lookup remote connection endpoints state, we
// do start the queue at this point. The transports will need it available, and we use the state's reset()
// function to close the queue again on failure.
@@ -1110,7 +1110,12 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
XmppClientToServerTransport.Stats stats = entry.getValue();
StringUtils.appendHeading(appendable, transportClass.getName());
- appendable.append(stats.toString()).append('\n');
+ if (stats != null) {
+ appendable.append(stats.toString());
+ } else {
+ appendable.append("No stats available.");
+ }
+ appendable.append('\n');
}
for (Map.Entry entry : filtersStats.entrySet()) {
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 f67ab5a11..03982d647 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
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2019-2020 Florian Schmaus
+ * Copyright 2019-2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -62,6 +62,10 @@ public final class ModularXmppClientToServerConnectionConfiguration extends Conn
// configuration, e.g. there is no edge from disconnected to connected.
throw new IllegalStateException(e);
}
+
+ for (ModularXmppClientToServerConnectionModuleDescriptor moduleDescriptor : moduleDescriptors) {
+ moduleDescriptor.validateConfiguration(this);
+ }
}
public void printStateGraphInDotFormat(PrintWriter pw, boolean breakStateName) {
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnectionModuleDescriptor.java b/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnectionModuleDescriptor.java
index 2c1054a36..02b4cd80a 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnectionModuleDescriptor.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnectionModuleDescriptor.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2019-2020 Florian Schmaus
+ * Copyright 2019-2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,6 +28,9 @@ public abstract class ModularXmppClientToServerConnectionModuleDescriptor {
protected abstract ModularXmppClientToServerConnectionModule extends ModularXmppClientToServerConnectionModuleDescriptor> constructXmppConnectionModule(
ModularXmppClientToServerConnectionInternal connectionInternal);
+ protected void validateConfiguration(ModularXmppClientToServerConnectionConfiguration configuration) {
+ }
+
public abstract static class Builder {
private final ModularXmppClientToServerConnectionConfiguration.Builder connectionConfigurationBuilder;
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 4a15467d2..e79f5d599 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
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2020 Aditya Borikar.
+ * Copyright 2020 Aditya Borikar, 2021 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,8 +19,12 @@ package org.jivesoftware.smack.c2s;
import org.jivesoftware.smack.packet.AbstractStreamClose;
import org.jivesoftware.smack.packet.AbstractStreamOpen;
+import org.jxmpp.jid.DomainBareJid;
+
public interface StreamOpenAndCloseFactory {
- AbstractStreamOpen createStreamOpen(CharSequence to, CharSequence from, String id, String lang);
+
+ AbstractStreamOpen createStreamOpen(DomainBareJid to, CharSequence from, String id, String lang);
AbstractStreamClose 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 4bedb51b6..a4f0db206 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
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2019-2020 Florian Schmaus
+ * Copyright 2019-2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -37,6 +37,8 @@ public abstract class XmppClientToServerTransport {
protected abstract void loadConnectionEndpoints(LookupConnectionEndpointsSuccess lookupConnectionEndpointsSuccess);
+ public abstract boolean hasUseableConnectionEndpoints();
+
/**
* Notify the transport that new outgoing data is available. Usually this method does not need to be called
* explicitly, only if the filters are modified so that they potentially produced new data.
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 139f1194f..bc8b9d441 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
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2020 Florian Schmaus
+ * Copyright 2020-2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
*/
package org.jivesoftware.smack.c2s.internal;
+import java.io.IOException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
@@ -39,8 +40,10 @@ import org.jivesoftware.smack.packet.Nonza;
import org.jivesoftware.smack.packet.TopLevelStreamElement;
import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.util.Consumer;
+import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.util.Supplier;
import org.jivesoftware.smack.xml.XmlPullParser;
+import org.jivesoftware.smack.xml.XmlPullParserException;
public abstract class ModularXmppClientToServerConnectionInternal {
@@ -85,9 +88,19 @@ public abstract class ModularXmppClientToServerConnectionInternal {
public abstract void notifyConnectionError(Exception e);
- public abstract void setCurrentConnectionExceptionAndNotify(Exception exception);
+ public final String onStreamOpen(String streamOpen) {
+ XmlPullParser streamOpenParser;
+ try {
+ streamOpenParser = PacketParserUtils.getParserFor(streamOpen);
+ } catch (XmlPullParserException | IOException e) {
+ // Should never happen.
+ throw new AssertionError(e);
+ }
+ String streamClose = onStreamOpen(streamOpenParser);
+ return streamClose;
+ }
- public abstract void onStreamOpen(XmlPullParser parser);
+ public abstract String onStreamOpen(XmlPullParser parser);
public abstract void onStreamClosed();
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/fsm/State.java b/smack-core/src/main/java/org/jivesoftware/smack/fsm/State.java
index a4a7b76ea..0bd7c42db 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/fsm/State.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/fsm/State.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2018-2020 Florian Schmaus
+ * Copyright 2018-2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@ import java.io.IOException;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smack.c2s.XmppClientToServerTransport;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
import org.jivesoftware.smack.c2s.internal.WalkStateGraphContext;
@@ -75,4 +76,24 @@ public abstract class State {
}
}
+ public abstract static class AbstractTransport extends State {
+
+ private final XmppClientToServerTransport transport;
+
+ protected AbstractTransport(XmppClientToServerTransport transport, StateDescriptor stateDescriptor,
+ ModularXmppClientToServerConnectionInternal connectionInternal) {
+ super(stateDescriptor, connectionInternal);
+ this.transport = transport;
+ }
+
+ @Override
+ public StateTransitionResult.TransitionImpossible isTransitionToPossible(WalkStateGraphContext walkStateGraphContext)
+ throws SmackException {
+ if (!transport.hasUseableConnectionEndpoints()) {
+ return new StateTransitionResult.TransitionImpossibleBecauseNoEndpointsDiscovered(transport);
+ }
+
+ return super.isTransitionToPossible(walkStateGraphContext);
+ }
+ }
}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/fsm/StateTransitionResult.java b/smack-core/src/main/java/org/jivesoftware/smack/fsm/StateTransitionResult.java
index a9acb4f32..7bc68af5f 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/fsm/StateTransitionResult.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/fsm/StateTransitionResult.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2018-2020 Florian Schmaus
+ * Copyright 2018-2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
*/
package org.jivesoftware.smack.fsm;
+import org.jivesoftware.smack.c2s.XmppClientToServerTransport;
+
public abstract class StateTransitionResult {
private final String message;
@@ -92,4 +94,10 @@ public abstract class StateTransitionResult {
super(stateDescriptor.getFullStateName(false) + " is not implemented (yet)");
}
}
+
+ public static class TransitionImpossibleBecauseNoEndpointsDiscovered extends TransitionImpossibleReason {
+ public TransitionImpossibleBecauseNoEndpointsDiscovered(XmppClientToServerTransport transport) {
+ super("The transport " + transport + " did not discover any endpoints");
+ }
+ }
}
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 698e7888d..c3ebea932 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 Florian Schmaus, Aditya Borikar
+ * Copyright 2020-2021 Florian Schmaus, 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.
@@ -28,6 +28,7 @@ import org.jivesoftware.smack.util.XmlStringBuilder;
* be achieved through {@link XMPPConnection#sendNonza(Nonza)}.
*/
public abstract class AbstractStreamOpen implements Nonza {
+ public static final String ETHERX_JABBER_STREAMS_NAMESPACE = "http://etherx.jabber.org/streams";
public static final String CLIENT_NAMESPACE = "jabber:client";
public static final String SERVER_NAMESPACE = "jabber:server";
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 fd284a2e5..2d7f6e9c8 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
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2018 Florian Schmaus
+ * Copyright 2018-2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,8 @@ public final class StreamClose extends AbstractStreamClose {
public static final StreamClose INSTANCE = new StreamClose();
+ public static final String STRING = "" + StreamOpen.ELEMENT + ">";
+
private StreamClose() {
}
@@ -39,4 +41,8 @@ public final class StreamClose extends AbstractStreamClose {
return StreamOpen.ELEMENT;
}
+ @Override
+ public String toString() {
+ return STRING;
+ }
}
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 959adc1fa..1c0c922a6 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
@@ -23,7 +23,9 @@ import org.jivesoftware.smack.util.XmlStringBuilder;
* The stream open tag.
*/
public final class StreamOpen extends AbstractStreamOpen {
- public static final String ELEMENT = "stream:stream";
+ public static final String UNPREFIXED_ELEMENT = "stream";
+
+ public static final String ELEMENT = "stream:" + UNPREFIXED_ELEMENT;
public StreamOpen(CharSequence to) {
this(to, null, null, null, StreamContentNamespace.client);
diff --git a/smack-java8-full/build.gradle b/smack-java8-full/build.gradle
index 8aadd7927..362566afc 100644
--- a/smack-java8-full/build.gradle
+++ b/smack-java8-full/build.gradle
@@ -12,7 +12,8 @@ dependencies {
api project(':smack-openpgp')
api project(':smack-resolver-minidns')
api project(':smack-resolver-minidns-dox')
- api project(':smack-websocket')
+ // TODO: Change this to smack-websocket-java11 once it arrives.
+ api project(':smack-websocket-okhttp')
api project(':smack-tcp')
testImplementation(testFixtures(project(":smack-core")))
diff --git a/smack-java8-full/src/main/java/org/jivesoftware/smack/full/WebSocketConnectionTest.java b/smack-java8-full/src/main/java/org/jivesoftware/smack/full/WebSocketConnectionTest.java
new file mode 100644
index 000000000..54c3d30d5
--- /dev/null
+++ b/smack-java8-full/src/main/java/org/jivesoftware/smack/full/WebSocketConnectionTest.java
@@ -0,0 +1,110 @@
+/**
+ *
+ * Copyright 2021 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.
+ */
+package org.jivesoftware.smack.full;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.Date;
+import java.util.logging.Logger;
+
+import org.jivesoftware.smack.SmackConfiguration;
+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.debugger.ConsoleDebugger;
+import org.jivesoftware.smack.packet.Message;
+import org.jivesoftware.smack.websocket.XmppWebSocketTransportModuleDescriptor;
+import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
+
+import org.jxmpp.util.XmppDateTime;
+
+public class WebSocketConnectionTest {
+
+ static {
+ SmackConfiguration.DEBUG = true;
+ }
+
+ public static void main(String[] args)
+ throws URISyntaxException, SmackException, IOException, XMPPException, InterruptedException {
+ String jid, password, websocketEndpoint, messageTo = null;
+ if (args.length < 3 || args.length > 4) {
+ throw new IllegalArgumentException();
+ }
+
+ jid = args[0];
+ password = args[1];
+ websocketEndpoint = args[2];
+ if (args.length >= 4) {
+ messageTo = args[3];
+ }
+
+ testWebSocketConnection(jid, password, websocketEndpoint, messageTo);
+ }
+
+ public static void testWebSocketConnection(String jid, String password, String websocketEndpoint)
+ throws URISyntaxException, SmackException, IOException, XMPPException, InterruptedException {
+ testWebSocketConnection(jid, password, websocketEndpoint, null);
+ }
+
+ public static void testWebSocketConnection(String jid, String password, String websocketEndpoint, String messageTo)
+ throws URISyntaxException, SmackException, IOException, XMPPException, InterruptedException {
+ ModularXmppClientToServerConnectionConfiguration.Builder builder = ModularXmppClientToServerConnectionConfiguration.builder();
+ builder.removeAllModules()
+ .setXmppAddressAndPassword(jid, password)
+ .setDebuggerFactory(ConsoleDebugger.Factory.INSTANCE)
+ ;
+
+ XmppWebSocketTransportModuleDescriptor.Builder websocketBuilder = XmppWebSocketTransportModuleDescriptor.getBuilder(builder);
+ websocketBuilder.explicitlySetWebSocketEndpointAndDiscovery(websocketEndpoint, false);
+ builder.addModule(websocketBuilder.build());
+
+ ModularXmppClientToServerConnectionConfiguration config = builder.build();
+ ModularXmppClientToServerConnection connection = new ModularXmppClientToServerConnection(config);
+
+ connection.setReplyTimeout(5 * 60 * 1000);
+
+ connection.addConnectionStateMachineListener((event, c) -> {
+ Logger.getAnonymousLogger().info("Connection event: " + event);
+ });
+
+ connection.connect();
+
+ connection.login();
+
+ if (messageTo != null) {
+ Message message = connection.getStanzaFactory().buildMessageStanza()
+ .to(messageTo)
+ .setBody("It is alive! " + XmppDateTime.formatXEP0082Date(new Date()))
+ .build()
+ ;
+ connection.sendStanza(message);
+ }
+
+ Thread.sleep(1000);
+
+ connection.disconnect();
+
+ ModularXmppClientToServerConnection.Stats connectionStats = connection.getStats();
+ ServiceDiscoveryManager.Stats serviceDiscoveryManagerStats = ServiceDiscoveryManager.getInstanceFor(connection).getStats();
+
+ // CHECKSTYLE:OFF
+ System.out.println("WebSocket successfully finished, yeah!\n" + connectionStats + '\n' + serviceDiscoveryManagerStats);
+ // CHECKSTYLE:ON
+ }
+}
diff --git a/smack-repl/src/main/java/org/igniterealtime/smack/smackrepl/Nio.java b/smack-repl/src/main/java/org/igniterealtime/smack/smackrepl/Nio.java
index af4b939d6..09d296bb5 100644
--- a/smack-repl/src/main/java/org/igniterealtime/smack/smackrepl/Nio.java
+++ b/smack-repl/src/main/java/org/igniterealtime/smack/smackrepl/Nio.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2018-2020 Florian Schmaus
+ * Copyright 2018-2021 Florian Schmaus
*
* This file is part of smack-repl.
*
@@ -25,8 +25,6 @@ import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
-import java.util.Date;
-import java.util.logging.Logger;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.SmackException;
@@ -38,18 +36,11 @@ import org.jivesoftware.smack.compression.XMPPInputOutputStream;
import org.jivesoftware.smack.compression.XMPPInputOutputStream.FlushMethod;
import org.jivesoftware.smack.debugger.ConsoleDebugger;
import org.jivesoftware.smack.debugger.SmackDebuggerFactory;
-import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.sm.StreamManagementModuleDescriptor;
import org.jivesoftware.smack.tcp.XmppTcpTransportModuleDescriptor;
-import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
-
-import org.jxmpp.util.XmppDateTime;
-
public class Nio {
- private static final Logger LOGGER = Logger.getLogger(Nio.class.getName());
-
public static void main(String[] args) throws SmackException, IOException, XMPPException, InterruptedException {
doNio(args[0], args[1], args[2]);
}
@@ -111,30 +102,7 @@ public class Nio {
connection.setReplyTimeout(5 * 60 * 1000);
- connection.addConnectionStateMachineListener((event, c) -> {
- LOGGER.info("Connection event: " + event);
- });
-
- connection.connect();
-
- connection.login();
-
- Message message = connection.getStanzaFactory().buildMessageStanza()
- .to("flo@geekplace.eu")
- .setBody("It is alive! " + XmppDateTime.formatXEP0082Date(new Date()))
- .build();
- connection.sendStanza(message);
-
- Thread.sleep(1000);
-
- connection.disconnect();
-
- ModularXmppClientToServerConnection.Stats connectionStats = connection.getStats();
- ServiceDiscoveryManager.Stats serviceDiscoveryManagerStats = ServiceDiscoveryManager.getInstanceFor(connection).getStats();
-
- // CHECKSTYLE:OFF
- System.out.println("NIO successfully finished, yeah!\n" + connectionStats + '\n' + serviceDiscoveryManagerStats);
- // CHECKSTYLE:ON
+ XmppTools.modularConnectionTest(connection, "flo@geekplace.eu");
}
}
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
index ee117dcb8..f693fb739 100644
--- a/smack-repl/src/main/java/org/igniterealtime/smack/smackrepl/WebSocketConnection.java
+++ b/smack-repl/src/main/java/org/igniterealtime/smack/smackrepl/WebSocketConnection.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2020 Aditya Borikar
+ * Copyright 2021 Florian Schmaus
*
* This file is part of smack-repl.
*
@@ -21,7 +21,6 @@
package org.igniterealtime.smack.smackrepl;
import java.io.IOException;
-import java.net.URI;
import java.net.URISyntaxException;
import org.jivesoftware.smack.SmackException;
@@ -29,25 +28,51 @@ import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
+import org.jivesoftware.smack.util.TLSUtils;
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]);
+ public static void main(String[] args)
+ throws URISyntaxException, SmackException, IOException, XMPPException, InterruptedException {
+ String jid, password, websocketEndpoint, messageTo = null;
+ if (args.length < 3 || args.length > 4) {
+ throw new IllegalArgumentException();
+ }
+
+ jid = args[0];
+ password = args[1];
+ websocketEndpoint = args[2];
+ if (args.length >= 4) {
+ messageTo = args[3];
+ }
+
+ TLSUtils.setDefaultTrustStoreTypeToJksIfRequired();
+
+ testWebSocketConnection(jid, password, websocketEndpoint, messageTo);
+ }
+
+ public static void testWebSocketConnection(String jid, String password, String websocketEndpoint)
+ throws URISyntaxException, SmackException, IOException, XMPPException, InterruptedException {
+ testWebSocketConnection(jid, password, websocketEndpoint, null);
+ }
+
+ public static void testWebSocketConnection(String jid, String password, String websocketEndpoint, String messageTo)
+ throws URISyntaxException, SmackException, IOException, XMPPException, InterruptedException {
+ ModularXmppClientToServerConnectionConfiguration.Builder builder = ModularXmppClientToServerConnectionConfiguration.builder();
+ builder.removeAllModules()
+ .setXmppAddressAndPassword(jid, password)
+ ;
- // 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);
+ websocketBuilder.explicitlySetWebSocketEndpointAndDiscovery(websocketEndpoint, false);
builder.addModule(websocketBuilder.build());
ModularXmppClientToServerConnectionConfiguration config = builder.build();
ModularXmppClientToServerConnection connection = new ModularXmppClientToServerConnection(config);
- connection.connect();
- connection.login();
- connection.disconnect();
+ connection.setReplyTimeout(5 * 60 * 1000);
+
+ XmppTools.modularConnectionTest(connection, messageTo);
}
}
diff --git a/smack-repl/src/main/java/org/igniterealtime/smack/smackrepl/XmppTools.java b/smack-repl/src/main/java/org/igniterealtime/smack/smackrepl/XmppTools.java
index 958dbe1b1..eeb6c7a40 100644
--- a/smack-repl/src/main/java/org/igniterealtime/smack/smackrepl/XmppTools.java
+++ b/smack-repl/src/main/java/org/igniterealtime/smack/smackrepl/XmppTools.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2016 Florian Schmaus
+ * Copyright 2016-2021 Florian Schmaus
*
* This file is part of smack-repl.
*
@@ -23,6 +23,8 @@ package org.igniterealtime.smack.smackrepl;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
+import java.util.Date;
+import java.util.logging.Logger;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.NoResponseException;
@@ -30,15 +32,19 @@ import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
+import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
+import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
import org.jivesoftware.smack.util.TLSUtils;
-
+import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.iqregister.AccountManager;
import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.jid.parts.Localpart;
+import org.jxmpp.stringprep.XmppStringprepException;
+import org.jxmpp.util.XmppDateTime;
public class XmppTools {
@@ -106,4 +112,40 @@ public class XmppTools {
connection.disconnect();
}
}
+
+ public static void modularConnectionTest(ModularXmppClientToServerConnection connection, String messageTo) throws XMPPException, SmackException, IOException, InterruptedException {
+ connection.addConnectionStateMachineListener((event, c) -> {
+ Logger.getAnonymousLogger().info("Connection event: " + event);
+ });
+
+ connection.connect();
+
+ connection.login();
+
+ XmppTools.sendItsAlive(messageTo, connection);
+
+ Thread.sleep(1000);
+
+ connection.disconnect();
+
+ ModularXmppClientToServerConnection.Stats connectionStats = connection.getStats();
+ ServiceDiscoveryManager.Stats serviceDiscoveryManagerStats = ServiceDiscoveryManager.getInstanceFor(connection).getStats();
+
+ // CHECKSTYLE:OFF
+ System.out.println("NIO successfully finished, yeah!\n" + connectionStats + '\n' + serviceDiscoveryManagerStats);
+ // CHECKSTYLE:ON
+ }
+
+ public static void sendItsAlive(String to, XMPPConnection connection)
+ throws XmppStringprepException, NotConnectedException, InterruptedException {
+ if (to == null) {
+ return;
+ }
+
+ Message message = connection.getStanzaFactory().buildMessageStanza()
+ .to(to)
+ .setBody("It is alive! " + XmppDateTime.formatXEP0082Date(new Date()))
+ .build();
+ connection.sendStanza(message);
+ }
}
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 8e3c8d282..d6e513036 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
@@ -83,6 +83,7 @@ import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.packet.StartTls;
import org.jivesoftware.smack.packet.StreamError;
+import org.jivesoftware.smack.packet.StreamOpen;
import org.jivesoftware.smack.proxy.ProxyInfo;
import org.jivesoftware.smack.sasl.packet.SaslNonza;
import org.jivesoftware.smack.sm.SMUtils;
@@ -961,6 +962,8 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
switch (eventType) {
case START_ELEMENT:
final String name = parser.getName();
+ final String namespace = parser.getNamespace();
+
switch (name) {
case Message.ELEMENT:
case IQ.IQ_ELEMENT:
@@ -972,7 +975,9 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
}
break;
case "stream":
- onStreamOpen(parser);
+ if (StreamOpen.ETHERX_JABBER_STREAMS_NAMESPACE.equals(namespace)) {
+ onStreamOpen(parser);
+ }
break;
case "error":
StreamError streamError = PacketParserUtils.parseStreamError(parser);
@@ -989,7 +994,6 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
openStreamAndResetParser();
break;
case "failure":
- String namespace = parser.getNamespace(null);
switch (namespace) {
case "urn:ietf:params:xml:ns:xmpp-tls":
// TLS negotiation has failed. The server will close the connection
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 276784e84..1719633a0 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
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2019-2020 Florian Schmaus
+ * Copyright 2019-2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -80,14 +80,12 @@ import org.jivesoftware.smack.tcp.rce.RemoteXmppTcpConnectionEndpoints;
import org.jivesoftware.smack.tcp.rce.RemoteXmppTcpConnectionEndpoints.Result;
import org.jivesoftware.smack.tcp.rce.Rfc6120TcpRemoteConnectionEndpoint;
import org.jivesoftware.smack.util.CollectionUtil;
-import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.UTF8;
import org.jivesoftware.smack.util.XmlStringBuilder;
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpointLookupFailure;
-import org.jivesoftware.smack.xml.XmlPullParser;
-import org.jivesoftware.smack.xml.XmlPullParserException;
+import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.Jid;
import org.jxmpp.jid.util.JidUtil;
import org.jxmpp.xml.splitter.Utf8ByteXmppXmlSplitter;
@@ -213,6 +211,8 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
}
final String prefixXmlns = "xmlns:" + prefix;
+ // TODO: Use the return value of onStreamOpen(), which now returns the
+ // corresponding stream close tag, instead of creating it here.
final StringBuilder streamClose = new StringBuilder(32);
final StringBuilder streamOpen = new StringBuilder(256);
@@ -253,14 +253,7 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
this.streamOpen = streamOpen.toString();
this.streamClose = streamClose.toString();
- XmlPullParser streamOpenParser;
- try {
- streamOpenParser = PacketParserUtils.getParserFor(this.streamOpen);
- } catch (XmlPullParserException | IOException e) {
- // Should never happen.
- throw new AssertionError(e);
- }
- connectionInternal.onStreamOpen(streamOpenParser);
+ connectionInternal.onStreamOpen(this.streamOpen);
}
@Override
@@ -586,7 +579,7 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
public StreamOpenAndCloseFactory getStreamOpenAndCloseFactory() {
return new StreamOpenAndCloseFactory() {
@Override
- public StreamOpen createStreamOpen(CharSequence to, CharSequence from, String id, String lang) {
+ public StreamOpen createStreamOpen(DomainBareJid 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;
@@ -603,6 +596,11 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
discoveredTcpEndpoints = null;
}
+ @Override
+ public boolean hasUseableConnectionEndpoints() {
+ return discoveredTcpEndpoints != null;
+ }
+
@Override
protected List> lookupConnectionEndpoints() {
// Assert that there are no stale discovered endpoints prior performing the lookup.
@@ -750,10 +748,10 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
return new EstablishingTcpConnectionState(stateDescriptor, connectionInternal);
}
- final class EstablishingTcpConnectionState extends State {
+ final class EstablishingTcpConnectionState extends State.AbstractTransport {
private EstablishingTcpConnectionState(EstablishingTcpConnectionStateDescriptor stateDescriptor,
ModularXmppClientToServerConnectionInternal connectionInternal) {
- super(stateDescriptor, connectionInternal);
+ super(tcpNioTransport, stateDescriptor, connectionInternal);
}
@Override
@@ -777,6 +775,10 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
connectionInternal.setTransport(tcpNioTransport);
+ // TODO: It appears this should be done in a generic way. I'd assume we always
+ // have to wait for stream features after the connection was established. If this is true then consider
+ // moving this into State.AbstractTransport. But I am not yet 100% positive that this is the case for every
+ // transport. Hence keep it here for now.
connectionInternal.newStreamOpenWaitForFeaturesSequence("stream features after initial connection");
return new TcpSocketConnectedResult(remoteAddress);
diff --git a/smack-websocket-okhttp/src/main/java/org/jivesoftware/smack/websocket/okhttp/LoggingInterceptor.java b/smack-websocket-okhttp/src/main/java/org/jivesoftware/smack/websocket/okhttp/LoggingInterceptor.java
index e6e14e0e0..99697617e 100644
--- a/smack-websocket-okhttp/src/main/java/org/jivesoftware/smack/websocket/okhttp/LoggingInterceptor.java
+++ b/smack-websocket-okhttp/src/main/java/org/jivesoftware/smack/websocket/okhttp/LoggingInterceptor.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2020 Aditya Borikar
+ * Copyright 2020 Aditya Borikar, 2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,6 @@
package org.jivesoftware.smack.websocket.okhttp;
import java.io.IOException;
-import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -27,39 +26,34 @@ 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;
+ private static final Logger LOGGER = Logger.getLogger(LoggingInterceptor.class.getName());
- public LoggingInterceptor(SmackDebugger smackDebugger) {
+ private final SmackDebugger debugger;
+ private final XmppXmlSplitter incomingXmlSplitter;
+ private final XmppXmlSplitter outgoingXmlSplitter;
+
+ 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);
+ incomingXmlSplitter = new XmppXmlSplitter(incomingTextPrinter);
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);
+ outgoingXmlSplitter = new XmppXmlSplitter(outgoingTextPrinter);
}
// 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) {
+ void interceptOpenResponse(Response response) {
Headers headers = response.headers();
Iterator> iterator = headers.iterator();
StringBuilder sb = new StringBuilder();
@@ -70,18 +64,18 @@ public final class LoggingInterceptor {
debugger.incomingStreamSink(sb);
}
- public void interceptReceivedText(String text) {
+ void interceptReceivedText(String text) {
try {
- incomingTextSplitter.write(text.getBytes(Charset.defaultCharset()));
+ incomingXmlSplitter.write(text);
} 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) {
+ void interceptSentText(String text) {
try {
- outgoingTextSplitter.write(text.getBytes(Charset.defaultCharset()));
+ outgoingXmlSplitter.write(text);
} 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-okhttp/src/main/java/org/jivesoftware/smack/websocket/okhttp/OkHttpWebSocket.java b/smack-websocket-okhttp/src/main/java/org/jivesoftware/smack/websocket/okhttp/OkHttpWebSocket.java
index 8523b350a..59d2dba7c 100644
--- a/smack-websocket-okhttp/src/main/java/org/jivesoftware/smack/websocket/okhttp/OkHttpWebSocket.java
+++ b/smack-websocket-okhttp/src/main/java/org/jivesoftware/smack/websocket/okhttp/OkHttpWebSocket.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2020 Aditya Borikar
+ * Copyright 2020 Aditya Borikar, 2020-2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,22 +16,17 @@
*/
package org.jivesoftware.smack.websocket.okhttp;
-import java.io.IOException;
+import java.net.URI;
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.SmackFuture;
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.elements.WebSocketOpenElement;
import org.jivesoftware.smack.websocket.impl.AbstractWebSocket;
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
-import org.jivesoftware.smack.xml.XmlPullParserException;
import okhttp3.OkHttpClient;
import okhttp3.Request;
@@ -43,135 +38,119 @@ public final class OkHttpWebSocket extends AbstractWebSocket {
private static final Logger LOGGER = Logger.getLogger(OkHttpWebSocket.class.getName());
- private static OkHttpClient okHttpClient = null;
+ private static final OkHttpClient okHttpClient = new OkHttpClient();
+
+ // This is a potential candidate to be placed into AbstractWebSocket, but I keep it here until smack-websocket-java11
+ // arrives.
+ private final SmackFuture.InternalSmackFuture future = new SmackFuture.InternalSmackFuture<>();
- private final ModularXmppClientToServerConnectionInternal connectionInternal;
private final LoggingInterceptor interceptor;
- private String openStreamHeader;
- private WebSocket currentWebSocket;
- private WebSocketConnectionPhase phase;
- private WebSocketRemoteConnectionEndpoint connectedEndpoint;
+ private final WebSocket okHttpWebSocket;
- public OkHttpWebSocket(ModularXmppClientToServerConnectionInternal connectionInternal) {
- this.connectionInternal = connectionInternal;
+ public OkHttpWebSocket(WebSocketRemoteConnectionEndpoint endpoint,
+ ModularXmppClientToServerConnectionInternal connectionInternal) {
+ super(endpoint, 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();
+ final URI uri = endpoint.getUri();
+ final String url = uri.toString();
+
Request request = new Request.Builder()
- .url(currentUri)
+ .url(url)
.header("Sec-WebSocket-Protocol", "xmpp")
.build();
- WebSocketListener listener = new WebSocketListener() {
+ okHttpWebSocket = okHttpClient.newWebSocket(request, listener);
+ }
- @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()));
+ private final WebSocketListener listener = new WebSocketListener() {
+
+ @Override
+ public void onOpen(WebSocket webSocket, Response response) {
+ LOGGER.log(Level.FINER, "OkHttp invoked onOpen() for {0}. Response: {1}",
+ new Object[] { webSocket, response });
+
+ if (interceptor != null) {
+ interceptor.interceptOpenResponse(response);
}
- @Override
- public void onMessage(WebSocket webSocket, String text) {
- if (interceptor != null) {
- interceptor.interceptReceivedText(text);
- }
- if (isCloseElement(text)) {
- connectionInternal.onStreamClosed();
- return;
- }
+ future.setResult(OkHttpWebSocket.this);
+ }
- 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 onMessage(WebSocket webSocket, String text) {
+ if (interceptor != null) {
+ interceptor.interceptReceivedText(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);
- }
+ onIncomingWebSocketElement(text);
+ }
+
+ @Override
+ public void onFailure(WebSocket webSocket, Throwable throwable, Response response) {
+ LOGGER.log(Level.FINER, "OkHttp invoked onFailure() for " + webSocket + ". Response: " + response, throwable);
+ WebSocketException websocketException = new WebSocketException(throwable);
+
+ // If we are already connected, then we need to notify the connection that it got tear down. Otherwise we
+ // need to notify the thread calling connect() that the connection failed.
+ if (future.wasSuccessful()) {
+ connectionInternal.notifyConnectionError(websocketException);
+ } else {
+ future.setException(websocketException);
}
- };
+ }
- // Creates an instance of websocket through okHttpClient.
- currentWebSocket = okHttpClient.newWebSocket(request, listener);
+ @Override
+ public void onClosing(WebSocket webSocket, int code, String reason) {
+ LOGGER.log(Level.FINER, "OkHttp invoked onClosing() for " + webSocket + ". Code: " + code + ". Reason: " + reason);
+ }
- // Open a new stream and wait until features are received.
- connectionInternal.waitForFeaturesReceived("Waiting to receive features");
+ @Override
+ public void onClosed(WebSocket webSocket, int code, String reason) {
+ LOGGER.log(Level.FINER, "OkHttp invoked onClosed() for " + webSocket + ". Code: " + code + ". Reason: " + reason);
+ }
- connectedEndpoint = endpoint;
+ };
+
+ @Override
+ public SmackFuture getFuture() {
+ return future;
}
@Override
- public void send(TopLevelStreamElement element) {
- String textToBeSent = element.toXML().toString();
+ public void send(String element) {
if (interceptor != null) {
- interceptor.interceptSentText(textToBeSent);
+ interceptor.interceptSentText(element);
}
- currentWebSocket.send(textToBeSent);
+ okHttpWebSocket.send(element);
}
@Override
public void disconnect(int code, String message) {
- currentWebSocket.close(code, message);
- LOGGER.log(Level.INFO, "WebSocket has been closed with message: " + message);
+ LOGGER.log(Level.INFO, "WebSocket closing with code: " + code + " and message: " + message);
+ okHttpWebSocket.close(code, message);
}
@Override
public boolean isConnectionSecure() {
- return connectedEndpoint.isSecureEndpoint();
+ return endpoint.isSecureEndpoint();
}
@Override
public boolean isConnected() {
- return connectedEndpoint == null ? false : true;
+ // TODO: Do we need this method at all if we create an AbstractWebSocket object for every endpoint?
+ return true;
}
@Override
public SSLSession getSSLSession() {
+ // TODO: What shall we do about this method, as it appears that OkHttp does not provide access to the used SSLSession?
return null;
}
}
diff --git a/smack-websocket-okhttp/src/main/java/org/jivesoftware/smack/websocket/okhttp/OkHttpWebSocketFactory.java b/smack-websocket-okhttp/src/main/java/org/jivesoftware/smack/websocket/okhttp/OkHttpWebSocketFactory.java
index ea6b459bc..64246a7cc 100644
--- a/smack-websocket-okhttp/src/main/java/org/jivesoftware/smack/websocket/okhttp/OkHttpWebSocketFactory.java
+++ b/smack-websocket-okhttp/src/main/java/org/jivesoftware/smack/websocket/okhttp/OkHttpWebSocketFactory.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2020 Florian Schmaus.
+ * Copyright 2020-2021 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,12 +19,13 @@ package org.jivesoftware.smack.websocket.okhttp;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
import org.jivesoftware.smack.websocket.impl.AbstractWebSocket;
import org.jivesoftware.smack.websocket.impl.WebSocketFactory;
+import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
public class OkHttpWebSocketFactory implements WebSocketFactory {
@Override
- public AbstractWebSocket create(ModularXmppClientToServerConnectionInternal connectionInternal) {
- return new OkHttpWebSocket(connectionInternal);
+ public AbstractWebSocket create(WebSocketRemoteConnectionEndpoint endpoint, ModularXmppClientToServerConnectionInternal connectionInternal) {
+ return new OkHttpWebSocket(endpoint, connectionInternal);
}
}
diff --git a/smack-websocket-okhttp/src/test/java/org/jivesoftware/smack/websocket/okhttp/OkHttpWebSocketFactoryServiceTest.java b/smack-websocket-okhttp/src/test/java/org/jivesoftware/smack/websocket/okhttp/OkHttpWebSocketFactoryServiceTest.java
index c1fadf85a..70d4c6084 100644
--- a/smack-websocket-okhttp/src/test/java/org/jivesoftware/smack/websocket/okhttp/OkHttpWebSocketFactoryServiceTest.java
+++ b/smack-websocket-okhttp/src/test/java/org/jivesoftware/smack/websocket/okhttp/OkHttpWebSocketFactoryServiceTest.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2020 Florian Schmaus.
+ * Copyright 2020-2021 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
*/
package org.jivesoftware.smack.websocket.okhttp;
+import java.net.URISyntaxException;
+
import org.jivesoftware.smack.websocket.test.WebSocketFactoryServiceTestUtil;
import org.junit.jupiter.api.Test;
@@ -23,7 +25,7 @@ import org.junit.jupiter.api.Test;
public class OkHttpWebSocketFactoryServiceTest {
@Test
- public void createWebSocketTest() {
+ public void createWebSocketTest() throws URISyntaxException {
WebSocketFactoryServiceTestUtil.createWebSocketTest(OkHttpWebSocket.class);
}
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
index c17aedaf6..7fb322596 100644
--- a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/WebSocketConnectionAttemptState.java
+++ b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/WebSocketConnectionAttemptState.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2020 Aditya Borikar, Florian Schmaus.
+ * Copyright 2020 Aditya Borikar, 2020-2021 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,22 +19,29 @@ package org.jivesoftware.smack.websocket;
import java.util.ArrayList;
import java.util.List;
+import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
+import org.jivesoftware.smack.SmackFuture;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
+import org.jivesoftware.smack.fsm.StateTransitionResult;
+import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.websocket.XmppWebSocketTransportModule.EstablishingWebSocketConnectionState;
import org.jivesoftware.smack.websocket.impl.AbstractWebSocket;
import org.jivesoftware.smack.websocket.impl.WebSocketFactoryService;
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
+import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpointLookup;
public final class WebSocketConnectionAttemptState {
private final ModularXmppClientToServerConnectionInternal connectionInternal;
private final XmppWebSocketTransportModule.XmppWebSocketTransport.DiscoveredWebSocketEndpoints discoveredEndpoints;
- private WebSocketRemoteConnectionEndpoint connectedEndpoint;
+ private AbstractWebSocket webSocket;
WebSocketConnectionAttemptState(ModularXmppClientToServerConnectionInternal connectionInternal,
XmppWebSocketTransportModule.XmppWebSocketTransport.DiscoveredWebSocketEndpoints discoveredWebSocketEndpoints,
EstablishingWebSocketConnectionState establishingWebSocketConnectionState) {
assert discoveredWebSocketEndpoints != null;
+ assert !discoveredWebSocketEndpoints.result.isEmpty();
+
this.connectionInternal = connectionInternal;
this.discoveredEndpoints = discoveredWebSocketEndpoints;
}
@@ -44,48 +51,96 @@ public final class WebSocketConnectionAttemptState {
*
* @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;
+ @SuppressWarnings({"incomplete-switch", "MissingCasesInEnumSwitch"})
+ StateTransitionResult.Failure establishWebSocketConnection() throws InterruptedException {
+ final WebSocketRemoteConnectionEndpointLookup.Result endpointLookupResult = discoveredEndpoints.result;
+ final List failures = new ArrayList<>(endpointLookupResult.discoveredEndpointCount());
- if (endpoints.isEmpty()) {
- throw new WebSocketException(new Throwable("No Endpoints discovered to establish connection"));
- }
+ webSocket = null;
- List connectionFailureList = new ArrayList<>();
- AbstractWebSocket websocket = WebSocketFactoryService.createWebSocket(connectionInternal);
-
- // 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);
- }
+ SecurityMode securityMode = connectionInternal.connection.getConfiguration().getSecurityMode();
+ switch (securityMode) {
+ case required:
+ case ifpossible:
+ establishWebSocketConnection(endpointLookupResult.discoveredSecureEndpoints, failures);
+ if (webSocket != null) {
+ return null;
}
}
- assert connectedEndpoint != null;
+ establishWebSocketConnection(endpointLookupResult.discoveredInsecureEndpoints, failures);
+ if (webSocket != null) {
+ return null;
+ }
- // Return connected websocket when no failure occurs.
- return websocket;
+ StateTransitionResult.Failure failure = FailedToConnectToAnyWebSocketEndpoint.create(failures);
+ return failure;
}
- /**
- * Returns the connected websocket endpoint.
- *
- * @return connected websocket endpoint
- */
- public WebSocketRemoteConnectionEndpoint getConnectedEndpoint() {
- return connectedEndpoint;
+ private void establishWebSocketConnection(List extends WebSocketRemoteConnectionEndpoint> webSocketEndpoints,
+ List failures) throws InterruptedException {
+ final int endpointCount = webSocketEndpoints.size();
+
+ List> futures = new ArrayList<>(endpointCount);
+ {
+ List webSockets = new ArrayList<>(endpointCount);
+ // First only create the AbstractWebSocket instances, in case a constructor throws.
+ for (WebSocketRemoteConnectionEndpoint endpoint : webSocketEndpoints) {
+ AbstractWebSocket webSocket = WebSocketFactoryService.createWebSocket(endpoint, connectionInternal);
+ webSockets.add(webSocket);
+ }
+
+ for (AbstractWebSocket webSocket : webSockets) {
+ SmackFuture future = webSocket.getFuture();
+ futures.add(future);
+ }
+ }
+
+ SmackFuture.await(futures, connectionInternal.connection.getReplyTimeout());
+
+ for (SmackFuture future : futures) {
+ AbstractWebSocket connectedWebSocket = future.getIfAvailable();
+ if (connectedWebSocket == null) {
+ Exception exception = future.getExceptionIfAvailable();
+ assert exception != null;
+ failures.add(exception);
+ continue;
+ }
+
+ if (webSocket == null) {
+ webSocket = connectedWebSocket;
+ // Continue here since we still need to read out the failure exceptions from potential further remaining
+ // futures and close remaining successfully connected ones.
+ continue;
+ }
+
+ connectedWebSocket.disconnect(1000, "Using other connection endpoint at " + webSocket.getEndpoint());
+ }
+ }
+
+ public AbstractWebSocket getConnectedWebSocket() {
+ return webSocket;
+ }
+
+ public static final class FailedToConnectToAnyWebSocketEndpoint extends StateTransitionResult.Failure {
+
+ private final List failures;
+
+ private FailedToConnectToAnyWebSocketEndpoint(String failureMessage, List failures) {
+ super(failureMessage);
+ this.failures = failures;
+ }
+
+ public List getFailures() {
+ return failures;
+ }
+
+ private static FailedToConnectToAnyWebSocketEndpoint create(List failures) {
+ StringBuilder sb = new StringBuilder(256);
+ StringUtils.appendTo(failures, sb, e -> sb.append(e.getMessage()));
+ String message = sb.toString();
+ return new FailedToConnectToAnyWebSocketEndpoint(message, failures);
+ }
}
}
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
index 554bb4257..5f6c602e9 100644
--- a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/WebSocketException.java
+++ b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/WebSocketException.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2020 Aditya Borikar
+ * Copyright 2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,23 +16,11 @@
*/
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);
+ super("WebSocketException: " + throwable.getMessage(), throwable);
}
- public List getThrowableList() {
- return throwableList;
- }
}
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
index db80a1b6e..8a0a4f8a6 100644
--- a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/XmppWebSocketTransportModule.java
+++ b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/XmppWebSocketTransportModule.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2020 Aditya Borikar
+ * Copyright 2020 Aditya Borikar, 2020-2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,20 +16,16 @@
*/
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.SmackException.NoResponseException;
+import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.SmackFuture;
import org.jivesoftware.smack.SmackFuture.InternalSmackFuture;
import org.jivesoftware.smack.XMPPException;
@@ -54,13 +50,13 @@ import org.jivesoftware.smack.websocket.XmppWebSocketTransportModule.XmppWebSock
import org.jivesoftware.smack.websocket.elements.WebSocketCloseElement;
import org.jivesoftware.smack.websocket.elements.WebSocketOpenElement;
import org.jivesoftware.smack.websocket.impl.AbstractWebSocket;
+import org.jivesoftware.smack.websocket.rce.InsecureWebSocketRemoteConnectionEndpoint;
+import org.jivesoftware.smack.websocket.rce.SecureWebSocketRemoteConnectionEndpoint;
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.
@@ -101,31 +97,37 @@ public final class XmppWebSocketTransportModule
}
}
- final class EstablishingWebSocketConnectionState extends State {
+ final class EstablishingWebSocketConnectionState extends State.AbstractTransport {
protected EstablishingWebSocketConnectionState(StateDescriptor stateDescriptor,
ModularXmppClientToServerConnectionInternal connectionInternal) {
- super(stateDescriptor, connectionInternal);
+ super(websocketTransport, stateDescriptor, connectionInternal);
}
@Override
- public AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
- throws IOException, SmackException, InterruptedException, XMPPException {
+ public AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) throws InterruptedException,
+ NoResponseException, NotConnectedException, SmackException, XMPPException {
WebSocketConnectionAttemptState connectionAttemptState = new WebSocketConnectionAttemptState(
connectionInternal, discoveredWebSocketEndpoints, this);
- try {
- websocket = connectionAttemptState.establishWebSocketConnection();
- } catch (InterruptedException | WebSocketException e) {
- StateTransitionResult.Failure failure = new StateTransitionResult.FailureCausedByException(e);
+ StateTransitionResult.Failure failure = connectionAttemptState.establishWebSocketConnection();
+ if (failure != null) {
return failure;
}
+ websocket = connectionAttemptState.getConnectedWebSocket();
+
connectionInternal.setTransport(websocketTransport);
- WebSocketRemoteConnectionEndpoint connectedEndpoint = connectionAttemptState.getConnectedEndpoint();
+ // TODO: It appears this should be done in a generic way. I'd assume we always
+ // have to wait for stream features after the connection was established. But I
+ // am not yet 100% positive that this is the case for every transport. Hence keep it here for now(?).
+ // See also similar comment in XmppTcpTransportModule.
+ // Maybe move this into ConnectedButUnauthenticated state's transitionInto() method? That seems to be the
+ // right place.
+ connectionInternal.newStreamOpenWaitForFeaturesSequence("stream features after initial connection");
// Construct a WebSocketConnectedResult using the connected endpoint.
- return new WebSocketConnectedResult(connectedEndpoint);
+ return new WebSocketConnectedResult(websocket.getEndpoint());
}
}
@@ -140,7 +142,7 @@ public final class XmppWebSocketTransportModule
final WebSocketRemoteConnectionEndpoint connectedEndpoint;
public WebSocketConnectedResult(WebSocketRemoteConnectionEndpoint connectedEndpoint) {
- super("WebSocket connection establised with endpoint: " + connectedEndpoint.getWebSocketEndpoint());
+ super("WebSocket connection establised with endpoint: " + connectedEndpoint);
this.connectedEndpoint = connectedEndpoint;
}
}
@@ -164,6 +166,12 @@ public final class XmppWebSocketTransportModule
discoveredWebSocketEndpoints = null;
}
+ @Override
+ public boolean hasUseableConnectionEndpoints() {
+ return discoveredWebSocketEndpoints != null;
+ }
+
+ @SuppressWarnings("incomplete-switch")
@Override
protected List> lookupConnectionEndpoints() {
// Assert that there are no stale discovered endpoints prior performing the lookup.
@@ -172,51 +180,56 @@ public final class XmppWebSocketTransportModule
InternalSmackFuture websocketEndpointsLookupFuture = new InternalSmackFuture<>();
connectionInternal.asyncGo(() -> {
+ Result result = null;
- WebSocketRemoteConnectionEndpoint providedEndpoint = null;
+ ModularXmppClientToServerConnectionConfiguration configuration = connectionInternal.connection.getConfiguration();
+ DomainBareJid host = configuration.getXMPPServiceDomain();
- // Check if there is a websocket endpoint already configured.
- URI uri = moduleDescriptor.getExplicitlyProvidedUri();
- if (uri != null) {
- providedEndpoint = new WebSocketRemoteConnectionEndpoint(uri);
+ if (moduleDescriptor.isWebSocketEndpointDiscoveryEnabled()) {
+ // Fetch remote endpoints.
+ result = WebSocketRemoteConnectionEndpointLookup.lookup(host);
}
- 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);
+ WebSocketRemoteConnectionEndpoint providedEndpoint = moduleDescriptor.getExplicitlyProvidedEndpoint();
+ if (providedEndpoint != null) {
+ // If there was not automatic lookup that produced a result, then create a result now.
+ if (result == null) {
+ result = new Result();
}
- // 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);
+ // We insert the provided endpoint at the beginning of the list, so that it is used first.
+ final int INSERT_INDEX = 0;
+ if (providedEndpoint instanceof SecureWebSocketRemoteConnectionEndpoint) {
+ SecureWebSocketRemoteConnectionEndpoint secureEndpoint = (SecureWebSocketRemoteConnectionEndpoint) providedEndpoint;
+ result.discoveredSecureEndpoints.add(INSERT_INDEX, secureEndpoint);
+ } else if (providedEndpoint instanceof InsecureWebSocketRemoteConnectionEndpoint) {
+ InsecureWebSocketRemoteConnectionEndpoint insecureEndpoint = (InsecureWebSocketRemoteConnectionEndpoint) providedEndpoint;
+ result.discoveredInsecureEndpoints.add(INSERT_INDEX, insecureEndpoint);
+ } else {
+ throw new AssertionError();
+ }
}
+
+ if (moduleDescriptor.isImplicitWebSocketEndpointEnabled()) {
+ String urlWithoutScheme = "://" + host + ":5443/ws";
+
+ SecureWebSocketRemoteConnectionEndpoint implicitSecureEndpoint = SecureWebSocketRemoteConnectionEndpoint.from(
+ WebSocketRemoteConnectionEndpoint.SECURE_WEB_SOCKET_SCHEME + urlWithoutScheme);
+ result.discoveredSecureEndpoints.add(implicitSecureEndpoint);
+
+ InsecureWebSocketRemoteConnectionEndpoint implicitInsecureEndpoint = InsecureWebSocketRemoteConnectionEndpoint.from(
+ WebSocketRemoteConnectionEndpoint.INSECURE_WEB_SOCKET_SCHEME + urlWithoutScheme);
+ result.discoveredInsecureEndpoints.add(implicitInsecureEndpoint);
+ }
+
+ final LookupConnectionEndpointsResult endpointsResult;
+ if (result.isEmpty()) {
+ endpointsResult = new WebSocketEndpointsDiscoveryFailed(result.lookupFailures);
+ } else {
+ endpointsResult = new DiscoveredWebSocketEndpoints(result);
+ }
+
+ websocketEndpointsLookupFuture.setResult(endpointsResult);
});
return Collections.singletonList(websocketEndpointsLookupFuture);
@@ -238,11 +251,11 @@ public final class XmppWebSocketTransportModule
@Override
protected void notifyAboutNewOutgoingElements() {
- Queue outgoingElementsQueue = connectionInternal.outgoingElementsQueue;
+ final 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);
+ for (TopLevelStreamElement topLevelStreamElement; (topLevelStreamElement = outgoingElementsQueue.poll()) != null;) {
+ websocket.send(topLevelStreamElement);
+ }
});
}
@@ -268,15 +281,11 @@ public final class XmppWebSocketTransportModule
@Override
public StreamOpenAndCloseFactory getStreamOpenAndCloseFactory() {
+ // TODO: Create extra class for this?
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;
- }
+ public AbstractStreamOpen createStreamOpen(DomainBareJid to, CharSequence from, String id, String lang) {
+ return new WebSocketOpenElement(to);
}
@Override
public AbstractStreamClose createStreamClose() {
@@ -295,10 +304,6 @@ public final class XmppWebSocketTransportModule
assert result != null;
this.result = result;
}
-
- public WebSocketRemoteConnectionEndpointLookup.Result getResult() {
- return result;
- }
}
/**
@@ -308,10 +313,13 @@ public final class XmppWebSocketTransportModule
final class WebSocketEndpointsDiscoveryFailed implements LookupConnectionEndpointsFailed {
final List lookupFailures;
- WebSocketEndpointsDiscoveryFailed(
- WebSocketRemoteConnectionEndpointLookup.Result result) {
- assert result != null;
- lookupFailures = Collections.unmodifiableList(result.lookupFailures);
+ WebSocketEndpointsDiscoveryFailed(RemoteConnectionEndpointLookupFailure lookupFailure) {
+ this(Collections.singletonList(lookupFailure));
+ }
+
+ WebSocketEndpointsDiscoveryFailed(List lookupFailures) {
+ assert lookupFailures != null;
+ this.lookupFailures = Collections.unmodifiableList(lookupFailures);
}
@Override
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
index 292fdceb0..b0d02e7c9 100644
--- a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/XmppWebSocketTransportModuleDescriptor.java
+++ b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/XmppWebSocketTransportModuleDescriptor.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2020 Aditya Borikar
+ * Copyright 2020 Aditya Borikar, 2020-2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@ import java.net.URISyntaxException;
import java.util.HashSet;
import java.util.Set;
+import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionModule;
@@ -29,6 +30,7 @@ import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionIn
import org.jivesoftware.smack.fsm.StateDescriptor;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.websocket.XmppWebSocketTransportModule.EstablishingWebSocketConnectionStateDescriptor;
+import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
/**
* The descriptor class for {@link XmppWebSocketTransportModule}.
@@ -37,12 +39,43 @@ import org.jivesoftware.smack.websocket.XmppWebSocketTransportModule.Establishin
* use {@link ModularXmppClientToServerConnectionConfiguration.Builder#addModule(ModularXmppClientToServerConnectionModuleDescriptor)}.
*/
public final class XmppWebSocketTransportModuleDescriptor extends ModularXmppClientToServerConnectionModuleDescriptor {
- private boolean performWebSocketEndpointDiscovery;
- private URI uri;
+ private final boolean performWebSocketEndpointDiscovery;
+ private final boolean implicitWebSocketEndpoint;
+ private final URI uri;
+ private final WebSocketRemoteConnectionEndpoint wsRce;
public XmppWebSocketTransportModuleDescriptor(Builder builder) {
this.performWebSocketEndpointDiscovery = builder.performWebSocketEndpointDiscovery;
+ this.implicitWebSocketEndpoint = builder.implicitWebSocketEndpoint;
+
this.uri = builder.uri;
+ if (uri != null) {
+ wsRce = WebSocketRemoteConnectionEndpoint.from(uri);
+ } else {
+ wsRce = null;
+ }
+ }
+
+ @Override
+ @SuppressWarnings({"incomplete-switch", "MissingCasesInEnumSwitch"})
+ protected void validateConfiguration(ModularXmppClientToServerConnectionConfiguration configuration) {
+ if (wsRce == null) {
+ return;
+ }
+
+ SecurityMode securityMode = configuration.getSecurityMode();
+ switch (securityMode) {
+ case required:
+ if (!wsRce.isSecureEndpoint()) {
+ throw new IllegalArgumentException("The provided WebSocket endpoint " + wsRce + " is not a secure endpoint, but the connection configuration requires secure endpoints");
+ }
+ break;
+ case disabled:
+ if (wsRce.isSecureEndpoint()) {
+ throw new IllegalArgumentException("The provided WebSocket endpoint " + wsRce + " is a secure endpoint, but the connection configuration has security disabled");
+ }
+ break;
+ }
}
/**
@@ -53,6 +86,10 @@ public final class XmppWebSocketTransportModuleDescriptor extends ModularXmppCli
return performWebSocketEndpointDiscovery;
}
+ public boolean isImplicitWebSocketEndpointEnabled() {
+ return implicitWebSocketEndpoint;
+ }
+
/**
* Returns explicitly configured websocket endpoint uri.
* @return uri
@@ -61,6 +98,10 @@ public final class XmppWebSocketTransportModuleDescriptor extends ModularXmppCli
return uri;
}
+ WebSocketRemoteConnectionEndpoint getExplicitlyProvidedEndpoint() {
+ return wsRce;
+ }
+
@Override
protected Set> getStateDescriptors() {
Set> res = new HashSet<>();
@@ -99,6 +140,7 @@ public final class XmppWebSocketTransportModuleDescriptor extends ModularXmppCli
*/
public static final class Builder extends ModularXmppClientToServerConnectionModuleDescriptor.Builder {
private boolean performWebSocketEndpointDiscovery = true;
+ private boolean implicitWebSocketEndpoint = true;
private URI uri;
private Builder(
@@ -119,15 +161,20 @@ public final class XmppWebSocketTransportModuleDescriptor extends ModularXmppCli
public Builder explicitlySetWebSocketEndpoint(CharSequence endpoint) throws URISyntaxException {
URI endpointUri = new URI(endpoint.toString());
- return explicitlySetWebSocketEndpointAndDiscovery(endpointUri, true);
+ return explicitlySetWebSocketEndpoint(endpointUri);
}
- public Builder explicitlySetWebSocketEndpoint(CharSequence endpoint, boolean performWebSocketEndpointDiscovery)
+ public Builder explicitlySetWebSocketEndpointAndDiscovery(CharSequence endpoint, boolean performWebSocketEndpointDiscovery)
throws URISyntaxException {
URI endpointUri = new URI(endpoint.toString());
return explicitlySetWebSocketEndpointAndDiscovery(endpointUri, performWebSocketEndpointDiscovery);
}
+ public Builder disableImplicitWebsocketEndpoint() {
+ implicitWebSocketEndpoint = false;
+ return this;
+ }
+
@Override
public ModularXmppClientToServerConnectionModuleDescriptor build() {
return new XmppWebSocketTransportModuleDescriptor(this);
diff --git a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/impl/AbstractWebSocket.java b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/impl/AbstractWebSocket.java
index d7e76e230..1c338e733 100644
--- a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/impl/AbstractWebSocket.java
+++ b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/impl/AbstractWebSocket.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2020 Aditya Borikar.
+ * Copyright 2020 Aditya Borikar, 2020-2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,40 +18,92 @@ package org.jivesoftware.smack.websocket.impl;
import javax.net.ssl.SSLSession;
+import org.jivesoftware.smack.SmackFuture;
+import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
import org.jivesoftware.smack.packet.TopLevelStreamElement;
+import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
public abstract class AbstractWebSocket {
- protected enum WebSocketConnectionPhase {
- openFrameSent,
- exchangingTopLevelStreamElements
+ protected final ModularXmppClientToServerConnectionInternal connectionInternal;
+
+ protected final WebSocketRemoteConnectionEndpoint endpoint;
+
+ protected AbstractWebSocket(WebSocketRemoteConnectionEndpoint endpoint,
+ ModularXmppClientToServerConnectionInternal connectionInternal) {
+ this.endpoint = endpoint;
+ this.connectionInternal = connectionInternal;
}
- protected static String getStreamFromOpenElement(String openElement) {
+ public final WebSocketRemoteConnectionEndpoint getEndpoint() {
+ return endpoint;
+ }
+
+ private String streamOpen;
+ private String streamClose;
+
+ protected final void onIncomingWebSocketElement(String element) {
+ // TODO: Once smack-websocket-java15 is there, we have to re-evaluate if the async operation here is still
+ // required, or if it should only be performed if OkHTTP is used.
+ if (isOpenElement(element)) {
+ // Transform the XMPP WebSocket element to a RFC 6120 open tag.
+ streamOpen = getStreamFromOpenElement(element);
+ streamClose = connectionInternal.onStreamOpen(streamOpen);
+ return;
+ }
+
+ if (isCloseElement(element)) {
+ connectionInternal.onStreamClosed();
+ return;
+ }
+
+ connectionInternal.withSmackDebugger(debugger -> debugger.onIncomingElementCompleted());
+
+ // TODO: Do we need to wrap the element again in the stream open to get the
+ // correct XML scoping (just like the modular TCP connection does)? It appears
+ // that this not really required, as onStreamOpen() will set the incomingStreamEnvironment, which is used for
+ // parsing.
+ String wrappedCompleteElement = streamOpen + element + streamClose;
+ connectionInternal.parseAndProcessElement(wrappedCompleteElement);
+ }
+
+ static String getStreamFromOpenElement(String openElement) {
String streamElement = openElement.replaceFirst("\\A\\s*\\z", ">");
return streamElement;
}
- protected static boolean isOpenElement(String text) {
+ // TODO: Make this method less fragile, e.g. by parsing a little bit into the element to ensure that this is an
+ // element qualified by the correct namespace.
+ static boolean isOpenElement(String text) {
if (text.startsWith(" element qualified by the correct namespace. The fragility comes due the fact that the element could,
+ // inter alia, be specified as
+ //
+ static boolean isCloseElement(String text) {
if (text.startsWith("")) {
return true;
}
return false;
}
- public abstract void connect(WebSocketRemoteConnectionEndpoint endpoint) throws Throwable;
+ public abstract SmackFuture getFuture();
- public abstract void send(TopLevelStreamElement element);
+ public final void send(TopLevelStreamElement element) {
+ XmlEnvironment outgoingStreamXmlEnvironment = connectionInternal.getOutgoingStreamXmlEnvironment();
+ String elementString = element.toXML(outgoingStreamXmlEnvironment).toString();
+ send(elementString);
+ }
+
+ protected abstract void send(String element);
public abstract void disconnect(int code, String message);
diff --git a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/impl/WebSocketFactory.java b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/impl/WebSocketFactory.java
index ddba0f2e8..81e6eed2c 100644
--- a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/impl/WebSocketFactory.java
+++ b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/impl/WebSocketFactory.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2020 Florian Schmaus.
+ * Copyright 2020-2021 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,9 +17,11 @@
package org.jivesoftware.smack.websocket.impl;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
+import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
public interface WebSocketFactory {
- AbstractWebSocket create(ModularXmppClientToServerConnectionInternal connectionInternal);
+ AbstractWebSocket create(WebSocketRemoteConnectionEndpoint endpoint,
+ ModularXmppClientToServerConnectionInternal connectionInternal);
}
diff --git a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/impl/WebSocketFactoryService.java b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/impl/WebSocketFactoryService.java
index 0929c1d96..8eb5e507a 100644
--- a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/impl/WebSocketFactoryService.java
+++ b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/impl/WebSocketFactoryService.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2020 Florian Schmaus.
+ * Copyright 2020-2021 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,12 +20,14 @@ import java.util.Iterator;
import java.util.ServiceLoader;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
+import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
public final class WebSocketFactoryService {
private static final ServiceLoader SERVICE_LOADER = ServiceLoader.load(WebSocketFactory.class);
- public static AbstractWebSocket createWebSocket(ModularXmppClientToServerConnectionInternal connectionInternal) {
+ public static AbstractWebSocket createWebSocket(WebSocketRemoteConnectionEndpoint endpoint,
+ ModularXmppClientToServerConnectionInternal connectionInternal) {
assert connectionInternal != null;
Iterator websocketFactories = SERVICE_LOADER.iterator();
@@ -34,7 +36,7 @@ public final class WebSocketFactoryService {
}
WebSocketFactory websocketFactory = websocketFactories.next();
- return websocketFactory.create(connectionInternal);
+ return websocketFactory.create(endpoint, connectionInternal);
}
}
diff --git a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/rce/InsecureWebSocketRemoteConnectionEndpoint.java b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/rce/InsecureWebSocketRemoteConnectionEndpoint.java
new file mode 100644
index 000000000..de013e111
--- /dev/null
+++ b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/rce/InsecureWebSocketRemoteConnectionEndpoint.java
@@ -0,0 +1,39 @@
+/**
+ *
+ * Copyright 2020-2021 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.
+ */
+package org.jivesoftware.smack.websocket.rce;
+
+import java.net.URI;
+
+public class InsecureWebSocketRemoteConnectionEndpoint extends WebSocketRemoteConnectionEndpoint {
+
+ protected InsecureWebSocketRemoteConnectionEndpoint(URI uri) {
+ super(uri);
+ }
+
+ @Override
+ public final boolean isSecureEndpoint() {
+ return false;
+ }
+
+ public static final InsecureWebSocketRemoteConnectionEndpoint from(CharSequence cs) {
+ URI uri = URI.create(cs.toString());
+ if (!uri.getScheme().equals(INSECURE_WEB_SOCKET_SCHEME)) {
+ throw new IllegalArgumentException(uri + " is not a insecure WebSocket");
+ }
+ return new InsecureWebSocketRemoteConnectionEndpoint(uri);
+ }
+}
diff --git a/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/rce/SecureWebSocketRemoteConnectionEndpoint.java b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/rce/SecureWebSocketRemoteConnectionEndpoint.java
new file mode 100644
index 000000000..d4b8bcc29
--- /dev/null
+++ b/smack-websocket/src/main/java/org/jivesoftware/smack/websocket/rce/SecureWebSocketRemoteConnectionEndpoint.java
@@ -0,0 +1,39 @@
+/**
+ *
+ * Copyright 2020-2021 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.
+ */
+package org.jivesoftware.smack.websocket.rce;
+
+import java.net.URI;
+
+public class SecureWebSocketRemoteConnectionEndpoint extends WebSocketRemoteConnectionEndpoint {
+
+ protected SecureWebSocketRemoteConnectionEndpoint(URI uri) {
+ super(uri);
+ }
+
+ @Override
+ public final boolean isSecureEndpoint() {
+ return true;
+ }
+
+ public static final SecureWebSocketRemoteConnectionEndpoint from(CharSequence cs) {
+ URI uri = URI.create(cs.toString());
+ if (!uri.getScheme().equals(SECURE_WEB_SOCKET_SCHEME)) {
+ throw new IllegalArgumentException(uri + " is not a secure WebSocket");
+ }
+ return new SecureWebSocketRemoteConnectionEndpoint(uri);
+ }
+}
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
index eab43afc3..6de5b9611 100644
--- 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
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2020 Aditya Borikar
+ * Copyright 2020-2021 Aditya Borikar, Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,66 +20,100 @@ import java.net.InetAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
+import java.util.Arrays;
import java.util.Collection;
-import java.util.Collections;
+import java.util.List;
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 {
+public abstract class WebSocketRemoteConnectionEndpoint implements RemoteConnectionEndpoint {
+
+ public static final String INSECURE_WEB_SOCKET_SCHEME = "ws";
+ public static final String SECURE_WEB_SOCKET_SCHEME = INSECURE_WEB_SOCKET_SCHEME + "s";
private static final Logger LOGGER = Logger.getAnonymousLogger();
private final URI uri;
+ private final UInt16 port;
- public WebSocketRemoteConnectionEndpoint(String uri) throws URISyntaxException {
- this(new URI(uri));
- }
-
- public WebSocketRemoteConnectionEndpoint(URI uri) {
+ protected 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");
+ int portInt = uri.getPort();
+ if (portInt >= 0) {
+ port = UInt16.from(portInt);
+ } else {
+ port = null;
}
}
- public URI getWebSocketEndpoint() {
+ public final URI getUri() {
return uri;
}
- public boolean isSecureEndpoint() {
- if (uri.getScheme().equals("wss")) {
- return true;
- }
- return false;
- }
-
@Override
- public CharSequence getHost() {
+ public final String getHost() {
return uri.getHost();
}
@Override
public UInt16 getPort() {
- return UInt16.from(uri.getPort());
+ return port;
+ }
+
+ public abstract boolean isSecureEndpoint();
+
+ private List extends InetAddress> inetAddresses;
+
+ private void resolveInetAddressesIfRequired() {
+ if (inetAddresses != null) {
+ return;
+ }
+
+ String host = getHost();
+ InetAddress[] addresses;
+ try {
+ addresses = InetAddress.getAllByName(host);
+ } catch (UnknownHostException e) {
+ LOGGER.log(Level.WARNING, "Could not resolve IP addresses of " + host, e);
+ return;
+ }
+ inetAddresses = Arrays.asList(addresses);
}
@Override
public Collection extends InetAddress> 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;
+ resolveInetAddressesIfRequired();
+ return inetAddresses;
}
@Override
public String getDescription() {
return null;
}
+
+ @Override
+ public String toString() {
+ return uri.toString();
+ }
+
+ public static WebSocketRemoteConnectionEndpoint from(CharSequence uriCharSequence) throws URISyntaxException {
+ String uriString = uriCharSequence.toString();
+ URI uri = URI.create(uriString);
+ return from(uri);
+ }
+
+ public static WebSocketRemoteConnectionEndpoint from(URI uri) {
+ String scheme = uri.getScheme();
+ switch (scheme) {
+ case INSECURE_WEB_SOCKET_SCHEME:
+ return new InsecureWebSocketRemoteConnectionEndpoint(uri);
+ case SECURE_WEB_SOCKET_SCHEME:
+ return new SecureWebSocketRemoteConnectionEndpoint(uri);
+ default:
+ throw new IllegalArgumentException("Only allowed protocols are " + INSECURE_WEB_SOCKET_SCHEME + " and " + SECURE_WEB_SOCKET_SCHEME);
+ }
+ }
}
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
index 4ea558325..347180f69 100644
--- 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
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2020 Aditya Borikar
+ * Copyright 2020 Aditya Borikar, Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,10 +20,9 @@ import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
-import java.util.Iterator;
+import java.util.Collections;
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;
@@ -33,9 +32,8 @@ import org.jxmpp.jid.DomainBareJid;
public final class WebSocketRemoteConnectionEndpointLookup {
- public static Result lookup(DomainBareJid domainBareJid, SecurityMode securityMode) {
+ public static Result lookup(DomainBareJid domainBareJid) {
List lookupFailures = new ArrayList<>(1);
- List discoveredRemoteConnectionEndpoints = new ArrayList<>();
List rcUriList = null;
try {
@@ -45,67 +43,69 @@ public final class WebSocketRemoteConnectionEndpointLookup {
} catch (IOException | XmlPullParserException | URISyntaxException e) {
lookupFailures.add(new RemoteConnectionEndpointLookupFailure.HttpLookupFailure(
domainBareJid, e));
- return new Result(discoveredRemoteConnectionEndpoints, lookupFailures);
+ return new Result(lookupFailures);
}
- if (rcUriList.isEmpty()) {
- throw new IllegalStateException("No endpoints were found inside host-meta");
+ List discoveredSecureEndpoints = new ArrayList<>(rcUriList.size());
+ List discoveredInsecureEndpoints = new ArrayList<>(rcUriList.size());
+
+ for (URI webSocketUri : rcUriList) {
+ WebSocketRemoteConnectionEndpoint wsRce = WebSocketRemoteConnectionEndpoint.from(webSocketUri);
+ if (wsRce instanceof SecureWebSocketRemoteConnectionEndpoint) {
+ SecureWebSocketRemoteConnectionEndpoint secureWsRce = (SecureWebSocketRemoteConnectionEndpoint) wsRce;
+ discoveredSecureEndpoints.add(secureWsRce);
+ } else if (wsRce instanceof InsecureWebSocketRemoteConnectionEndpoint) {
+ InsecureWebSocketRemoteConnectionEndpoint insecureWsRce = (InsecureWebSocketRemoteConnectionEndpoint) wsRce;
+ discoveredInsecureEndpoints.add(insecureWsRce);
+ } else {
+ // WebSocketRemoteConnectionEndpoint.from() must return an instance which type is one of the above.
+ throw new AssertionError();
+ }
}
- // 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);
+ return new Result(discoveredSecureEndpoints, discoveredInsecureEndpoints, lookupFailures);
}
public static final class Result {
- public final List discoveredRemoteConnectionEndpoints;
+ public final List discoveredSecureEndpoints;
+ public final List discoveredInsecureEndpoints;
public final List lookupFailures;
- public Result(List discoveredRemoteConnectionEndpoints,
+ public Result() {
+ this(Collections.emptyList());
+ }
+
+ public Result(List lookupFailures) {
+ // The list of endpoints needs to be mutable, because maybe a user supplied endpoint will be added to it.
+ // Hence we do not use Collections.emptyList() as argument for the discovered endpoints.
+ this(new ArrayList<>(1), new ArrayList<>(1), lookupFailures);
+ }
+
+ public Result(List discoveredSecureEndpoints,
+ List discoveredInsecureEndpoints,
List lookupFailures) {
- this.discoveredRemoteConnectionEndpoints = discoveredRemoteConnectionEndpoints;
+ this.discoveredSecureEndpoints = discoveredSecureEndpoints;
+ this.discoveredInsecureEndpoints = discoveredInsecureEndpoints;
this.lookupFailures = lookupFailures;
}
- public List getDiscoveredRemoteConnectionEndpoints() {
- return discoveredRemoteConnectionEndpoints;
+ public boolean isEmpty() {
+ return discoveredSecureEndpoints.isEmpty() && discoveredInsecureEndpoints.isEmpty();
+ }
+
+ public int discoveredEndpointCount() {
+ return discoveredSecureEndpoints.size() + discoveredInsecureEndpoints.size();
+ }
+
+ // TODO: Remove the following methods since the fields are already public? Or make the fields private and use
+ // the methods? I tend to remove the methods, as their method name is pretty long. But OTOH the fields reference
+ // mutable datastructes, which is uncommon to be public.
+ public List getDiscoveredSecureRemoteConnectionEndpoints() {
+ return discoveredSecureEndpoints;
+ }
+
+ public List getDiscoveredInsecureRemoteConnectionEndpoints() {
+ return discoveredInsecureEndpoints;
}
public List getLookupFailures() {
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
index c21a93497..bc0ee1cd0 100644
--- a/smack-websocket/src/test/java/org/jivesoftware/smack/websocket/XmppWebSocketTransportModuleTest.java
+++ b/smack-websocket/src/test/java/org/jivesoftware/smack/websocket/XmppWebSocketTransportModuleTest.java
@@ -17,24 +17,15 @@
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;
@@ -64,42 +55,6 @@ public class XmppWebSocketTransportModuleTest {
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();
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
index 075abc7c4..b189f0bf7 100644
--- 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
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2020 Aditya Borikar
+ * Copyright 2020 Aditya Borikar, 2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,20 +26,22 @@ 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);
+ WebSocketRemoteConnectionEndpoint endpoint = WebSocketRemoteConnectionEndpoint.from(endpointString);
assertEquals("fooDomain.org", endpoint.getHost());
assertEquals(UInt16.from(7070), endpoint.getPort());
- assertEquals(endpointString, endpoint.getWebSocketEndpoint().toString());
+ assertEquals(endpointString, endpoint.getUri().toString());
}
@Test
public void faultyEndpointTest() {
String faultyProtocolString = "wst://fooDomain.org:7070/ws/";
assertThrows(IllegalArgumentException.class, () -> {
- new WebSocketRemoteConnectionEndpoint(faultyProtocolString);
+ WebSocketRemoteConnectionEndpoint.from(faultyProtocolString);
});
}
+
}
diff --git a/smack-websocket/src/testFixtures/java/org/jivesoftware/smack/websocket/test/WebSocketFactoryServiceTestUtil.java b/smack-websocket/src/testFixtures/java/org/jivesoftware/smack/websocket/test/WebSocketFactoryServiceTestUtil.java
index ddafb5bf2..cb84133da 100644
--- a/smack-websocket/src/testFixtures/java/org/jivesoftware/smack/websocket/test/WebSocketFactoryServiceTestUtil.java
+++ b/smack-websocket/src/testFixtures/java/org/jivesoftware/smack/websocket/test/WebSocketFactoryServiceTestUtil.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2020 Florian Schmaus.
+ * Copyright 2020-2021 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,16 +20,21 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
+import java.net.URISyntaxException;
+
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
import org.jivesoftware.smack.websocket.impl.AbstractWebSocket;
import org.jivesoftware.smack.websocket.impl.WebSocketFactoryService;
+import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
public class WebSocketFactoryServiceTestUtil {
- public static void createWebSocketTest(Class extends AbstractWebSocket> expected) {
+ public static void createWebSocketTest(Class extends AbstractWebSocket> expected) throws URISyntaxException {
+ WebSocketRemoteConnectionEndpoint endpoint = WebSocketRemoteConnectionEndpoint.from("wss://example.org");
+
ModularXmppClientToServerConnectionInternal connectionInternal = mock(ModularXmppClientToServerConnectionInternal.class);
- AbstractWebSocket websocket = WebSocketFactoryService.createWebSocket(connectionInternal);
+ AbstractWebSocket websocket = WebSocketFactoryService.createWebSocket(endpoint, connectionInternal);
assertEquals(expected, websocket.getClass());
}