2020-05-14 14:35:37 +02:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* Copyright 2020 Aditya Borikar
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
package org.jivesoftware.smack.websocket;
|
|
|
|
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.net.URI;
|
|
|
|
import java.util.Arrays;
|
|
|
|
import java.util.Collections;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.Queue;
|
|
|
|
import java.util.logging.Level;
|
|
|
|
import java.util.logging.Logger;
|
|
|
|
|
|
|
|
import javax.net.ssl.SSLSession;
|
|
|
|
|
|
|
|
import org.jivesoftware.smack.AsyncButOrdered;
|
|
|
|
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
|
|
|
|
import org.jivesoftware.smack.SmackException;
|
|
|
|
import org.jivesoftware.smack.SmackFuture;
|
|
|
|
import org.jivesoftware.smack.SmackFuture.InternalSmackFuture;
|
|
|
|
import org.jivesoftware.smack.XMPPException;
|
|
|
|
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.ConnectedButUnauthenticatedStateDescriptor;
|
|
|
|
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.LookupRemoteConnectionEndpointsStateDescriptor;
|
|
|
|
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
|
|
|
|
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionModule;
|
|
|
|
import org.jivesoftware.smack.c2s.StreamOpenAndCloseFactory;
|
|
|
|
import org.jivesoftware.smack.c2s.XmppClientToServerTransport;
|
|
|
|
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
|
|
|
|
import org.jivesoftware.smack.c2s.internal.WalkStateGraphContext;
|
|
|
|
import org.jivesoftware.smack.fsm.State;
|
|
|
|
import org.jivesoftware.smack.fsm.StateDescriptor;
|
|
|
|
import org.jivesoftware.smack.fsm.StateTransitionResult;
|
|
|
|
import org.jivesoftware.smack.fsm.StateTransitionResult.AttemptResult;
|
|
|
|
import org.jivesoftware.smack.packet.AbstractStreamClose;
|
|
|
|
import org.jivesoftware.smack.packet.AbstractStreamOpen;
|
|
|
|
import org.jivesoftware.smack.packet.TopLevelStreamElement;
|
|
|
|
import org.jivesoftware.smack.util.StringUtils;
|
|
|
|
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpointLookupFailure;
|
2020-09-01 21:30:14 +02:00
|
|
|
import org.jivesoftware.smack.websocket.XmppWebSocketTransportModule.XmppWebSocketTransport.DiscoveredWebSocketEndpoints;
|
|
|
|
import org.jivesoftware.smack.websocket.elements.WebSocketCloseElement;
|
|
|
|
import org.jivesoftware.smack.websocket.elements.WebSocketOpenElement;
|
|
|
|
import org.jivesoftware.smack.websocket.impl.AbstractWebSocket;
|
|
|
|
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
|
|
|
|
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpointLookup;
|
|
|
|
import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpointLookup.Result;
|
2020-05-14 14:35:37 +02:00
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
2020-09-01 21:30:14 +02:00
|
|
|
public final class XmppWebSocketTransportModule
|
|
|
|
extends ModularXmppClientToServerConnectionModule<XmppWebSocketTransportModuleDescriptor> {
|
|
|
|
private final XmppWebSocketTransport websocketTransport;
|
2020-05-14 14:35:37 +02:00
|
|
|
|
2020-09-01 21:30:14 +02:00
|
|
|
private AbstractWebSocket websocket;
|
2020-05-14 14:35:37 +02:00
|
|
|
|
2020-09-01 21:30:14 +02:00
|
|
|
protected XmppWebSocketTransportModule(XmppWebSocketTransportModuleDescriptor moduleDescriptor,
|
2020-05-14 14:35:37 +02:00
|
|
|
ModularXmppClientToServerConnectionInternal connectionInternal) {
|
|
|
|
super(moduleDescriptor, connectionInternal);
|
|
|
|
|
2020-09-01 21:30:14 +02:00
|
|
|
websocketTransport = new XmppWebSocketTransport(connectionInternal);
|
2020-05-14 14:35:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2020-09-01 21:30:14 +02:00
|
|
|
protected XmppWebSocketTransport getTransport() {
|
2020-05-14 14:35:37 +02:00
|
|
|
return websocketTransport;
|
|
|
|
}
|
|
|
|
|
2020-09-01 21:30:14 +02:00
|
|
|
static final class EstablishingWebSocketConnectionStateDescriptor extends StateDescriptor {
|
|
|
|
private EstablishingWebSocketConnectionStateDescriptor() {
|
|
|
|
super(XmppWebSocketTransportModule.EstablishingWebSocketConnectionState.class);
|
2020-05-14 14:35:37 +02:00
|
|
|
addPredeccessor(LookupRemoteConnectionEndpointsStateDescriptor.class);
|
|
|
|
addSuccessor(ConnectedButUnauthenticatedStateDescriptor.class);
|
|
|
|
|
2020-09-01 21:30:14 +02:00
|
|
|
// This states preference to TCP transports over this WebSocket transport implementation.
|
2020-05-14 14:35:37 +02:00
|
|
|
declareInferiorityTo("org.jivesoftware.smack.tcp.XmppTcpTransportModule$EstablishingTcpConnectionStateDescriptor");
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected State constructState(ModularXmppClientToServerConnectionInternal connectionInternal) {
|
2020-09-01 21:30:14 +02:00
|
|
|
XmppWebSocketTransportModule websocketTransportModule = connectionInternal.connection.getConnectionModuleFor(
|
|
|
|
XmppWebSocketTransportModuleDescriptor.class);
|
|
|
|
return websocketTransportModule.constructEstablishingWebSocketConnectionState(this, connectionInternal);
|
2020-05-14 14:35:37 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-01 21:30:14 +02:00
|
|
|
final class EstablishingWebSocketConnectionState extends State {
|
|
|
|
protected EstablishingWebSocketConnectionState(StateDescriptor stateDescriptor,
|
2020-05-14 14:35:37 +02:00
|
|
|
ModularXmppClientToServerConnectionInternal connectionInternal) {
|
|
|
|
super(stateDescriptor, connectionInternal);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
|
|
|
|
throws IOException, SmackException, InterruptedException, XMPPException {
|
2020-09-01 21:30:14 +02:00
|
|
|
WebSocketConnectionAttemptState connectionAttemptState = new WebSocketConnectionAttemptState(
|
|
|
|
connectionInternal, discoveredWebSocketEndpoints, this);
|
2020-05-14 14:35:37 +02:00
|
|
|
|
|
|
|
try {
|
2020-09-01 21:30:14 +02:00
|
|
|
websocket = connectionAttemptState.establishWebSocketConnection();
|
|
|
|
} catch (InterruptedException | WebSocketException e) {
|
2020-05-14 14:35:37 +02:00
|
|
|
StateTransitionResult.Failure failure = new StateTransitionResult.FailureCausedByException<Exception>(e);
|
|
|
|
return failure;
|
|
|
|
}
|
|
|
|
|
|
|
|
connectionInternal.setTransport(websocketTransport);
|
|
|
|
|
2020-09-01 21:30:14 +02:00
|
|
|
WebSocketRemoteConnectionEndpoint connectedEndpoint = connectionAttemptState.getConnectedEndpoint();
|
2020-05-14 14:35:37 +02:00
|
|
|
|
2020-09-01 21:30:14 +02:00
|
|
|
// Construct a WebSocketConnectedResult using the connected endpoint.
|
|
|
|
return new WebSocketConnectedResult(connectedEndpoint);
|
2020-05-14 14:35:37 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-01 21:30:14 +02:00
|
|
|
public EstablishingWebSocketConnectionState constructEstablishingWebSocketConnectionState(
|
|
|
|
EstablishingWebSocketConnectionStateDescriptor establishingWebSocketConnectionStateDescriptor,
|
2020-05-14 14:35:37 +02:00
|
|
|
ModularXmppClientToServerConnectionInternal connectionInternal) {
|
2020-09-01 21:30:14 +02:00
|
|
|
return new EstablishingWebSocketConnectionState(establishingWebSocketConnectionStateDescriptor,
|
2020-05-14 14:35:37 +02:00
|
|
|
connectionInternal);
|
|
|
|
}
|
|
|
|
|
2020-09-01 21:30:14 +02:00
|
|
|
public static final class WebSocketConnectedResult extends StateTransitionResult.Success {
|
|
|
|
final WebSocketRemoteConnectionEndpoint connectedEndpoint;
|
2020-05-14 14:35:37 +02:00
|
|
|
|
2020-09-01 21:30:14 +02:00
|
|
|
public WebSocketConnectedResult(WebSocketRemoteConnectionEndpoint connectedEndpoint) {
|
|
|
|
super("WebSocket connection establised with endpoint: " + connectedEndpoint.getWebSocketEndpoint());
|
2020-05-14 14:35:37 +02:00
|
|
|
this.connectedEndpoint = connectedEndpoint;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-01 21:30:14 +02:00
|
|
|
private DiscoveredWebSocketEndpoints discoveredWebSocketEndpoints;
|
2020-05-14 14:35:37 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Transport class for {@link ModularXmppClientToServerConnectionModule}'s websocket implementation.
|
|
|
|
*/
|
2020-09-01 21:30:14 +02:00
|
|
|
public final class XmppWebSocketTransport extends XmppClientToServerTransport {
|
2020-05-14 14:35:37 +02:00
|
|
|
|
|
|
|
AsyncButOrdered<Queue<TopLevelStreamElement>> asyncButOrderedOutgoingElementsQueue;
|
|
|
|
|
2020-09-01 21:30:14 +02:00
|
|
|
protected XmppWebSocketTransport(ModularXmppClientToServerConnectionInternal connectionInternal) {
|
2020-05-14 14:35:37 +02:00
|
|
|
super(connectionInternal);
|
|
|
|
asyncButOrderedOutgoingElementsQueue = new AsyncButOrdered<Queue<TopLevelStreamElement>>();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void resetDiscoveredConnectionEndpoints() {
|
2020-09-01 21:30:14 +02:00
|
|
|
discoveredWebSocketEndpoints = null;
|
2020-05-14 14:35:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected List<SmackFuture<LookupConnectionEndpointsResult, Exception>> lookupConnectionEndpoints() {
|
|
|
|
// Assert that there are no stale discovered endpoints prior performing the lookup.
|
2020-09-01 21:30:14 +02:00
|
|
|
assert discoveredWebSocketEndpoints == null;
|
2020-05-14 14:35:37 +02:00
|
|
|
|
|
|
|
InternalSmackFuture<LookupConnectionEndpointsResult, Exception> websocketEndpointsLookupFuture = new InternalSmackFuture<>();
|
|
|
|
|
|
|
|
connectionInternal.asyncGo(() -> {
|
|
|
|
|
2020-09-01 21:30:14 +02:00
|
|
|
WebSocketRemoteConnectionEndpoint providedEndpoint = null;
|
2020-05-14 14:35:37 +02:00
|
|
|
|
|
|
|
// Check if there is a websocket endpoint already configured.
|
|
|
|
URI uri = moduleDescriptor.getExplicitlyProvidedUri();
|
|
|
|
if (uri != null) {
|
2020-09-01 21:30:14 +02:00
|
|
|
providedEndpoint = new WebSocketRemoteConnectionEndpoint(uri);
|
2020-05-14 14:35:37 +02:00
|
|
|
}
|
|
|
|
|
2020-09-01 21:30:14 +02:00
|
|
|
if (!moduleDescriptor.isWebSocketEndpointDiscoveryEnabled()) {
|
2020-05-14 14:35:37 +02:00
|
|
|
// 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))) {
|
2020-09-01 21:30:14 +02:00
|
|
|
throw new IllegalStateException("Explicitly configured uri: " + providedEndpoint.getWebSocketEndpoint().toString()
|
2020-05-14 14:35:37 +02:00
|
|
|
+ " does not comply with the configured security mode: " + mode);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate Result for explicitly configured endpoint.
|
|
|
|
Result manualResult = new Result(Arrays.asList(providedEndpoint), null);
|
|
|
|
|
2020-09-01 21:30:14 +02:00
|
|
|
LookupConnectionEndpointsResult endpointsResult = new DiscoveredWebSocketEndpoints(manualResult);
|
2020-05-14 14:35:37 +02:00
|
|
|
|
|
|
|
websocketEndpointsLookupFuture.setResult(endpointsResult);
|
|
|
|
} else {
|
|
|
|
DomainBareJid host = connectionInternal.connection.getXMPPServiceDomain();
|
|
|
|
ModularXmppClientToServerConnectionConfiguration configuration = connectionInternal.connection.getConfiguration();
|
|
|
|
SecurityMode mode = configuration.getSecurityMode();
|
|
|
|
|
|
|
|
// Fetch remote endpoints.
|
2020-09-01 21:30:14 +02:00
|
|
|
Result xep0156result = WebSocketRemoteConnectionEndpointLookup.lookup(host, mode);
|
2020-05-14 14:35:37 +02:00
|
|
|
|
2020-09-01 21:30:14 +02:00
|
|
|
List<WebSocketRemoteConnectionEndpoint> discoveredEndpoints = xep0156result.discoveredRemoteConnectionEndpoints;
|
2020-05-14 14:35:37 +02:00
|
|
|
|
|
|
|
// Generate result considering both manual and fetched endpoints.
|
|
|
|
Result finalResult = new Result(discoveredEndpoints, xep0156result.getLookupFailures());
|
|
|
|
|
2020-09-01 21:30:14 +02:00
|
|
|
LookupConnectionEndpointsResult endpointsResult = new DiscoveredWebSocketEndpoints(finalResult);
|
2020-05-14 14:35:37 +02:00
|
|
|
|
|
|
|
websocketEndpointsLookupFuture.setResult(endpointsResult);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return Collections.singletonList(websocketEndpointsLookupFuture);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void loadConnectionEndpoints(LookupConnectionEndpointsSuccess lookupConnectionEndpointsSuccess) {
|
2020-09-01 21:30:14 +02:00
|
|
|
discoveredWebSocketEndpoints = (DiscoveredWebSocketEndpoints) lookupConnectionEndpointsSuccess;
|
2020-05-14 14:35:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void afterFiltersClosed() {
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void disconnect() {
|
2020-09-01 21:30:14 +02:00
|
|
|
websocket.disconnect(1000, "WebSocket closed normally");
|
2020-05-14 14:35:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void notifyAboutNewOutgoingElements() {
|
|
|
|
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);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public SSLSession getSslSession() {
|
|
|
|
return websocket.getSSLSession();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean isTransportSecured() {
|
|
|
|
return websocket.isConnectionSecure();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean isConnected() {
|
|
|
|
return websocket.isConnected();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Stats getStats() {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public StreamOpenAndCloseFactory getStreamOpenAndCloseFactory() {
|
|
|
|
return new StreamOpenAndCloseFactory() {
|
|
|
|
@Override
|
|
|
|
public AbstractStreamOpen createStreamOpen(CharSequence to, CharSequence from, String id, String lang) {
|
|
|
|
try {
|
2020-09-01 21:30:14 +02:00
|
|
|
return new WebSocketOpenElement(JidCreate.domainBareFrom(to));
|
2020-05-14 14:35:37 +02:00
|
|
|
} catch (XmppStringprepException e) {
|
|
|
|
Logger.getAnonymousLogger().log(Level.WARNING, "Couldn't create OpenElement", e);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@Override
|
|
|
|
public AbstractStreamClose createStreamClose() {
|
2020-09-01 21:30:14 +02:00
|
|
|
return new WebSocketCloseElement();
|
2020-05-14 14:35:37 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Contains {@link Result} for successfully discovered endpoints.
|
|
|
|
*/
|
2020-09-01 21:30:14 +02:00
|
|
|
public final class DiscoveredWebSocketEndpoints implements LookupConnectionEndpointsSuccess {
|
|
|
|
final WebSocketRemoteConnectionEndpointLookup.Result result;
|
2020-05-14 14:35:37 +02:00
|
|
|
|
2020-09-01 21:30:14 +02:00
|
|
|
DiscoveredWebSocketEndpoints(Result result) {
|
2020-05-14 14:35:37 +02:00
|
|
|
assert result != null;
|
|
|
|
this.result = result;
|
|
|
|
}
|
|
|
|
|
2020-09-01 21:30:14 +02:00
|
|
|
public WebSocketRemoteConnectionEndpointLookup.Result getResult() {
|
2020-05-14 14:35:37 +02:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Contains list of {@link RemoteConnectionEndpointLookupFailure} when no endpoint
|
|
|
|
* could be found during http lookup.
|
|
|
|
*/
|
2020-09-01 21:30:14 +02:00
|
|
|
final class WebSocketEndpointsDiscoveryFailed implements LookupConnectionEndpointsFailed {
|
2020-05-14 14:35:37 +02:00
|
|
|
final List<RemoteConnectionEndpointLookupFailure> lookupFailures;
|
|
|
|
|
2020-09-01 21:30:14 +02:00
|
|
|
WebSocketEndpointsDiscoveryFailed(
|
|
|
|
WebSocketRemoteConnectionEndpointLookup.Result result) {
|
2020-05-14 14:35:37 +02:00
|
|
|
assert result != null;
|
|
|
|
lookupFailures = Collections.unmodifiableList(result.lookupFailures);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public String toString() {
|
|
|
|
StringBuilder str = new StringBuilder();
|
|
|
|
StringUtils.appendTo(lookupFailures, str);
|
|
|
|
return str.toString();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|