mirror of
https://github.com/vanitasvitae/Smack.git
synced 2025-09-09 09:09:38 +02:00
Introduce Smack's Modular Connection Architecture
This is a complete redesign of what was previously XmppNioTcpConnection. The new architecture allows to extend an XMPP client to server (c2s) connection with new transport bindings and other extensions.
This commit is contained in:
parent
cec312fe64
commit
cc636fff21
142 changed files with 6819 additions and 4068 deletions
|
@ -1,21 +0,0 @@
|
|||
.PHONY := all clean
|
||||
|
||||
GRADLE_QUITE_ARGS := --quiet --console plain
|
||||
|
||||
XMPP_NIO_TCP_CONNECTION_STATE_GRAPH_PNG := src/javadoc/org/jivesoftware/smack/tcp/doc-files/XmppNioTcpConnectionStateGraph.png
|
||||
XMPP_NIO_TCP_CONNECTION_STATE_GRAPH_DOT := $(XMPP_NIO_TCP_CONNECTION_STATE_GRAPH_PNG:.png=.dot)
|
||||
|
||||
GENERATED_FILES := $(XMPP_NIO_TCP_CONNECTION_STATE_GRAPH_PNG) $(XMPP_NIO_TCP_CONNECTION_STATE_GRAPH_DOT)
|
||||
|
||||
all: $(XMPP_NIO_TCP_CONNECTION_STATE_GRAPH_PNG)
|
||||
|
||||
clean:
|
||||
rm -f $(GENERATED_FILES)
|
||||
|
||||
%.png: %.dot
|
||||
dot -Tpng -o $@ $^
|
||||
|
||||
$(XMPP_NIO_TCP_CONNECTION_STATE_GRAPH_DOT): src/main/java/org/jivesoftware/smack/tcp/XmppNioTcpConnection.java ../smack-core/src/main/java/org/jivesoftware/smack/fsm/AbstractXmppStateMachineConnection.java
|
||||
# TODO: This also creates the dot file even if the command
|
||||
# fails. It would be better if this was not the case.
|
||||
gradle $(GRADLE_QUITE_ARGS) :smack-repl:printXmppNioTcpConnectionStateGraph > $@
|
|
@ -1,2 +0,0 @@
|
|||
/XmppNioTcpConnectionStateGraph.png
|
||||
/XmppNioTcpConnectionStateGraph.dot
|
|
@ -0,0 +1,141 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2019-2020 Florian Schmaus
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jivesoftware.smack.sm;
|
||||
|
||||
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.AuthenticatedAndResourceBoundStateDescriptor;
|
||||
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.AuthenticatedButUnboundStateDescriptor;
|
||||
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.ResourceBindingStateDescriptor;
|
||||
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionModule;
|
||||
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
|
||||
import org.jivesoftware.smack.c2s.internal.WalkStateGraphContext;
|
||||
import org.jivesoftware.smack.compression.CompressionModule.CompressionStateDescriptor;
|
||||
import org.jivesoftware.smack.fsm.State;
|
||||
import org.jivesoftware.smack.fsm.StateDescriptor;
|
||||
import org.jivesoftware.smack.fsm.StateTransitionResult;
|
||||
|
||||
public class StreamManagementModule extends ModularXmppClientToServerConnectionModule<StreamManagementModuleDescriptor> {
|
||||
|
||||
protected StreamManagementModule(StreamManagementModuleDescriptor moduleDescriptor,
|
||||
ModularXmppClientToServerConnectionInternal connectionInternal) {
|
||||
super(moduleDescriptor, connectionInternal);
|
||||
}
|
||||
|
||||
private boolean useSm = true;
|
||||
|
||||
private boolean useSmResumption = true;
|
||||
|
||||
public static final class EnableStreamManagementStateDescriptor extends StateDescriptor {
|
||||
|
||||
private EnableStreamManagementStateDescriptor() {
|
||||
super(StreamManagementModule.EnableStreamManagementState.class, 198, StateDescriptor.Property.notImplemented);
|
||||
|
||||
addPredeccessor(ResourceBindingStateDescriptor.class);
|
||||
addSuccessor(AuthenticatedAndResourceBoundStateDescriptor.class);
|
||||
declarePrecedenceOver(AuthenticatedAndResourceBoundStateDescriptor.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected StreamManagementModule.EnableStreamManagementState constructState(ModularXmppClientToServerConnectionInternal connectionInternal) {
|
||||
// This is the trick: the module is constructed prior the states, so we get the actual state out of the module by fetching the module from the connection.
|
||||
StreamManagementModule smModule = connectionInternal.connection.getConnectionModuleFor(StreamManagementModuleDescriptor.class);
|
||||
return smModule.constructEnableStreamMangementState(this, connectionInternal);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private EnableStreamManagementState constructEnableStreamMangementState(
|
||||
EnableStreamManagementStateDescriptor enableStreamManagementStateDescriptor,
|
||||
ModularXmppClientToServerConnectionInternal connectionInternal) {
|
||||
return new EnableStreamManagementState(enableStreamManagementStateDescriptor, connectionInternal);
|
||||
}
|
||||
|
||||
private final class EnableStreamManagementState extends State {
|
||||
private EnableStreamManagementState(EnableStreamManagementStateDescriptor stateDescriptor,
|
||||
ModularXmppClientToServerConnectionInternal connectionInternal) {
|
||||
super(stateDescriptor, connectionInternal);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StateTransitionResult.TransitionImpossible isTransitionToPossible(WalkStateGraphContext walkStateGraphContext) {
|
||||
if (!useSm) {
|
||||
return new StateTransitionResult.TransitionImpossibleReason("Stream management not enabled");
|
||||
}
|
||||
|
||||
return new StateTransitionResult.TransitionImpossibleBecauseNotImplemented(stateDescriptor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) {
|
||||
throw new IllegalStateException("SM not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
public static final class ResumeStreamStateDescriptor extends StateDescriptor {
|
||||
private ResumeStreamStateDescriptor() {
|
||||
super(StreamManagementModule.ResumeStreamState.class, 198, StateDescriptor.Property.notImplemented);
|
||||
|
||||
addPredeccessor(AuthenticatedButUnboundStateDescriptor.class);
|
||||
addSuccessor(AuthenticatedAndResourceBoundStateDescriptor.class);
|
||||
declarePrecedenceOver(ResourceBindingStateDescriptor.class);
|
||||
declareInferiortyTo(CompressionStateDescriptor.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected StreamManagementModule.ResumeStreamState constructState(ModularXmppClientToServerConnectionInternal connectionInternal) {
|
||||
StreamManagementModule smModule = connectionInternal.connection.getConnectionModuleFor(StreamManagementModuleDescriptor.class);
|
||||
return smModule.constructResumeStreamState(this, connectionInternal);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private ResumeStreamState constructResumeStreamState(
|
||||
ResumeStreamStateDescriptor resumeStreamStateDescriptor,
|
||||
ModularXmppClientToServerConnectionInternal connectionInternal) {
|
||||
return new ResumeStreamState(resumeStreamStateDescriptor, connectionInternal);
|
||||
}
|
||||
|
||||
private final class ResumeStreamState extends State {
|
||||
private ResumeStreamState(ResumeStreamStateDescriptor stateDescriptor,
|
||||
ModularXmppClientToServerConnectionInternal connectionInternal) {
|
||||
super(stateDescriptor, connectionInternal);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public StateTransitionResult.TransitionImpossible isTransitionToPossible(WalkStateGraphContext walkStateGraphContext) {
|
||||
if (!useSmResumption) {
|
||||
return new StateTransitionResult.TransitionImpossibleReason("Stream resumption not enabled");
|
||||
}
|
||||
|
||||
return new StateTransitionResult.TransitionImpossibleBecauseNotImplemented(stateDescriptor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) {
|
||||
throw new IllegalStateException("Stream resumption not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
public void setStreamManagementEnabled(boolean useSm) {
|
||||
this.useSm = useSm;
|
||||
}
|
||||
|
||||
public void setStreamResumptionEnabled(boolean useSmResumption) {
|
||||
this.useSmResumption = useSmResumption;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2019-2020 Florian Schmaus
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jivesoftware.smack.sm;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
|
||||
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionModuleDescriptor;
|
||||
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
|
||||
import org.jivesoftware.smack.fsm.StateDescriptor;
|
||||
import org.jivesoftware.smack.sm.StreamManagementModule.EnableStreamManagementStateDescriptor;
|
||||
import org.jivesoftware.smack.sm.StreamManagementModule.ResumeStreamStateDescriptor;
|
||||
|
||||
public class StreamManagementModuleDescriptor extends ModularXmppClientToServerConnectionModuleDescriptor {
|
||||
|
||||
private static final StreamManagementModuleDescriptor INSTANCE = new StreamManagementModuleDescriptor();
|
||||
|
||||
@Override
|
||||
protected Set<Class<? extends StateDescriptor>> getStateDescriptors() {
|
||||
Set<Class<? extends StateDescriptor>> res = new HashSet<>();
|
||||
res.add(EnableStreamManagementStateDescriptor.class);
|
||||
res.add(ResumeStreamStateDescriptor.class);
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected StreamManagementModule constructXmppConnectionModule(
|
||||
ModularXmppClientToServerConnectionInternal connectionInternal) {
|
||||
return new StreamManagementModule(this, connectionInternal);
|
||||
}
|
||||
|
||||
public static class Builder extends ModularXmppClientToServerConnectionModuleDescriptor.Builder {
|
||||
|
||||
protected Builder(ModularXmppClientToServerConnectionConfiguration.Builder connectionConfigurationBuilder) {
|
||||
super(connectionConfigurationBuilder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected StreamManagementModuleDescriptor build() {
|
||||
return INSTANCE;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,175 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2019-2020 Florian Schmaus
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jivesoftware.smack.tcp;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.jivesoftware.smack.SmackException.ConnectionException;
|
||||
import org.jivesoftware.smack.SmackException.EndpointConnectionException;
|
||||
import org.jivesoftware.smack.SynchronizationPoint;
|
||||
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
|
||||
import org.jivesoftware.smack.tcp.XmppTcpTransportModule.EstablishingTcpConnectionState;
|
||||
import org.jivesoftware.smack.tcp.rce.Rfc6120TcpRemoteConnectionEndpoint;
|
||||
import org.jivesoftware.smack.util.Async;
|
||||
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpoint;
|
||||
import org.jivesoftware.smack.util.rce.RemoteConnectionException;
|
||||
|
||||
public final class ConnectionAttemptState {
|
||||
|
||||
private final ModularXmppClientToServerConnectionInternal connectionInternal;
|
||||
|
||||
private final XmppTcpTransportModule.XmppTcpNioTransport.DiscoveredTcpEndpoints discoveredEndpoints;
|
||||
|
||||
private final EstablishingTcpConnectionState establishingTcpConnectionState;
|
||||
|
||||
// TODO: Check if we can re-use the socket channel in case some InetSocketAddress fail to connect to.
|
||||
final SocketChannel socketChannel;
|
||||
|
||||
final List<RemoteConnectionException<?>> connectionExceptions;
|
||||
final SynchronizationPoint<ConnectionException> tcpConnectionEstablishedSyncPoint;
|
||||
|
||||
final Iterator<Rfc6120TcpRemoteConnectionEndpoint> connectionEndpointIterator;
|
||||
/** The current connection endpoint we are trying */
|
||||
Rfc6120TcpRemoteConnectionEndpoint connectionEndpoint;
|
||||
Iterator<? extends InetAddress> inetAddressIterator;
|
||||
|
||||
ConnectionAttemptState(ModularXmppClientToServerConnectionInternal connectionInternal,
|
||||
XmppTcpTransportModule.XmppTcpNioTransport.DiscoveredTcpEndpoints discoveredEndpoints,
|
||||
EstablishingTcpConnectionState establishingTcpConnectionState) throws IOException {
|
||||
this.connectionInternal = connectionInternal;
|
||||
this.discoveredEndpoints = discoveredEndpoints;
|
||||
this.establishingTcpConnectionState = establishingTcpConnectionState;
|
||||
|
||||
socketChannel = SocketChannel.open();
|
||||
socketChannel.configureBlocking(false);
|
||||
|
||||
connectionEndpointIterator = discoveredEndpoints.result.discoveredRemoteConnectionEndpoints.iterator();
|
||||
connectionEndpoint = connectionEndpointIterator.next();
|
||||
connectionExceptions = new ArrayList<>(discoveredEndpoints.result.discoveredRemoteConnectionEndpoints.size());
|
||||
|
||||
tcpConnectionEstablishedSyncPoint = new SynchronizationPoint<>(connectionInternal.connection,
|
||||
"TCP connection establishment");
|
||||
}
|
||||
|
||||
void establishTcpConnection() {
|
||||
RemoteConnectionEndpoint.InetSocketAddressCoupling<Rfc6120TcpRemoteConnectionEndpoint> address = nextAddress();
|
||||
establishTcpConnection(address);
|
||||
}
|
||||
|
||||
private void establishTcpConnection(
|
||||
RemoteConnectionEndpoint.InetSocketAddressCoupling<Rfc6120TcpRemoteConnectionEndpoint> address) {
|
||||
TcpHostEvent.ConnectingToHostEvent connectingToHostEvent = new TcpHostEvent.ConnectingToHostEvent(
|
||||
establishingTcpConnectionState, address);
|
||||
connectionInternal.invokeConnectionStateMachineListener(connectingToHostEvent);
|
||||
|
||||
final boolean connected;
|
||||
final InetSocketAddress inetSocketAddress = address.getInetSocketAddress();
|
||||
try {
|
||||
connected = socketChannel.connect(inetSocketAddress);
|
||||
} catch (IOException e) {
|
||||
onIOExceptionWhenEstablishingTcpConnection(e, address);
|
||||
return;
|
||||
}
|
||||
|
||||
if (connected) {
|
||||
TcpHostEvent.ConnectedToHostEvent connectedToHostEvent = new TcpHostEvent.ConnectedToHostEvent(
|
||||
establishingTcpConnectionState, address, true);
|
||||
connectionInternal.invokeConnectionStateMachineListener(connectedToHostEvent);
|
||||
|
||||
tcpConnectionEstablishedSyncPoint.reportSuccess();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
connectionInternal.registerWithSelector(socketChannel, SelectionKey.OP_CONNECT,
|
||||
(selectedChannel, selectedSelectionKey) -> {
|
||||
SocketChannel selectedSocketChannel = (SocketChannel) selectedChannel;
|
||||
|
||||
boolean finishConnected;
|
||||
try {
|
||||
finishConnected = selectedSocketChannel.finishConnect();
|
||||
} catch (IOException e) {
|
||||
Async.go(() -> onIOExceptionWhenEstablishingTcpConnection(e, address));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!finishConnected) {
|
||||
Async.go(() -> onIOExceptionWhenEstablishingTcpConnection(new IOException("finishConnect() failed"), address));
|
||||
return;
|
||||
}
|
||||
|
||||
TcpHostEvent.ConnectedToHostEvent connectedToHostEvent = new TcpHostEvent.ConnectedToHostEvent(
|
||||
establishingTcpConnectionState, address, false);
|
||||
connectionInternal.invokeConnectionStateMachineListener(connectedToHostEvent);
|
||||
|
||||
// Do not set 'state' here, since this is processed by a reactor thread, which doesn't hold
|
||||
// the objects lock.
|
||||
tcpConnectionEstablishedSyncPoint.reportSuccess();
|
||||
});
|
||||
} catch (ClosedChannelException e) {
|
||||
onIOExceptionWhenEstablishingTcpConnection(e, address);
|
||||
}
|
||||
}
|
||||
|
||||
private void onIOExceptionWhenEstablishingTcpConnection(IOException exception,
|
||||
RemoteConnectionEndpoint.InetSocketAddressCoupling<Rfc6120TcpRemoteConnectionEndpoint> failedAddress) {
|
||||
RemoteConnectionEndpoint.InetSocketAddressCoupling<Rfc6120TcpRemoteConnectionEndpoint> nextInetSocketAddress = nextAddress();
|
||||
if (nextInetSocketAddress == null) {
|
||||
EndpointConnectionException connectionException = EndpointConnectionException.from(
|
||||
discoveredEndpoints.result.lookupFailures, connectionExceptions);
|
||||
tcpConnectionEstablishedSyncPoint.reportFailure(connectionException);
|
||||
return;
|
||||
}
|
||||
|
||||
tcpConnectionEstablishedSyncPoint.resetTimeout();
|
||||
|
||||
RemoteConnectionException<Rfc6120TcpRemoteConnectionEndpoint> rce = new RemoteConnectionException<>(
|
||||
failedAddress, exception);
|
||||
connectionExceptions.add(rce);
|
||||
|
||||
TcpHostEvent.ConnectionToHostFailedEvent connectionToHostFailedEvent = new TcpHostEvent.ConnectionToHostFailedEvent(
|
||||
establishingTcpConnectionState, nextInetSocketAddress, exception);
|
||||
connectionInternal.invokeConnectionStateMachineListener(connectionToHostFailedEvent);
|
||||
|
||||
establishTcpConnection(nextInetSocketAddress);
|
||||
}
|
||||
|
||||
private RemoteConnectionEndpoint.InetSocketAddressCoupling<Rfc6120TcpRemoteConnectionEndpoint> nextAddress() {
|
||||
if (inetAddressIterator == null || !inetAddressIterator.hasNext()) {
|
||||
if (!connectionEndpointIterator.hasNext()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
connectionEndpoint = connectionEndpointIterator.next();
|
||||
inetAddressIterator = connectionEndpoint.getInetAddresses().iterator();
|
||||
// Every valid connection addresspoint must have a non-empty collection of inet addresses.
|
||||
assert inetAddressIterator.hasNext();
|
||||
}
|
||||
|
||||
InetAddress inetAddress = inetAddressIterator.next();
|
||||
|
||||
return new RemoteConnectionEndpoint.InetSocketAddressCoupling<>(connectionEndpoint, inetAddress);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright © 2014 Florian Schmaus
|
||||
* Copyright © 2014-2020 Florian Schmaus
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -16,10 +16,17 @@
|
|||
*/
|
||||
package org.jivesoftware.smack.tcp;
|
||||
|
||||
import org.jivesoftware.smack.SmackConfiguration;
|
||||
import org.jivesoftware.smack.initializer.UrlInitializer;
|
||||
import org.jivesoftware.smack.sm.StreamManagementModuleDescriptor;
|
||||
|
||||
public class TCPInitializer extends UrlInitializer {
|
||||
|
||||
static {
|
||||
SmackConfiguration.addModule(StreamManagementModuleDescriptor.class);
|
||||
SmackConfiguration.addModule(XmppTcpTransportModuleDescriptor.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getProvidersUri() {
|
||||
return "classpath:org.jivesoftware.smack.tcp/smacktcp.providers";
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2019-2020 Florian Schmaus
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jivesoftware.smack.tcp;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.jivesoftware.smack.fsm.ConnectionStateEvent.DetailedTransitionIntoInformation;
|
||||
import org.jivesoftware.smack.fsm.State;
|
||||
import org.jivesoftware.smack.tcp.rce.Rfc6120TcpRemoteConnectionEndpoint;
|
||||
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpoint;
|
||||
|
||||
public abstract class TcpHostEvent extends DetailedTransitionIntoInformation {
|
||||
protected final RemoteConnectionEndpoint.InetSocketAddressCoupling<Rfc6120TcpRemoteConnectionEndpoint> address;
|
||||
|
||||
protected TcpHostEvent(State state, RemoteConnectionEndpoint.InetSocketAddressCoupling<Rfc6120TcpRemoteConnectionEndpoint> address) {
|
||||
super(state);
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public RemoteConnectionEndpoint.InetSocketAddressCoupling<Rfc6120TcpRemoteConnectionEndpoint> getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + ": " + address;
|
||||
}
|
||||
|
||||
public static final class ConnectingToHostEvent extends TcpHostEvent {
|
||||
ConnectingToHostEvent(State state,
|
||||
RemoteConnectionEndpoint.InetSocketAddressCoupling<Rfc6120TcpRemoteConnectionEndpoint> address) {
|
||||
super(state, address);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class ConnectedToHostEvent extends TcpHostEvent {
|
||||
private final boolean connectionEstablishedImmediately;
|
||||
|
||||
ConnectedToHostEvent(State state, RemoteConnectionEndpoint.InetSocketAddressCoupling<Rfc6120TcpRemoteConnectionEndpoint> address, boolean immediately) {
|
||||
super(state, address);
|
||||
this.connectionEstablishedImmediately = immediately;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + (connectionEstablishedImmediately ? "" : " not") + " connected immediately";
|
||||
}
|
||||
}
|
||||
|
||||
public static final class ConnectionToHostFailedEvent extends TcpHostEvent {
|
||||
private final IOException ioException;
|
||||
|
||||
ConnectionToHostFailedEvent(State state,
|
||||
RemoteConnectionEndpoint.InetSocketAddressCoupling<Rfc6120TcpRemoteConnectionEndpoint> address,
|
||||
IOException ioException) {
|
||||
super(state, address);
|
||||
this.ioException = ioException;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + ioException;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -64,6 +64,7 @@ import org.jivesoftware.smack.SmackException;
|
|||
import org.jivesoftware.smack.SmackException.AlreadyConnectedException;
|
||||
import org.jivesoftware.smack.SmackException.AlreadyLoggedInException;
|
||||
import org.jivesoftware.smack.SmackException.ConnectionException;
|
||||
import org.jivesoftware.smack.SmackException.EndpointConnectionException;
|
||||
import org.jivesoftware.smack.SmackException.NoResponseException;
|
||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||
import org.jivesoftware.smack.SmackException.NotLoggedInException;
|
||||
|
@ -78,6 +79,7 @@ import org.jivesoftware.smack.XMPPException.StreamErrorException;
|
|||
import org.jivesoftware.smack.compress.packet.Compress;
|
||||
import org.jivesoftware.smack.compress.packet.Compressed;
|
||||
import org.jivesoftware.smack.compression.XMPPInputOutputStream;
|
||||
import org.jivesoftware.smack.datatypes.UInt16;
|
||||
import org.jivesoftware.smack.filter.StanzaFilter;
|
||||
import org.jivesoftware.smack.packet.Element;
|
||||
import org.jivesoftware.smack.packet.IQ;
|
||||
|
@ -105,6 +107,8 @@ import org.jivesoftware.smack.sm.packet.StreamManagement.Resumed;
|
|||
import org.jivesoftware.smack.sm.packet.StreamManagement.StreamManagementFeature;
|
||||
import org.jivesoftware.smack.sm.predicates.Predicate;
|
||||
import org.jivesoftware.smack.sm.provider.ParseStreamManagement;
|
||||
import org.jivesoftware.smack.tcp.rce.RemoteXmppTcpConnectionEndpoints;
|
||||
import org.jivesoftware.smack.tcp.rce.Rfc6120TcpRemoteConnectionEndpoint;
|
||||
import org.jivesoftware.smack.util.ArrayBlockingQueueWithShutdown;
|
||||
import org.jivesoftware.smack.util.Async;
|
||||
import org.jivesoftware.smack.util.CloseableUtil;
|
||||
|
@ -112,7 +116,7 @@ import org.jivesoftware.smack.util.PacketParserUtils;
|
|||
import org.jivesoftware.smack.util.StringUtils;
|
||||
import org.jivesoftware.smack.util.TLSUtils;
|
||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||
import org.jivesoftware.smack.util.dns.HostAddress;
|
||||
import org.jivesoftware.smack.util.rce.RemoteConnectionException;
|
||||
import org.jivesoftware.smack.xml.SmackXmlParser;
|
||||
import org.jivesoftware.smack.xml.XmlPullParser;
|
||||
import org.jivesoftware.smack.xml.XmlPullParserException;
|
||||
|
@ -556,19 +560,23 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
|||
}
|
||||
|
||||
private void connectUsingConfiguration() throws ConnectionException, IOException, InterruptedException {
|
||||
List<HostAddress> failedAddresses = populateHostAddresses();
|
||||
RemoteXmppTcpConnectionEndpoints.Result<Rfc6120TcpRemoteConnectionEndpoint> result = RemoteXmppTcpConnectionEndpoints.lookup(config);
|
||||
|
||||
List<RemoteConnectionException<Rfc6120TcpRemoteConnectionEndpoint>> connectionExceptions = new ArrayList<>();
|
||||
|
||||
SocketFactory socketFactory = config.getSocketFactory();
|
||||
ProxyInfo proxyInfo = config.getProxyInfo();
|
||||
int timeout = config.getConnectTimeout();
|
||||
if (socketFactory == null) {
|
||||
socketFactory = SocketFactory.getDefault();
|
||||
}
|
||||
for (HostAddress hostAddress : hostAddresses) {
|
||||
Iterator<InetAddress> inetAddresses;
|
||||
String host = hostAddress.getHost();
|
||||
int port = hostAddress.getPort();
|
||||
for (Rfc6120TcpRemoteConnectionEndpoint endpoint : result.discoveredRemoteConnectionEndpoints) {
|
||||
Iterator<? extends InetAddress> inetAddresses;
|
||||
String host = endpoint.getHost().toString();
|
||||
UInt16 portUint16 = endpoint.getPort();
|
||||
int port = portUint16.intValue();
|
||||
if (proxyInfo == null) {
|
||||
inetAddresses = hostAddress.getInetAddresses().iterator();
|
||||
inetAddresses = endpoint.getInetAddresses().iterator();
|
||||
assert inetAddresses.hasNext();
|
||||
|
||||
innerloop: while (inetAddresses.hasNext()) {
|
||||
|
@ -584,7 +592,9 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
|||
try {
|
||||
socket = socketFuture.getOrThrow();
|
||||
} catch (IOException e) {
|
||||
hostAddress.setException(inetAddress, e);
|
||||
RemoteConnectionException<Rfc6120TcpRemoteConnectionEndpoint> rce = new RemoteConnectionException<>(
|
||||
endpoint, inetAddress, e);
|
||||
connectionExceptions.add(rce);
|
||||
if (inetAddresses.hasNext()) {
|
||||
continue innerloop;
|
||||
} else {
|
||||
|
@ -594,34 +604,36 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
|||
LOGGER.finer("Established TCP connection to " + inetSocketAddress);
|
||||
// We found a host to connect to, return here
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.port = portUint16;
|
||||
return;
|
||||
}
|
||||
failedAddresses.add(hostAddress);
|
||||
} else {
|
||||
// TODO: Move this into the inner-loop above. There appears no reason why we should not try a proxy
|
||||
// connection to every inet address of each connection endpoint.
|
||||
socket = socketFactory.createSocket();
|
||||
StringUtils.requireNotNullNorEmpty(host, "Host of HostAddress " + hostAddress + " must not be null when using a Proxy");
|
||||
StringUtils.requireNotNullNorEmpty(host, "Host of endpoint " + endpoint + " must not be null when using a Proxy");
|
||||
final String hostAndPort = host + " at port " + port;
|
||||
LOGGER.finer("Trying to establish TCP connection via Proxy to " + hostAndPort);
|
||||
try {
|
||||
proxyInfo.getProxySocketConnection().connect(socket, host, port, timeout);
|
||||
} catch (IOException e) {
|
||||
CloseableUtil.maybeClose(socket, LOGGER);
|
||||
hostAddress.setException(e);
|
||||
failedAddresses.add(hostAddress);
|
||||
RemoteConnectionException<Rfc6120TcpRemoteConnectionEndpoint> rce = new RemoteConnectionException<>(endpoint, null, e);
|
||||
connectionExceptions.add(rce);
|
||||
continue;
|
||||
}
|
||||
LOGGER.finer("Established TCP connection to " + hostAndPort);
|
||||
// We found a host to connect to, return here
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.port = portUint16;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// There are no more host addresses to try
|
||||
// throw an exception and report all tried
|
||||
// HostAddresses in the exception
|
||||
throw ConnectionException.from(failedAddresses);
|
||||
throw EndpointConnectionException.from(result.lookupFailures, connectionExceptions);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -815,7 +827,6 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
|||
*/
|
||||
@Override
|
||||
protected void connectInternal() throws SmackException, IOException, XMPPException, InterruptedException {
|
||||
closingStreamReceived.init();
|
||||
// Establishes the TCP connection to the server and does setup the reader and writer. Throws an exception if
|
||||
// there is an error establishing the connection
|
||||
connectUsingConfiguration();
|
||||
|
@ -1125,6 +1136,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
|||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
// TODO: Move the call closingStreamReceived.reportFailure(e) into notifyConnectionError?
|
||||
closingStreamReceived.reportFailure(e);
|
||||
// The exception can be ignored if the the connection is 'done'
|
||||
// or if the it was caused because the socket got closed. It can not be ignored if it
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,91 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2019-2020 Florian Schmaus
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jivesoftware.smack.tcp;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
|
||||
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionModuleDescriptor;
|
||||
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
|
||||
import org.jivesoftware.smack.fsm.StateDescriptor;
|
||||
import org.jivesoftware.smack.tcp.XmppTcpTransportModule.EstablishTlsStateDescriptor;
|
||||
import org.jivesoftware.smack.tcp.XmppTcpTransportModule.EstablishingTcpConnectionStateDescriptor;
|
||||
|
||||
public class XmppTcpTransportModuleDescriptor extends ModularXmppClientToServerConnectionModuleDescriptor {
|
||||
|
||||
private final boolean startTls;
|
||||
private final boolean directTls;
|
||||
|
||||
public XmppTcpTransportModuleDescriptor(Builder builder) {
|
||||
startTls = builder.startTls;
|
||||
directTls = builder.directTls;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<Class<? extends StateDescriptor>> getStateDescriptors() {
|
||||
Set<Class<? extends StateDescriptor>> res = new HashSet<>();
|
||||
res.add(EstablishingTcpConnectionStateDescriptor.class);
|
||||
if (startTls) {
|
||||
res.add(EstablishTlsStateDescriptor.class);
|
||||
}
|
||||
if (directTls) {
|
||||
// TODO: Add direct TLS.
|
||||
throw new IllegalArgumentException("DirectTLS is not implemented yet");
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected XmppTcpTransportModule constructXmppConnectionModule(ModularXmppClientToServerConnectionInternal connectionInternal) {
|
||||
return new XmppTcpTransportModule(this, connectionInternal);
|
||||
}
|
||||
|
||||
public boolean isStartTlsEnabled() {
|
||||
return startTls;
|
||||
}
|
||||
|
||||
public boolean isDirectTlsEnabled() {
|
||||
return directTls;
|
||||
}
|
||||
|
||||
public static final class Builder extends ModularXmppClientToServerConnectionModuleDescriptor.Builder {
|
||||
|
||||
private Builder(ModularXmppClientToServerConnectionConfiguration.Builder connectionConfigurationBuilder) {
|
||||
super(connectionConfigurationBuilder);
|
||||
}
|
||||
|
||||
private boolean startTls = true;
|
||||
|
||||
private boolean directTls = false;
|
||||
|
||||
public Builder disableDirectTls() {
|
||||
directTls = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder disableStartTls() {
|
||||
startTls = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected XmppTcpTransportModuleDescriptor build() {
|
||||
return new XmppTcpTransportModuleDescriptor(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2020 Florian Schmaus.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jivesoftware.smack.tcp.rce;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
|
||||
import org.jivesoftware.smack.datatypes.UInt16;
|
||||
import org.jivesoftware.smack.util.rce.SingleAddressRemoteConnectionEndpoint;
|
||||
|
||||
import org.minidns.record.A;
|
||||
import org.minidns.record.AAAA;
|
||||
import org.minidns.record.InternetAddressRR;
|
||||
|
||||
public final class IpTcpRemoteConnectionEndpoint<IARR extends InternetAddressRR>
|
||||
implements Rfc6120TcpRemoteConnectionEndpoint, SingleAddressRemoteConnectionEndpoint {
|
||||
|
||||
private final CharSequence host;
|
||||
|
||||
private final UInt16 port;
|
||||
|
||||
private final IARR internetAddressResourceRecord;
|
||||
|
||||
public IpTcpRemoteConnectionEndpoint(CharSequence host, UInt16 port, IARR internetAddressResourceRecord) {
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.internetAddressResourceRecord = internetAddressResourceRecord;
|
||||
}
|
||||
|
||||
public static IpTcpRemoteConnectionEndpoint<InternetAddressRR> from(CharSequence host, int port,
|
||||
InetAddress inetAddress) {
|
||||
InternetAddressRR internetAddressResourceRecord;
|
||||
// TODO: Use InternetAddressRR.from(InetAddress) once MiniDNS is updated.
|
||||
if (inetAddress instanceof Inet4Address) {
|
||||
internetAddressResourceRecord = new A((Inet4Address) inetAddress);
|
||||
} else {
|
||||
internetAddressResourceRecord = new AAAA((Inet6Address) inetAddress);
|
||||
}
|
||||
|
||||
return new IpTcpRemoteConnectionEndpoint<InternetAddressRR>(host, UInt16.from(port),
|
||||
internetAddressResourceRecord);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UInt16 getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetAddress getInetAddress() {
|
||||
return internetAddressResourceRecord.getInetAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "RFC 6120 A/AAAA Endpoint + [" + host + ":" + port + "]";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,232 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2015-2020 Florian Schmaus.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jivesoftware.smack.tcp.rce;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.jivesoftware.smack.ConnectionConfiguration;
|
||||
import org.jivesoftware.smack.ConnectionConfiguration.DnssecMode;
|
||||
import org.jivesoftware.smack.util.DNSUtil;
|
||||
import org.jivesoftware.smack.util.dns.DNSResolver;
|
||||
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpoint;
|
||||
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpointLookupFailure;
|
||||
|
||||
import org.minidns.dnsname.DnsName;
|
||||
import org.minidns.record.InternetAddressRR;
|
||||
import org.minidns.record.SRV;
|
||||
import org.minidns.util.SrvUtil;
|
||||
|
||||
public class RemoteXmppTcpConnectionEndpoints {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(RemoteXmppTcpConnectionEndpoints.class.getName());
|
||||
|
||||
public static final String XMPP_CLIENT_DNS_SRV_PREFIX = "_xmpp-client._tcp";
|
||||
public static final String XMPP_SERVER_DNS_SRV_PREFIX = "_xmpp-server._tcp";
|
||||
|
||||
/**
|
||||
* Lookups remote connection endpoints on the server for XMPP connections over TCP taking A, AAAA and SRV resource
|
||||
* records into account. If no host address was configured and all lookups failed, for example with NX_DOMAIN, then
|
||||
* result will be populated with the empty list.
|
||||
*
|
||||
* @param config the connection configuration to lookup the endpoints for.
|
||||
* @return a lookup result.
|
||||
*/
|
||||
public static Result<Rfc6120TcpRemoteConnectionEndpoint> lookup(ConnectionConfiguration config) {
|
||||
List<Rfc6120TcpRemoteConnectionEndpoint> discoveredRemoteConnectionEndpoints;
|
||||
List<RemoteConnectionEndpointLookupFailure> lookupFailures;
|
||||
|
||||
final InetAddress hostAddress = config.getHostAddress();
|
||||
final DnsName host = config.getHost();
|
||||
|
||||
if (hostAddress != null) {
|
||||
lookupFailures = Collections.emptyList();
|
||||
|
||||
IpTcpRemoteConnectionEndpoint<InternetAddressRR> connectionEndpoint = IpTcpRemoteConnectionEndpoint.from(
|
||||
hostAddress.toString(), config.getPort(), hostAddress);
|
||||
discoveredRemoteConnectionEndpoints = Collections.singletonList(connectionEndpoint);
|
||||
} else if (host != null) {
|
||||
lookupFailures = new ArrayList<>(1);
|
||||
|
||||
List<InetAddress> hostAddresses = DNSUtil.getDNSResolver().lookupHostAddress(host,
|
||||
lookupFailures, config.getDnssecMode());
|
||||
|
||||
if (hostAddresses != null) {
|
||||
discoveredRemoteConnectionEndpoints = new ArrayList<>(hostAddresses.size());
|
||||
int port = config.getPort();
|
||||
for (InetAddress inetAddress : hostAddresses) {
|
||||
IpTcpRemoteConnectionEndpoint<InternetAddressRR> connectionEndpoint = IpTcpRemoteConnectionEndpoint.from(
|
||||
host, port, inetAddress);
|
||||
discoveredRemoteConnectionEndpoints.add(connectionEndpoint);
|
||||
}
|
||||
} else {
|
||||
discoveredRemoteConnectionEndpoints = Collections.emptyList();
|
||||
}
|
||||
} else {
|
||||
lookupFailures = new ArrayList<>();
|
||||
|
||||
// N.B.: Important to use config.serviceName and not AbstractXMPPConnection.serviceName
|
||||
DnsName dnsName = config.getXmppServiceDomainAsDnsNameIfPossible();
|
||||
if (dnsName == null) {
|
||||
// TODO: ConnectionConfiguration should check on construction time that either the given XMPP service
|
||||
// name is also a valid DNS name, or that a host is explicitly configured.
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
discoveredRemoteConnectionEndpoints = resolveXmppServiceDomain(dnsName, lookupFailures, config.getDnssecMode());
|
||||
}
|
||||
|
||||
// Either the populated host addresses are not empty *or* there must be at least one failed address.
|
||||
assert !discoveredRemoteConnectionEndpoints.isEmpty() || !lookupFailures.isEmpty();
|
||||
|
||||
return new Result<>(discoveredRemoteConnectionEndpoints, lookupFailures);
|
||||
}
|
||||
|
||||
public static final class Result<RCE extends RemoteConnectionEndpoint> {
|
||||
public final List<RCE> discoveredRemoteConnectionEndpoints;
|
||||
public final List<RemoteConnectionEndpointLookupFailure> lookupFailures;
|
||||
|
||||
private Result(List<RCE> discoveredRemoteConnectionEndpoints, List<RemoteConnectionEndpointLookupFailure> lookupFailures) {
|
||||
this.discoveredRemoteConnectionEndpoints = discoveredRemoteConnectionEndpoints;
|
||||
this.lookupFailures = lookupFailures;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("ImmutableEnumChecker")
|
||||
enum DomainType {
|
||||
server(XMPP_SERVER_DNS_SRV_PREFIX),
|
||||
client(XMPP_CLIENT_DNS_SRV_PREFIX),
|
||||
;
|
||||
public final DnsName srvPrefix;
|
||||
|
||||
DomainType(String srvPrefixString) {
|
||||
srvPrefix = DnsName.from(srvPrefixString);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of HostAddresses under which the specified XMPP server can be reached at for client-to-server
|
||||
* communication. A DNS lookup for a SRV record in the form "_xmpp-client._tcp.example.com" is attempted, according
|
||||
* to section 3.2.1 of RFC 6120. If that lookup fails, it's assumed that the XMPP server lives at the host resolved
|
||||
* by a DNS lookup at the specified domain on the default port of 5222.
|
||||
* <p>
|
||||
* As an example, a lookup for "example.com" may return "im.example.com:5269".
|
||||
* </p>
|
||||
*
|
||||
* @param domain the domain.
|
||||
* @param lookupFailures on optional list that will be populated with host addresses that failed to resolve.
|
||||
* @param dnssecMode DNSSec mode.
|
||||
* @return List of HostAddress, which encompasses the hostname and port that the
|
||||
* XMPP server can be reached at for the specified domain.
|
||||
*/
|
||||
public static List<Rfc6120TcpRemoteConnectionEndpoint> resolveXmppServiceDomain(DnsName domain,
|
||||
List<RemoteConnectionEndpointLookupFailure> lookupFailures, DnssecMode dnssecMode) {
|
||||
DNSResolver dnsResolver = getDnsResolverOrThrow();
|
||||
return resolveDomain(domain, DomainType.client, lookupFailures, dnssecMode, dnsResolver);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of HostAddresses under which the specified XMPP server can be reached at for server-to-server
|
||||
* communication. A DNS lookup for a SRV record in the form "_xmpp-server._tcp.example.com" is attempted, according
|
||||
* to section 3.2.1 of RFC 6120. If that lookup fails , it's assumed that the XMPP server lives at the host resolved
|
||||
* by a DNS lookup at the specified domain on the default port of 5269.
|
||||
* <p>
|
||||
* As an example, a lookup for "example.com" may return "im.example.com:5269".
|
||||
* </p>
|
||||
*
|
||||
* @param domain the domain.
|
||||
* @param lookupFailures a list that will be populated with host addresses that failed to resolve.
|
||||
* @param dnssecMode DNSSec mode.
|
||||
* @return List of HostAddress, which encompasses the hostname and port that the
|
||||
* XMPP server can be reached at for the specified domain.
|
||||
*/
|
||||
public static List<Rfc6120TcpRemoteConnectionEndpoint> resolveXmppServerDomain(DnsName domain,
|
||||
List<RemoteConnectionEndpointLookupFailure> lookupFailures, DnssecMode dnssecMode) {
|
||||
DNSResolver dnsResolver = getDnsResolverOrThrow();
|
||||
return resolveDomain(domain, DomainType.server, lookupFailures, dnssecMode, dnsResolver);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param domain the domain.
|
||||
* @param domainType the XMPP domain type, server or client.
|
||||
* @param failedAddresses a list that will be populated with host addresses that failed to resolve.
|
||||
* @return a list of resolver host addresses for this domain.
|
||||
*/
|
||||
private static List<Rfc6120TcpRemoteConnectionEndpoint> resolveDomain(DnsName domain, DomainType domainType,
|
||||
List<RemoteConnectionEndpointLookupFailure> lookupFailures, DnssecMode dnssecMode, DNSResolver dnsResolver) {
|
||||
List<Rfc6120TcpRemoteConnectionEndpoint> endpoints = new ArrayList<>();
|
||||
|
||||
// Step one: Do SRV lookups
|
||||
DnsName srvDomain = DnsName.from(domainType.srvPrefix, domain);
|
||||
|
||||
Collection<SRV> srvRecords = dnsResolver.lookupSrvRecords(srvDomain, lookupFailures, dnssecMode);
|
||||
if (srvRecords != null && !srvRecords.isEmpty()) {
|
||||
if (LOGGER.isLoggable(Level.FINE)) {
|
||||
String logMessage = "Resolved SRV RR for " + srvDomain + ":";
|
||||
for (SRV r : srvRecords)
|
||||
logMessage += " " + r;
|
||||
LOGGER.fine(logMessage);
|
||||
}
|
||||
|
||||
List<SRV> sortedSrvRecords = SrvUtil.sortSrvRecords(srvRecords);
|
||||
|
||||
for (SRV srv : sortedSrvRecords) {
|
||||
List<InetAddress> targetInetAddresses = dnsResolver.lookupHostAddress(srv.target, lookupFailures, dnssecMode);
|
||||
SrvXmppRemoteConnectionEndpoint endpoint = new SrvXmppRemoteConnectionEndpoint(srv, targetInetAddresses);
|
||||
endpoints.add(endpoint);
|
||||
}
|
||||
} else {
|
||||
LOGGER.info("Could not resolve DNS SRV resource records for " + srvDomain + ". Consider adding those.");
|
||||
}
|
||||
|
||||
int defaultPort;
|
||||
switch (domainType) {
|
||||
case client:
|
||||
defaultPort = 5222;
|
||||
break;
|
||||
case server:
|
||||
defaultPort = 5269;
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
// Step two: Add the hostname to the end of the list
|
||||
List<InetAddress> hostAddresses = dnsResolver.lookupHostAddress(domain, lookupFailures, dnssecMode);
|
||||
if (hostAddresses != null) {
|
||||
for (InetAddress inetAddress : hostAddresses) {
|
||||
IpTcpRemoteConnectionEndpoint<InternetAddressRR> endpoint = IpTcpRemoteConnectionEndpoint.from(domain, defaultPort, inetAddress);
|
||||
endpoints.add(endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
return endpoints;
|
||||
}
|
||||
|
||||
private static DNSResolver getDnsResolverOrThrow() {
|
||||
final DNSResolver dnsResolver = DNSUtil.getDNSResolver();
|
||||
if (dnsResolver == null) {
|
||||
throw new IllegalStateException("No DNS resolver configured in Smack");
|
||||
}
|
||||
return dnsResolver;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2020 Florian Schmaus.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jivesoftware.smack.tcp.rce;
|
||||
|
||||
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpoint;
|
||||
|
||||
public interface Rfc6120TcpRemoteConnectionEndpoint extends RemoteConnectionEndpoint {
|
||||
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2020 Florian Schmaus.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jivesoftware.smack.tcp.rce;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.jivesoftware.smack.datatypes.UInt16;
|
||||
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpoint;
|
||||
|
||||
import org.minidns.record.SRV;
|
||||
|
||||
public abstract class SrvRemoteConnectionEndpoint implements RemoteConnectionEndpoint {
|
||||
|
||||
protected final SRV srv;
|
||||
|
||||
protected final UInt16 port;
|
||||
|
||||
private final List<? extends InetAddress> inetAddresses;
|
||||
|
||||
protected SrvRemoteConnectionEndpoint(SRV srv, List<? extends InetAddress> inetAddresses) {
|
||||
this.srv = srv;
|
||||
this.port = UInt16.from(srv.port);
|
||||
this.inetAddresses = inetAddresses;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final CharSequence getHost() {
|
||||
return srv.target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final UInt16 getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Collection<? extends InetAddress> getInetAddresses() {
|
||||
return inetAddresses;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2020 Florian Schmaus.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jivesoftware.smack.tcp.rce;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.List;
|
||||
|
||||
import org.minidns.record.SRV;
|
||||
|
||||
public final class SrvXmppRemoteConnectionEndpoint extends SrvRemoteConnectionEndpoint
|
||||
implements Rfc6120TcpRemoteConnectionEndpoint {
|
||||
|
||||
protected SrvXmppRemoteConnectionEndpoint(SRV srv, List<? extends InetAddress> inetAddresses) {
|
||||
super(srv, inetAddresses);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "RFC 6120 SRV Endpoint + ['xmpp', " + srv + "]";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2020 Florian Schmaus.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jivesoftware.smack.tcp.rce;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.List;
|
||||
|
||||
import org.minidns.record.SRV;
|
||||
|
||||
public class SrvXmppsRemoteConnectionEndpoint extends SrvRemoteConnectionEndpoint {
|
||||
|
||||
protected SrvXmppsRemoteConnectionEndpoint(SRV srv, List<? extends InetAddress> inetAddresses) {
|
||||
super(srv, inetAddresses);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "XEP-0368 SRV Endpoint + ['xmpps', " + srv + "]";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2020 Florian Schmaus
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Smack's internal API for XMPP connections over TCP.
|
||||
*/
|
||||
package org.jivesoftware.smack.tcp.rce;
|
|
@ -1,33 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2018 Florian Schmaus
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jivesoftware.smack.tcp;
|
||||
|
||||
import org.jivesoftware.smack.fsm.StateDescriptor;
|
||||
import org.jivesoftware.smack.fsm.StateDescriptorGraph.GraphVertex;
|
||||
import org.jivesoftware.smack.tcp.XmppNioTcpConnection.InstantShutdownStateDescriptor;
|
||||
|
||||
public class XmppNioTcpConnectionTest {
|
||||
|
||||
public void graphComplete() {
|
||||
assertContains(XmppNioTcpConnection.INITIAL_STATE_DESCRIPTOR_VERTEX, InstantShutdownStateDescriptor.class);
|
||||
}
|
||||
|
||||
private static void assertContains(GraphVertex<StateDescriptor> graph, Class<? extends StateDescriptor> state) {
|
||||
// TODO: Implement this.
|
||||
throw new Error("Implement me: " + graph + " " + state);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2018-2020 Florian Schmaus.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jivesoftware.smack.tcp.rce;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
import org.jivesoftware.smack.datatypes.UInt16;
|
||||
import org.jivesoftware.smack.tcp.rce.RemoteXmppTcpConnectionEndpoints.DomainType;
|
||||
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpoint;
|
||||
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpointLookupFailure;
|
||||
import org.jivesoftware.smack.util.rce.RemoteConnectionException;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.minidns.record.A;
|
||||
|
||||
public class RemoteXmppTcpConnectionEndpointsTest {
|
||||
|
||||
@Test
|
||||
public void simpleDomainTypeTest() {
|
||||
DomainType client = DomainType.client;
|
||||
assertEquals(RemoteXmppTcpConnectionEndpoints.XMPP_CLIENT_DNS_SRV_PREFIX, client.srvPrefix.ace);
|
||||
|
||||
DomainType server = DomainType.server;
|
||||
assertEquals(RemoteXmppTcpConnectionEndpoints.XMPP_SERVER_DNS_SRV_PREFIX, server.srvPrefix.ace);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConnectionException() {
|
||||
List<RemoteConnectionException<? extends RemoteConnectionEndpoint>> connectionExceptions = new ArrayList<>();
|
||||
|
||||
{
|
||||
A aRr = new A("1.2.3.4");
|
||||
UInt16 port = UInt16.from(1234);
|
||||
String host = "example.org";
|
||||
IpTcpRemoteConnectionEndpoint<A> remoteConnectionEndpoint = new IpTcpRemoteConnectionEndpoint<>(host, port,
|
||||
aRr);
|
||||
Exception exception = new Exception("Failed for some reason");
|
||||
|
||||
RemoteConnectionException<IpTcpRemoteConnectionEndpoint<A>> remoteConnectionException = RemoteConnectionException.from(
|
||||
remoteConnectionEndpoint, exception);
|
||||
connectionExceptions.add(remoteConnectionException);
|
||||
}
|
||||
|
||||
{
|
||||
A aRr = new A("1.3.3.7");
|
||||
UInt16 port = UInt16.from(5678);
|
||||
String host = "other.example.org";
|
||||
IpTcpRemoteConnectionEndpoint<A> remoteConnectionEndpoint = new IpTcpRemoteConnectionEndpoint<>(host, port,
|
||||
aRr);
|
||||
Exception exception = new Exception("Failed for some other reason");
|
||||
|
||||
RemoteConnectionException<IpTcpRemoteConnectionEndpoint<A>> remoteConnectionException = RemoteConnectionException.from(
|
||||
remoteConnectionEndpoint, exception);
|
||||
connectionExceptions.add(remoteConnectionException);
|
||||
}
|
||||
|
||||
List<RemoteConnectionEndpointLookupFailure> lookupFailures = Collections.emptyList();
|
||||
SmackException.EndpointConnectionException endpointConnectionException = SmackException.EndpointConnectionException.from(
|
||||
lookupFailures, connectionExceptions);
|
||||
|
||||
String message = endpointConnectionException.getMessage();
|
||||
assertEquals("The following addresses failed: "
|
||||
+ "'RFC 6120 A/AAAA Endpoint + [example.org:1234] (/1.2.3.4:1234)' failed because: java.lang.Exception: Failed for some reason, "
|
||||
+ "'RFC 6120 A/AAAA Endpoint + [other.example.org:5678] (/1.3.3.7:5678)' failed because: java.lang.Exception: Failed for some other reason",
|
||||
message);
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue