WebSocketConnectionAttemptState.java

  1. /**
  2.  *
  3.  * Copyright 2020 Aditya Borikar, 2020-2021 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.websocket;

  18. import java.util.ArrayList;
  19. import java.util.List;

  20. import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
  21. import org.jivesoftware.smack.SmackFuture;
  22. import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
  23. import org.jivesoftware.smack.fsm.StateTransitionResult;
  24. import org.jivesoftware.smack.util.StringUtils;
  25. import org.jivesoftware.smack.websocket.impl.AbstractWebSocket;
  26. import org.jivesoftware.smack.websocket.impl.WebSocketFactory;
  27. import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint;
  28. import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpointLookup;

  29. public final class WebSocketConnectionAttemptState {

  30.     private final ModularXmppClientToServerConnectionInternal connectionInternal;
  31.     private final XmppWebSocketTransportModule.XmppWebSocketTransport.DiscoveredWebSocketEndpoints discoveredEndpoints;
  32.     private final WebSocketFactory webSocketFactory;

  33.     private AbstractWebSocket webSocket;

  34.     WebSocketConnectionAttemptState(ModularXmppClientToServerConnectionInternal connectionInternal,
  35.                     XmppWebSocketTransportModule.XmppWebSocketTransport.DiscoveredWebSocketEndpoints discoveredWebSocketEndpoints,
  36.                     WebSocketFactory webSocketFactory) {
  37.         assert discoveredWebSocketEndpoints != null;
  38.         assert !discoveredWebSocketEndpoints.result.isEmpty();

  39.         this.connectionInternal = connectionInternal;
  40.         this.discoveredEndpoints = discoveredWebSocketEndpoints;
  41.         this.webSocketFactory = webSocketFactory;
  42.     }

  43.     /**
  44.      * Establish  a websocket connection with one of the discoveredRemoteConnectionEndpoints.<br>
  45.      *
  46.      * @return {@link AbstractWebSocket} with which connection is established
  47.      * @throws InterruptedException if the calling thread was interrupted
  48.      */
  49.     @SuppressWarnings({"incomplete-switch", "MissingCasesInEnumSwitch"})
  50.     StateTransitionResult.Failure establishWebSocketConnection() throws InterruptedException {
  51.         final WebSocketRemoteConnectionEndpointLookup.Result endpointLookupResult = discoveredEndpoints.result;
  52.         final List<Exception> failures = new ArrayList<>(endpointLookupResult.discoveredEndpointCount());

  53.         webSocket = null;

  54.         SecurityMode securityMode = connectionInternal.connection.getConfiguration().getSecurityMode();
  55.         switch (securityMode) {
  56.         case required:
  57.         case ifpossible:
  58.             establishWebSocketConnection(endpointLookupResult.discoveredSecureEndpoints, failures);
  59.             if (webSocket != null) {
  60.                 return null;
  61.             }
  62.         }

  63.         establishWebSocketConnection(endpointLookupResult.discoveredInsecureEndpoints, failures);
  64.         if (webSocket != null) {
  65.             return null;
  66.         }

  67.         StateTransitionResult.Failure failure = FailedToConnectToAnyWebSocketEndpoint.create(failures);
  68.         return failure;
  69.     }

  70.     private void establishWebSocketConnection(List<? extends WebSocketRemoteConnectionEndpoint> webSocketEndpoints,
  71.                     List<Exception> failures) throws InterruptedException {
  72.         final int endpointCount = webSocketEndpoints.size();

  73.         List<SmackFuture<AbstractWebSocket, Exception>> futures = new ArrayList<>(endpointCount);
  74.         {
  75.             List<AbstractWebSocket> webSockets = new ArrayList<>(endpointCount);
  76.             // First only create the AbstractWebSocket instances, in case a constructor throws.
  77.             for (WebSocketRemoteConnectionEndpoint endpoint : webSocketEndpoints) {
  78.                 AbstractWebSocket webSocket = webSocketFactory.create(endpoint, connectionInternal);
  79.                 webSockets.add(webSocket);
  80.             }

  81.             for (AbstractWebSocket webSocket : webSockets) {
  82.                 SmackFuture<AbstractWebSocket, Exception> future = webSocket.getFuture();
  83.                 futures.add(future);
  84.             }
  85.         }

  86.         SmackFuture.await(futures, connectionInternal.connection.getReplyTimeout());

  87.         for (SmackFuture<AbstractWebSocket, Exception> future : futures) {
  88.             AbstractWebSocket connectedWebSocket = future.getIfAvailable();
  89.             if (connectedWebSocket == null) {
  90.                 Exception exception = future.getExceptionIfAvailable();
  91.                 assert exception != null;
  92.                 failures.add(exception);
  93.                 continue;
  94.             }

  95.             if (webSocket == null) {
  96.                 webSocket = connectedWebSocket;
  97.                 // Continue here since we still need to read out the failure exceptions from potential further remaining
  98.                 // futures and close remaining successfully connected ones.
  99.                 continue;
  100.             }

  101.             connectedWebSocket.disconnect(1000, "Using other connection endpoint at " + webSocket.getEndpoint());
  102.         }
  103.     }

  104.     public AbstractWebSocket getConnectedWebSocket() {
  105.         return webSocket;
  106.     }

  107.     public static final class FailedToConnectToAnyWebSocketEndpoint extends StateTransitionResult.Failure {

  108.         private final List<Exception> failures;

  109.         private FailedToConnectToAnyWebSocketEndpoint(String failureMessage, List<Exception> failures) {
  110.             super(failureMessage);
  111.             this.failures = failures;
  112.         }

  113.         public List<Exception> getFailures() {
  114.             return failures;
  115.         }

  116.         private static FailedToConnectToAnyWebSocketEndpoint create(List<Exception> failures) {
  117.             StringBuilder sb = new StringBuilder(256);
  118.             StringUtils.appendTo(failures, sb, e -> sb.append(e.getMessage()));
  119.             String message = sb.toString();
  120.             return new FailedToConnectToAnyWebSocketEndpoint(message, failures);
  121.         }
  122.     }
  123. }