2020-04-04 13:03:31 +02:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* 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.EndpointConnectionException;
|
|
|
|
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
|
2020-05-26 21:39:08 +02:00
|
|
|
import org.jivesoftware.smack.fsm.StateTransitionResult;
|
2020-04-04 13:03:31 +02:00
|
|
|
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;
|
2020-05-26 21:39:08 +02:00
|
|
|
|
|
|
|
EndpointConnectionException connectionException;
|
|
|
|
boolean connected;
|
|
|
|
long deadline;
|
2020-04-04 13:03:31 +02:00
|
|
|
|
|
|
|
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);
|
|
|
|
|
2020-05-26 21:39:08 +02:00
|
|
|
List<Rfc6120TcpRemoteConnectionEndpoint> endpoints = discoveredEndpoints.result.discoveredRemoteConnectionEndpoints;
|
|
|
|
connectionEndpointIterator = endpoints.iterator();
|
|
|
|
connectionExceptions = new ArrayList<>(endpoints.size());
|
2020-04-04 13:03:31 +02:00
|
|
|
}
|
|
|
|
|
2020-05-26 21:39:08 +02:00
|
|
|
StateTransitionResult.Failure establishTcpConnection() throws InterruptedException {
|
2020-04-04 13:03:31 +02:00
|
|
|
RemoteConnectionEndpoint.InetSocketAddressCoupling<Rfc6120TcpRemoteConnectionEndpoint> address = nextAddress();
|
|
|
|
establishTcpConnection(address);
|
2020-05-26 21:39:08 +02:00
|
|
|
|
|
|
|
synchronized (this) {
|
|
|
|
while (!connected && connectionException == null) {
|
|
|
|
final long now = System.currentTimeMillis();
|
|
|
|
if (now >= deadline) {
|
|
|
|
return new StateTransitionResult.FailureCausedByTimeout("Timeout waiting to establish connection");
|
|
|
|
}
|
|
|
|
wait (deadline - now);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (connected) {
|
|
|
|
assert connectionException == null;
|
|
|
|
// Success case: we have been able to establish a connection to one remote endpoint.
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return new StateTransitionResult.FailureCausedByException<Exception>(connectionException);
|
2020-04-04 13:03:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private void establishTcpConnection(
|
|
|
|
RemoteConnectionEndpoint.InetSocketAddressCoupling<Rfc6120TcpRemoteConnectionEndpoint> address) {
|
|
|
|
TcpHostEvent.ConnectingToHostEvent connectingToHostEvent = new TcpHostEvent.ConnectingToHostEvent(
|
|
|
|
establishingTcpConnectionState, address);
|
|
|
|
connectionInternal.invokeConnectionStateMachineListener(connectingToHostEvent);
|
|
|
|
|
|
|
|
final InetSocketAddress inetSocketAddress = address.getInetSocketAddress();
|
2020-05-26 21:39:08 +02:00
|
|
|
// TODO: Should use "connect timeout" instead of reply timeout. But first connect timeout needs to be moved from
|
|
|
|
// XMPPTCPConnectionConfiguration. into XMPPConnectionConfiguration.
|
|
|
|
deadline = System.currentTimeMillis() + connectionInternal.connection.getReplyTimeout();
|
2020-04-04 13:03:31 +02:00
|
|
|
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);
|
|
|
|
|
2020-05-26 21:39:08 +02:00
|
|
|
synchronized (this) {
|
|
|
|
notifyAll();
|
|
|
|
}
|
2020-04-04 13:03:31 +02:00
|
|
|
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);
|
|
|
|
|
2020-05-26 21:39:08 +02:00
|
|
|
connected = true;
|
|
|
|
synchronized (ConnectionAttemptState.this) {
|
|
|
|
notifyAll();
|
|
|
|
}
|
2020-04-04 13:03:31 +02:00
|
|
|
});
|
|
|
|
} catch (ClosedChannelException e) {
|
|
|
|
onIOExceptionWhenEstablishingTcpConnection(e, address);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void onIOExceptionWhenEstablishingTcpConnection(IOException exception,
|
|
|
|
RemoteConnectionEndpoint.InetSocketAddressCoupling<Rfc6120TcpRemoteConnectionEndpoint> failedAddress) {
|
|
|
|
RemoteConnectionEndpoint.InetSocketAddressCoupling<Rfc6120TcpRemoteConnectionEndpoint> nextInetSocketAddress = nextAddress();
|
|
|
|
if (nextInetSocketAddress == null) {
|
2020-05-26 21:39:08 +02:00
|
|
|
connectionException = EndpointConnectionException.from(
|
2020-04-04 13:03:31 +02:00
|
|
|
discoveredEndpoints.result.lookupFailures, connectionExceptions);
|
2020-05-26 21:39:08 +02:00
|
|
|
synchronized (this) {
|
|
|
|
notifyAll();
|
|
|
|
}
|
2020-04-04 13:03:31 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|