001/** 002 * 003 * Copyright 2019-2020 Florian Schmaus 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.jivesoftware.smack.tcp; 018 019import java.io.IOException; 020import java.net.InetAddress; 021import java.net.InetSocketAddress; 022import java.nio.channels.ClosedChannelException; 023import java.nio.channels.SelectionKey; 024import java.nio.channels.SocketChannel; 025import java.util.ArrayList; 026import java.util.Iterator; 027import java.util.List; 028 029import org.jivesoftware.smack.SmackException.EndpointConnectionException; 030import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal; 031import org.jivesoftware.smack.fsm.StateTransitionResult; 032import org.jivesoftware.smack.tcp.XmppTcpTransportModule.EstablishingTcpConnectionState; 033import org.jivesoftware.smack.tcp.rce.Rfc6120TcpRemoteConnectionEndpoint; 034import org.jivesoftware.smack.util.Async; 035import org.jivesoftware.smack.util.rce.RemoteConnectionEndpoint; 036import org.jivesoftware.smack.util.rce.RemoteConnectionException; 037 038public final class ConnectionAttemptState { 039 040 private final ModularXmppClientToServerConnectionInternal connectionInternal; 041 042 private final XmppTcpTransportModule.XmppTcpNioTransport.DiscoveredTcpEndpoints discoveredEndpoints; 043 044 private final EstablishingTcpConnectionState establishingTcpConnectionState; 045 046 // TODO: Check if we can re-use the socket channel in case some InetSocketAddress fail to connect to. 047 final SocketChannel socketChannel; 048 049 final List<RemoteConnectionException<?>> connectionExceptions; 050 051 EndpointConnectionException connectionException; 052 boolean connected; 053 long deadline; 054 055 final Iterator<Rfc6120TcpRemoteConnectionEndpoint> connectionEndpointIterator; 056 /** The current connection endpoint we are trying */ 057 Rfc6120TcpRemoteConnectionEndpoint connectionEndpoint; 058 Iterator<? extends InetAddress> inetAddressIterator; 059 060 ConnectionAttemptState(ModularXmppClientToServerConnectionInternal connectionInternal, 061 XmppTcpTransportModule.XmppTcpNioTransport.DiscoveredTcpEndpoints discoveredEndpoints, 062 EstablishingTcpConnectionState establishingTcpConnectionState) throws IOException { 063 this.connectionInternal = connectionInternal; 064 this.discoveredEndpoints = discoveredEndpoints; 065 this.establishingTcpConnectionState = establishingTcpConnectionState; 066 067 socketChannel = SocketChannel.open(); 068 socketChannel.configureBlocking(false); 069 070 List<Rfc6120TcpRemoteConnectionEndpoint> endpoints = discoveredEndpoints.result.discoveredRemoteConnectionEndpoints; 071 connectionEndpointIterator = endpoints.iterator(); 072 connectionExceptions = new ArrayList<>(endpoints.size()); 073 } 074 075 StateTransitionResult.Failure establishTcpConnection() throws InterruptedException { 076 RemoteConnectionEndpoint.InetSocketAddressCoupling<Rfc6120TcpRemoteConnectionEndpoint> address = nextAddress(); 077 establishTcpConnection(address); 078 079 synchronized (this) { 080 while (!connected && connectionException == null) { 081 final long now = System.currentTimeMillis(); 082 if (now >= deadline) { 083 return new StateTransitionResult.FailureCausedByTimeout("Timeout waiting to establish connection"); 084 } 085 wait (deadline - now); 086 } 087 } 088 if (connected) { 089 assert connectionException == null; 090 // Success case: we have been able to establish a connection to one remote endpoint. 091 return null; 092 } 093 094 return new StateTransitionResult.FailureCausedByException<Exception>(connectionException); 095 } 096 097 private void establishTcpConnection( 098 RemoteConnectionEndpoint.InetSocketAddressCoupling<Rfc6120TcpRemoteConnectionEndpoint> address) { 099 TcpHostEvent.ConnectingToHostEvent connectingToHostEvent = new TcpHostEvent.ConnectingToHostEvent( 100 establishingTcpConnectionState, address); 101 connectionInternal.invokeConnectionStateMachineListener(connectingToHostEvent); 102 103 final InetSocketAddress inetSocketAddress = address.getInetSocketAddress(); 104 // TODO: Should use "connect timeout" instead of reply timeout. But first connect timeout needs to be moved from 105 // XMPPTCPConnectionConfiguration. into XMPPConnectionConfiguration. 106 deadline = System.currentTimeMillis() + connectionInternal.connection.getReplyTimeout(); 107 try { 108 connected = socketChannel.connect(inetSocketAddress); 109 } catch (IOException e) { 110 onIOExceptionWhenEstablishingTcpConnection(e, address); 111 return; 112 } 113 114 if (connected) { 115 TcpHostEvent.ConnectedToHostEvent connectedToHostEvent = new TcpHostEvent.ConnectedToHostEvent( 116 establishingTcpConnectionState, address, true); 117 connectionInternal.invokeConnectionStateMachineListener(connectedToHostEvent); 118 119 synchronized (this) { 120 notifyAll(); 121 } 122 return; 123 } 124 125 try { 126 connectionInternal.registerWithSelector(socketChannel, SelectionKey.OP_CONNECT, 127 (selectedChannel, selectedSelectionKey) -> { 128 SocketChannel selectedSocketChannel = (SocketChannel) selectedChannel; 129 130 boolean finishConnected; 131 try { 132 finishConnected = selectedSocketChannel.finishConnect(); 133 } catch (IOException e) { 134 Async.go(() -> onIOExceptionWhenEstablishingTcpConnection(e, address)); 135 return; 136 } 137 138 if (!finishConnected) { 139 Async.go(() -> onIOExceptionWhenEstablishingTcpConnection(new IOException("finishConnect() failed"), address)); 140 return; 141 } 142 143 TcpHostEvent.ConnectedToHostEvent connectedToHostEvent = new TcpHostEvent.ConnectedToHostEvent( 144 establishingTcpConnectionState, address, false); 145 connectionInternal.invokeConnectionStateMachineListener(connectedToHostEvent); 146 147 connected = true; 148 synchronized (ConnectionAttemptState.this) { 149 notifyAll(); 150 } 151 }); 152 } catch (ClosedChannelException e) { 153 onIOExceptionWhenEstablishingTcpConnection(e, address); 154 } 155 } 156 157 private void onIOExceptionWhenEstablishingTcpConnection(IOException exception, 158 RemoteConnectionEndpoint.InetSocketAddressCoupling<Rfc6120TcpRemoteConnectionEndpoint> failedAddress) { 159 RemoteConnectionEndpoint.InetSocketAddressCoupling<Rfc6120TcpRemoteConnectionEndpoint> nextInetSocketAddress = nextAddress(); 160 if (nextInetSocketAddress == null) { 161 connectionException = EndpointConnectionException.from( 162 discoveredEndpoints.result.lookupFailures, connectionExceptions); 163 synchronized (this) { 164 notifyAll(); 165 } 166 return; 167 } 168 169 RemoteConnectionException<Rfc6120TcpRemoteConnectionEndpoint> rce = new RemoteConnectionException<>( 170 failedAddress, exception); 171 connectionExceptions.add(rce); 172 173 TcpHostEvent.ConnectionToHostFailedEvent connectionToHostFailedEvent = new TcpHostEvent.ConnectionToHostFailedEvent( 174 establishingTcpConnectionState, nextInetSocketAddress, exception); 175 connectionInternal.invokeConnectionStateMachineListener(connectionToHostFailedEvent); 176 177 establishTcpConnection(nextInetSocketAddress); 178 } 179 180 private RemoteConnectionEndpoint.InetSocketAddressCoupling<Rfc6120TcpRemoteConnectionEndpoint> nextAddress() { 181 if (inetAddressIterator == null || !inetAddressIterator.hasNext()) { 182 if (!connectionEndpointIterator.hasNext()) { 183 return null; 184 } 185 186 connectionEndpoint = connectionEndpointIterator.next(); 187 inetAddressIterator = connectionEndpoint.getInetAddresses().iterator(); 188 // Every valid connection addresspoint must have a non-empty collection of inet addresses. 189 assert inetAddressIterator.hasNext(); 190 } 191 192 InetAddress inetAddress = inetAddressIterator.next(); 193 194 return new RemoteConnectionEndpoint.InetSocketAddressCoupling<>(connectionEndpoint, inetAddress); 195 } 196}