ConnectionAttemptState.java

  1. /**
  2.  *
  3.  * Copyright 2019-2020 Florian Schmaus
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.jivesoftware.smack.tcp;

  18. import java.io.IOException;
  19. import java.net.InetAddress;
  20. import java.net.InetSocketAddress;
  21. import java.nio.channels.ClosedChannelException;
  22. import java.nio.channels.SelectionKey;
  23. import java.nio.channels.SocketChannel;
  24. import java.util.ArrayList;
  25. import java.util.Iterator;
  26. import java.util.List;

  27. import org.jivesoftware.smack.SmackException.EndpointConnectionException;
  28. import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
  29. import org.jivesoftware.smack.fsm.StateTransitionResult;
  30. import org.jivesoftware.smack.tcp.XmppTcpTransportModule.EstablishingTcpConnectionState;
  31. import org.jivesoftware.smack.tcp.rce.Rfc6120TcpRemoteConnectionEndpoint;
  32. import org.jivesoftware.smack.util.Async;
  33. import org.jivesoftware.smack.util.rce.RemoteConnectionEndpoint;
  34. import org.jivesoftware.smack.util.rce.RemoteConnectionException;

  35. public final class ConnectionAttemptState {

  36.     private final ModularXmppClientToServerConnectionInternal connectionInternal;

  37.     private final XmppTcpTransportModule.XmppTcpNioTransport.DiscoveredTcpEndpoints discoveredEndpoints;

  38.     private final EstablishingTcpConnectionState establishingTcpConnectionState;

  39.     // TODO: Check if we can re-use the socket channel in case some InetSocketAddress fail to connect to.
  40.     final SocketChannel socketChannel;

  41.     final List<RemoteConnectionException<?>> connectionExceptions;

  42.     EndpointConnectionException connectionException;
  43.     boolean connected;
  44.     long deadline;

  45.     final Iterator<Rfc6120TcpRemoteConnectionEndpoint> connectionEndpointIterator;
  46.     /** The current connection endpoint we are trying */
  47.     Rfc6120TcpRemoteConnectionEndpoint connectionEndpoint;
  48.     Iterator<? extends InetAddress> inetAddressIterator;

  49.     ConnectionAttemptState(ModularXmppClientToServerConnectionInternal connectionInternal,
  50.                     XmppTcpTransportModule.XmppTcpNioTransport.DiscoveredTcpEndpoints discoveredEndpoints,
  51.                     EstablishingTcpConnectionState establishingTcpConnectionState) throws IOException {
  52.         this.connectionInternal = connectionInternal;
  53.         this.discoveredEndpoints = discoveredEndpoints;
  54.         this.establishingTcpConnectionState = establishingTcpConnectionState;

  55.         socketChannel = SocketChannel.open();
  56.         socketChannel.configureBlocking(false);

  57.         List<Rfc6120TcpRemoteConnectionEndpoint> endpoints = discoveredEndpoints.result.discoveredRemoteConnectionEndpoints;
  58.         connectionEndpointIterator = endpoints.iterator();
  59.         connectionExceptions = new ArrayList<>(endpoints.size());
  60.     }

  61.     StateTransitionResult.Failure establishTcpConnection() throws InterruptedException {
  62.         RemoteConnectionEndpoint.InetSocketAddressCoupling<Rfc6120TcpRemoteConnectionEndpoint> address = nextAddress();
  63.         establishTcpConnection(address);

  64.         synchronized (this) {
  65.             while (!connected && connectionException == null) {
  66.                 final long now = System.currentTimeMillis();
  67.                 if (now >= deadline) {
  68.                     return new StateTransitionResult.FailureCausedByTimeout("Timeout waiting to establish connection");
  69.                 }
  70.                 wait (deadline - now);
  71.             }
  72.         }
  73.         if (connected) {
  74.             assert connectionException == null;
  75.             // Success case: we have been able to establish a connection to one remote endpoint.
  76.             return null;
  77.         }

  78.         return new StateTransitionResult.FailureCausedByException<Exception>(connectionException);
  79.     }

  80.     private void establishTcpConnection(
  81.                     RemoteConnectionEndpoint.InetSocketAddressCoupling<Rfc6120TcpRemoteConnectionEndpoint> address) {
  82.         TcpHostEvent.ConnectingToHostEvent connectingToHostEvent = new TcpHostEvent.ConnectingToHostEvent(
  83.                         establishingTcpConnectionState, address);
  84.         connectionInternal.invokeConnectionStateMachineListener(connectingToHostEvent);

  85.         final InetSocketAddress inetSocketAddress = address.getInetSocketAddress();
  86.         // TODO: Should use "connect timeout" instead of reply timeout. But first connect timeout needs to be moved from
  87.         // XMPPTCPConnectionConfiguration. into XMPPConnectionConfiguration.
  88.         deadline = System.currentTimeMillis() + connectionInternal.connection.getReplyTimeout();
  89.         try {
  90.             connected = socketChannel.connect(inetSocketAddress);
  91.         } catch (IOException e) {
  92.             onIOExceptionWhenEstablishingTcpConnection(e, address);
  93.             return;
  94.         }

  95.         if (connected) {
  96.             TcpHostEvent.ConnectedToHostEvent connectedToHostEvent = new TcpHostEvent.ConnectedToHostEvent(
  97.                             establishingTcpConnectionState, address, true);
  98.             connectionInternal.invokeConnectionStateMachineListener(connectedToHostEvent);

  99.             synchronized (this) {
  100.                 notifyAll();
  101.             }
  102.             return;
  103.         }

  104.         try {
  105.             connectionInternal.registerWithSelector(socketChannel, SelectionKey.OP_CONNECT,
  106.                     (selectedChannel, selectedSelectionKey) -> {
  107.                         SocketChannel selectedSocketChannel = (SocketChannel) selectedChannel;

  108.                         boolean finishConnected;
  109.                         try {
  110.                             finishConnected = selectedSocketChannel.finishConnect();
  111.                         } catch (IOException e) {
  112.                             Async.go(() -> onIOExceptionWhenEstablishingTcpConnection(e, address));
  113.                             return;
  114.                         }

  115.                         if (!finishConnected) {
  116.                             Async.go(() -> onIOExceptionWhenEstablishingTcpConnection(new IOException("finishConnect() failed"), address));
  117.                             return;
  118.                         }

  119.                         TcpHostEvent.ConnectedToHostEvent connectedToHostEvent = new TcpHostEvent.ConnectedToHostEvent(
  120.                                         establishingTcpConnectionState, address, false);
  121.                         connectionInternal.invokeConnectionStateMachineListener(connectedToHostEvent);

  122.                         connected = true;
  123.                         synchronized (ConnectionAttemptState.this) {
  124.                             notifyAll();
  125.                         }
  126.                     });
  127.         } catch (ClosedChannelException e) {
  128.             onIOExceptionWhenEstablishingTcpConnection(e, address);
  129.         }
  130.     }

  131.     private void onIOExceptionWhenEstablishingTcpConnection(IOException exception,
  132.                     RemoteConnectionEndpoint.InetSocketAddressCoupling<Rfc6120TcpRemoteConnectionEndpoint> failedAddress) {
  133.         RemoteConnectionEndpoint.InetSocketAddressCoupling<Rfc6120TcpRemoteConnectionEndpoint> nextInetSocketAddress = nextAddress();
  134.         if (nextInetSocketAddress == null) {
  135.             connectionException = EndpointConnectionException.from(
  136.                             discoveredEndpoints.result.lookupFailures, connectionExceptions);
  137.             synchronized (this) {
  138.                 notifyAll();
  139.             }
  140.             return;
  141.         }

  142.         RemoteConnectionException<Rfc6120TcpRemoteConnectionEndpoint> rce = new RemoteConnectionException<>(
  143.                         failedAddress, exception);
  144.         connectionExceptions.add(rce);

  145.         TcpHostEvent.ConnectionToHostFailedEvent connectionToHostFailedEvent = new TcpHostEvent.ConnectionToHostFailedEvent(
  146.                         establishingTcpConnectionState, nextInetSocketAddress, exception);
  147.         connectionInternal.invokeConnectionStateMachineListener(connectionToHostFailedEvent);

  148.         establishTcpConnection(nextInetSocketAddress);
  149.     }

  150.     private RemoteConnectionEndpoint.InetSocketAddressCoupling<Rfc6120TcpRemoteConnectionEndpoint> nextAddress() {
  151.         if (inetAddressIterator == null || !inetAddressIterator.hasNext()) {
  152.             if (!connectionEndpointIterator.hasNext()) {
  153.                 return null;
  154.             }

  155.             connectionEndpoint = connectionEndpointIterator.next();
  156.             inetAddressIterator = connectionEndpoint.getInetAddresses().iterator();
  157.             // Every valid connection addresspoint must have a non-empty collection of inet addresses.
  158.             assert inetAddressIterator.hasNext();
  159.         }

  160.         InetAddress inetAddress = inetAddressIterator.next();

  161.         return new RemoteConnectionEndpoint.InetSocketAddressCoupling<>(connectionEndpoint, inetAddress);
  162.     }
  163. }