ConnectionAttemptState.java
- /**
- *
- * 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;
- import org.jivesoftware.smack.fsm.StateTransitionResult;
- 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;
- EndpointConnectionException connectionException;
- boolean connected;
- long deadline;
- 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);
- List<Rfc6120TcpRemoteConnectionEndpoint> endpoints = discoveredEndpoints.result.discoveredRemoteConnectionEndpoints;
- connectionEndpointIterator = endpoints.iterator();
- connectionExceptions = new ArrayList<>(endpoints.size());
- }
- StateTransitionResult.Failure establishTcpConnection() throws InterruptedException {
- RemoteConnectionEndpoint.InetSocketAddressCoupling<Rfc6120TcpRemoteConnectionEndpoint> address = nextAddress();
- establishTcpConnection(address);
- 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);
- }
- private void establishTcpConnection(
- RemoteConnectionEndpoint.InetSocketAddressCoupling<Rfc6120TcpRemoteConnectionEndpoint> address) {
- TcpHostEvent.ConnectingToHostEvent connectingToHostEvent = new TcpHostEvent.ConnectingToHostEvent(
- establishingTcpConnectionState, address);
- connectionInternal.invokeConnectionStateMachineListener(connectingToHostEvent);
- final InetSocketAddress inetSocketAddress = address.getInetSocketAddress();
- // 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();
- 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);
- synchronized (this) {
- notifyAll();
- }
- 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);
- connected = true;
- synchronized (ConnectionAttemptState.this) {
- notifyAll();
- }
- });
- } catch (ClosedChannelException e) {
- onIOExceptionWhenEstablishingTcpConnection(e, address);
- }
- }
- private void onIOExceptionWhenEstablishingTcpConnection(IOException exception,
- RemoteConnectionEndpoint.InetSocketAddressCoupling<Rfc6120TcpRemoteConnectionEndpoint> failedAddress) {
- RemoteConnectionEndpoint.InetSocketAddressCoupling<Rfc6120TcpRemoteConnectionEndpoint> nextInetSocketAddress = nextAddress();
- if (nextInetSocketAddress == null) {
- connectionException = EndpointConnectionException.from(
- discoveredEndpoints.result.lookupFailures, connectionExceptions);
- synchronized (this) {
- notifyAll();
- }
- 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);
- }
- }