Merge pull request #399 from adiaholic/websocket

Websocket support through RFC 7395
This commit is contained in:
Florian Schmaus 2020-08-29 16:34:32 +02:00 committed by GitHub
commit e78ef2b668
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 1943 additions and 3 deletions

View File

@ -288,6 +288,9 @@ tasks.withType(Javadoc) {
// fixtures, and we want to have mockito also available in
// test, so we use API here.
testFixturesApi "org.mockito:mockito-core:3.3.3"
// To mock final classes
testImplementation 'org.mockito:mockito-inline:3.3.3'
testImplementation 'com.jamesmurty.utils:java-xmlbuilder:1.2'
errorprone 'com.google.errorprone:error_prone_core:2.3.4'

View File

@ -30,6 +30,7 @@ include 'smack-core',
'smack-omemo-signal-integration-test',
'smack-repl',
'smack-openpgp',
'smack-websocket',
'smack-xmlparser',
'smack-xmlparser-stax',
'smack-xmlparser-xpp3'

View File

@ -61,13 +61,13 @@ import org.jivesoftware.smack.fsm.StateTransitionResult;
import org.jivesoftware.smack.fsm.StateTransitionResult.AttemptResult;
import org.jivesoftware.smack.internal.AbstractStats;
import org.jivesoftware.smack.internal.SmackTlsContext;
import org.jivesoftware.smack.packet.AbstractStreamClose;
import org.jivesoftware.smack.packet.AbstractStreamOpen;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Nonza;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.packet.StreamClose;
import org.jivesoftware.smack.packet.StreamError;
import org.jivesoftware.smack.packet.TopLevelStreamElement;
import org.jivesoftware.smack.packet.XmlEnvironment;
@ -138,6 +138,11 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
ModularXmppClientToServerConnection.this.notifyConnectionError(e);
}
@Override
public void setCurrentConnectionExceptionAndNotify(Exception exception) {
ModularXmppClientToServerConnection.this.setCurrentConnectionExceptionAndNotify(exception);
}
@Override
public void onStreamOpen(XmlPullParser parser) {
ModularXmppClientToServerConnection.this.onStreamOpen(parser);
@ -179,6 +184,11 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
return inputOutputFilters.listIterator(inputOutputFilters.size());
}
@Override
public void waitForFeaturesReceived(String waitFor) throws InterruptedException, SmackException, XMPPException {
ModularXmppClientToServerConnection.this.waitForFeaturesReceived(waitFor);
}
@Override
public void newStreamOpenWaitForFeaturesSequence(String waitFor) throws InterruptedException,
SmackException, XMPPException {
@ -930,7 +940,9 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) {
closingStreamReceived = false;
boolean streamCloseIssued = outgoingElementsQueue.offerAndShutdown(StreamClose.INSTANCE);
StreamOpenAndCloseFactory openAndCloseFactory = activeTransport.getStreamOpenAndCloseFactory();
AbstractStreamClose closeStreamElement = openAndCloseFactory.createStreamClose();
boolean streamCloseIssued = outgoingElementsQueue.offerAndShutdown(closeStreamElement);
if (streamCloseIssued) {
activeTransport.notifyAboutNewOutgoingElements();

View File

@ -85,6 +85,8 @@ public abstract class ModularXmppClientToServerConnectionInternal {
public abstract void notifyConnectionError(Exception e);
public abstract void setCurrentConnectionExceptionAndNotify(Exception exception);
public abstract void onStreamOpen(XmlPullParser parser);
public abstract void onStreamClosed();
@ -99,6 +101,8 @@ public abstract class ModularXmppClientToServerConnectionInternal {
public abstract ListIterator<XmppInputOutputFilter> getXmppInputOutputFilterEndIterator();
public abstract void waitForFeaturesReceived(String waitFor) throws InterruptedException, SmackException, XMPPException;
public abstract void newStreamOpenWaitForFeaturesSequence(String waitFor) throws InterruptedException,
NoResponseException, NotConnectedException, SmackException, XMPPException;

View File

@ -19,6 +19,7 @@ package org.jivesoftware.smack.packet;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.packet.StreamOpen.StreamContentNamespace;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.XmlStringBuilder;
/**
* AbstractStreamOpen is actually a {@link TopLevelStreamElement}, however we
@ -80,4 +81,9 @@ public abstract class AbstractStreamOpen implements Nonza {
throw new IllegalStateException();
}
}
protected final void addCommonAttributes(XmlStringBuilder xml) {
xml.optAttribute("to", to);
xml.optAttribute("version", VERSION);
}
}

View File

@ -18,6 +18,7 @@ package org.jivesoftware.smack.util.rce;
import org.jivesoftware.smack.util.ToStringUtil;
import org.jxmpp.jid.DomainBareJid;
import org.minidns.dnsname.DnsName;
public abstract class RemoteConnectionEndpointLookupFailure {
@ -67,4 +68,17 @@ public abstract class RemoteConnectionEndpointLookupFailure {
return dnsName;
}
}
public static class HttpLookupFailure extends RemoteConnectionEndpointLookupFailure {
private final DomainBareJid host;
public HttpLookupFailure(DomainBareJid host, Exception exception) {
super("Http lookup exception for " + host, exception);
this.host = host;
}
public DomainBareJid getHost() {
return host;
}
}
}

View File

@ -20,6 +20,7 @@
<className>org.jivesoftware.smack.android.AndroidSmackInitializer</className>
<className>org.jivesoftware.smack.java7.Java7SmackInitializer</className>
<className>org.jivesoftware.smack.im.SmackImInitializer</className>
<className>org.jivesoftware.smack.websocket.WebsocketInitializer</className>
<className>org.jivesoftware.smackx.omemo.OmemoInitializer</className>
<className>org.jivesoftware.smackx.ox.util.OpenPgpInitializer</className>
</optionalStartupClasses>

View File

@ -44,6 +44,7 @@ import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
import org.jivesoftware.smack.util.MultiMap;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.websocket.XmppWebsocketTransportModuleDescriptor;
import org.jivesoftware.smackx.admin.ServiceAdministrationManager;
import org.jivesoftware.smackx.iqregister.AccountManager;
@ -86,6 +87,15 @@ public class XmppConnectionManager {
.applyExtraConfguration(cb -> cb.removeModule(CompressionModuleDescriptor.class))
.build()
);
addConnectionDescriptor(
XmppConnectionDescriptor.buildWith(ModularXmppClientToServerConnection.class, ModularXmppClientToServerConnectionConfiguration.class, ModularXmppClientToServerConnectionConfiguration.Builder.class)
.withNickname("modular-websocket")
.applyExtraConfguration(cb -> {
cb.removeAllModules();
cb.addModule(XmppWebsocketTransportModuleDescriptor.class);
})
.build()
);
} catch (NoSuchMethodException | SecurityException e) {
throw new AssertionError(e);
}

View File

@ -12,6 +12,7 @@ dependencies {
api project(':smack-openpgp')
api project(':smack-resolver-minidns')
api project(':smack-resolver-minidns-dox')
api project(':smack-websocket')
api project(':smack-tcp')
testImplementation 'com.google.guava:guava:28.2-jre'

View File

@ -1,6 +1,6 @@
digraph {
"Disconnected" -> "LookupRemoteConnectionEndpoints";
"LookupRemoteConnectionEndpoints" -> "EstablishingTcpConnection";
"LookupRemoteConnectionEndpoints" -> "EstablishingTcpConnection" [xlabel="1"];
"EstablishingTcpConnection" -> "EstablishTls (RFC 6120 § 5)" [xlabel="1"];
"EstablishTls (RFC 6120 § 5)" -> "ConnectedButUnauthenticated";
"ConnectedButUnauthenticated" -> "Bind2 (XEP-0386)" [xlabel="1"];
@ -32,5 +32,7 @@ digraph {
"ConnectedButUnauthenticated" -> "InstantShutdown" [xlabel="5"];
"ConnectedButUnauthenticated" [ style=filled ]
"EstablishingTcpConnection" -> "ConnectedButUnauthenticated" [xlabel="2"];
"LookupRemoteConnectionEndpoints" -> "EstablishingWebsocketConnection" [xlabel="2"];
"EstablishingWebsocketConnection" -> "ConnectedButUnauthenticated";
"Disconnected" [ style=filled ]
}

View File

@ -0,0 +1,53 @@
/**
*
* Copyright 2020 Aditya Borikar
*
* This file is part of smack-repl.
*
* smack-repl is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.igniterealtime.smack.smackrepl;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
import org.jivesoftware.smack.websocket.XmppWebsocketTransportModuleDescriptor;
public class WebsocketConnection {
public static void main(String[] args) throws SmackException, IOException, XMPPException, InterruptedException, URISyntaxException {
ModularXmppClientToServerConnectionConfiguration.Builder builder = ModularXmppClientToServerConnectionConfiguration.builder();
builder.removeAllModules();
builder.setXmppAddressAndPassword(args[0], args[1]);
// Set a fallback uri into websocket transport descriptor and add this descriptor into connection builder.
XmppWebsocketTransportModuleDescriptor.Builder websocketBuilder = XmppWebsocketTransportModuleDescriptor.getBuilder(builder);
websocketBuilder.explicitlySetWebsocketEndpointAndDiscovery(new URI(args[2]), false);
builder.addModule(websocketBuilder.build());
ModularXmppClientToServerConnectionConfiguration config = builder.build();
ModularXmppClientToServerConnection connection = new ModularXmppClientToServerConnection(config);
connection.connect();
connection.login();
connection.disconnect();
}
}

View File

@ -0,0 +1,10 @@
description = """\
Smack for standard XMPP connections over Websockets."""
dependencies {
compile project(':smack-core')
testFixturesApi(testFixtures(project(":smack-core")))
implementation("com.squareup.okhttp3:okhttp:4.6.0")
}

View File

@ -0,0 +1,101 @@
/**
*
* Copyright 2020 Aditya Borikar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.websocket;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
import org.jivesoftware.smack.websocket.XmppWebsocketTransportModule.EstablishingWebsocketConnectionState;
import org.jivesoftware.smack.websocket.implementations.AbstractWebsocket;
import org.jivesoftware.smack.websocket.implementations.WebsocketImplProvider;
import org.jivesoftware.smack.websocket.implementations.okhttp.OkHttpWebsocket;
import org.jivesoftware.smack.websocket.rce.WebsocketRemoteConnectionEndpoint;
public final class WebsocketConnectionAttemptState {
private final ModularXmppClientToServerConnectionInternal connectionInternal;
private final XmppWebsocketTransportModule.XmppWebsocketTransport.DiscoveredWebsocketEndpoints discoveredEndpoints;
private WebsocketRemoteConnectionEndpoint connectedEndpoint;
WebsocketConnectionAttemptState(ModularXmppClientToServerConnectionInternal connectionInternal,
XmppWebsocketTransportModule.XmppWebsocketTransport.DiscoveredWebsocketEndpoints discoveredWebsocketEndpoints,
EstablishingWebsocketConnectionState establishingWebsocketConnectionState) {
assert discoveredWebsocketEndpoints != null;
this.connectionInternal = connectionInternal;
this.discoveredEndpoints = discoveredWebsocketEndpoints;
}
/**
* Establish a websocket connection with one of the discoveredRemoteConnectionEndpoints.<br>
*
* @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;
if (endpoints.isEmpty()) {
throw new WebsocketException(new Throwable("No Endpoints discovered to establish connection"));
}
List<Throwable> connectionFailureList = new ArrayList<>();
AbstractWebsocket websocket;
try {
// Obtain desired websocket implementation by using WebsocketImplProvider
websocket = WebsocketImplProvider.getWebsocketImpl(OkHttpWebsocket.class, connectionInternal, discoveredEndpoints);
} catch (NoSuchMethodException | SecurityException | InstantiationException |
IllegalAccessException | IllegalArgumentException | InvocationTargetException exception) {
throw new WebsocketException(exception);
}
// Keep iterating over available endpoints until a connection is establised or all endpoints are tried to create a connection with.
for (WebsocketRemoteConnectionEndpoint endpoint : endpoints) {
try {
websocket.connect(endpoint);
connectedEndpoint = endpoint;
break;
} catch (Throwable t) {
connectionFailureList.add(t);
// If the number of entries in connectionFailureList is equal to the number of endpoints,
// it means that all endpoints have been tried and have been unsuccessful.
if (connectionFailureList.size() == endpoints.size()) {
WebsocketException websocketException = new WebsocketException(connectionFailureList);
throw new WebsocketException(websocketException);
}
}
}
assert connectedEndpoint != null;
// Return connected websocket when no failure occurs.
return websocket;
}
/**
* Returns the connected websocket endpoint.
*
* @return connected websocket endpoint
*/
public WebsocketRemoteConnectionEndpoint getConnectedEndpoint() {
return connectedEndpoint;
}
}

View File

@ -0,0 +1,38 @@
/**
*
* Copyright 2020 Aditya Borikar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.websocket;
import java.util.Collections;
import java.util.List;
public final class WebsocketException extends Exception {
private static final long serialVersionUID = 1L;
private final List<Throwable> throwableList;
public WebsocketException(List<Throwable> throwableList) {
this.throwableList = throwableList;
}
public WebsocketException(Throwable throwable) {
this.throwableList = Collections.singletonList(throwable);
}
public List<Throwable> getThrowableList() {
return throwableList;
}
}

View File

@ -0,0 +1,28 @@
/**
*
* Copyright 2020 Aditya Borikar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.websocket;
import org.jivesoftware.smack.SmackConfiguration;
import org.jivesoftware.smack.initializer.UrlInitializer;
public final class WebsocketInitializer extends UrlInitializer {
static {
SmackConfiguration.addModule(XmppWebsocketTransportModuleDescriptor.class);
}
}

View File

@ -0,0 +1,325 @@
/**
*
* Copyright 2020 Aditya Borikar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.websocket;
import java.io.IOException;
import java.net.URI;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Queue;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.SSLSession;
import org.jivesoftware.smack.AsyncButOrdered;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackFuture;
import org.jivesoftware.smack.SmackFuture.InternalSmackFuture;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.ConnectedButUnauthenticatedStateDescriptor;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.LookupRemoteConnectionEndpointsStateDescriptor;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionModule;
import org.jivesoftware.smack.c2s.StreamOpenAndCloseFactory;
import org.jivesoftware.smack.c2s.XmppClientToServerTransport;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
import org.jivesoftware.smack.c2s.internal.WalkStateGraphContext;
import org.jivesoftware.smack.fsm.State;
import org.jivesoftware.smack.fsm.StateDescriptor;
import org.jivesoftware.smack.fsm.StateTransitionResult;
import org.jivesoftware.smack.fsm.StateTransitionResult.AttemptResult;
import org.jivesoftware.smack.packet.AbstractStreamClose;
import org.jivesoftware.smack.packet.AbstractStreamOpen;
import org.jivesoftware.smack.packet.TopLevelStreamElement;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpointLookupFailure;
import org.jivesoftware.smack.websocket.XmppWebsocketTransportModule.XmppWebsocketTransport.DiscoveredWebsocketEndpoints;
import org.jivesoftware.smack.websocket.elements.WebsocketCloseElement;
import org.jivesoftware.smack.websocket.elements.WebsocketOpenElement;
import org.jivesoftware.smack.websocket.implementations.AbstractWebsocket;
import org.jivesoftware.smack.websocket.rce.WebsocketRemoteConnectionEndpoint;
import org.jivesoftware.smack.websocket.rce.WebsocketRemoteConnectionEndpointLookup;
import org.jivesoftware.smack.websocket.rce.WebsocketRemoteConnectionEndpointLookup.Result;
import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException;
/**
* The websocket transport module that goes with Smack's modular architecture.
*/
public final class XmppWebsocketTransportModule
extends ModularXmppClientToServerConnectionModule<XmppWebsocketTransportModuleDescriptor> {
private final XmppWebsocketTransport websocketTransport;
private AbstractWebsocket websocket;
protected XmppWebsocketTransportModule(XmppWebsocketTransportModuleDescriptor moduleDescriptor,
ModularXmppClientToServerConnectionInternal connectionInternal) {
super(moduleDescriptor, connectionInternal);
websocketTransport = new XmppWebsocketTransport(connectionInternal);
}
@Override
protected XmppWebsocketTransport getTransport() {
return websocketTransport;
}
static final class EstablishingWebsocketConnectionStateDescriptor extends StateDescriptor {
private EstablishingWebsocketConnectionStateDescriptor() {
super(XmppWebsocketTransportModule.EstablishingWebsocketConnectionState.class);
addPredeccessor(LookupRemoteConnectionEndpointsStateDescriptor.class);
addSuccessor(ConnectedButUnauthenticatedStateDescriptor.class);
// This states preference to TCP transports over this Websocket transport implementation.
declareInferiorityTo("org.jivesoftware.smack.tcp.XmppTcpTransportModule$EstablishingTcpConnectionStateDescriptor");
}
@Override
protected State constructState(ModularXmppClientToServerConnectionInternal connectionInternal) {
XmppWebsocketTransportModule websocketTransportModule = connectionInternal.connection.getConnectionModuleFor(
XmppWebsocketTransportModuleDescriptor.class);
return websocketTransportModule.constructEstablishingWebsocketConnectionState(this, connectionInternal);
}
}
final class EstablishingWebsocketConnectionState extends State {
protected EstablishingWebsocketConnectionState(StateDescriptor stateDescriptor,
ModularXmppClientToServerConnectionInternal connectionInternal) {
super(stateDescriptor, connectionInternal);
}
@Override
public AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
throws IOException, SmackException, InterruptedException, XMPPException {
WebsocketConnectionAttemptState connectionAttemptState = new WebsocketConnectionAttemptState(
connectionInternal, discoveredWebsocketEndpoints, this);
try {
websocket = connectionAttemptState.establishWebsocketConnection();
} catch (InterruptedException | WebsocketException e) {
StateTransitionResult.Failure failure = new StateTransitionResult.FailureCausedByException<Exception>(e);
return failure;
}
connectionInternal.setTransport(websocketTransport);
WebsocketRemoteConnectionEndpoint connectedEndpoint = connectionAttemptState.getConnectedEndpoint();
// Construct a WebsocketConnectedResult using the connected endpoint.
return new WebsocketConnectedResult(connectedEndpoint);
}
}
public EstablishingWebsocketConnectionState constructEstablishingWebsocketConnectionState(
EstablishingWebsocketConnectionStateDescriptor establishingWebsocketConnectionStateDescriptor,
ModularXmppClientToServerConnectionInternal connectionInternal) {
return new EstablishingWebsocketConnectionState(establishingWebsocketConnectionStateDescriptor,
connectionInternal);
}
public static final class WebsocketConnectedResult extends StateTransitionResult.Success {
final WebsocketRemoteConnectionEndpoint connectedEndpoint;
public WebsocketConnectedResult(WebsocketRemoteConnectionEndpoint connectedEndpoint) {
super("Websocket connection establised with endpoint: " + connectedEndpoint.getWebsocketEndpoint());
this.connectedEndpoint = connectedEndpoint;
}
}
private DiscoveredWebsocketEndpoints discoveredWebsocketEndpoints;
/**
* Transport class for {@link ModularXmppClientToServerConnectionModule}'s websocket implementation.
*/
public final class XmppWebsocketTransport extends XmppClientToServerTransport {
AsyncButOrdered<Queue<TopLevelStreamElement>> asyncButOrderedOutgoingElementsQueue;
protected XmppWebsocketTransport(ModularXmppClientToServerConnectionInternal connectionInternal) {
super(connectionInternal);
asyncButOrderedOutgoingElementsQueue = new AsyncButOrdered<Queue<TopLevelStreamElement>>();
}
@Override
protected void resetDiscoveredConnectionEndpoints() {
discoveredWebsocketEndpoints = null;
}
@Override
protected List<SmackFuture<LookupConnectionEndpointsResult, Exception>> lookupConnectionEndpoints() {
// Assert that there are no stale discovered endpoints prior performing the lookup.
assert discoveredWebsocketEndpoints == null;
InternalSmackFuture<LookupConnectionEndpointsResult, Exception> websocketEndpointsLookupFuture = new InternalSmackFuture<>();
connectionInternal.asyncGo(() -> {
WebsocketRemoteConnectionEndpoint providedEndpoint = null;
// Check if there is a websocket endpoint already configured.
URI uri = moduleDescriptor.getExplicitlyProvidedUri();
if (uri != null) {
providedEndpoint = new WebsocketRemoteConnectionEndpoint(uri);
}
if (!moduleDescriptor.isWebsocketEndpointDiscoveryEnabled()) {
// If discovery is disabled, assert that the provided endpoint isn't null.
assert providedEndpoint != null;
SecurityMode mode = connectionInternal.connection.getConfiguration().getSecurityMode();
if ((providedEndpoint.isSecureEndpoint() &&
mode.equals(SecurityMode.disabled))
|| (!providedEndpoint.isSecureEndpoint() &&
mode.equals(SecurityMode.required))) {
throw new IllegalStateException("Explicitly configured uri: " + providedEndpoint.getWebsocketEndpoint().toString()
+ " does not comply with the configured security mode: " + mode);
}
// Generate Result for explicitly configured endpoint.
Result manualResult = new Result(Arrays.asList(providedEndpoint), null);
LookupConnectionEndpointsResult endpointsResult = new DiscoveredWebsocketEndpoints(manualResult);
websocketEndpointsLookupFuture.setResult(endpointsResult);
} else {
DomainBareJid host = connectionInternal.connection.getXMPPServiceDomain();
ModularXmppClientToServerConnectionConfiguration configuration = connectionInternal.connection.getConfiguration();
SecurityMode mode = configuration.getSecurityMode();
// Fetch remote endpoints.
Result xep0156result = WebsocketRemoteConnectionEndpointLookup.lookup(host, mode);
List<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);
}
});
return Collections.singletonList(websocketEndpointsLookupFuture);
}
@Override
protected void loadConnectionEndpoints(LookupConnectionEndpointsSuccess lookupConnectionEndpointsSuccess) {
discoveredWebsocketEndpoints = (DiscoveredWebsocketEndpoints) lookupConnectionEndpointsSuccess;
}
@Override
protected void afterFiltersClosed() {
}
@Override
protected void disconnect() {
websocket.disconnect(1000, "Websocket closed normally");
}
@Override
protected void notifyAboutNewOutgoingElements() {
Queue<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 {
return new WebsocketOpenElement(JidCreate.domainBareFrom(to));
} catch (XmppStringprepException e) {
Logger.getAnonymousLogger().log(Level.WARNING, "Couldn't create OpenElement", e);
return null;
}
}
@Override
public AbstractStreamClose createStreamClose() {
return new WebsocketCloseElement();
}
};
}
/**
* Contains {@link Result} for successfully discovered endpoints.
*/
public final class DiscoveredWebsocketEndpoints implements LookupConnectionEndpointsSuccess {
final WebsocketRemoteConnectionEndpointLookup.Result result;
DiscoveredWebsocketEndpoints(Result result) {
assert result != null;
this.result = result;
}
public WebsocketRemoteConnectionEndpointLookup.Result getResult() {
return result;
}
}
/**
* Contains list of {@link RemoteConnectionEndpointLookupFailure} when no endpoint
* could be found during http lookup.
*/
final class WebsocketEndpointsDiscoveryFailed implements LookupConnectionEndpointsFailed {
final List<RemoteConnectionEndpointLookupFailure> lookupFailures;
WebsocketEndpointsDiscoveryFailed(
WebsocketRemoteConnectionEndpointLookup.Result result) {
assert result != null;
lookupFailures = Collections.unmodifiableList(result.lookupFailures);
}
@Override
public String toString() {
StringBuilder str = new StringBuilder();
StringUtils.appendTo(lookupFailures, str);
return str.toString();
}
}
}
}

View File

@ -0,0 +1,136 @@
/**
*
* Copyright 2020 Aditya Borikar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.websocket;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashSet;
import java.util.Set;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionModule;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionModuleDescriptor;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
import org.jivesoftware.smack.fsm.StateDescriptor;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.websocket.XmppWebsocketTransportModule.EstablishingWebsocketConnectionStateDescriptor;
/**
* The descriptor class for {@link XmppWebsocketTransportModule}.
* <br>
* To add {@link XmppWebsocketTransportModule} to {@link ModularXmppClientToServerConnection},
* use {@link ModularXmppClientToServerConnectionConfiguration.Builder#addModule(ModularXmppClientToServerConnectionModuleDescriptor)}.
*/
public final class XmppWebsocketTransportModuleDescriptor extends ModularXmppClientToServerConnectionModuleDescriptor {
private boolean performWebsocketEndpointDiscovery;
private URI uri;
public XmppWebsocketTransportModuleDescriptor(Builder builder) {
this.performWebsocketEndpointDiscovery = builder.performWebsocketEndpointDiscovery;
this.uri = builder.uri;
}
/**
* Returns true if websocket endpoint discovery is true, returns false otherwise.
* @return boolean
*/
public boolean isWebsocketEndpointDiscoveryEnabled() {
return performWebsocketEndpointDiscovery;
}
/**
* Returns explicitly configured websocket endpoint uri.
* @return uri
*/
public URI getExplicitlyProvidedUri() {
return uri;
}
@Override
protected Set<Class<? extends StateDescriptor>> getStateDescriptors() {
Set<Class<? extends StateDescriptor>> res = new HashSet<>();
res.add(EstablishingWebsocketConnectionStateDescriptor.class);
return res;
}
@Override
protected ModularXmppClientToServerConnectionModule<? extends ModularXmppClientToServerConnectionModuleDescriptor> constructXmppConnectionModule(
ModularXmppClientToServerConnectionInternal connectionInternal) {
return new XmppWebsocketTransportModule(this, connectionInternal);
}
/**
* Returns a new instance of {@link Builder}.
* <br>
* @return Builder
* @param connectionConfigurationBuilder {@link ModularXmppClientToServerConnectionConfiguration.Builder}.
*/
public static Builder getBuilder(
ModularXmppClientToServerConnectionConfiguration.Builder connectionConfigurationBuilder) {
return new Builder(connectionConfigurationBuilder);
}
/**
* Builder class for {@link XmppWebsocketTransportModuleDescriptor}.
* <br>
* To obtain an instance of {@link XmppWebsocketTransportModuleDescriptor.Builder}, use {@link XmppWebsocketTransportModuleDescriptor#getBuilder(ModularXmppClientToServerConnectionConfiguration.Builder)} method.
* <br>
* Use {@link Builder#explicitlySetWebsocketEndpoint(URI)} to configure the URI of an endpoint as a backup in case connection couldn't be established with endpoints through http lookup.
* <br>
* Use {@link Builder#explicitlySetWebsocketEndpointAndDiscovery(URI, boolean)} to configure endpoint and disallow websocket endpoint discovery through http lookup.
* By default, {@link Builder#performWebsocketEndpointDiscovery} is set to true.
* <br>
* Use {@link Builder#build()} to obtain {@link XmppWebsocketTransportModuleDescriptor}.
*/
public static final class Builder extends ModularXmppClientToServerConnectionModuleDescriptor.Builder {
private boolean performWebsocketEndpointDiscovery = true;
private URI uri;
private Builder(
ModularXmppClientToServerConnectionConfiguration.Builder connectionConfigurationBuilder) {
super(connectionConfigurationBuilder);
}
public Builder explicitlySetWebsocketEndpoint(URI endpoint) {
return explicitlySetWebsocketEndpointAndDiscovery(endpoint, true);
}
public Builder explicitlySetWebsocketEndpointAndDiscovery(URI endpoint, boolean performWebsocketEndpointDiscovery) {
Objects.requireNonNull(endpoint, "Provided endpoint URI must not be null");
this.uri = endpoint;
this.performWebsocketEndpointDiscovery = performWebsocketEndpointDiscovery;
return this;
}
public Builder explicitlySetWebsocketEndpoint(CharSequence endpoint) throws URISyntaxException {
URI endpointUri = new URI(endpoint.toString());
return explicitlySetWebsocketEndpointAndDiscovery(endpointUri, true);
}
public Builder explicitlySetWebsocketEndpoint(CharSequence endpoint, boolean performWebsocketEndpointDiscovery)
throws URISyntaxException {
URI endpointUri = new URI(endpoint.toString());
return explicitlySetWebsocketEndpointAndDiscovery(endpointUri, performWebsocketEndpointDiscovery);
}
@Override
public ModularXmppClientToServerConnectionModuleDescriptor build() {
return new XmppWebsocketTransportModuleDescriptor(this);
}
}
}

View File

@ -0,0 +1,47 @@
/**
*
* Copyright 2020 Aditya Borikar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.websocket.elements;
import org.jivesoftware.smack.packet.Nonza;
import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.util.XmlStringBuilder;
import org.jxmpp.jid.DomainBareJid;
public abstract class AbstractWebsocketNonza implements Nonza {
public static final String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-framing";
private static final String VERSION = "1.0";
private final DomainBareJid to;
public AbstractWebsocketNonza(DomainBareJid jid) {
this.to = jid;
}
@Override
public String getNamespace() {
return NAMESPACE;
}
@Override
public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) {
XmlStringBuilder xml = new XmlStringBuilder(this, xmlEnvironment);
xml.attribute("to", to.toString());
xml.attribute("version", VERSION);
xml.closeEmptyElement();
return xml;
}
}

View File

@ -0,0 +1,49 @@
/**
*
* Copyright 2020 Aditya Borikar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.websocket.elements;
import javax.xml.namespace.QName;
import org.jivesoftware.smack.packet.AbstractStreamClose;
import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.util.XmlStringBuilder;
public final class WebsocketCloseElement extends AbstractStreamClose {
public static final String ELEMENT = "close";
public static final String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-framing";
public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
public WebsocketCloseElement() {
}
@Override
public String getElementName() {
return ELEMENT;
}
@Override
public String getNamespace() {
return NAMESPACE;
}
@Override
public CharSequence toXML(XmlEnvironment xmlEnvironment) {
XmlStringBuilder xml = new XmlStringBuilder(this);
xml.closeEmptyElement();
return xml;
}
}

View File

@ -0,0 +1,54 @@
/**
*
* Copyright 2020 Aditya Borikar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.websocket.elements;
import javax.xml.namespace.QName;
import org.jivesoftware.smack.packet.AbstractStreamOpen;
import org.jivesoftware.smack.packet.StreamOpen.StreamContentNamespace;
import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.util.XmlStringBuilder;
import org.jxmpp.jid.DomainBareJid;
public final class WebsocketOpenElement extends AbstractStreamOpen {
public static final String ELEMENT = "open";
public static final String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-framing";
public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
public WebsocketOpenElement(DomainBareJid to) {
super(to, null, null, null, StreamContentNamespace.client);
}
@Override
public String getElementName() {
return ELEMENT;
}
@Override
public String getNamespace() {
return NAMESPACE;
}
@Override
public CharSequence toXML(XmlEnvironment xmlEnvironment) {
XmlStringBuilder xml = new XmlStringBuilder(this);
addCommonAttributes(xml);
xml.closeEmptyElement();
return xml;
}
}

View File

@ -0,0 +1,20 @@
/**
*
* Copyright 2020 Aditya Borikar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* This package contains Stanzas required to open and close stream.
*/
package org.jivesoftware.smack.websocket.elements;

View File

@ -0,0 +1,63 @@
/**
*
* Copyright 2020 Aditya Borikar.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.websocket.implementations;
import javax.net.ssl.SSLSession;
import org.jivesoftware.smack.packet.TopLevelStreamElement;
import org.jivesoftware.smack.websocket.rce.WebsocketRemoteConnectionEndpoint;
public abstract class AbstractWebsocket {
protected enum WebsocketConnectionPhase {
openFrameSent,
exchangingTopLevelStreamElements
}
protected static String getStreamFromOpenElement(String openElement) {
String streamElement = openElement.replaceFirst("\\A<open ", "<stream ")
.replace("urn:ietf:params:xml:ns:xmpp-framing", "jabber:client")
.replaceFirst("/>\\s*\\z", ">");
return streamElement;
}
protected static boolean isOpenElement(String text) {
if (text.startsWith("<open ")) {
return true;
}
return false;
}
protected 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 void send(TopLevelStreamElement element);
public abstract void disconnect(int code, String message);
public abstract boolean isConnectionSecure();
public abstract SSLSession getSSLSession();
public abstract boolean isConnected();
}

View File

@ -0,0 +1,35 @@
/**
*
* Copyright 2020 Aditya Borikar.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.websocket.implementations;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.websocket.XmppWebsocketTransportModule.XmppWebsocketTransport.DiscoveredWebsocketEndpoints;
public final class WebsocketImplProvider {
public static AbstractWebsocket getWebsocketImpl(Class<? extends AbstractWebsocket> websocketImpl, ModularXmppClientToServerConnectionInternal connectionInternal, DiscoveredWebsocketEndpoints discoveredWebsocketEndpoints) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Objects.requireNonNull(connectionInternal, "ConnectionInternal cannot be null");
// Creates an instance of the constructor for the desired websocket implementation.
Constructor<? extends AbstractWebsocket> constructor = websocketImpl.getConstructor(ModularXmppClientToServerConnectionInternal.class, DiscoveredWebsocketEndpoints.class);
return (AbstractWebsocket) constructor.newInstance(connectionInternal, discoveredWebsocketEndpoints);
}
}

View File

@ -0,0 +1,90 @@
/**
*
* Copyright 2020 Aditya Borikar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.websocket.implementations.okhttp;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smack.debugger.SmackDebugger;
import okhttp3.Headers;
import okhttp3.Response;
import org.jxmpp.xml.splitter.Utf8ByteXmppXmlSplitter;
import org.jxmpp.xml.splitter.XmlPrettyPrinter;
import org.jxmpp.xml.splitter.XmppXmlSplitter;
public final class LoggingInterceptor {
private static final Logger LOGGER = Logger.getAnonymousLogger();
private static final int MAX_ELEMENT_SIZE = 64 * 1024;
private final SmackDebugger debugger;
private final Utf8ByteXmppXmlSplitter incomingTextSplitter;
private final Utf8ByteXmppXmlSplitter outgoingTextSplitter;
public LoggingInterceptor(SmackDebugger smackDebugger) {
this.debugger = smackDebugger;
XmlPrettyPrinter incomingTextPrinter = XmlPrettyPrinter.builder()
.setPrettyWriter(sb -> debugger.incomingStreamSink(sb))
.setTabWidth(4)
.build();
XmppXmlSplitter incomingXmlSplitter = new XmppXmlSplitter(MAX_ELEMENT_SIZE, null,
incomingTextPrinter);
incomingTextSplitter = new Utf8ByteXmppXmlSplitter(incomingXmlSplitter);
XmlPrettyPrinter outgoingTextPrinter = XmlPrettyPrinter.builder()
.setPrettyWriter(sb -> debugger.outgoingStreamSink(sb))
.setTabWidth(4)
.build();
XmppXmlSplitter outgoingXmlSplitter = new XmppXmlSplitter(MAX_ELEMENT_SIZE, null,
outgoingTextPrinter);
outgoingTextSplitter = new Utf8ByteXmppXmlSplitter(outgoingXmlSplitter);
}
// Open response received here isn't in the form of an Xml an so, there isn't much to format.
public void interceptOpenResponse(Response response) {
Headers headers = response.headers();
Iterator<?> iterator = headers.iterator();
StringBuilder sb = new StringBuilder();
sb.append("Received headers:");
while (iterator.hasNext()) {
sb.append("\n\t" + iterator.next());
}
debugger.incomingStreamSink(sb);
}
public void interceptReceivedText(String text) {
try {
incomingTextSplitter.write(text.getBytes(Charset.defaultCharset()));
} catch (IOException e) {
// Connections shouldn't be terminated due to exceptions encountered during debugging. hence only log them.
LOGGER.log(Level.WARNING, "IOException encountered while parsing received text: " + text, e);
}
}
public void interceptSentText(String text) {
try {
outgoingTextSplitter.write(text.getBytes(Charset.defaultCharset()));
} catch (IOException e) {
// Connections shouldn't be terminated due to exceptions encountered during debugging, hence only log them.
LOGGER.log(Level.WARNING, "IOException encountered while parsing outgoing text: " + text, e);
}
}
}

View File

@ -0,0 +1,179 @@
/**
*
* Copyright 2020 Aditya Borikar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.websocket.implementations.okhttp;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.SSLSession;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
import org.jivesoftware.smack.packet.TopLevelStreamElement;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.websocket.WebsocketException;
import org.jivesoftware.smack.websocket.XmppWebsocketTransportModule.XmppWebsocketTransport.DiscoveredWebsocketEndpoints;
import org.jivesoftware.smack.websocket.elements.WebsocketOpenElement;
import org.jivesoftware.smack.websocket.implementations.AbstractWebsocket;
import org.jivesoftware.smack.websocket.rce.WebsocketRemoteConnectionEndpoint;
import org.jivesoftware.smack.xml.XmlPullParserException;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;
public final class OkHttpWebsocket extends AbstractWebsocket {
private static final Logger LOGGER = Logger.getLogger(OkHttpWebsocket.class.getName());
private static OkHttpClient okHttpClient = null;
private final ModularXmppClientToServerConnectionInternal connectionInternal;
private final LoggingInterceptor interceptor;
private String openStreamHeader;
private WebSocket currentWebsocket;
private WebsocketConnectionPhase phase;
private WebsocketRemoteConnectionEndpoint connectedEndpoint;
public OkHttpWebsocket(ModularXmppClientToServerConnectionInternal connectionInternal,
DiscoveredWebsocketEndpoints discoveredWebsocketEndpoints) {
this.connectionInternal = connectionInternal;
if (okHttpClient == null) {
// Creates an instance of okHttp client.
OkHttpClient.Builder builder = new OkHttpClient.Builder();
okHttpClient = builder.build();
}
// Add some mechanism to enable and disable this interceptor.
if (connectionInternal.smackDebugger != null) {
interceptor = new LoggingInterceptor(connectionInternal.smackDebugger);
} else {
interceptor = null;
}
}
@Override
public void connect(WebsocketRemoteConnectionEndpoint endpoint) throws InterruptedException, SmackException, XMPPException {
final String currentUri = endpoint.getWebsocketEndpoint().toString();
Request request = new Request.Builder()
.url(currentUri)
.header("Sec-WebSocket-Protocol", "xmpp")
.build();
WebSocketListener listener = new WebSocketListener() {
@Override
public void onOpen(WebSocket webSocket, Response response) {
LOGGER.log(Level.FINER, "Websocket is open");
phase = WebsocketConnectionPhase.openFrameSent;
if (interceptor != null) {
interceptor.interceptOpenResponse(response);
}
send(new WebsocketOpenElement(connectionInternal.connection.getXMPPServiceDomain()));
}
@Override
public void onMessage(WebSocket webSocket, String text) {
if (interceptor != null) {
interceptor.interceptReceivedText(text);
}
if (isCloseElement(text)) {
connectionInternal.onStreamClosed();
return;
}
String closingStream = "</stream>";
switch (phase) {
case openFrameSent:
if (isOpenElement(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
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
LOGGER.log(Level.INFO, "Exception caught", t);
WebsocketException websocketException = new WebsocketException(t);
if (connectionInternal.connection.isConnected()) {
connectionInternal.notifyConnectionError(websocketException);
} else {
connectionInternal.setCurrentConnectionExceptionAndNotify(websocketException);
}
}
};
// Creates an instance of websocket through okHttpClient.
currentWebsocket = okHttpClient.newWebSocket(request, listener);
// Open a new stream and wait until features are received.
connectionInternal.waitForFeaturesReceived("Waiting to receive features");
connectedEndpoint = endpoint;
}
@Override
public void send(TopLevelStreamElement element) {
String textToBeSent = element.toXML().toString();
if (interceptor != null) {
interceptor.interceptSentText(textToBeSent);
}
currentWebsocket.send(textToBeSent);
}
@Override
public void disconnect(int code, String message) {
currentWebsocket.close(code, message);
LOGGER.log(Level.INFO, "Websocket has been closed with message: " + message);
}
@Override
public boolean isConnectionSecure() {
return connectedEndpoint.isSecureEndpoint();
}
@Override
public boolean isConnected() {
return connectedEndpoint == null ? false : true;
}
@Override
public SSLSession getSSLSession() {
return null;
}
}

View File

@ -0,0 +1,17 @@
/**
*
* Copyright 2020 Aditya Borikar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.websocket.implementations.okhttp;

View File

@ -0,0 +1,20 @@
/**
*
* Copyright 2020 Aditya Borikar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* This package contains websocket implementations to be plugged inside websocket transport.
*/
package org.jivesoftware.smack.websocket.implementations;

View File

@ -0,0 +1,20 @@
/**
*
* Copyright 2020 Aditya Borikar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Websocket related classes for Smack.
*/
package org.jivesoftware.smack.websocket;

View File

@ -0,0 +1,85 @@
/**
*
* Copyright 2020 Aditya Borikar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.websocket.rce;
import java.net.InetAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.Collection;
import java.util.Collections;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smack.datatypes.UInt16;
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpoint;
public final class WebsocketRemoteConnectionEndpoint implements RemoteConnectionEndpoint {
private static final Logger LOGGER = Logger.getAnonymousLogger();
private final URI uri;
public WebsocketRemoteConnectionEndpoint(String uri) throws URISyntaxException {
this(new URI(uri));
}
public WebsocketRemoteConnectionEndpoint(URI uri) {
this.uri = uri;
String scheme = uri.getScheme();
if (!(scheme.equals("ws") || scheme.equals("wss"))) {
throw new IllegalArgumentException("Only allowed protocols are ws and wss");
}
}
public URI getWebsocketEndpoint() {
return uri;
}
public boolean isSecureEndpoint() {
if (uri.getScheme().equals("wss")) {
return true;
}
return false;
}
@Override
public CharSequence getHost() {
return uri.getHost();
}
@Override
public UInt16 getPort() {
return UInt16.from(uri.getPort());
}
@Override
public Collection<? 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;
}
@Override
public String getDescription() {
return null;
}
}

View File

@ -0,0 +1,115 @@
/**
*
* Copyright 2020 Aditya Borikar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.websocket.rce;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.altconnections.HttpLookupMethod;
import org.jivesoftware.smack.altconnections.HttpLookupMethod.LinkRelation;
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpointLookupFailure;
import org.jivesoftware.smack.xml.XmlPullParserException;
import org.jxmpp.jid.DomainBareJid;
public final class WebsocketRemoteConnectionEndpointLookup {
public static Result lookup(DomainBareJid domainBareJid, SecurityMode securityMode) {
List<RemoteConnectionEndpointLookupFailure> lookupFailures = new ArrayList<>(1);
List<WebsocketRemoteConnectionEndpoint> discoveredRemoteConnectionEndpoints = new ArrayList<>();
List<URI> rcUriList = null;
try {
// Look for remote connection endpoints by making use of http lookup method described inside XEP-0156.
rcUriList = HttpLookupMethod.lookup(domainBareJid,
LinkRelation.WEBSOCKET);
} catch (IOException | XmlPullParserException | URISyntaxException e) {
lookupFailures.add(new RemoteConnectionEndpointLookupFailure.HttpLookupFailure(
domainBareJid, e));
return new Result(discoveredRemoteConnectionEndpoints, lookupFailures);
}
if (rcUriList.isEmpty()) {
throw new IllegalStateException("No endpoints were found inside host-meta");
}
// Convert rcUriList to List<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);
}
public static final class Result {
public final List<WebsocketRemoteConnectionEndpoint> discoveredRemoteConnectionEndpoints;
public final List<RemoteConnectionEndpointLookupFailure> lookupFailures;
public Result(List<WebsocketRemoteConnectionEndpoint> discoveredRemoteConnectionEndpoints,
List<RemoteConnectionEndpointLookupFailure> lookupFailures) {
this.discoveredRemoteConnectionEndpoints = discoveredRemoteConnectionEndpoints;
this.lookupFailures = lookupFailures;
}
public List<WebsocketRemoteConnectionEndpoint> getDiscoveredRemoteConnectionEndpoints() {
return discoveredRemoteConnectionEndpoints;
}
public List<RemoteConnectionEndpointLookupFailure> getLookupFailures() {
return lookupFailures;
}
}
}

View File

@ -0,0 +1,20 @@
/**
*
* Copyright 2020 Aditya Borikar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* This package contains websocket endpoint classes needed by the websocket transport.
*/
package org.jivesoftware.smack.websocket.rce;

View File

@ -0,0 +1,28 @@
/**
*
* Copyright 2020 Aditya Borikar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.websocket;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.Test;
public class WebsocketConnectionAttemptStateTest {
@Test
public void constructorTest() {
assertThrows(AssertionError.class, () -> new WebsocketConnectionAttemptState(null, null, null));
}
}

View File

@ -0,0 +1,32 @@
/**
*
* Copyright 2020 Aditya Borikar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.websocket;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.List;
import org.junit.jupiter.api.Test;
public class WebsocketInitializerTest {
@Test
public void testExtensionInitializer() {
WebsocketInitializer initializer = new WebsocketInitializer();
List<Exception> exceptions = initializer.initialize();
assertTrue(exceptions.size() == 0);
}
}

View File

@ -0,0 +1,124 @@
/**
*
* Copyright 2020 Aditya Borikar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.websocket;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpointLookupFailure;
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpointLookupFailure.HttpLookupFailure;
import org.jivesoftware.smack.websocket.XmppWebsocketTransportModule.XmppWebsocketTransport.DiscoveredWebsocketEndpoints;
import org.jivesoftware.smack.websocket.XmppWebsocketTransportModule.XmppWebsocketTransport.WebsocketEndpointsDiscoveryFailed;
import org.jivesoftware.smack.websocket.rce.WebsocketRemoteConnectionEndpoint;
import org.jivesoftware.smack.websocket.rce.WebsocketRemoteConnectionEndpointLookup.Result;
import org.junit.jupiter.api.Test;
import org.jxmpp.stringprep.XmppStringprepException;
public class XmppWebsocketTransportModuleTest {
@Test
public void createWebsocketModuleConnectionInstanceTest() throws URISyntaxException, XmppStringprepException {
ModularXmppClientToServerConnectionConfiguration.Builder builder = ModularXmppClientToServerConnectionConfiguration
.builder();
builder.removeAllModules();
builder.addModule(XmppWebsocketTransportModuleDescriptor.class);
builder.setXmppAddressAndPassword("user5@localhost.org", "user5");
builder.setHost("localhost.org");
XmppWebsocketTransportModuleDescriptor.Builder websocketBuilder = XmppWebsocketTransportModuleDescriptor.getBuilder(builder);
websocketBuilder.explicitlySetWebsocketEndpointAndDiscovery(new URI("wss://localhost.org:7443/ws/"), false);
ModularXmppClientToServerConnectionConfiguration config = builder.build();
ModularXmppClientToServerConnection connection = new ModularXmppClientToServerConnection(config);
assertNotNull(connection);
}
@Test
public void createDescriptorTest() throws URISyntaxException, XmppStringprepException {
XmppWebsocketTransportModuleDescriptor websocketTransportModuleDescriptor = getWebsocketDescriptor();
assertNotNull(websocketTransportModuleDescriptor);
}
@Test
public void websocketEndpointDiscoveryTest() throws URISyntaxException {
XmppWebsocketTransportModuleDescriptor websocketTransportModuleDescriptor = getWebsocketDescriptor();
ModularXmppClientToServerConnectionInternal connectionInternal = mock(ModularXmppClientToServerConnectionInternal.class);
XmppWebsocketTransportModule transportModule
= new XmppWebsocketTransportModule(websocketTransportModuleDescriptor, connectionInternal);
XmppWebsocketTransportModule.XmppWebsocketTransport transport = transportModule.getTransport();
assertThrows(AssertionError.class, () -> transport.new DiscoveredWebsocketEndpoints(null));
assertThrows(AssertionError.class, () -> transport.new WebsocketEndpointsDiscoveryFailed(null));
WebsocketRemoteConnectionEndpoint endpoint = new WebsocketRemoteConnectionEndpoint("wss://localhost.org:7443/ws/");
List<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();
ModularXmppClientToServerConnectionInternal connectionInternal = mock(ModularXmppClientToServerConnectionInternal.class);
XmppWebsocketTransportModule transportModule
= new XmppWebsocketTransportModule(websocketTransportModuleDescriptor, connectionInternal);
XmppWebsocketTransportModule.XmppWebsocketTransport transport = transportModule.getTransport();
assertNotNull(transport.lookupConnectionEndpoints());
}
private static XmppWebsocketTransportModuleDescriptor getWebsocketDescriptor() throws URISyntaxException {
ModularXmppClientToServerConnectionConfiguration.Builder builder = ModularXmppClientToServerConnectionConfiguration
.builder();
XmppWebsocketTransportModuleDescriptor.Builder websocketBuilder = XmppWebsocketTransportModuleDescriptor.getBuilder(builder);
websocketBuilder.explicitlySetWebsocketEndpointAndDiscovery(new URI("wss://localhost.org:7443/ws/"), false);
return (XmppWebsocketTransportModuleDescriptor) websocketBuilder.build();
}
}

View File

@ -0,0 +1,43 @@
/**
*
* Copyright 2020 Aditya Borikar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.websocket.elements;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlNotSimilar;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
import org.junit.jupiter.api.Test;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException;
public class WebsocketElementTest {
private static final String OPEN_ELEMENT = "<open xmlns='urn:ietf:params:xml:ns:xmpp-framing' to='foodomain.foo' version='1.0'/>";
private static final String CLOSE_ELEMENT = "<close xmlns='urn:ietf:params:xml:ns:xmpp-framing'/>";
@Test
public void websocketOpenElementTest() throws XmppStringprepException {
String openElementXml = new WebsocketOpenElement(JidCreate.domainBareFrom("foodomain.foo")).toXML().toString();
assertXmlSimilar(OPEN_ELEMENT, openElementXml);
assertXmlNotSimilar(CLOSE_ELEMENT, new WebsocketOpenElement(JidCreate.domainBareFrom("foodomain.foo")).toXML());
}
@Test
public void websocketCloseElementTest() throws XmppStringprepException {
String closeElementXml = new WebsocketCloseElement().toXML().toString();
assertXmlSimilar(CLOSE_ELEMENT, closeElementXml);
assertXmlNotSimilar(OPEN_ELEMENT, closeElementXml);
}
}

View File

@ -0,0 +1,47 @@
/**
*
* Copyright 2020 Aditya Borikar.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.websocket.implementations;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
public final class AbstractWebsocketTest {
private static final String OPEN_ELEMENT = "<open from='localhost.org' id='aov9ihhmmn' xmlns='urn:ietf:params:xml:ns:xmpp-framing' xml:lang='en' version='1.0'/>";
private static final String OPEN_STREAM = "<stream from='localhost.org' id='aov9ihhmmn' xmlns='jabber:client' xml:lang='en' version='1.0'>";
private static final String CLOSE_ELEMENT = "<close xmlns='urn:ietf:params:xml:ns:xmpp-framing'/>";
@Test
public void getStreamFromOpenElementTest() {
String generatedOpenStream = AbstractWebsocket.getStreamFromOpenElement(OPEN_ELEMENT);
assertEquals(generatedOpenStream, OPEN_STREAM);
}
@Test
public void isOpenElementTest() {
assertTrue(AbstractWebsocket.isOpenElement(OPEN_ELEMENT));
assertFalse(AbstractWebsocket.isOpenElement(OPEN_STREAM));
}
@Test
public void isCloseElementTest() {
assertTrue(AbstractWebsocket.isCloseElement(CLOSE_ELEMENT));
assertFalse(AbstractWebsocket.isCloseElement(OPEN_STREAM));
}
}

View File

@ -0,0 +1,61 @@
/**
*
* Copyright 2020 Aditya Borikar.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.websocket.implementations;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.lang.reflect.InvocationTargetException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
import org.jivesoftware.smack.websocket.XmppWebsocketTransportModule.XmppWebsocketTransport.DiscoveredWebsocketEndpoints;
import org.jivesoftware.smack.websocket.implementations.okhttp.OkHttpWebsocket;
import org.jivesoftware.smack.websocket.rce.WebsocketRemoteConnectionEndpoint;
import org.jivesoftware.smack.websocket.rce.WebsocketRemoteConnectionEndpointLookup.Result;
import org.junit.jupiter.api.Test;
public class ProviderTest {
@Test
public void providerTest() {
assertThrows(IllegalArgumentException.class, () -> WebsocketImplProvider.getWebsocketImpl(OkHttpWebsocket.class, null, null));
}
@Test
public void getImplTest() throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, URISyntaxException {
WebsocketRemoteConnectionEndpoint endpoint = new WebsocketRemoteConnectionEndpoint("wss://localhost.org:7443/ws/");
List<WebsocketRemoteConnectionEndpoint> discoveredRemoteConnectionEndpoints = new ArrayList<>();
discoveredRemoteConnectionEndpoints.add(endpoint);
Result result = new Result(discoveredRemoteConnectionEndpoints, null);
DiscoveredWebsocketEndpoints discoveredWebsocketEndpoints = mock(DiscoveredWebsocketEndpoints.class);
when(discoveredWebsocketEndpoints.getResult()).thenReturn(result);
ModularXmppClientToServerConnectionInternal connectionInternal = mock(ModularXmppClientToServerConnectionInternal.class);
assertNotNull(WebsocketImplProvider.getWebsocketImpl(OkHttpWebsocket.class, connectionInternal, discoveredWebsocketEndpoints));
}
}

View File

@ -0,0 +1,45 @@
/**
*
* Copyright 2020 Aditya Borikar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.websocket.rce;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.net.URISyntaxException;
import org.jivesoftware.smack.datatypes.UInt16;
import org.junit.jupiter.api.Test;
public class WebsocketRemoteConnectionEndpointTest {
@Test
public void endpointTest() throws URISyntaxException {
String endpointString = "ws://fooDomain.org:7070/ws/";
WebsocketRemoteConnectionEndpoint endpoint = new WebsocketRemoteConnectionEndpoint(endpointString);
assertEquals("fooDomain.org", endpoint.getHost());
assertEquals(UInt16.from(7070), endpoint.getPort());
assertEquals(endpointString, endpoint.getWebsocketEndpoint().toString());
}
@Test
public void faultyEndpointTest() {
String faultyProtocolString = "wst://fooDomain.org:7070/ws/";
assertThrows(IllegalArgumentException.class, () -> {
new WebsocketRemoteConnectionEndpoint(faultyProtocolString);
});
}
}

View File

@ -0,0 +1 @@
mock-maker-inline