mirror of
https://github.com/vanitasvitae/Smack.git
synced 2025-09-09 09:09:38 +02: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 2020 Aditya Borikar, Florian Schmaus.
|
||||
* Copyright 2020 Aditya Borikar, 2020-2021 Florian Schmaus.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -19,22 +19,29 @@ package org.jivesoftware.smack.websocket;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
|
||||
import org.jivesoftware.smack.SmackFuture;
|
||||
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
|
||||
import org.jivesoftware.smack.fsm.StateTransitionResult;
|
||||
import org.jivesoftware.smack.util.StringUtils;
|
||||
import org.jivesoftware.smack.websocket.XmppWebSocketTransportModule.EstablishingWebSocketConnectionState;
|
||||
import org.jivesoftware.smack.websocket.impl.AbstractWebSocket;
|
||||
import org.jivesoftware.smack.websocket.impl.WebSocketFactoryService;
|
||||
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
|
||||
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpointLookup;
|
||||
|
||||
public final class WebSocketConnectionAttemptState {
|
||||
private final ModularXmppClientToServerConnectionInternal connectionInternal;
|
||||
private final XmppWebSocketTransportModule.XmppWebSocketTransport.DiscoveredWebSocketEndpoints discoveredEndpoints;
|
||||
|
||||
private WebSocketRemoteConnectionEndpoint connectedEndpoint;
|
||||
private AbstractWebSocket webSocket;
|
||||
|
||||
WebSocketConnectionAttemptState(ModularXmppClientToServerConnectionInternal connectionInternal,
|
||||
XmppWebSocketTransportModule.XmppWebSocketTransport.DiscoveredWebSocketEndpoints discoveredWebSocketEndpoints,
|
||||
EstablishingWebSocketConnectionState establishingWebSocketConnectionState) {
|
||||
assert discoveredWebSocketEndpoints != null;
|
||||
assert !discoveredWebSocketEndpoints.result.isEmpty();
|
||||
|
||||
this.connectionInternal = connectionInternal;
|
||||
this.discoveredEndpoints = discoveredWebSocketEndpoints;
|
||||
}
|
||||
|
@ -44,48 +51,96 @@ public final class WebSocketConnectionAttemptState {
|
|||
*
|
||||
* @return {@link AbstractWebSocket} with which connection is establised
|
||||
* @throws InterruptedException if the calling thread was interrupted
|
||||
* @throws WebSocketException if encounters a websocket exception
|
||||
*/
|
||||
AbstractWebSocket establishWebSocketConnection() throws InterruptedException, WebSocketException {
|
||||
List<WebSocketRemoteConnectionEndpoint> endpoints = discoveredEndpoints.result.discoveredRemoteConnectionEndpoints;
|
||||
@SuppressWarnings({"incomplete-switch", "MissingCasesInEnumSwitch"})
|
||||
StateTransitionResult.Failure establishWebSocketConnection() throws InterruptedException {
|
||||
final WebSocketRemoteConnectionEndpointLookup.Result endpointLookupResult = discoveredEndpoints.result;
|
||||
final List<Exception> failures = new ArrayList<>(endpointLookupResult.discoveredEndpointCount());
|
||||
|
||||
if (endpoints.isEmpty()) {
|
||||
throw new WebSocketException(new Throwable("No Endpoints discovered to establish connection"));
|
||||
}
|
||||
webSocket = null;
|
||||
|
||||
List<Throwable> connectionFailureList = new ArrayList<>();
|
||||
AbstractWebSocket websocket = WebSocketFactoryService.createWebSocket(connectionInternal);
|
||||
|
||||
// Keep iterating over available endpoints until a connection is establised or all endpoints are tried to create a connection with.
|
||||
for (WebSocketRemoteConnectionEndpoint endpoint : endpoints) {
|
||||
try {
|
||||
websocket.connect(endpoint);
|
||||
connectedEndpoint = endpoint;
|
||||
break;
|
||||
} catch (Throwable t) {
|
||||
connectionFailureList.add(t);
|
||||
|
||||
// If the number of entries in connectionFailureList is equal to the number of endpoints,
|
||||
// it means that all endpoints have been tried and have been unsuccessful.
|
||||
if (connectionFailureList.size() == endpoints.size()) {
|
||||
WebSocketException websocketException = new WebSocketException(connectionFailureList);
|
||||
throw new WebSocketException(websocketException);
|
||||
}
|
||||
SecurityMode securityMode = connectionInternal.connection.getConfiguration().getSecurityMode();
|
||||
switch (securityMode) {
|
||||
case required:
|
||||
case ifpossible:
|
||||
establishWebSocketConnection(endpointLookupResult.discoveredSecureEndpoints, failures);
|
||||
if (webSocket != null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
assert connectedEndpoint != null;
|
||||
establishWebSocketConnection(endpointLookupResult.discoveredInsecureEndpoints, failures);
|
||||
if (webSocket != null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Return connected websocket when no failure occurs.
|
||||
return websocket;
|
||||
StateTransitionResult.Failure failure = FailedToConnectToAnyWebSocketEndpoint.create(failures);
|
||||
return failure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the connected websocket endpoint.
|
||||
*
|
||||
* @return connected websocket endpoint
|
||||
*/
|
||||
public WebSocketRemoteConnectionEndpoint getConnectedEndpoint() {
|
||||
return connectedEndpoint;
|
||||
private void establishWebSocketConnection(List<? extends WebSocketRemoteConnectionEndpoint> webSocketEndpoints,
|
||||
List<Exception> failures) throws InterruptedException {
|
||||
final int endpointCount = webSocketEndpoints.size();
|
||||
|
||||
List<SmackFuture<AbstractWebSocket, Exception>> futures = new ArrayList<>(endpointCount);
|
||||
{
|
||||
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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -16,23 +16,11 @@
|
|||
*/
|
||||
package org.jivesoftware.smack.websocket;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public final class WebSocketException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final List<Throwable> throwableList;
|
||||
|
||||
public WebSocketException(List<Throwable> throwableList) {
|
||||
this.throwableList = throwableList;
|
||||
}
|
||||
|
||||
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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -16,20 +16,16 @@
|
|||
*/
|
||||
package org.jivesoftware.smack.websocket;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.net.ssl.SSLSession;
|
||||
|
||||
import org.jivesoftware.smack.AsyncButOrdered;
|
||||
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
import org.jivesoftware.smack.SmackException.NoResponseException;
|
||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||
import org.jivesoftware.smack.SmackFuture;
|
||||
import org.jivesoftware.smack.SmackFuture.InternalSmackFuture;
|
||||
import org.jivesoftware.smack.XMPPException;
|
||||
|
@ -54,13 +50,13 @@ import org.jivesoftware.smack.websocket.XmppWebSocketTransportModule.XmppWebSock
|
|||
import org.jivesoftware.smack.websocket.elements.WebSocketCloseElement;
|
||||
import org.jivesoftware.smack.websocket.elements.WebSocketOpenElement;
|
||||
import org.jivesoftware.smack.websocket.impl.AbstractWebSocket;
|
||||
import org.jivesoftware.smack.websocket.rce.InsecureWebSocketRemoteConnectionEndpoint;
|
||||
import org.jivesoftware.smack.websocket.rce.SecureWebSocketRemoteConnectionEndpoint;
|
||||
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
|
||||
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpointLookup;
|
||||
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpointLookup.Result;
|
||||
|
||||
import org.jxmpp.jid.DomainBareJid;
|
||||
import org.jxmpp.jid.impl.JidCreate;
|
||||
import org.jxmpp.stringprep.XmppStringprepException;
|
||||
|
||||
/**
|
||||
* The websocket transport module that goes with Smack's modular architecture.
|
||||
|
@ -101,31 +97,37 @@ public final class XmppWebSocketTransportModule
|
|||
}
|
||||
}
|
||||
|
||||
final class EstablishingWebSocketConnectionState extends State {
|
||||
final class EstablishingWebSocketConnectionState extends State.AbstractTransport {
|
||||
protected EstablishingWebSocketConnectionState(StateDescriptor stateDescriptor,
|
||||
ModularXmppClientToServerConnectionInternal connectionInternal) {
|
||||
super(stateDescriptor, connectionInternal);
|
||||
super(websocketTransport, stateDescriptor, connectionInternal);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
|
||||
throws IOException, SmackException, InterruptedException, XMPPException {
|
||||
public AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) throws InterruptedException,
|
||||
NoResponseException, NotConnectedException, SmackException, XMPPException {
|
||||
WebSocketConnectionAttemptState connectionAttemptState = new WebSocketConnectionAttemptState(
|
||||
connectionInternal, discoveredWebSocketEndpoints, this);
|
||||
|
||||
try {
|
||||
websocket = connectionAttemptState.establishWebSocketConnection();
|
||||
} catch (InterruptedException | WebSocketException e) {
|
||||
StateTransitionResult.Failure failure = new StateTransitionResult.FailureCausedByException<Exception>(e);
|
||||
StateTransitionResult.Failure failure = connectionAttemptState.establishWebSocketConnection();
|
||||
if (failure != null) {
|
||||
return failure;
|
||||
}
|
||||
|
||||
websocket = connectionAttemptState.getConnectedWebSocket();
|
||||
|
||||
connectionInternal.setTransport(websocketTransport);
|
||||
|
||||
WebSocketRemoteConnectionEndpoint connectedEndpoint = connectionAttemptState.getConnectedEndpoint();
|
||||
// TODO: It appears this should be done in a generic way. I'd assume we always
|
||||
// have to wait for stream features after the connection was established. But I
|
||||
// am not yet 100% positive that this is the case for every transport. Hence keep it here for now(?).
|
||||
// See also similar comment in XmppTcpTransportModule.
|
||||
// Maybe move this into ConnectedButUnauthenticated state's transitionInto() method? That seems to be the
|
||||
// right place.
|
||||
connectionInternal.newStreamOpenWaitForFeaturesSequence("stream features after initial connection");
|
||||
|
||||
// Construct a WebSocketConnectedResult using the connected endpoint.
|
||||
return new WebSocketConnectedResult(connectedEndpoint);
|
||||
return new WebSocketConnectedResult(websocket.getEndpoint());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -140,7 +142,7 @@ public final class XmppWebSocketTransportModule
|
|||
final WebSocketRemoteConnectionEndpoint connectedEndpoint;
|
||||
|
||||
public WebSocketConnectedResult(WebSocketRemoteConnectionEndpoint connectedEndpoint) {
|
||||
super("WebSocket connection establised with endpoint: " + connectedEndpoint.getWebSocketEndpoint());
|
||||
super("WebSocket connection establised with endpoint: " + connectedEndpoint);
|
||||
this.connectedEndpoint = connectedEndpoint;
|
||||
}
|
||||
}
|
||||
|
@ -164,6 +166,12 @@ public final class XmppWebSocketTransportModule
|
|||
discoveredWebSocketEndpoints = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasUseableConnectionEndpoints() {
|
||||
return discoveredWebSocketEndpoints != null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("incomplete-switch")
|
||||
@Override
|
||||
protected List<SmackFuture<LookupConnectionEndpointsResult, Exception>> lookupConnectionEndpoints() {
|
||||
// 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<>();
|
||||
|
||||
connectionInternal.asyncGo(() -> {
|
||||
Result result = null;
|
||||
|
||||
WebSocketRemoteConnectionEndpoint providedEndpoint = null;
|
||||
ModularXmppClientToServerConnectionConfiguration configuration = connectionInternal.connection.getConfiguration();
|
||||
DomainBareJid host = configuration.getXMPPServiceDomain();
|
||||
|
||||
// Check if there is a websocket endpoint already configured.
|
||||
URI uri = moduleDescriptor.getExplicitlyProvidedUri();
|
||||
if (uri != null) {
|
||||
providedEndpoint = new WebSocketRemoteConnectionEndpoint(uri);
|
||||
if (moduleDescriptor.isWebSocketEndpointDiscoveryEnabled()) {
|
||||
// Fetch remote endpoints.
|
||||
result = WebSocketRemoteConnectionEndpointLookup.lookup(host);
|
||||
}
|
||||
|
||||
if (!moduleDescriptor.isWebSocketEndpointDiscoveryEnabled()) {
|
||||
// If discovery is disabled, assert that the provided endpoint isn't null.
|
||||
assert providedEndpoint != null;
|
||||
|
||||
SecurityMode mode = connectionInternal.connection.getConfiguration().getSecurityMode();
|
||||
if ((providedEndpoint.isSecureEndpoint() &&
|
||||
mode.equals(SecurityMode.disabled))
|
||||
|| (!providedEndpoint.isSecureEndpoint() &&
|
||||
mode.equals(SecurityMode.required))) {
|
||||
throw new IllegalStateException("Explicitly configured uri: " + providedEndpoint.getWebSocketEndpoint().toString()
|
||||
+ " does not comply with the configured security mode: " + mode);
|
||||
WebSocketRemoteConnectionEndpoint providedEndpoint = moduleDescriptor.getExplicitlyProvidedEndpoint();
|
||||
if (providedEndpoint != null) {
|
||||
// If there was not automatic lookup that produced a result, then create a result now.
|
||||
if (result == null) {
|
||||
result = new Result();
|
||||
}
|
||||
|
||||
// Generate Result for explicitly configured endpoint.
|
||||
Result manualResult = new Result(Arrays.asList(providedEndpoint), null);
|
||||
|
||||
LookupConnectionEndpointsResult endpointsResult = new DiscoveredWebSocketEndpoints(manualResult);
|
||||
|
||||
websocketEndpointsLookupFuture.setResult(endpointsResult);
|
||||
} else {
|
||||
DomainBareJid host = connectionInternal.connection.getXMPPServiceDomain();
|
||||
ModularXmppClientToServerConnectionConfiguration configuration = connectionInternal.connection.getConfiguration();
|
||||
SecurityMode mode = configuration.getSecurityMode();
|
||||
|
||||
// Fetch remote endpoints.
|
||||
Result xep0156result = WebSocketRemoteConnectionEndpointLookup.lookup(host, mode);
|
||||
|
||||
List<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);
|
||||
// We insert the provided endpoint at the beginning of the list, so that it is used first.
|
||||
final int INSERT_INDEX = 0;
|
||||
if (providedEndpoint instanceof SecureWebSocketRemoteConnectionEndpoint) {
|
||||
SecureWebSocketRemoteConnectionEndpoint secureEndpoint = (SecureWebSocketRemoteConnectionEndpoint) providedEndpoint;
|
||||
result.discoveredSecureEndpoints.add(INSERT_INDEX, secureEndpoint);
|
||||
} else if (providedEndpoint instanceof InsecureWebSocketRemoteConnectionEndpoint) {
|
||||
InsecureWebSocketRemoteConnectionEndpoint insecureEndpoint = (InsecureWebSocketRemoteConnectionEndpoint) providedEndpoint;
|
||||
result.discoveredInsecureEndpoints.add(INSERT_INDEX, insecureEndpoint);
|
||||
} else {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
if (moduleDescriptor.isImplicitWebSocketEndpointEnabled()) {
|
||||
String urlWithoutScheme = "://" + host + ":5443/ws";
|
||||
|
||||
SecureWebSocketRemoteConnectionEndpoint implicitSecureEndpoint = SecureWebSocketRemoteConnectionEndpoint.from(
|
||||
WebSocketRemoteConnectionEndpoint.SECURE_WEB_SOCKET_SCHEME + urlWithoutScheme);
|
||||
result.discoveredSecureEndpoints.add(implicitSecureEndpoint);
|
||||
|
||||
InsecureWebSocketRemoteConnectionEndpoint implicitInsecureEndpoint = InsecureWebSocketRemoteConnectionEndpoint.from(
|
||||
WebSocketRemoteConnectionEndpoint.INSECURE_WEB_SOCKET_SCHEME + urlWithoutScheme);
|
||||
result.discoveredInsecureEndpoints.add(implicitInsecureEndpoint);
|
||||
}
|
||||
|
||||
final LookupConnectionEndpointsResult endpointsResult;
|
||||
if (result.isEmpty()) {
|
||||
endpointsResult = new WebSocketEndpointsDiscoveryFailed(result.lookupFailures);
|
||||
} else {
|
||||
endpointsResult = new DiscoveredWebSocketEndpoints(result);
|
||||
}
|
||||
|
||||
websocketEndpointsLookupFuture.setResult(endpointsResult);
|
||||
});
|
||||
|
||||
return Collections.singletonList(websocketEndpointsLookupFuture);
|
||||
|
@ -238,11 +251,11 @@ public final class XmppWebSocketTransportModule
|
|||
|
||||
@Override
|
||||
protected void notifyAboutNewOutgoingElements() {
|
||||
Queue<TopLevelStreamElement> outgoingElementsQueue = connectionInternal.outgoingElementsQueue;
|
||||
final Queue<TopLevelStreamElement> outgoingElementsQueue = connectionInternal.outgoingElementsQueue;
|
||||
asyncButOrderedOutgoingElementsQueue.performAsyncButOrdered(outgoingElementsQueue, () -> {
|
||||
// Once new outgoingElement is notified, send the top level stream element obtained by polling.
|
||||
TopLevelStreamElement topLevelStreamElement = outgoingElementsQueue.poll();
|
||||
websocket.send(topLevelStreamElement);
|
||||
for (TopLevelStreamElement topLevelStreamElement; (topLevelStreamElement = outgoingElementsQueue.poll()) != null;) {
|
||||
websocket.send(topLevelStreamElement);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -268,15 +281,11 @@ public final class XmppWebSocketTransportModule
|
|||
|
||||
@Override
|
||||
public StreamOpenAndCloseFactory getStreamOpenAndCloseFactory() {
|
||||
// TODO: Create extra class for this?
|
||||
return new StreamOpenAndCloseFactory() {
|
||||
@Override
|
||||
public AbstractStreamOpen createStreamOpen(CharSequence to, CharSequence from, String id, String lang) {
|
||||
try {
|
||||
return new WebSocketOpenElement(JidCreate.domainBareFrom(to));
|
||||
} catch (XmppStringprepException e) {
|
||||
Logger.getAnonymousLogger().log(Level.WARNING, "Couldn't create OpenElement", e);
|
||||
return null;
|
||||
}
|
||||
public AbstractStreamOpen createStreamOpen(DomainBareJid to, CharSequence from, String id, String lang) {
|
||||
return new WebSocketOpenElement(to);
|
||||
}
|
||||
@Override
|
||||
public AbstractStreamClose createStreamClose() {
|
||||
|
@ -295,10 +304,6 @@ public final class XmppWebSocketTransportModule
|
|||
assert result != null;
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
public WebSocketRemoteConnectionEndpointLookup.Result getResult() {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -308,10 +313,13 @@ public final class XmppWebSocketTransportModule
|
|||
final class WebSocketEndpointsDiscoveryFailed implements LookupConnectionEndpointsFailed {
|
||||
final List<RemoteConnectionEndpointLookupFailure> lookupFailures;
|
||||
|
||||
WebSocketEndpointsDiscoveryFailed(
|
||||
WebSocketRemoteConnectionEndpointLookup.Result result) {
|
||||
assert result != null;
|
||||
lookupFailures = Collections.unmodifiableList(result.lookupFailures);
|
||||
WebSocketEndpointsDiscoveryFailed(RemoteConnectionEndpointLookupFailure lookupFailure) {
|
||||
this(Collections.singletonList(lookupFailure));
|
||||
}
|
||||
|
||||
WebSocketEndpointsDiscoveryFailed(List<RemoteConnectionEndpointLookupFailure> lookupFailures) {
|
||||
assert lookupFailures != null;
|
||||
this.lookupFailures = Collections.unmodifiableList(lookupFailures);
|
||||
}
|
||||
|
||||
@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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -21,6 +21,7 @@ import java.net.URISyntaxException;
|
|||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
|
||||
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
|
||||
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
|
||||
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionModule;
|
||||
|
@ -29,6 +30,7 @@ import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionIn
|
|||
import org.jivesoftware.smack.fsm.StateDescriptor;
|
||||
import org.jivesoftware.smack.util.Objects;
|
||||
import org.jivesoftware.smack.websocket.XmppWebSocketTransportModule.EstablishingWebSocketConnectionStateDescriptor;
|
||||
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
|
||||
|
||||
/**
|
||||
* The descriptor class for {@link XmppWebSocketTransportModule}.
|
||||
|
@ -37,12 +39,43 @@ import org.jivesoftware.smack.websocket.XmppWebSocketTransportModule.Establishin
|
|||
* use {@link ModularXmppClientToServerConnectionConfiguration.Builder#addModule(ModularXmppClientToServerConnectionModuleDescriptor)}.
|
||||
*/
|
||||
public final class XmppWebSocketTransportModuleDescriptor extends ModularXmppClientToServerConnectionModuleDescriptor {
|
||||
private boolean performWebSocketEndpointDiscovery;
|
||||
private URI uri;
|
||||
private final boolean performWebSocketEndpointDiscovery;
|
||||
private final boolean implicitWebSocketEndpoint;
|
||||
private final URI uri;
|
||||
private final WebSocketRemoteConnectionEndpoint wsRce;
|
||||
|
||||
public XmppWebSocketTransportModuleDescriptor(Builder builder) {
|
||||
this.performWebSocketEndpointDiscovery = builder.performWebSocketEndpointDiscovery;
|
||||
this.implicitWebSocketEndpoint = builder.implicitWebSocketEndpoint;
|
||||
|
||||
this.uri = builder.uri;
|
||||
if (uri != null) {
|
||||
wsRce = WebSocketRemoteConnectionEndpoint.from(uri);
|
||||
} else {
|
||||
wsRce = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({"incomplete-switch", "MissingCasesInEnumSwitch"})
|
||||
protected void validateConfiguration(ModularXmppClientToServerConnectionConfiguration configuration) {
|
||||
if (wsRce == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
SecurityMode securityMode = configuration.getSecurityMode();
|
||||
switch (securityMode) {
|
||||
case required:
|
||||
if (!wsRce.isSecureEndpoint()) {
|
||||
throw new IllegalArgumentException("The provided WebSocket endpoint " + wsRce + " is not a secure endpoint, but the connection configuration requires secure endpoints");
|
||||
}
|
||||
break;
|
||||
case disabled:
|
||||
if (wsRce.isSecureEndpoint()) {
|
||||
throw new IllegalArgumentException("The provided WebSocket endpoint " + wsRce + " is a secure endpoint, but the connection configuration has security disabled");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -53,6 +86,10 @@ public final class XmppWebSocketTransportModuleDescriptor extends ModularXmppCli
|
|||
return performWebSocketEndpointDiscovery;
|
||||
}
|
||||
|
||||
public boolean isImplicitWebSocketEndpointEnabled() {
|
||||
return implicitWebSocketEndpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns explicitly configured websocket endpoint uri.
|
||||
* @return uri
|
||||
|
@ -61,6 +98,10 @@ public final class XmppWebSocketTransportModuleDescriptor extends ModularXmppCli
|
|||
return uri;
|
||||
}
|
||||
|
||||
WebSocketRemoteConnectionEndpoint getExplicitlyProvidedEndpoint() {
|
||||
return wsRce;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<Class<? extends StateDescriptor>> getStateDescriptors() {
|
||||
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 {
|
||||
private boolean performWebSocketEndpointDiscovery = true;
|
||||
private boolean implicitWebSocketEndpoint = true;
|
||||
private URI uri;
|
||||
|
||||
private Builder(
|
||||
|
@ -119,15 +161,20 @@ public final class XmppWebSocketTransportModuleDescriptor extends ModularXmppCli
|
|||
|
||||
public Builder explicitlySetWebSocketEndpoint(CharSequence endpoint) throws URISyntaxException {
|
||||
URI endpointUri = new URI(endpoint.toString());
|
||||
return explicitlySetWebSocketEndpointAndDiscovery(endpointUri, true);
|
||||
return explicitlySetWebSocketEndpoint(endpointUri);
|
||||
}
|
||||
|
||||
public Builder explicitlySetWebSocketEndpoint(CharSequence endpoint, boolean performWebSocketEndpointDiscovery)
|
||||
public Builder explicitlySetWebSocketEndpointAndDiscovery(CharSequence endpoint, boolean performWebSocketEndpointDiscovery)
|
||||
throws URISyntaxException {
|
||||
URI endpointUri = new URI(endpoint.toString());
|
||||
return explicitlySetWebSocketEndpointAndDiscovery(endpointUri, performWebSocketEndpointDiscovery);
|
||||
}
|
||||
|
||||
public Builder disableImplicitWebsocketEndpoint() {
|
||||
implicitWebSocketEndpoint = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModularXmppClientToServerConnectionModuleDescriptor build() {
|
||||
return new XmppWebSocketTransportModuleDescriptor(this);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2020 Aditya Borikar.
|
||||
* Copyright 2020 Aditya Borikar, 2020-2021 Florian Schmaus
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -18,40 +18,92 @@ package org.jivesoftware.smack.websocket.impl;
|
|||
|
||||
import javax.net.ssl.SSLSession;
|
||||
|
||||
import org.jivesoftware.smack.SmackFuture;
|
||||
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
|
||||
import org.jivesoftware.smack.packet.TopLevelStreamElement;
|
||||
import org.jivesoftware.smack.packet.XmlEnvironment;
|
||||
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
|
||||
|
||||
public abstract class AbstractWebSocket {
|
||||
|
||||
protected enum WebSocketConnectionPhase {
|
||||
openFrameSent,
|
||||
exchangingTopLevelStreamElements
|
||||
protected final ModularXmppClientToServerConnectionInternal connectionInternal;
|
||||
|
||||
protected final WebSocketRemoteConnectionEndpoint endpoint;
|
||||
|
||||
protected AbstractWebSocket(WebSocketRemoteConnectionEndpoint endpoint,
|
||||
ModularXmppClientToServerConnectionInternal connectionInternal) {
|
||||
this.endpoint = endpoint;
|
||||
this.connectionInternal = connectionInternal;
|
||||
}
|
||||
|
||||
protected static String getStreamFromOpenElement(String openElement) {
|
||||
public final WebSocketRemoteConnectionEndpoint getEndpoint() {
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
private String streamOpen;
|
||||
private String streamClose;
|
||||
|
||||
protected final void onIncomingWebSocketElement(String element) {
|
||||
// TODO: Once smack-websocket-java15 is there, we have to re-evaluate if the async operation here is still
|
||||
// required, or if it should only be performed if OkHTTP is used.
|
||||
if (isOpenElement(element)) {
|
||||
// Transform the XMPP WebSocket <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 ")
|
||||
.replace("urn:ietf:params:xml:ns:xmpp-framing", "jabber:client")
|
||||
.replaceFirst("/>\\s*\\z", ">");
|
||||
return streamElement;
|
||||
}
|
||||
|
||||
protected static boolean isOpenElement(String text) {
|
||||
// TODO: Make this method less fragile, e.g. by parsing a little bit into the element to ensure that this is an
|
||||
// <open/> element qualified by the correct namespace.
|
||||
static boolean isOpenElement(String text) {
|
||||
if (text.startsWith("<open ")) {
|
||||
return true;
|
||||
}
|
||||
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'/>")) {
|
||||
return true;
|
||||
}
|
||||
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);
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2020 Florian Schmaus.
|
||||
* Copyright 2020-2021 Florian Schmaus.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -17,9 +17,11 @@
|
|||
package org.jivesoftware.smack.websocket.impl;
|
||||
|
||||
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
|
||||
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
|
||||
|
||||
public interface WebSocketFactory {
|
||||
|
||||
AbstractWebSocket create(ModularXmppClientToServerConnectionInternal connectionInternal);
|
||||
AbstractWebSocket create(WebSocketRemoteConnectionEndpoint endpoint,
|
||||
ModularXmppClientToServerConnectionInternal connectionInternal);
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2020 Florian Schmaus.
|
||||
* Copyright 2020-2021 Florian Schmaus.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -20,12 +20,14 @@ import java.util.Iterator;
|
|||
import java.util.ServiceLoader;
|
||||
|
||||
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
|
||||
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
|
||||
|
||||
public final class WebSocketFactoryService {
|
||||
|
||||
private static final ServiceLoader<WebSocketFactory> SERVICE_LOADER = ServiceLoader.load(WebSocketFactory.class);
|
||||
|
||||
public static AbstractWebSocket createWebSocket(ModularXmppClientToServerConnectionInternal connectionInternal) {
|
||||
public static AbstractWebSocket createWebSocket(WebSocketRemoteConnectionEndpoint endpoint,
|
||||
ModularXmppClientToServerConnectionInternal connectionInternal) {
|
||||
assert connectionInternal != null;
|
||||
|
||||
Iterator<WebSocketFactory> websocketFactories = SERVICE_LOADER.iterator();
|
||||
|
@ -34,7 +36,7 @@ public final class WebSocketFactoryService {
|
|||
}
|
||||
|
||||
WebSocketFactory websocketFactory = websocketFactories.next();
|
||||
return websocketFactory.create(connectionInternal);
|
||||
return websocketFactory.create(endpoint, connectionInternal);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -20,66 +20,100 @@ import java.net.InetAddress;
|
|||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.jivesoftware.smack.datatypes.UInt16;
|
||||
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpoint;
|
||||
|
||||
public final class WebSocketRemoteConnectionEndpoint implements RemoteConnectionEndpoint {
|
||||
public abstract class WebSocketRemoteConnectionEndpoint implements RemoteConnectionEndpoint {
|
||||
|
||||
public static final String INSECURE_WEB_SOCKET_SCHEME = "ws";
|
||||
public static final String SECURE_WEB_SOCKET_SCHEME = INSECURE_WEB_SOCKET_SCHEME + "s";
|
||||
|
||||
private static final Logger LOGGER = Logger.getAnonymousLogger();
|
||||
|
||||
private final URI uri;
|
||||
private final UInt16 port;
|
||||
|
||||
public WebSocketRemoteConnectionEndpoint(String uri) throws URISyntaxException {
|
||||
this(new URI(uri));
|
||||
}
|
||||
|
||||
public WebSocketRemoteConnectionEndpoint(URI uri) {
|
||||
protected WebSocketRemoteConnectionEndpoint(URI uri) {
|
||||
this.uri = uri;
|
||||
String scheme = uri.getScheme();
|
||||
if (!(scheme.equals("ws") || scheme.equals("wss"))) {
|
||||
throw new IllegalArgumentException("Only allowed protocols are ws and wss");
|
||||
int portInt = uri.getPort();
|
||||
if (portInt >= 0) {
|
||||
port = UInt16.from(portInt);
|
||||
} else {
|
||||
port = null;
|
||||
}
|
||||
}
|
||||
|
||||
public URI getWebSocketEndpoint() {
|
||||
public final URI getUri() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
public boolean isSecureEndpoint() {
|
||||
if (uri.getScheme().equals("wss")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getHost() {
|
||||
public final String getHost() {
|
||||
return uri.getHost();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UInt16 getPort() {
|
||||
return UInt16.from(uri.getPort());
|
||||
return port;
|
||||
}
|
||||
|
||||
public abstract boolean isSecureEndpoint();
|
||||
|
||||
private List<? extends InetAddress> inetAddresses;
|
||||
|
||||
private void resolveInetAddressesIfRequired() {
|
||||
if (inetAddresses != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String host = getHost();
|
||||
InetAddress[] addresses;
|
||||
try {
|
||||
addresses = InetAddress.getAllByName(host);
|
||||
} catch (UnknownHostException e) {
|
||||
LOGGER.log(Level.WARNING, "Could not resolve IP addresses of " + host, e);
|
||||
return;
|
||||
}
|
||||
inetAddresses = Arrays.asList(addresses);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends InetAddress> getInetAddresses() {
|
||||
try {
|
||||
InetAddress address = InetAddress.getByName(getHost().toString());
|
||||
return Collections.singletonList(address);
|
||||
} catch (UnknownHostException e) {
|
||||
LOGGER.log(Level.INFO, "Unknown Host Exception ", e);
|
||||
}
|
||||
return null;
|
||||
resolveInetAddressesIfRequired();
|
||||
return inetAddresses;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return uri.toString();
|
||||
}
|
||||
|
||||
public static WebSocketRemoteConnectionEndpoint from(CharSequence uriCharSequence) throws URISyntaxException {
|
||||
String uriString = uriCharSequence.toString();
|
||||
URI uri = URI.create(uriString);
|
||||
return from(uri);
|
||||
}
|
||||
|
||||
public static WebSocketRemoteConnectionEndpoint from(URI uri) {
|
||||
String scheme = uri.getScheme();
|
||||
switch (scheme) {
|
||||
case INSECURE_WEB_SOCKET_SCHEME:
|
||||
return new InsecureWebSocketRemoteConnectionEndpoint(uri);
|
||||
case SECURE_WEB_SOCKET_SCHEME:
|
||||
return new SecureWebSocketRemoteConnectionEndpoint(uri);
|
||||
default:
|
||||
throw new IllegalArgumentException("Only allowed protocols are " + INSECURE_WEB_SOCKET_SCHEME + " and " + SECURE_WEB_SOCKET_SCHEME);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2020 Aditya Borikar
|
||||
* Copyright 2020 Aditya Borikar, Florian Schmaus
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -20,10 +20,9 @@ import java.io.IOException;
|
|||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
|
||||
import org.jivesoftware.smack.altconnections.HttpLookupMethod;
|
||||
import org.jivesoftware.smack.altconnections.HttpLookupMethod.LinkRelation;
|
||||
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpointLookupFailure;
|
||||
|
@ -33,9 +32,8 @@ import org.jxmpp.jid.DomainBareJid;
|
|||
|
||||
public final class WebSocketRemoteConnectionEndpointLookup {
|
||||
|
||||
public static Result lookup(DomainBareJid domainBareJid, SecurityMode securityMode) {
|
||||
public static Result lookup(DomainBareJid domainBareJid) {
|
||||
List<RemoteConnectionEndpointLookupFailure> lookupFailures = new ArrayList<>(1);
|
||||
List<WebSocketRemoteConnectionEndpoint> discoveredRemoteConnectionEndpoints = new ArrayList<>();
|
||||
|
||||
List<URI> rcUriList = null;
|
||||
try {
|
||||
|
@ -45,67 +43,69 @@ public final class WebSocketRemoteConnectionEndpointLookup {
|
|||
} catch (IOException | XmlPullParserException | URISyntaxException e) {
|
||||
lookupFailures.add(new RemoteConnectionEndpointLookupFailure.HttpLookupFailure(
|
||||
domainBareJid, e));
|
||||
return new Result(discoveredRemoteConnectionEndpoints, lookupFailures);
|
||||
return new Result(lookupFailures);
|
||||
}
|
||||
|
||||
if (rcUriList.isEmpty()) {
|
||||
throw new IllegalStateException("No endpoints were found inside host-meta");
|
||||
List<SecureWebSocketRemoteConnectionEndpoint> discoveredSecureEndpoints = new ArrayList<>(rcUriList.size());
|
||||
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>
|
||||
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);
|
||||
return new Result(discoveredSecureEndpoints, discoveredInsecureEndpoints, lookupFailures);
|
||||
}
|
||||
|
||||
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 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) {
|
||||
this.discoveredRemoteConnectionEndpoints = discoveredRemoteConnectionEndpoints;
|
||||
this.discoveredSecureEndpoints = discoveredSecureEndpoints;
|
||||
this.discoveredInsecureEndpoints = discoveredInsecureEndpoints;
|
||||
this.lookupFailures = lookupFailures;
|
||||
}
|
||||
|
||||
public List<WebSocketRemoteConnectionEndpoint> getDiscoveredRemoteConnectionEndpoints() {
|
||||
return discoveredRemoteConnectionEndpoints;
|
||||
public boolean isEmpty() {
|
||||
return discoveredSecureEndpoints.isEmpty() && discoveredInsecureEndpoints.isEmpty();
|
||||
}
|
||||
|
||||
public int discoveredEndpointCount() {
|
||||
return discoveredSecureEndpoints.size() + discoveredInsecureEndpoints.size();
|
||||
}
|
||||
|
||||
// TODO: Remove the following methods since the fields are already public? Or make the fields private and use
|
||||
// the methods? I tend to remove the methods, as their method name is pretty long. But OTOH the fields reference
|
||||
// mutable datastructes, which is uncommon to be public.
|
||||
public List<SecureWebSocketRemoteConnectionEndpoint> getDiscoveredSecureRemoteConnectionEndpoints() {
|
||||
return discoveredSecureEndpoints;
|
||||
}
|
||||
|
||||
public List<InsecureWebSocketRemoteConnectionEndpoint> getDiscoveredInsecureRemoteConnectionEndpoints() {
|
||||
return discoveredInsecureEndpoints;
|
||||
}
|
||||
|
||||
public List<RemoteConnectionEndpointLookupFailure> getLookupFailures() {
|
||||
|
|
|
@ -17,24 +17,15 @@
|
|||
package org.jivesoftware.smack.websocket;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
|
||||
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
|
||||
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
|
||||
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpointLookupFailure;
|
||||
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpointLookupFailure.HttpLookupFailure;
|
||||
import org.jivesoftware.smack.websocket.XmppWebSocketTransportModule.XmppWebSocketTransport.DiscoveredWebSocketEndpoints;
|
||||
import org.jivesoftware.smack.websocket.XmppWebSocketTransportModule.XmppWebSocketTransport.WebSocketEndpointsDiscoveryFailed;
|
||||
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
|
||||
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpointLookup.Result;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.jxmpp.stringprep.XmppStringprepException;
|
||||
|
@ -64,42 +55,6 @@ public class XmppWebSocketTransportModuleTest {
|
|||
assertNotNull(websocketTransportModuleDescriptor);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void websocketEndpointDiscoveryTest() throws URISyntaxException {
|
||||
XmppWebSocketTransportModuleDescriptor websocketTransportModuleDescriptor = getWebSocketDescriptor();
|
||||
ModularXmppClientToServerConnectionInternal connectionInternal = mock(ModularXmppClientToServerConnectionInternal.class);
|
||||
|
||||
XmppWebSocketTransportModule transportModule
|
||||
= new XmppWebSocketTransportModule(websocketTransportModuleDescriptor, connectionInternal);
|
||||
|
||||
XmppWebSocketTransportModule.XmppWebSocketTransport transport = transportModule.getTransport();
|
||||
|
||||
assertThrows(AssertionError.class, () -> transport.new DiscoveredWebSocketEndpoints(null));
|
||||
assertThrows(AssertionError.class, () -> transport.new WebSocketEndpointsDiscoveryFailed(null));
|
||||
|
||||
WebSocketRemoteConnectionEndpoint endpoint = new WebSocketRemoteConnectionEndpoint("wss://localhost.org:7443/ws/");
|
||||
|
||||
List<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
|
||||
public void lookupConnectionEndpointsTest() throws URISyntaxException {
|
||||
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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -26,20 +26,22 @@ import org.jivesoftware.smack.datatypes.UInt16;
|
|||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class WebSocketRemoteConnectionEndpointTest {
|
||||
|
||||
@Test
|
||||
public void endpointTest() throws URISyntaxException {
|
||||
String endpointString = "ws://fooDomain.org:7070/ws/";
|
||||
WebSocketRemoteConnectionEndpoint endpoint = new WebSocketRemoteConnectionEndpoint(endpointString);
|
||||
WebSocketRemoteConnectionEndpoint endpoint = WebSocketRemoteConnectionEndpoint.from(endpointString);
|
||||
assertEquals("fooDomain.org", endpoint.getHost());
|
||||
assertEquals(UInt16.from(7070), endpoint.getPort());
|
||||
assertEquals(endpointString, endpoint.getWebSocketEndpoint().toString());
|
||||
assertEquals(endpointString, endpoint.getUri().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void faultyEndpointTest() {
|
||||
String faultyProtocolString = "wst://fooDomain.org:7070/ws/";
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
new WebSocketRemoteConnectionEndpoint(faultyProtocolString);
|
||||
WebSocketRemoteConnectionEndpoint.from(faultyProtocolString);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2020 Florian Schmaus.
|
||||
* Copyright 2020-2021 Florian Schmaus.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -20,16 +20,21 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
|
||||
import org.jivesoftware.smack.websocket.impl.AbstractWebSocket;
|
||||
import org.jivesoftware.smack.websocket.impl.WebSocketFactoryService;
|
||||
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
|
||||
|
||||
public class WebSocketFactoryServiceTestUtil {
|
||||
|
||||
public static void createWebSocketTest(Class<? extends AbstractWebSocket> expected) {
|
||||
public static void createWebSocketTest(Class<? extends AbstractWebSocket> expected) throws URISyntaxException {
|
||||
WebSocketRemoteConnectionEndpoint endpoint = WebSocketRemoteConnectionEndpoint.from("wss://example.org");
|
||||
|
||||
ModularXmppClientToServerConnectionInternal connectionInternal = mock(ModularXmppClientToServerConnectionInternal.class);
|
||||
|
||||
AbstractWebSocket websocket = WebSocketFactoryService.createWebSocket(connectionInternal);
|
||||
AbstractWebSocket websocket = WebSocketFactoryService.createWebSocket(endpoint, connectionInternal);
|
||||
assertEquals(expected, websocket.getClass());
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue