mirror of
https://github.com/vanitasvitae/Smack.git
synced 2024-12-22 10:37:59 +01:00
Rework WebSocket code
Related to SMACK-835.
This commit is contained in:
parent
0c013e4f29
commit
c5a546554b
38 changed files with 953 additions and 498 deletions
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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);
|
return SMACK_REACTOR.schedule(runnable, delay, unit, ScheduledAction.Kind.NonBlocking);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onStreamOpen(XmlPullParser parser) {
|
/**
|
||||||
// We found an opening stream.
|
* Must be called when a XMPP stream open tag is encountered. Sets values like the stream ID and the incoming stream
|
||||||
if ("jabber:client".equals(parser.getNamespace(null))) {
|
* XML environment.
|
||||||
streamId = parser.getAttributeValue("", "id");
|
* <p>
|
||||||
incomingStreamXmlEnvironment = XmlEnvironment.from(parser);
|
* This method also returns a matching stream close tag. For example if the stream open is {@code <stream …>}, then
|
||||||
|
* {@code </stream>} is returned. But if it is {@code <stream:stream>}, then {@code </stream:stream>} is returned.
|
||||||
|
* Or if it is {@code <foo:stream>}, then {@code </foo:stream>} is returned.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @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");
|
streamId = parser.getAttributeValue("id");
|
||||||
if (reportedServerDomainString == null) {
|
incomingStreamXmlEnvironment = XmlEnvironment.from(parser);
|
||||||
// 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.
|
String reportedServerDomainString = parser.getAttributeValue("from");
|
||||||
return;
|
// 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;
|
DomainBareJid reportedServerDomain;
|
||||||
try {
|
try {
|
||||||
reportedServerDomain = JidCreate.domainBareFrom(reportedServerDomainString);
|
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);
|
+ "' 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 "</stream>";
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final void sendStreamOpen() throws NotConnectedException, InterruptedException {
|
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
|
// 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
|
// 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.)
|
// response from the server (see e.g. RFC 6120 § 9.1.1 Step 2.)
|
||||||
CharSequence to = getXMPPServiceDomain();
|
DomainBareJid to = getXMPPServiceDomain();
|
||||||
CharSequence from = null;
|
CharSequence from = null;
|
||||||
CharSequence localpart = config.getUsername();
|
CharSequence localpart = config.getUsername();
|
||||||
if (localpart != null) {
|
if (localpart != null) {
|
||||||
|
@ -2247,7 +2264,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
updateOutgoingStreamXmlEnvironmentOnStreamOpen(streamOpen);
|
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);
|
return new StreamOpen(to, from, id, lang);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2017-2020 Florian Schmaus
|
* Copyright 2017-2021 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -75,6 +75,10 @@ public abstract class SmackFuture<V, E extends Exception> implements Future<V>,
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final synchronized boolean isDone() {
|
public final synchronized boolean isDone() {
|
||||||
|
return result != null || exception != null || cancelled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final synchronized boolean wasSuccessful() {
|
||||||
return result != null;
|
return result != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,6 +166,10 @@ public abstract class SmackFuture<V, E extends Exception> implements Future<V>,
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public E getExceptionIfAvailable() {
|
||||||
|
return exception;
|
||||||
|
}
|
||||||
|
|
||||||
protected final synchronized void maybeInvokeCallbacks() {
|
protected final synchronized void maybeInvokeCallbacks() {
|
||||||
if (cancelled) {
|
if (cancelled) {
|
||||||
return;
|
return;
|
||||||
|
@ -326,6 +334,11 @@ public abstract class SmackFuture<V, E extends Exception> implements Future<V>,
|
||||||
return 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 {
|
public static boolean await(Collection<? extends SmackFuture<?, ?>> futures, long timeout, TimeUnit unit) throws InterruptedException {
|
||||||
CountDownLatch latch = new CountDownLatch(futures.size());
|
CountDownLatch latch = new CountDownLatch(futures.size());
|
||||||
for (SmackFuture<?, ?> future : futures) {
|
for (SmackFuture<?, ?> future : futures) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2018-2020 Florian Schmaus
|
* Copyright 2018-2021 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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
|
@Override
|
||||||
public void setCurrentConnectionExceptionAndNotify(Exception exception) {
|
public String onStreamOpen(XmlPullParser parser) {
|
||||||
ModularXmppClientToServerConnection.this.setCurrentConnectionExceptionAndNotify(exception);
|
return ModularXmppClientToServerConnection.this.onStreamOpen(parser);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStreamOpen(XmlPullParser parser) {
|
|
||||||
ModularXmppClientToServerConnection.this.onStreamOpen(parser);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -571,7 +566,7 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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();
|
StreamOpenAndCloseFactory streamOpenAndCloseFactory = activeTransport.getStreamOpenAndCloseFactory();
|
||||||
return streamOpenAndCloseFactory.createStreamOpen(to, from, id, lang);
|
return streamOpenAndCloseFactory.createStreamOpen(to, from, id, lang);
|
||||||
}
|
}
|
||||||
|
@ -720,6 +715,11 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
throw SmackException.NoEndpointsDiscoveredException.from(lookupFailures);
|
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
|
// 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()
|
// 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.
|
// function to close the queue again on failure.
|
||||||
|
@ -1110,7 +1110,12 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
XmppClientToServerTransport.Stats stats = entry.getValue();
|
XmppClientToServerTransport.Stats stats = entry.getValue();
|
||||||
|
|
||||||
StringUtils.appendHeading(appendable, transportClass.getName());
|
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<String, Object> entry : filtersStats.entrySet()) {
|
for (Map.Entry<String, Object> entry : filtersStats.entrySet()) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2019-2020 Florian Schmaus
|
* Copyright 2019-2021 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.
|
// configuration, e.g. there is no edge from disconnected to connected.
|
||||||
throw new IllegalStateException(e);
|
throw new IllegalStateException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (ModularXmppClientToServerConnectionModuleDescriptor moduleDescriptor : moduleDescriptors) {
|
||||||
|
moduleDescriptor.validateConfiguration(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void printStateGraphInDotFormat(PrintWriter pw, boolean breakStateName) {
|
public void printStateGraphInDotFormat(PrintWriter pw, boolean breakStateName) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2019-2020 Florian Schmaus
|
* Copyright 2019-2021 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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(
|
protected abstract ModularXmppClientToServerConnectionModule<? extends ModularXmppClientToServerConnectionModuleDescriptor> constructXmppConnectionModule(
|
||||||
ModularXmppClientToServerConnectionInternal connectionInternal);
|
ModularXmppClientToServerConnectionInternal connectionInternal);
|
||||||
|
|
||||||
|
protected void validateConfiguration(ModularXmppClientToServerConnectionConfiguration configuration) {
|
||||||
|
}
|
||||||
|
|
||||||
public abstract static class Builder {
|
public abstract static class Builder {
|
||||||
private final ModularXmppClientToServerConnectionConfiguration.Builder connectionConfigurationBuilder;
|
private final ModularXmppClientToServerConnectionConfiguration.Builder connectionConfigurationBuilder;
|
||||||
|
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.AbstractStreamClose;
|
||||||
import org.jivesoftware.smack.packet.AbstractStreamOpen;
|
import org.jivesoftware.smack.packet.AbstractStreamOpen;
|
||||||
|
|
||||||
|
import org.jxmpp.jid.DomainBareJid;
|
||||||
|
|
||||||
public interface StreamOpenAndCloseFactory {
|
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();
|
AbstractStreamClose createStreamClose();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2019-2020 Florian Schmaus
|
* Copyright 2019-2021 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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);
|
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
|
* 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.
|
* explicitly, only if the filters are modified so that they potentially produced new data.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2020 Florian Schmaus
|
* Copyright 2020-2021 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,6 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smack.c2s.internal;
|
package org.jivesoftware.smack.c2s.internal;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.nio.channels.ClosedChannelException;
|
import java.nio.channels.ClosedChannelException;
|
||||||
import java.nio.channels.SelectableChannel;
|
import java.nio.channels.SelectableChannel;
|
||||||
import java.nio.channels.SelectionKey;
|
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.TopLevelStreamElement;
|
||||||
import org.jivesoftware.smack.packet.XmlEnvironment;
|
import org.jivesoftware.smack.packet.XmlEnvironment;
|
||||||
import org.jivesoftware.smack.util.Consumer;
|
import org.jivesoftware.smack.util.Consumer;
|
||||||
|
import org.jivesoftware.smack.util.PacketParserUtils;
|
||||||
import org.jivesoftware.smack.util.Supplier;
|
import org.jivesoftware.smack.util.Supplier;
|
||||||
import org.jivesoftware.smack.xml.XmlPullParser;
|
import org.jivesoftware.smack.xml.XmlPullParser;
|
||||||
|
import org.jivesoftware.smack.xml.XmlPullParserException;
|
||||||
|
|
||||||
public abstract class ModularXmppClientToServerConnectionInternal {
|
public abstract class ModularXmppClientToServerConnectionInternal {
|
||||||
|
|
||||||
|
@ -85,9 +88,19 @@ public abstract class ModularXmppClientToServerConnectionInternal {
|
||||||
|
|
||||||
public abstract void notifyConnectionError(Exception e);
|
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();
|
public abstract void onStreamClosed();
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2018-2020 Florian Schmaus
|
* Copyright 2018-2021 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.SmackException;
|
||||||
import org.jivesoftware.smack.XMPPException;
|
import org.jivesoftware.smack.XMPPException;
|
||||||
|
import org.jivesoftware.smack.c2s.XmppClientToServerTransport;
|
||||||
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
|
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
|
||||||
import org.jivesoftware.smack.c2s.internal.WalkStateGraphContext;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2018-2020 Florian Schmaus
|
* Copyright 2018-2021 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,6 +16,8 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smack.fsm;
|
package org.jivesoftware.smack.fsm;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.c2s.XmppClientToServerTransport;
|
||||||
|
|
||||||
public abstract class StateTransitionResult {
|
public abstract class StateTransitionResult {
|
||||||
|
|
||||||
private final String message;
|
private final String message;
|
||||||
|
@ -92,4 +94,10 @@ public abstract class StateTransitionResult {
|
||||||
super(stateDescriptor.getFullStateName(false) + " is not implemented (yet)");
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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)}.
|
* be achieved through {@link XMPPConnection#sendNonza(Nonza)}.
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractStreamOpen implements 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 CLIENT_NAMESPACE = "jabber:client";
|
||||||
public static final String SERVER_NAMESPACE = "jabber:server";
|
public static final String SERVER_NAMESPACE = "jabber:server";
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2018 Florian Schmaus
|
* Copyright 2018-2021 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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 StreamClose INSTANCE = new StreamClose();
|
||||||
|
|
||||||
|
public static final String STRING = "</" + StreamOpen.ELEMENT + ">";
|
||||||
|
|
||||||
private StreamClose() {
|
private StreamClose() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,4 +41,8 @@ public final class StreamClose extends AbstractStreamClose {
|
||||||
return StreamOpen.ELEMENT;
|
return StreamOpen.ELEMENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return STRING;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,9 @@ import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
* The stream open <b>tag</b>.
|
* The stream open <b>tag</b>.
|
||||||
*/
|
*/
|
||||||
public final class StreamOpen extends AbstractStreamOpen {
|
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) {
|
public StreamOpen(CharSequence to) {
|
||||||
this(to, null, null, null, StreamContentNamespace.client);
|
this(to, null, null, null, StreamContentNamespace.client);
|
||||||
|
|
|
@ -12,7 +12,8 @@ dependencies {
|
||||||
api project(':smack-openpgp')
|
api project(':smack-openpgp')
|
||||||
api project(':smack-resolver-minidns')
|
api project(':smack-resolver-minidns')
|
||||||
api project(':smack-resolver-minidns-dox')
|
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')
|
api project(':smack-tcp')
|
||||||
|
|
||||||
testImplementation(testFixtures(project(":smack-core")))
|
testImplementation(testFixtures(project(":smack-core")))
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2018-2020 Florian Schmaus
|
* Copyright 2018-2021 Florian Schmaus
|
||||||
*
|
*
|
||||||
* This file is part of smack-repl.
|
* This file is part of smack-repl.
|
||||||
*
|
*
|
||||||
|
@ -25,8 +25,6 @@ import java.io.IOException;
|
||||||
import java.io.OutputStreamWriter;
|
import java.io.OutputStreamWriter;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Date;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
|
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
|
||||||
import org.jivesoftware.smack.SmackException;
|
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.compression.XMPPInputOutputStream.FlushMethod;
|
||||||
import org.jivesoftware.smack.debugger.ConsoleDebugger;
|
import org.jivesoftware.smack.debugger.ConsoleDebugger;
|
||||||
import org.jivesoftware.smack.debugger.SmackDebuggerFactory;
|
import org.jivesoftware.smack.debugger.SmackDebuggerFactory;
|
||||||
import org.jivesoftware.smack.packet.Message;
|
|
||||||
import org.jivesoftware.smack.sm.StreamManagementModuleDescriptor;
|
import org.jivesoftware.smack.sm.StreamManagementModuleDescriptor;
|
||||||
import org.jivesoftware.smack.tcp.XmppTcpTransportModuleDescriptor;
|
import org.jivesoftware.smack.tcp.XmppTcpTransportModuleDescriptor;
|
||||||
|
|
||||||
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
|
|
||||||
|
|
||||||
import org.jxmpp.util.XmppDateTime;
|
|
||||||
|
|
||||||
public class Nio {
|
public class Nio {
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(Nio.class.getName());
|
|
||||||
|
|
||||||
public static void main(String[] args) throws SmackException, IOException, XMPPException, InterruptedException {
|
public static void main(String[] args) throws SmackException, IOException, XMPPException, InterruptedException {
|
||||||
doNio(args[0], args[1], args[2]);
|
doNio(args[0], args[1], args[2]);
|
||||||
}
|
}
|
||||||
|
@ -111,30 +102,7 @@ public class Nio {
|
||||||
|
|
||||||
connection.setReplyTimeout(5 * 60 * 1000);
|
connection.setReplyTimeout(5 * 60 * 1000);
|
||||||
|
|
||||||
connection.addConnectionStateMachineListener((event, c) -> {
|
XmppTools.modularConnectionTest(connection, "flo@geekplace.eu");
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2020 Aditya Borikar
|
* Copyright 2021 Florian Schmaus
|
||||||
*
|
*
|
||||||
* This file is part of smack-repl.
|
* This file is part of smack-repl.
|
||||||
*
|
*
|
||||||
|
@ -21,7 +21,6 @@
|
||||||
package org.igniterealtime.smack.smackrepl;
|
package org.igniterealtime.smack.smackrepl;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
|
|
||||||
import org.jivesoftware.smack.SmackException;
|
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.ModularXmppClientToServerConnection;
|
||||||
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
|
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
|
||||||
|
import org.jivesoftware.smack.util.TLSUtils;
|
||||||
import org.jivesoftware.smack.websocket.XmppWebSocketTransportModuleDescriptor;
|
import org.jivesoftware.smack.websocket.XmppWebSocketTransportModuleDescriptor;
|
||||||
|
|
||||||
public class WebSocketConnection {
|
public class WebSocketConnection {
|
||||||
|
|
||||||
public static void main(String[] args) throws SmackException, IOException, XMPPException, InterruptedException, URISyntaxException {
|
public static void main(String[] args)
|
||||||
ModularXmppClientToServerConnectionConfiguration.Builder builder = ModularXmppClientToServerConnectionConfiguration.builder();
|
throws URISyntaxException, SmackException, IOException, XMPPException, InterruptedException {
|
||||||
builder.removeAllModules();
|
String jid, password, websocketEndpoint, messageTo = null;
|
||||||
builder.setXmppAddressAndPassword(args[0], args[1]);
|
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);
|
XmppWebSocketTransportModuleDescriptor.Builder websocketBuilder = XmppWebSocketTransportModuleDescriptor.getBuilder(builder);
|
||||||
websocketBuilder.explicitlySetWebSocketEndpointAndDiscovery(new URI(args[2]), false);
|
websocketBuilder.explicitlySetWebSocketEndpointAndDiscovery(websocketEndpoint, false);
|
||||||
builder.addModule(websocketBuilder.build());
|
builder.addModule(websocketBuilder.build());
|
||||||
|
|
||||||
ModularXmppClientToServerConnectionConfiguration config = builder.build();
|
ModularXmppClientToServerConnectionConfiguration config = builder.build();
|
||||||
ModularXmppClientToServerConnection connection = new ModularXmppClientToServerConnection(config);
|
ModularXmppClientToServerConnection connection = new ModularXmppClientToServerConnection(config);
|
||||||
|
|
||||||
connection.connect();
|
connection.setReplyTimeout(5 * 60 * 1000);
|
||||||
connection.login();
|
|
||||||
connection.disconnect();
|
XmppTools.modularConnectionTest(connection, messageTo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2016 Florian Schmaus
|
* Copyright 2016-2021 Florian Schmaus
|
||||||
*
|
*
|
||||||
* This file is part of smack-repl.
|
* This file is part of smack-repl.
|
||||||
*
|
*
|
||||||
|
@ -23,6 +23,8 @@ package org.igniterealtime.smack.smackrepl;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.KeyManagementException;
|
import java.security.KeyManagementException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import org.jivesoftware.smack.SmackException;
|
import org.jivesoftware.smack.SmackException;
|
||||||
import org.jivesoftware.smack.SmackException.NoResponseException;
|
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.XMPPConnection;
|
||||||
import org.jivesoftware.smack.XMPPException;
|
import org.jivesoftware.smack.XMPPException;
|
||||||
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
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.XMPPTCPConnection;
|
||||||
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
|
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
|
||||||
import org.jivesoftware.smack.util.TLSUtils;
|
import org.jivesoftware.smack.util.TLSUtils;
|
||||||
|
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
|
||||||
import org.jivesoftware.smackx.iqregister.AccountManager;
|
import org.jivesoftware.smackx.iqregister.AccountManager;
|
||||||
|
|
||||||
import org.jxmpp.jid.DomainBareJid;
|
import org.jxmpp.jid.DomainBareJid;
|
||||||
import org.jxmpp.jid.impl.JidCreate;
|
import org.jxmpp.jid.impl.JidCreate;
|
||||||
import org.jxmpp.jid.parts.Localpart;
|
import org.jxmpp.jid.parts.Localpart;
|
||||||
|
import org.jxmpp.stringprep.XmppStringprepException;
|
||||||
|
import org.jxmpp.util.XmppDateTime;
|
||||||
|
|
||||||
public class XmppTools {
|
public class XmppTools {
|
||||||
|
|
||||||
|
@ -106,4 +112,40 @@ public class XmppTools {
|
||||||
connection.disconnect();
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,6 +83,7 @@ import org.jivesoftware.smack.packet.Presence;
|
||||||
import org.jivesoftware.smack.packet.Stanza;
|
import org.jivesoftware.smack.packet.Stanza;
|
||||||
import org.jivesoftware.smack.packet.StartTls;
|
import org.jivesoftware.smack.packet.StartTls;
|
||||||
import org.jivesoftware.smack.packet.StreamError;
|
import org.jivesoftware.smack.packet.StreamError;
|
||||||
|
import org.jivesoftware.smack.packet.StreamOpen;
|
||||||
import org.jivesoftware.smack.proxy.ProxyInfo;
|
import org.jivesoftware.smack.proxy.ProxyInfo;
|
||||||
import org.jivesoftware.smack.sasl.packet.SaslNonza;
|
import org.jivesoftware.smack.sasl.packet.SaslNonza;
|
||||||
import org.jivesoftware.smack.sm.SMUtils;
|
import org.jivesoftware.smack.sm.SMUtils;
|
||||||
|
@ -961,6 +962,8 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
switch (eventType) {
|
switch (eventType) {
|
||||||
case START_ELEMENT:
|
case START_ELEMENT:
|
||||||
final String name = parser.getName();
|
final String name = parser.getName();
|
||||||
|
final String namespace = parser.getNamespace();
|
||||||
|
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case Message.ELEMENT:
|
case Message.ELEMENT:
|
||||||
case IQ.IQ_ELEMENT:
|
case IQ.IQ_ELEMENT:
|
||||||
|
@ -972,7 +975,9 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "stream":
|
case "stream":
|
||||||
onStreamOpen(parser);
|
if (StreamOpen.ETHERX_JABBER_STREAMS_NAMESPACE.equals(namespace)) {
|
||||||
|
onStreamOpen(parser);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case "error":
|
case "error":
|
||||||
StreamError streamError = PacketParserUtils.parseStreamError(parser);
|
StreamError streamError = PacketParserUtils.parseStreamError(parser);
|
||||||
|
@ -989,7 +994,6 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
openStreamAndResetParser();
|
openStreamAndResetParser();
|
||||||
break;
|
break;
|
||||||
case "failure":
|
case "failure":
|
||||||
String namespace = parser.getNamespace(null);
|
|
||||||
switch (namespace) {
|
switch (namespace) {
|
||||||
case "urn:ietf:params:xml:ns:xmpp-tls":
|
case "urn:ietf:params:xml:ns:xmpp-tls":
|
||||||
// TLS negotiation has failed. The server will close the connection
|
// TLS negotiation has failed. The server will close the connection
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2019-2020 Florian Schmaus
|
* Copyright 2019-2021 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.RemoteXmppTcpConnectionEndpoints.Result;
|
||||||
import org.jivesoftware.smack.tcp.rce.Rfc6120TcpRemoteConnectionEndpoint;
|
import org.jivesoftware.smack.tcp.rce.Rfc6120TcpRemoteConnectionEndpoint;
|
||||||
import org.jivesoftware.smack.util.CollectionUtil;
|
import org.jivesoftware.smack.util.CollectionUtil;
|
||||||
import org.jivesoftware.smack.util.PacketParserUtils;
|
|
||||||
import org.jivesoftware.smack.util.StringUtils;
|
import org.jivesoftware.smack.util.StringUtils;
|
||||||
import org.jivesoftware.smack.util.UTF8;
|
import org.jivesoftware.smack.util.UTF8;
|
||||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpointLookupFailure;
|
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.Jid;
|
||||||
import org.jxmpp.jid.util.JidUtil;
|
import org.jxmpp.jid.util.JidUtil;
|
||||||
import org.jxmpp.xml.splitter.Utf8ByteXmppXmlSplitter;
|
import org.jxmpp.xml.splitter.Utf8ByteXmppXmlSplitter;
|
||||||
|
@ -213,6 +211,8 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
|
||||||
}
|
}
|
||||||
|
|
||||||
final String prefixXmlns = "xmlns:" + prefix;
|
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 streamClose = new StringBuilder(32);
|
||||||
final StringBuilder streamOpen = new StringBuilder(256);
|
final StringBuilder streamOpen = new StringBuilder(256);
|
||||||
|
|
||||||
|
@ -253,14 +253,7 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
|
||||||
this.streamOpen = streamOpen.toString();
|
this.streamOpen = streamOpen.toString();
|
||||||
this.streamClose = streamClose.toString();
|
this.streamClose = streamClose.toString();
|
||||||
|
|
||||||
XmlPullParser streamOpenParser;
|
connectionInternal.onStreamOpen(this.streamOpen);
|
||||||
try {
|
|
||||||
streamOpenParser = PacketParserUtils.getParserFor(this.streamOpen);
|
|
||||||
} catch (XmlPullParserException | IOException e) {
|
|
||||||
// Should never happen.
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
connectionInternal.onStreamOpen(streamOpenParser);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -586,7 +579,7 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
|
||||||
public StreamOpenAndCloseFactory getStreamOpenAndCloseFactory() {
|
public StreamOpenAndCloseFactory getStreamOpenAndCloseFactory() {
|
||||||
return new StreamOpenAndCloseFactory() {
|
return new StreamOpenAndCloseFactory() {
|
||||||
@Override
|
@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();
|
String xmlLang = connectionInternal.connection.getConfiguration().getXmlLang();
|
||||||
StreamOpen streamOpen = new StreamOpen(to, from, id, xmlLang, StreamOpen.StreamContentNamespace.client);
|
StreamOpen streamOpen = new StreamOpen(to, from, id, xmlLang, StreamOpen.StreamContentNamespace.client);
|
||||||
return streamOpen;
|
return streamOpen;
|
||||||
|
@ -603,6 +596,11 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
|
||||||
discoveredTcpEndpoints = null;
|
discoveredTcpEndpoints = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasUseableConnectionEndpoints() {
|
||||||
|
return discoveredTcpEndpoints != null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<SmackFuture<LookupConnectionEndpointsResult, Exception>> lookupConnectionEndpoints() {
|
protected List<SmackFuture<LookupConnectionEndpointsResult, Exception>> lookupConnectionEndpoints() {
|
||||||
// Assert that there are no stale discovered endpoints prior performing the lookup.
|
// 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);
|
return new EstablishingTcpConnectionState(stateDescriptor, connectionInternal);
|
||||||
}
|
}
|
||||||
|
|
||||||
final class EstablishingTcpConnectionState extends State {
|
final class EstablishingTcpConnectionState extends State.AbstractTransport {
|
||||||
private EstablishingTcpConnectionState(EstablishingTcpConnectionStateDescriptor stateDescriptor,
|
private EstablishingTcpConnectionState(EstablishingTcpConnectionStateDescriptor stateDescriptor,
|
||||||
ModularXmppClientToServerConnectionInternal connectionInternal) {
|
ModularXmppClientToServerConnectionInternal connectionInternal) {
|
||||||
super(stateDescriptor, connectionInternal);
|
super(tcpNioTransport, stateDescriptor, connectionInternal);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -777,6 +775,10 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
|
||||||
|
|
||||||
connectionInternal.setTransport(tcpNioTransport);
|
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");
|
connectionInternal.newStreamOpenWaitForFeaturesSequence("stream features after initial connection");
|
||||||
|
|
||||||
return new TcpSocketConnectedResult(remoteAddress);
|
return new TcpSocketConnectedResult(remoteAddress);
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -17,7 +17,6 @@
|
||||||
package org.jivesoftware.smack.websocket.okhttp;
|
package org.jivesoftware.smack.websocket.okhttp;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
@ -27,39 +26,34 @@ import org.jivesoftware.smack.debugger.SmackDebugger;
|
||||||
import okhttp3.Headers;
|
import okhttp3.Headers;
|
||||||
import okhttp3.Response;
|
import okhttp3.Response;
|
||||||
|
|
||||||
import org.jxmpp.xml.splitter.Utf8ByteXmppXmlSplitter;
|
|
||||||
import org.jxmpp.xml.splitter.XmlPrettyPrinter;
|
import org.jxmpp.xml.splitter.XmlPrettyPrinter;
|
||||||
import org.jxmpp.xml.splitter.XmppXmlSplitter;
|
import org.jxmpp.xml.splitter.XmppXmlSplitter;
|
||||||
|
|
||||||
public final class LoggingInterceptor {
|
public final class LoggingInterceptor {
|
||||||
private static final Logger LOGGER = Logger.getAnonymousLogger();
|
private static final Logger LOGGER = Logger.getLogger(LoggingInterceptor.class.getName());
|
||||||
private static final int MAX_ELEMENT_SIZE = 64 * 1024;
|
|
||||||
private final SmackDebugger debugger;
|
|
||||||
private final Utf8ByteXmppXmlSplitter incomingTextSplitter;
|
|
||||||
private final Utf8ByteXmppXmlSplitter outgoingTextSplitter;
|
|
||||||
|
|
||||||
public LoggingInterceptor(SmackDebugger smackDebugger) {
|
private final SmackDebugger debugger;
|
||||||
|
private final XmppXmlSplitter incomingXmlSplitter;
|
||||||
|
private final XmppXmlSplitter outgoingXmlSplitter;
|
||||||
|
|
||||||
|
LoggingInterceptor(SmackDebugger smackDebugger) {
|
||||||
this.debugger = smackDebugger;
|
this.debugger = smackDebugger;
|
||||||
|
|
||||||
XmlPrettyPrinter incomingTextPrinter = XmlPrettyPrinter.builder()
|
XmlPrettyPrinter incomingTextPrinter = XmlPrettyPrinter.builder()
|
||||||
.setPrettyWriter(sb -> debugger.incomingStreamSink(sb))
|
.setPrettyWriter(sb -> debugger.incomingStreamSink(sb))
|
||||||
.setTabWidth(4)
|
.setTabWidth(4)
|
||||||
.build();
|
.build();
|
||||||
XmppXmlSplitter incomingXmlSplitter = new XmppXmlSplitter(MAX_ELEMENT_SIZE, null,
|
incomingXmlSplitter = new XmppXmlSplitter(incomingTextPrinter);
|
||||||
incomingTextPrinter);
|
|
||||||
incomingTextSplitter = new Utf8ByteXmppXmlSplitter(incomingXmlSplitter);
|
|
||||||
|
|
||||||
XmlPrettyPrinter outgoingTextPrinter = XmlPrettyPrinter.builder()
|
XmlPrettyPrinter outgoingTextPrinter = XmlPrettyPrinter.builder()
|
||||||
.setPrettyWriter(sb -> debugger.outgoingStreamSink(sb))
|
.setPrettyWriter(sb -> debugger.outgoingStreamSink(sb))
|
||||||
.setTabWidth(4)
|
.setTabWidth(4)
|
||||||
.build();
|
.build();
|
||||||
XmppXmlSplitter outgoingXmlSplitter = new XmppXmlSplitter(MAX_ELEMENT_SIZE, null,
|
outgoingXmlSplitter = new XmppXmlSplitter(outgoingTextPrinter);
|
||||||
outgoingTextPrinter);
|
|
||||||
outgoingTextSplitter = new Utf8ByteXmppXmlSplitter(outgoingXmlSplitter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open response received here isn't in the form of an Xml an so, there isn't much to format.
|
// 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();
|
Headers headers = response.headers();
|
||||||
Iterator<?> iterator = headers.iterator();
|
Iterator<?> iterator = headers.iterator();
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
@ -70,18 +64,18 @@ public final class LoggingInterceptor {
|
||||||
debugger.incomingStreamSink(sb);
|
debugger.incomingStreamSink(sb);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void interceptReceivedText(String text) {
|
void interceptReceivedText(String text) {
|
||||||
try {
|
try {
|
||||||
incomingTextSplitter.write(text.getBytes(Charset.defaultCharset()));
|
incomingXmlSplitter.write(text);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// Connections shouldn't be terminated due to exceptions encountered during debugging. hence only log them.
|
// 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);
|
LOGGER.log(Level.WARNING, "IOException encountered while parsing received text: " + text, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void interceptSentText(String text) {
|
void interceptSentText(String text) {
|
||||||
try {
|
try {
|
||||||
outgoingTextSplitter.write(text.getBytes(Charset.defaultCharset()));
|
outgoingXmlSplitter.write(text);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// Connections shouldn't be terminated due to exceptions encountered during debugging, hence only log them.
|
// 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);
|
LOGGER.log(Level.WARNING, "IOException encountered while parsing outgoing text: " + text, e);
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,22 +16,17 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smack.websocket.okhttp;
|
package org.jivesoftware.smack.websocket.okhttp;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.net.URI;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.net.ssl.SSLSession;
|
import javax.net.ssl.SSLSession;
|
||||||
|
|
||||||
import org.jivesoftware.smack.SmackException;
|
import org.jivesoftware.smack.SmackFuture;
|
||||||
import org.jivesoftware.smack.XMPPException;
|
|
||||||
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
|
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.WebSocketException;
|
||||||
import org.jivesoftware.smack.websocket.elements.WebSocketOpenElement;
|
|
||||||
import org.jivesoftware.smack.websocket.impl.AbstractWebSocket;
|
import org.jivesoftware.smack.websocket.impl.AbstractWebSocket;
|
||||||
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
|
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
|
||||||
import org.jivesoftware.smack.xml.XmlPullParserException;
|
|
||||||
|
|
||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
import okhttp3.Request;
|
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 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<AbstractWebSocket, Exception> future = new SmackFuture.InternalSmackFuture<>();
|
||||||
|
|
||||||
private final ModularXmppClientToServerConnectionInternal connectionInternal;
|
|
||||||
private final LoggingInterceptor interceptor;
|
private final LoggingInterceptor interceptor;
|
||||||
|
|
||||||
private String openStreamHeader;
|
private final WebSocket okHttpWebSocket;
|
||||||
private WebSocket currentWebSocket;
|
|
||||||
private WebSocketConnectionPhase phase;
|
|
||||||
private WebSocketRemoteConnectionEndpoint connectedEndpoint;
|
|
||||||
|
|
||||||
public OkHttpWebSocket(ModularXmppClientToServerConnectionInternal connectionInternal) {
|
public OkHttpWebSocket(WebSocketRemoteConnectionEndpoint endpoint,
|
||||||
this.connectionInternal = connectionInternal;
|
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) {
|
if (connectionInternal.smackDebugger != null) {
|
||||||
interceptor = new LoggingInterceptor(connectionInternal.smackDebugger);
|
interceptor = new LoggingInterceptor(connectionInternal.smackDebugger);
|
||||||
} else {
|
} else {
|
||||||
interceptor = null;
|
interceptor = null;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
final URI uri = endpoint.getUri();
|
||||||
public void connect(WebSocketRemoteConnectionEndpoint endpoint) throws InterruptedException, SmackException, XMPPException {
|
final String url = uri.toString();
|
||||||
final String currentUri = endpoint.getWebSocketEndpoint().toString();
|
|
||||||
Request request = new Request.Builder()
|
Request request = new Request.Builder()
|
||||||
.url(currentUri)
|
.url(url)
|
||||||
.header("Sec-WebSocket-Protocol", "xmpp")
|
.header("Sec-WebSocket-Protocol", "xmpp")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
WebSocketListener listener = new WebSocketListener() {
|
okHttpWebSocket = okHttpClient.newWebSocket(request, listener);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
private final WebSocketListener listener = new WebSocketListener() {
|
||||||
public void onOpen(WebSocket webSocket, Response response) {
|
|
||||||
LOGGER.log(Level.FINER, "WebSocket is open");
|
@Override
|
||||||
phase = WebSocketConnectionPhase.openFrameSent;
|
public void onOpen(WebSocket webSocket, Response response) {
|
||||||
if (interceptor != null) {
|
LOGGER.log(Level.FINER, "OkHttp invoked onOpen() for {0}. Response: {1}",
|
||||||
interceptor.interceptOpenResponse(response);
|
new Object[] { webSocket, response });
|
||||||
}
|
|
||||||
send(new WebSocketOpenElement(connectionInternal.connection.getXMPPServiceDomain()));
|
if (interceptor != null) {
|
||||||
|
interceptor.interceptOpenResponse(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
future.setResult(OkHttpWebSocket.this);
|
||||||
public void onMessage(WebSocket webSocket, String text) {
|
}
|
||||||
if (interceptor != null) {
|
|
||||||
interceptor.interceptReceivedText(text);
|
|
||||||
}
|
|
||||||
if (isCloseElement(text)) {
|
|
||||||
connectionInternal.onStreamClosed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String closingStream = "</stream>";
|
@Override
|
||||||
switch (phase) {
|
public void onMessage(WebSocket webSocket, String text) {
|
||||||
case openFrameSent:
|
if (interceptor != null) {
|
||||||
if (isOpenElement(text)) {
|
interceptor.interceptReceivedText(text);
|
||||||
// Converts the <open> element received into <stream> 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
|
onIncomingWebSocketElement(text);
|
||||||
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
|
}
|
||||||
LOGGER.log(Level.INFO, "Exception caught", t);
|
|
||||||
WebSocketException websocketException = new WebSocketException(t);
|
@Override
|
||||||
if (connectionInternal.connection.isConnected()) {
|
public void onFailure(WebSocket webSocket, Throwable throwable, Response response) {
|
||||||
connectionInternal.notifyConnectionError(websocketException);
|
LOGGER.log(Level.FINER, "OkHttp invoked onFailure() for " + webSocket + ". Response: " + response, throwable);
|
||||||
} else {
|
WebSocketException websocketException = new WebSocketException(throwable);
|
||||||
connectionInternal.setCurrentConnectionExceptionAndNotify(websocketException);
|
|
||||||
}
|
// 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.
|
@Override
|
||||||
currentWebSocket = okHttpClient.newWebSocket(request, listener);
|
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.
|
@Override
|
||||||
connectionInternal.waitForFeaturesReceived("Waiting to receive features");
|
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<AbstractWebSocket, Exception> getFuture() {
|
||||||
|
return future;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void send(TopLevelStreamElement element) {
|
public void send(String element) {
|
||||||
String textToBeSent = element.toXML().toString();
|
|
||||||
if (interceptor != null) {
|
if (interceptor != null) {
|
||||||
interceptor.interceptSentText(textToBeSent);
|
interceptor.interceptSentText(element);
|
||||||
}
|
}
|
||||||
currentWebSocket.send(textToBeSent);
|
okHttpWebSocket.send(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void disconnect(int code, String message) {
|
public void disconnect(int code, String message) {
|
||||||
currentWebSocket.close(code, message);
|
LOGGER.log(Level.INFO, "WebSocket closing with code: " + code + " and message: " + message);
|
||||||
LOGGER.log(Level.INFO, "WebSocket has been closed with message: " + message);
|
okHttpWebSocket.close(code, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isConnectionSecure() {
|
public boolean isConnectionSecure() {
|
||||||
return connectedEndpoint.isSecureEndpoint();
|
return endpoint.isSecureEndpoint();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isConnected() {
|
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
|
@Override
|
||||||
public SSLSession getSSLSession() {
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2020 Florian Schmaus.
|
* Copyright 2020-2021 Florian Schmaus.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.c2s.internal.ModularXmppClientToServerConnectionInternal;
|
||||||
import org.jivesoftware.smack.websocket.impl.AbstractWebSocket;
|
import org.jivesoftware.smack.websocket.impl.AbstractWebSocket;
|
||||||
import org.jivesoftware.smack.websocket.impl.WebSocketFactory;
|
import org.jivesoftware.smack.websocket.impl.WebSocketFactory;
|
||||||
|
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
|
||||||
|
|
||||||
public class OkHttpWebSocketFactory implements WebSocketFactory {
|
public class OkHttpWebSocketFactory implements WebSocketFactory {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AbstractWebSocket create(ModularXmppClientToServerConnectionInternal connectionInternal) {
|
public AbstractWebSocket create(WebSocketRemoteConnectionEndpoint endpoint, ModularXmppClientToServerConnectionInternal connectionInternal) {
|
||||||
return new OkHttpWebSocket(connectionInternal);
|
return new OkHttpWebSocket(endpoint, connectionInternal);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2020 Florian Schmaus.
|
* Copyright 2020-2021 Florian Schmaus.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,6 +16,8 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smack.websocket.okhttp;
|
package org.jivesoftware.smack.websocket.okhttp;
|
||||||
|
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
|
||||||
import org.jivesoftware.smack.websocket.test.WebSocketFactoryServiceTestUtil;
|
import org.jivesoftware.smack.websocket.test.WebSocketFactoryServiceTestUtil;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
@ -23,7 +25,7 @@ import org.junit.jupiter.api.Test;
|
||||||
public class OkHttpWebSocketFactoryServiceTest {
|
public class OkHttpWebSocketFactoryServiceTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void createWebSocketTest() {
|
public void createWebSocketTest() throws URISyntaxException {
|
||||||
WebSocketFactoryServiceTestUtil.createWebSocketTest(OkHttpWebSocket.class);
|
WebSocketFactoryServiceTestUtil.createWebSocketTest(OkHttpWebSocket.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.ArrayList;
|
||||||
import java.util.List;
|
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.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.XmppWebSocketTransportModule.EstablishingWebSocketConnectionState;
|
||||||
import org.jivesoftware.smack.websocket.impl.AbstractWebSocket;
|
import org.jivesoftware.smack.websocket.impl.AbstractWebSocket;
|
||||||
import org.jivesoftware.smack.websocket.impl.WebSocketFactoryService;
|
import org.jivesoftware.smack.websocket.impl.WebSocketFactoryService;
|
||||||
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
|
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
|
||||||
|
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpointLookup;
|
||||||
|
|
||||||
public final class WebSocketConnectionAttemptState {
|
public final class WebSocketConnectionAttemptState {
|
||||||
private final ModularXmppClientToServerConnectionInternal connectionInternal;
|
private final ModularXmppClientToServerConnectionInternal connectionInternal;
|
||||||
private final XmppWebSocketTransportModule.XmppWebSocketTransport.DiscoveredWebSocketEndpoints discoveredEndpoints;
|
private final XmppWebSocketTransportModule.XmppWebSocketTransport.DiscoveredWebSocketEndpoints discoveredEndpoints;
|
||||||
|
|
||||||
private WebSocketRemoteConnectionEndpoint connectedEndpoint;
|
private AbstractWebSocket webSocket;
|
||||||
|
|
||||||
WebSocketConnectionAttemptState(ModularXmppClientToServerConnectionInternal connectionInternal,
|
WebSocketConnectionAttemptState(ModularXmppClientToServerConnectionInternal connectionInternal,
|
||||||
XmppWebSocketTransportModule.XmppWebSocketTransport.DiscoveredWebSocketEndpoints discoveredWebSocketEndpoints,
|
XmppWebSocketTransportModule.XmppWebSocketTransport.DiscoveredWebSocketEndpoints discoveredWebSocketEndpoints,
|
||||||
EstablishingWebSocketConnectionState establishingWebSocketConnectionState) {
|
EstablishingWebSocketConnectionState establishingWebSocketConnectionState) {
|
||||||
assert discoveredWebSocketEndpoints != null;
|
assert discoveredWebSocketEndpoints != null;
|
||||||
|
assert !discoveredWebSocketEndpoints.result.isEmpty();
|
||||||
|
|
||||||
this.connectionInternal = connectionInternal;
|
this.connectionInternal = connectionInternal;
|
||||||
this.discoveredEndpoints = discoveredWebSocketEndpoints;
|
this.discoveredEndpoints = discoveredWebSocketEndpoints;
|
||||||
}
|
}
|
||||||
|
@ -44,48 +51,96 @@ public final class WebSocketConnectionAttemptState {
|
||||||
*
|
*
|
||||||
* @return {@link AbstractWebSocket} with which connection is establised
|
* @return {@link AbstractWebSocket} with which connection is establised
|
||||||
* @throws InterruptedException if the calling thread was interrupted
|
* @throws InterruptedException if the calling thread was interrupted
|
||||||
* @throws WebSocketException if encounters a websocket exception
|
|
||||||
*/
|
*/
|
||||||
AbstractWebSocket establishWebSocketConnection() throws InterruptedException, WebSocketException {
|
@SuppressWarnings({"incomplete-switch", "MissingCasesInEnumSwitch"})
|
||||||
List<WebSocketRemoteConnectionEndpoint> endpoints = discoveredEndpoints.result.discoveredRemoteConnectionEndpoints;
|
StateTransitionResult.Failure establishWebSocketConnection() throws InterruptedException {
|
||||||
|
final WebSocketRemoteConnectionEndpointLookup.Result endpointLookupResult = discoveredEndpoints.result;
|
||||||
|
final List<Exception> failures = new ArrayList<>(endpointLookupResult.discoveredEndpointCount());
|
||||||
|
|
||||||
if (endpoints.isEmpty()) {
|
webSocket = null;
|
||||||
throw new WebSocketException(new Throwable("No Endpoints discovered to establish connection"));
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Throwable> connectionFailureList = new ArrayList<>();
|
SecurityMode securityMode = connectionInternal.connection.getConfiguration().getSecurityMode();
|
||||||
AbstractWebSocket websocket = WebSocketFactoryService.createWebSocket(connectionInternal);
|
switch (securityMode) {
|
||||||
|
case required:
|
||||||
// Keep iterating over available endpoints until a connection is establised or all endpoints are tried to create a connection with.
|
case ifpossible:
|
||||||
for (WebSocketRemoteConnectionEndpoint endpoint : endpoints) {
|
establishWebSocketConnection(endpointLookupResult.discoveredSecureEndpoints, failures);
|
||||||
try {
|
if (webSocket != null) {
|
||||||
websocket.connect(endpoint);
|
return null;
|
||||||
connectedEndpoint = endpoint;
|
|
||||||
break;
|
|
||||||
} catch (Throwable t) {
|
|
||||||
connectionFailureList.add(t);
|
|
||||||
|
|
||||||
// If the number of entries in connectionFailureList is equal to the number of endpoints,
|
|
||||||
// it means that all endpoints have been tried and have been unsuccessful.
|
|
||||||
if (connectionFailureList.size() == endpoints.size()) {
|
|
||||||
WebSocketException websocketException = new WebSocketException(connectionFailureList);
|
|
||||||
throw new WebSocketException(websocketException);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert connectedEndpoint != null;
|
establishWebSocketConnection(endpointLookupResult.discoveredInsecureEndpoints, failures);
|
||||||
|
if (webSocket != null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Return connected websocket when no failure occurs.
|
StateTransitionResult.Failure failure = FailedToConnectToAnyWebSocketEndpoint.create(failures);
|
||||||
return websocket;
|
return failure;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void establishWebSocketConnection(List<? extends WebSocketRemoteConnectionEndpoint> webSocketEndpoints,
|
||||||
* Returns the connected websocket endpoint.
|
List<Exception> failures) throws InterruptedException {
|
||||||
*
|
final int endpointCount = webSocketEndpoints.size();
|
||||||
* @return connected websocket endpoint
|
|
||||||
*/
|
List<SmackFuture<AbstractWebSocket, Exception>> futures = new ArrayList<>(endpointCount);
|
||||||
public WebSocketRemoteConnectionEndpoint getConnectedEndpoint() {
|
{
|
||||||
return connectedEndpoint;
|
List<AbstractWebSocket> 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<AbstractWebSocket, Exception> future = webSocket.getFuture();
|
||||||
|
futures.add(future);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SmackFuture.await(futures, connectionInternal.connection.getReplyTimeout());
|
||||||
|
|
||||||
|
for (SmackFuture<AbstractWebSocket, Exception> 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<Exception> failures;
|
||||||
|
|
||||||
|
private FailedToConnectToAnyWebSocketEndpoint(String failureMessage, List<Exception> failures) {
|
||||||
|
super(failureMessage);
|
||||||
|
this.failures = failures;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Exception> getFailures() {
|
||||||
|
return failures;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FailedToConnectToAnyWebSocketEndpoint create(List<Exception> failures) {
|
||||||
|
StringBuilder sb = new StringBuilder(256);
|
||||||
|
StringUtils.appendTo(failures, sb, e -> sb.append(e.getMessage()));
|
||||||
|
String message = sb.toString();
|
||||||
|
return new FailedToConnectToAnyWebSocketEndpoint(message, failures);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2020 Aditya Borikar
|
* Copyright 2021 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,23 +16,11 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smack.websocket;
|
package org.jivesoftware.smack.websocket;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public final class WebSocketException extends Exception {
|
public final class WebSocketException extends Exception {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
private final List<Throwable> throwableList;
|
|
||||||
|
|
||||||
public WebSocketException(List<Throwable> throwableList) {
|
|
||||||
this.throwableList = throwableList;
|
|
||||||
}
|
|
||||||
|
|
||||||
public WebSocketException(Throwable throwable) {
|
public WebSocketException(Throwable throwable) {
|
||||||
this.throwableList = Collections.singletonList(throwable);
|
super("WebSocketException: " + throwable.getMessage(), throwable);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Throwable> getThrowableList() {
|
|
||||||
return throwableList;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,20 +16,16 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smack.websocket;
|
package org.jivesoftware.smack.websocket;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.net.ssl.SSLSession;
|
import javax.net.ssl.SSLSession;
|
||||||
|
|
||||||
import org.jivesoftware.smack.AsyncButOrdered;
|
import org.jivesoftware.smack.AsyncButOrdered;
|
||||||
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
|
|
||||||
import org.jivesoftware.smack.SmackException;
|
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;
|
||||||
import org.jivesoftware.smack.SmackFuture.InternalSmackFuture;
|
import org.jivesoftware.smack.SmackFuture.InternalSmackFuture;
|
||||||
import org.jivesoftware.smack.XMPPException;
|
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.WebSocketCloseElement;
|
||||||
import org.jivesoftware.smack.websocket.elements.WebSocketOpenElement;
|
import org.jivesoftware.smack.websocket.elements.WebSocketOpenElement;
|
||||||
import org.jivesoftware.smack.websocket.impl.AbstractWebSocket;
|
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.WebSocketRemoteConnectionEndpoint;
|
||||||
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpointLookup;
|
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpointLookup;
|
||||||
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpointLookup.Result;
|
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpointLookup.Result;
|
||||||
|
|
||||||
import org.jxmpp.jid.DomainBareJid;
|
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.
|
* 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,
|
protected EstablishingWebSocketConnectionState(StateDescriptor stateDescriptor,
|
||||||
ModularXmppClientToServerConnectionInternal connectionInternal) {
|
ModularXmppClientToServerConnectionInternal connectionInternal) {
|
||||||
super(stateDescriptor, connectionInternal);
|
super(websocketTransport, stateDescriptor, connectionInternal);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
|
public AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) throws InterruptedException,
|
||||||
throws IOException, SmackException, InterruptedException, XMPPException {
|
NoResponseException, NotConnectedException, SmackException, XMPPException {
|
||||||
WebSocketConnectionAttemptState connectionAttemptState = new WebSocketConnectionAttemptState(
|
WebSocketConnectionAttemptState connectionAttemptState = new WebSocketConnectionAttemptState(
|
||||||
connectionInternal, discoveredWebSocketEndpoints, this);
|
connectionInternal, discoveredWebSocketEndpoints, this);
|
||||||
|
|
||||||
try {
|
StateTransitionResult.Failure failure = connectionAttemptState.establishWebSocketConnection();
|
||||||
websocket = connectionAttemptState.establishWebSocketConnection();
|
if (failure != null) {
|
||||||
} catch (InterruptedException | WebSocketException e) {
|
|
||||||
StateTransitionResult.Failure failure = new StateTransitionResult.FailureCausedByException<Exception>(e);
|
|
||||||
return failure;
|
return failure;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
websocket = connectionAttemptState.getConnectedWebSocket();
|
||||||
|
|
||||||
connectionInternal.setTransport(websocketTransport);
|
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.
|
// 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;
|
final WebSocketRemoteConnectionEndpoint connectedEndpoint;
|
||||||
|
|
||||||
public WebSocketConnectedResult(WebSocketRemoteConnectionEndpoint connectedEndpoint) {
|
public WebSocketConnectedResult(WebSocketRemoteConnectionEndpoint connectedEndpoint) {
|
||||||
super("WebSocket connection establised with endpoint: " + connectedEndpoint.getWebSocketEndpoint());
|
super("WebSocket connection establised with endpoint: " + connectedEndpoint);
|
||||||
this.connectedEndpoint = connectedEndpoint;
|
this.connectedEndpoint = connectedEndpoint;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -164,6 +166,12 @@ public final class XmppWebSocketTransportModule
|
||||||
discoveredWebSocketEndpoints = null;
|
discoveredWebSocketEndpoints = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasUseableConnectionEndpoints() {
|
||||||
|
return discoveredWebSocketEndpoints != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("incomplete-switch")
|
||||||
@Override
|
@Override
|
||||||
protected List<SmackFuture<LookupConnectionEndpointsResult, Exception>> lookupConnectionEndpoints() {
|
protected List<SmackFuture<LookupConnectionEndpointsResult, Exception>> lookupConnectionEndpoints() {
|
||||||
// Assert that there are no stale discovered endpoints prior performing the lookup.
|
// Assert that there are no stale discovered endpoints prior performing the lookup.
|
||||||
|
@ -172,51 +180,56 @@ public final class XmppWebSocketTransportModule
|
||||||
InternalSmackFuture<LookupConnectionEndpointsResult, Exception> websocketEndpointsLookupFuture = new InternalSmackFuture<>();
|
InternalSmackFuture<LookupConnectionEndpointsResult, Exception> websocketEndpointsLookupFuture = new InternalSmackFuture<>();
|
||||||
|
|
||||||
connectionInternal.asyncGo(() -> {
|
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.
|
if (moduleDescriptor.isWebSocketEndpointDiscoveryEnabled()) {
|
||||||
URI uri = moduleDescriptor.getExplicitlyProvidedUri();
|
// Fetch remote endpoints.
|
||||||
if (uri != null) {
|
result = WebSocketRemoteConnectionEndpointLookup.lookup(host);
|
||||||
providedEndpoint = new WebSocketRemoteConnectionEndpoint(uri);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!moduleDescriptor.isWebSocketEndpointDiscoveryEnabled()) {
|
WebSocketRemoteConnectionEndpoint providedEndpoint = moduleDescriptor.getExplicitlyProvidedEndpoint();
|
||||||
// If discovery is disabled, assert that the provided endpoint isn't null.
|
if (providedEndpoint != null) {
|
||||||
assert providedEndpoint != null;
|
// If there was not automatic lookup that produced a result, then create a result now.
|
||||||
|
if (result == null) {
|
||||||
SecurityMode mode = connectionInternal.connection.getConfiguration().getSecurityMode();
|
result = new Result();
|
||||||
if ((providedEndpoint.isSecureEndpoint() &&
|
|
||||||
mode.equals(SecurityMode.disabled))
|
|
||||||
|| (!providedEndpoint.isSecureEndpoint() &&
|
|
||||||
mode.equals(SecurityMode.required))) {
|
|
||||||
throw new IllegalStateException("Explicitly configured uri: " + providedEndpoint.getWebSocketEndpoint().toString()
|
|
||||||
+ " does not comply with the configured security mode: " + mode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate Result for explicitly configured endpoint.
|
// We insert the provided endpoint at the beginning of the list, so that it is used first.
|
||||||
Result manualResult = new Result(Arrays.asList(providedEndpoint), null);
|
final int INSERT_INDEX = 0;
|
||||||
|
if (providedEndpoint instanceof SecureWebSocketRemoteConnectionEndpoint) {
|
||||||
LookupConnectionEndpointsResult endpointsResult = new DiscoveredWebSocketEndpoints(manualResult);
|
SecureWebSocketRemoteConnectionEndpoint secureEndpoint = (SecureWebSocketRemoteConnectionEndpoint) providedEndpoint;
|
||||||
|
result.discoveredSecureEndpoints.add(INSERT_INDEX, secureEndpoint);
|
||||||
websocketEndpointsLookupFuture.setResult(endpointsResult);
|
} else if (providedEndpoint instanceof InsecureWebSocketRemoteConnectionEndpoint) {
|
||||||
} else {
|
InsecureWebSocketRemoteConnectionEndpoint insecureEndpoint = (InsecureWebSocketRemoteConnectionEndpoint) providedEndpoint;
|
||||||
DomainBareJid host = connectionInternal.connection.getXMPPServiceDomain();
|
result.discoveredInsecureEndpoints.add(INSERT_INDEX, insecureEndpoint);
|
||||||
ModularXmppClientToServerConnectionConfiguration configuration = connectionInternal.connection.getConfiguration();
|
} else {
|
||||||
SecurityMode mode = configuration.getSecurityMode();
|
throw new AssertionError();
|
||||||
|
}
|
||||||
// Fetch remote endpoints.
|
|
||||||
Result xep0156result = WebSocketRemoteConnectionEndpointLookup.lookup(host, mode);
|
|
||||||
|
|
||||||
List<WebSocketRemoteConnectionEndpoint> 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
return Collections.singletonList(websocketEndpointsLookupFuture);
|
||||||
|
@ -238,11 +251,11 @@ public final class XmppWebSocketTransportModule
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void notifyAboutNewOutgoingElements() {
|
protected void notifyAboutNewOutgoingElements() {
|
||||||
Queue<TopLevelStreamElement> outgoingElementsQueue = connectionInternal.outgoingElementsQueue;
|
final Queue<TopLevelStreamElement> outgoingElementsQueue = connectionInternal.outgoingElementsQueue;
|
||||||
asyncButOrderedOutgoingElementsQueue.performAsyncButOrdered(outgoingElementsQueue, () -> {
|
asyncButOrderedOutgoingElementsQueue.performAsyncButOrdered(outgoingElementsQueue, () -> {
|
||||||
// Once new outgoingElement is notified, send the top level stream element obtained by polling.
|
for (TopLevelStreamElement topLevelStreamElement; (topLevelStreamElement = outgoingElementsQueue.poll()) != null;) {
|
||||||
TopLevelStreamElement topLevelStreamElement = outgoingElementsQueue.poll();
|
websocket.send(topLevelStreamElement);
|
||||||
websocket.send(topLevelStreamElement);
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,15 +281,11 @@ public final class XmppWebSocketTransportModule
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public StreamOpenAndCloseFactory getStreamOpenAndCloseFactory() {
|
public StreamOpenAndCloseFactory getStreamOpenAndCloseFactory() {
|
||||||
|
// TODO: Create extra class for this?
|
||||||
return new StreamOpenAndCloseFactory() {
|
return new StreamOpenAndCloseFactory() {
|
||||||
@Override
|
@Override
|
||||||
public AbstractStreamOpen createStreamOpen(CharSequence to, CharSequence from, String id, String lang) {
|
public AbstractStreamOpen createStreamOpen(DomainBareJid to, CharSequence from, String id, String lang) {
|
||||||
try {
|
return new WebSocketOpenElement(to);
|
||||||
return new WebSocketOpenElement(JidCreate.domainBareFrom(to));
|
|
||||||
} catch (XmppStringprepException e) {
|
|
||||||
Logger.getAnonymousLogger().log(Level.WARNING, "Couldn't create OpenElement", e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public AbstractStreamClose createStreamClose() {
|
public AbstractStreamClose createStreamClose() {
|
||||||
|
@ -295,10 +304,6 @@ public final class XmppWebSocketTransportModule
|
||||||
assert result != null;
|
assert result != null;
|
||||||
this.result = result;
|
this.result = result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public WebSocketRemoteConnectionEndpointLookup.Result getResult() {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -308,10 +313,13 @@ public final class XmppWebSocketTransportModule
|
||||||
final class WebSocketEndpointsDiscoveryFailed implements LookupConnectionEndpointsFailed {
|
final class WebSocketEndpointsDiscoveryFailed implements LookupConnectionEndpointsFailed {
|
||||||
final List<RemoteConnectionEndpointLookupFailure> lookupFailures;
|
final List<RemoteConnectionEndpointLookupFailure> lookupFailures;
|
||||||
|
|
||||||
WebSocketEndpointsDiscoveryFailed(
|
WebSocketEndpointsDiscoveryFailed(RemoteConnectionEndpointLookupFailure lookupFailure) {
|
||||||
WebSocketRemoteConnectionEndpointLookup.Result result) {
|
this(Collections.singletonList(lookupFailure));
|
||||||
assert result != null;
|
}
|
||||||
lookupFailures = Collections.unmodifiableList(result.lookupFailures);
|
|
||||||
|
WebSocketEndpointsDiscoveryFailed(List<RemoteConnectionEndpointLookupFailure> lookupFailures) {
|
||||||
|
assert lookupFailures != null;
|
||||||
|
this.lookupFailures = Collections.unmodifiableList(lookupFailures);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
|
||||||
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
|
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
|
||||||
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
|
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
|
||||||
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionModule;
|
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.fsm.StateDescriptor;
|
||||||
import org.jivesoftware.smack.util.Objects;
|
import org.jivesoftware.smack.util.Objects;
|
||||||
import org.jivesoftware.smack.websocket.XmppWebSocketTransportModule.EstablishingWebSocketConnectionStateDescriptor;
|
import org.jivesoftware.smack.websocket.XmppWebSocketTransportModule.EstablishingWebSocketConnectionStateDescriptor;
|
||||||
|
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The descriptor class for {@link XmppWebSocketTransportModule}.
|
* The descriptor class for {@link XmppWebSocketTransportModule}.
|
||||||
|
@ -37,12 +39,43 @@ import org.jivesoftware.smack.websocket.XmppWebSocketTransportModule.Establishin
|
||||||
* use {@link ModularXmppClientToServerConnectionConfiguration.Builder#addModule(ModularXmppClientToServerConnectionModuleDescriptor)}.
|
* use {@link ModularXmppClientToServerConnectionConfiguration.Builder#addModule(ModularXmppClientToServerConnectionModuleDescriptor)}.
|
||||||
*/
|
*/
|
||||||
public final class XmppWebSocketTransportModuleDescriptor extends ModularXmppClientToServerConnectionModuleDescriptor {
|
public final class XmppWebSocketTransportModuleDescriptor extends ModularXmppClientToServerConnectionModuleDescriptor {
|
||||||
private boolean performWebSocketEndpointDiscovery;
|
private final boolean performWebSocketEndpointDiscovery;
|
||||||
private URI uri;
|
private final boolean implicitWebSocketEndpoint;
|
||||||
|
private final URI uri;
|
||||||
|
private final WebSocketRemoteConnectionEndpoint wsRce;
|
||||||
|
|
||||||
public XmppWebSocketTransportModuleDescriptor(Builder builder) {
|
public XmppWebSocketTransportModuleDescriptor(Builder builder) {
|
||||||
this.performWebSocketEndpointDiscovery = builder.performWebSocketEndpointDiscovery;
|
this.performWebSocketEndpointDiscovery = builder.performWebSocketEndpointDiscovery;
|
||||||
|
this.implicitWebSocketEndpoint = builder.implicitWebSocketEndpoint;
|
||||||
|
|
||||||
this.uri = builder.uri;
|
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;
|
return performWebSocketEndpointDiscovery;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isImplicitWebSocketEndpointEnabled() {
|
||||||
|
return implicitWebSocketEndpoint;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns explicitly configured websocket endpoint uri.
|
* Returns explicitly configured websocket endpoint uri.
|
||||||
* @return uri
|
* @return uri
|
||||||
|
@ -61,6 +98,10 @@ public final class XmppWebSocketTransportModuleDescriptor extends ModularXmppCli
|
||||||
return uri;
|
return uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WebSocketRemoteConnectionEndpoint getExplicitlyProvidedEndpoint() {
|
||||||
|
return wsRce;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Set<Class<? extends StateDescriptor>> getStateDescriptors() {
|
protected Set<Class<? extends StateDescriptor>> getStateDescriptors() {
|
||||||
Set<Class<? extends StateDescriptor>> res = new HashSet<>();
|
Set<Class<? extends StateDescriptor>> res = new HashSet<>();
|
||||||
|
@ -99,6 +140,7 @@ public final class XmppWebSocketTransportModuleDescriptor extends ModularXmppCli
|
||||||
*/
|
*/
|
||||||
public static final class Builder extends ModularXmppClientToServerConnectionModuleDescriptor.Builder {
|
public static final class Builder extends ModularXmppClientToServerConnectionModuleDescriptor.Builder {
|
||||||
private boolean performWebSocketEndpointDiscovery = true;
|
private boolean performWebSocketEndpointDiscovery = true;
|
||||||
|
private boolean implicitWebSocketEndpoint = true;
|
||||||
private URI uri;
|
private URI uri;
|
||||||
|
|
||||||
private Builder(
|
private Builder(
|
||||||
|
@ -119,15 +161,20 @@ public final class XmppWebSocketTransportModuleDescriptor extends ModularXmppCli
|
||||||
|
|
||||||
public Builder explicitlySetWebSocketEndpoint(CharSequence endpoint) throws URISyntaxException {
|
public Builder explicitlySetWebSocketEndpoint(CharSequence endpoint) throws URISyntaxException {
|
||||||
URI endpointUri = new URI(endpoint.toString());
|
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 {
|
throws URISyntaxException {
|
||||||
URI endpointUri = new URI(endpoint.toString());
|
URI endpointUri = new URI(endpoint.toString());
|
||||||
return explicitlySetWebSocketEndpointAndDiscovery(endpointUri, performWebSocketEndpointDiscovery);
|
return explicitlySetWebSocketEndpointAndDiscovery(endpointUri, performWebSocketEndpointDiscovery);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Builder disableImplicitWebsocketEndpoint() {
|
||||||
|
implicitWebSocketEndpoint = false;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ModularXmppClientToServerConnectionModuleDescriptor build() {
|
public ModularXmppClientToServerConnectionModuleDescriptor build() {
|
||||||
return new XmppWebSocketTransportModuleDescriptor(this);
|
return new XmppWebSocketTransportModuleDescriptor(this);
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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 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.TopLevelStreamElement;
|
||||||
|
import org.jivesoftware.smack.packet.XmlEnvironment;
|
||||||
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
|
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
|
||||||
|
|
||||||
public abstract class AbstractWebSocket {
|
public abstract class AbstractWebSocket {
|
||||||
|
|
||||||
protected enum WebSocketConnectionPhase {
|
protected final ModularXmppClientToServerConnectionInternal connectionInternal;
|
||||||
openFrameSent,
|
|
||||||
exchangingTopLevelStreamElements
|
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 <open/> element to a RFC 6120 <stream> 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<open ", "<stream ")
|
String streamElement = openElement.replaceFirst("\\A<open ", "<stream ")
|
||||||
.replace("urn:ietf:params:xml:ns:xmpp-framing", "jabber:client")
|
.replace("urn:ietf:params:xml:ns:xmpp-framing", "jabber:client")
|
||||||
.replaceFirst("/>\\s*\\z", ">");
|
.replaceFirst("/>\\s*\\z", ">");
|
||||||
return streamElement;
|
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
|
||||||
|
// <open/> element qualified by the correct namespace.
|
||||||
|
static boolean isOpenElement(String text) {
|
||||||
if (text.startsWith("<open ")) {
|
if (text.startsWith("<open ")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static boolean isCloseElement(String text) {
|
// TODO: Make this method less fragile, e.g. by parsing a little bit into the element to ensure that this is an
|
||||||
|
// <close/> element qualified by the correct namespace. The fragility comes due the fact that the element could,
|
||||||
|
// inter alia, be specified as
|
||||||
|
// <close:close xmlns:close="urn:ietf:params:xml:ns:xmpp-framing"/>
|
||||||
|
static boolean isCloseElement(String text) {
|
||||||
if (text.startsWith("<close xmlns='urn:ietf:params:xml:ns:xmpp-framing'/>")) {
|
if (text.startsWith("<close xmlns='urn:ietf:params:xml:ns:xmpp-framing'/>")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void connect(WebSocketRemoteConnectionEndpoint endpoint) throws Throwable;
|
public abstract SmackFuture<AbstractWebSocket, Exception> 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);
|
public abstract void disconnect(int code, String message);
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2020 Florian Schmaus.
|
* Copyright 2020-2021 Florian Schmaus.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -17,9 +17,11 @@
|
||||||
package org.jivesoftware.smack.websocket.impl;
|
package org.jivesoftware.smack.websocket.impl;
|
||||||
|
|
||||||
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
|
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
|
||||||
|
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
|
||||||
|
|
||||||
public interface WebSocketFactory {
|
public interface WebSocketFactory {
|
||||||
|
|
||||||
AbstractWebSocket create(ModularXmppClientToServerConnectionInternal connectionInternal);
|
AbstractWebSocket create(WebSocketRemoteConnectionEndpoint endpoint,
|
||||||
|
ModularXmppClientToServerConnectionInternal connectionInternal);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2020 Florian Schmaus.
|
* Copyright 2020-2021 Florian Schmaus.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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 java.util.ServiceLoader;
|
||||||
|
|
||||||
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
|
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
|
||||||
|
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
|
||||||
|
|
||||||
public final class WebSocketFactoryService {
|
public final class WebSocketFactoryService {
|
||||||
|
|
||||||
private static final ServiceLoader<WebSocketFactory> SERVICE_LOADER = ServiceLoader.load(WebSocketFactory.class);
|
private static final ServiceLoader<WebSocketFactory> SERVICE_LOADER = ServiceLoader.load(WebSocketFactory.class);
|
||||||
|
|
||||||
public static AbstractWebSocket createWebSocket(ModularXmppClientToServerConnectionInternal connectionInternal) {
|
public static AbstractWebSocket createWebSocket(WebSocketRemoteConnectionEndpoint endpoint,
|
||||||
|
ModularXmppClientToServerConnectionInternal connectionInternal) {
|
||||||
assert connectionInternal != null;
|
assert connectionInternal != null;
|
||||||
|
|
||||||
Iterator<WebSocketFactory> websocketFactories = SERVICE_LOADER.iterator();
|
Iterator<WebSocketFactory> websocketFactories = SERVICE_LOADER.iterator();
|
||||||
|
@ -34,7 +36,7 @@ public final class WebSocketFactoryService {
|
||||||
}
|
}
|
||||||
|
|
||||||
WebSocketFactory websocketFactory = websocketFactories.next();
|
WebSocketFactory websocketFactory = websocketFactories.next();
|
||||||
return websocketFactory.create(connectionInternal);
|
return websocketFactory.create(endpoint, connectionInternal);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.List;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import org.jivesoftware.smack.datatypes.UInt16;
|
import org.jivesoftware.smack.datatypes.UInt16;
|
||||||
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpoint;
|
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 static final Logger LOGGER = Logger.getAnonymousLogger();
|
||||||
|
|
||||||
private final URI uri;
|
private final URI uri;
|
||||||
|
private final UInt16 port;
|
||||||
|
|
||||||
public WebSocketRemoteConnectionEndpoint(String uri) throws URISyntaxException {
|
protected WebSocketRemoteConnectionEndpoint(URI uri) {
|
||||||
this(new URI(uri));
|
|
||||||
}
|
|
||||||
|
|
||||||
public WebSocketRemoteConnectionEndpoint(URI uri) {
|
|
||||||
this.uri = uri;
|
this.uri = uri;
|
||||||
String scheme = uri.getScheme();
|
int portInt = uri.getPort();
|
||||||
if (!(scheme.equals("ws") || scheme.equals("wss"))) {
|
if (portInt >= 0) {
|
||||||
throw new IllegalArgumentException("Only allowed protocols are ws and wss");
|
port = UInt16.from(portInt);
|
||||||
|
} else {
|
||||||
|
port = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public URI getWebSocketEndpoint() {
|
public final URI getUri() {
|
||||||
return uri;
|
return uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSecureEndpoint() {
|
|
||||||
if (uri.getScheme().equals("wss")) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CharSequence getHost() {
|
public final String getHost() {
|
||||||
return uri.getHost();
|
return uri.getHost();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UInt16 getPort() {
|
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
|
@Override
|
||||||
public Collection<? extends InetAddress> getInetAddresses() {
|
public Collection<? extends InetAddress> getInetAddresses() {
|
||||||
try {
|
resolveInetAddressesIfRequired();
|
||||||
InetAddress address = InetAddress.getByName(getHost().toString());
|
return inetAddresses;
|
||||||
return Collections.singletonList(address);
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
LOGGER.log(Level.INFO, "Unknown Host Exception ", e);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDescription() {
|
public String getDescription() {
|
||||||
return null;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2020 Aditya Borikar
|
* Copyright 2020 Aditya Borikar, Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
|
|
||||||
import org.jivesoftware.smack.altconnections.HttpLookupMethod;
|
import org.jivesoftware.smack.altconnections.HttpLookupMethod;
|
||||||
import org.jivesoftware.smack.altconnections.HttpLookupMethod.LinkRelation;
|
import org.jivesoftware.smack.altconnections.HttpLookupMethod.LinkRelation;
|
||||||
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpointLookupFailure;
|
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpointLookupFailure;
|
||||||
|
@ -33,9 +32,8 @@ import org.jxmpp.jid.DomainBareJid;
|
||||||
|
|
||||||
public final class WebSocketRemoteConnectionEndpointLookup {
|
public final class WebSocketRemoteConnectionEndpointLookup {
|
||||||
|
|
||||||
public static Result lookup(DomainBareJid domainBareJid, SecurityMode securityMode) {
|
public static Result lookup(DomainBareJid domainBareJid) {
|
||||||
List<RemoteConnectionEndpointLookupFailure> lookupFailures = new ArrayList<>(1);
|
List<RemoteConnectionEndpointLookupFailure> lookupFailures = new ArrayList<>(1);
|
||||||
List<WebSocketRemoteConnectionEndpoint> discoveredRemoteConnectionEndpoints = new ArrayList<>();
|
|
||||||
|
|
||||||
List<URI> rcUriList = null;
|
List<URI> rcUriList = null;
|
||||||
try {
|
try {
|
||||||
|
@ -45,67 +43,69 @@ public final class WebSocketRemoteConnectionEndpointLookup {
|
||||||
} catch (IOException | XmlPullParserException | URISyntaxException e) {
|
} catch (IOException | XmlPullParserException | URISyntaxException e) {
|
||||||
lookupFailures.add(new RemoteConnectionEndpointLookupFailure.HttpLookupFailure(
|
lookupFailures.add(new RemoteConnectionEndpointLookupFailure.HttpLookupFailure(
|
||||||
domainBareJid, e));
|
domainBareJid, e));
|
||||||
return new Result(discoveredRemoteConnectionEndpoints, lookupFailures);
|
return new Result(lookupFailures);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rcUriList.isEmpty()) {
|
List<SecureWebSocketRemoteConnectionEndpoint> discoveredSecureEndpoints = new ArrayList<>(rcUriList.size());
|
||||||
throw new IllegalStateException("No endpoints were found inside host-meta");
|
List<InsecureWebSocketRemoteConnectionEndpoint> 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<WebSocketRemoteConnectionEndpoint>
|
return new Result(discoveredSecureEndpoints, discoveredInsecureEndpoints, lookupFailures);
|
||||||
Iterator<URI> iterator = rcUriList.iterator();
|
|
||||||
List<WebSocketRemoteConnectionEndpoint> 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<WebSocketRemoteConnectionEndpoint> secureEndpointsForSecurityModeIfPossible = new ArrayList<>();
|
|
||||||
List<WebSocketRemoteConnectionEndpoint> insecureEndpointsForSecurityModeIfPossible = new ArrayList<>();
|
|
||||||
for (WebSocketRemoteConnectionEndpoint uri : rceList) {
|
|
||||||
if (uri.isSecureEndpoint()) {
|
|
||||||
secureEndpointsForSecurityModeIfPossible.add(uri);
|
|
||||||
} else {
|
|
||||||
insecureEndpointsForSecurityModeIfPossible.add(uri);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
discoveredRemoteConnectionEndpoints = secureEndpointsForSecurityModeIfPossible;
|
|
||||||
discoveredRemoteConnectionEndpoints.addAll(insecureEndpointsForSecurityModeIfPossible);
|
|
||||||
break;
|
|
||||||
case required:
|
|
||||||
case disabled:
|
|
||||||
/**
|
|
||||||
* If, SecurityMode equals to required, accept wss endpoints (secure endpoints) only or,
|
|
||||||
* if SecurityMode equals to disabled, accept ws endpoints (unsecure endpoints) only.
|
|
||||||
*/
|
|
||||||
for (WebSocketRemoteConnectionEndpoint uri : rceList) {
|
|
||||||
if ((securityMode.equals(SecurityMode.disabled) && !uri.isSecureEndpoint())
|
|
||||||
|| (securityMode.equals(SecurityMode.required) && uri.isSecureEndpoint())) {
|
|
||||||
discoveredRemoteConnectionEndpoints.add(uri);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
return new Result(discoveredRemoteConnectionEndpoints, lookupFailures);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class Result {
|
public static final class Result {
|
||||||
public final List<WebSocketRemoteConnectionEndpoint> discoveredRemoteConnectionEndpoints;
|
public final List<SecureWebSocketRemoteConnectionEndpoint> discoveredSecureEndpoints;
|
||||||
|
public final List<InsecureWebSocketRemoteConnectionEndpoint> discoveredInsecureEndpoints;
|
||||||
public final List<RemoteConnectionEndpointLookupFailure> lookupFailures;
|
public final List<RemoteConnectionEndpointLookupFailure> lookupFailures;
|
||||||
|
|
||||||
public Result(List<WebSocketRemoteConnectionEndpoint> discoveredRemoteConnectionEndpoints,
|
public Result() {
|
||||||
|
this(Collections.emptyList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result(List<RemoteConnectionEndpointLookupFailure> 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<SecureWebSocketRemoteConnectionEndpoint> discoveredSecureEndpoints,
|
||||||
|
List<InsecureWebSocketRemoteConnectionEndpoint> discoveredInsecureEndpoints,
|
||||||
List<RemoteConnectionEndpointLookupFailure> lookupFailures) {
|
List<RemoteConnectionEndpointLookupFailure> lookupFailures) {
|
||||||
this.discoveredRemoteConnectionEndpoints = discoveredRemoteConnectionEndpoints;
|
this.discoveredSecureEndpoints = discoveredSecureEndpoints;
|
||||||
|
this.discoveredInsecureEndpoints = discoveredInsecureEndpoints;
|
||||||
this.lookupFailures = lookupFailures;
|
this.lookupFailures = lookupFailures;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<WebSocketRemoteConnectionEndpoint> getDiscoveredRemoteConnectionEndpoints() {
|
public boolean isEmpty() {
|
||||||
return discoveredRemoteConnectionEndpoints;
|
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<SecureWebSocketRemoteConnectionEndpoint> getDiscoveredSecureRemoteConnectionEndpoints() {
|
||||||
|
return discoveredSecureEndpoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<InsecureWebSocketRemoteConnectionEndpoint> getDiscoveredInsecureRemoteConnectionEndpoints() {
|
||||||
|
return discoveredInsecureEndpoints;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<RemoteConnectionEndpointLookupFailure> getLookupFailures() {
|
public List<RemoteConnectionEndpointLookupFailure> getLookupFailures() {
|
||||||
|
|
|
@ -17,24 +17,15 @@
|
||||||
package org.jivesoftware.smack.websocket;
|
package org.jivesoftware.smack.websocket;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
||||||
|
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
|
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
|
||||||
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
|
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
|
||||||
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
|
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.junit.jupiter.api.Test;
|
||||||
import org.jxmpp.stringprep.XmppStringprepException;
|
import org.jxmpp.stringprep.XmppStringprepException;
|
||||||
|
@ -64,42 +55,6 @@ public class XmppWebSocketTransportModuleTest {
|
||||||
assertNotNull(websocketTransportModuleDescriptor);
|
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<WebSocketRemoteConnectionEndpoint> discoveredRemoteConnectionEndpoints = new ArrayList<>();
|
|
||||||
discoveredRemoteConnectionEndpoints.add(endpoint);
|
|
||||||
|
|
||||||
HttpLookupFailure httpLookupFailure = new RemoteConnectionEndpointLookupFailure.HttpLookupFailure(null, null);
|
|
||||||
List<RemoteConnectionEndpointLookupFailure> 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
|
@Test
|
||||||
public void lookupConnectionEndpointsTest() throws URISyntaxException {
|
public void lookupConnectionEndpointsTest() throws URISyntaxException {
|
||||||
XmppWebSocketTransportModuleDescriptor websocketTransportModuleDescriptor = getWebSocketDescriptor();
|
XmppWebSocketTransportModuleDescriptor websocketTransportModuleDescriptor = getWebSocketDescriptor();
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
public class WebSocketRemoteConnectionEndpointTest {
|
public class WebSocketRemoteConnectionEndpointTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void endpointTest() throws URISyntaxException {
|
public void endpointTest() throws URISyntaxException {
|
||||||
String endpointString = "ws://fooDomain.org:7070/ws/";
|
String endpointString = "ws://fooDomain.org:7070/ws/";
|
||||||
WebSocketRemoteConnectionEndpoint endpoint = new WebSocketRemoteConnectionEndpoint(endpointString);
|
WebSocketRemoteConnectionEndpoint endpoint = WebSocketRemoteConnectionEndpoint.from(endpointString);
|
||||||
assertEquals("fooDomain.org", endpoint.getHost());
|
assertEquals("fooDomain.org", endpoint.getHost());
|
||||||
assertEquals(UInt16.from(7070), endpoint.getPort());
|
assertEquals(UInt16.from(7070), endpoint.getPort());
|
||||||
assertEquals(endpointString, endpoint.getWebSocketEndpoint().toString());
|
assertEquals(endpointString, endpoint.getUri().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void faultyEndpointTest() {
|
public void faultyEndpointTest() {
|
||||||
String faultyProtocolString = "wst://fooDomain.org:7070/ws/";
|
String faultyProtocolString = "wst://fooDomain.org:7070/ws/";
|
||||||
assertThrows(IllegalArgumentException.class, () -> {
|
assertThrows(IllegalArgumentException.class, () -> {
|
||||||
new WebSocketRemoteConnectionEndpoint(faultyProtocolString);
|
WebSocketRemoteConnectionEndpoint.from(faultyProtocolString);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2020 Florian Schmaus.
|
* Copyright 2020-2021 Florian Schmaus.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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 static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
|
||||||
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
|
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
|
||||||
import org.jivesoftware.smack.websocket.impl.AbstractWebSocket;
|
import org.jivesoftware.smack.websocket.impl.AbstractWebSocket;
|
||||||
import org.jivesoftware.smack.websocket.impl.WebSocketFactoryService;
|
import org.jivesoftware.smack.websocket.impl.WebSocketFactoryService;
|
||||||
|
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
|
||||||
|
|
||||||
public class WebSocketFactoryServiceTestUtil {
|
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);
|
ModularXmppClientToServerConnectionInternal connectionInternal = mock(ModularXmppClientToServerConnectionInternal.class);
|
||||||
|
|
||||||
AbstractWebSocket websocket = WebSocketFactoryService.createWebSocket(connectionInternal);
|
AbstractWebSocket websocket = WebSocketFactoryService.createWebSocket(endpoint, connectionInternal);
|
||||||
assertEquals(expected, websocket.getClass());
|
assertEquals(expected, websocket.getClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue