001/** 002 * 003 * Copyright 2020 Aditya Borikar, 2020-2021 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.websocket; 018 019import java.util.ArrayList; 020import java.util.List; 021 022import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode; 023import org.jivesoftware.smack.SmackFuture; 024import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal; 025import org.jivesoftware.smack.fsm.StateTransitionResult; 026import org.jivesoftware.smack.util.StringUtils; 027import org.jivesoftware.smack.websocket.impl.AbstractWebSocket; 028import org.jivesoftware.smack.websocket.impl.WebSocketFactory; 029import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint; 030import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpointLookup; 031 032public final class WebSocketConnectionAttemptState { 033 034 private final ModularXmppClientToServerConnectionInternal connectionInternal; 035 private final XmppWebSocketTransportModule.XmppWebSocketTransport.DiscoveredWebSocketEndpoints discoveredEndpoints; 036 private final WebSocketFactory webSocketFactory; 037 038 private AbstractWebSocket webSocket; 039 040 WebSocketConnectionAttemptState(ModularXmppClientToServerConnectionInternal connectionInternal, 041 XmppWebSocketTransportModule.XmppWebSocketTransport.DiscoveredWebSocketEndpoints discoveredWebSocketEndpoints, 042 WebSocketFactory webSocketFactory) { 043 assert discoveredWebSocketEndpoints != null; 044 assert !discoveredWebSocketEndpoints.result.isEmpty(); 045 046 this.connectionInternal = connectionInternal; 047 this.discoveredEndpoints = discoveredWebSocketEndpoints; 048 this.webSocketFactory = webSocketFactory; 049 } 050 051 /** 052 * Establish a websocket connection with one of the discoveredRemoteConnectionEndpoints.<br> 053 * 054 * @return {@link AbstractWebSocket} with which connection is established 055 * @throws InterruptedException if the calling thread was interrupted 056 */ 057 @SuppressWarnings({"incomplete-switch", "MissingCasesInEnumSwitch"}) 058 StateTransitionResult.Failure establishWebSocketConnection() throws InterruptedException { 059 final WebSocketRemoteConnectionEndpointLookup.Result endpointLookupResult = discoveredEndpoints.result; 060 final List<Exception> failures = new ArrayList<>(endpointLookupResult.discoveredEndpointCount()); 061 062 webSocket = null; 063 064 SecurityMode securityMode = connectionInternal.connection.getConfiguration().getSecurityMode(); 065 switch (securityMode) { 066 case required: 067 case ifpossible: 068 establishWebSocketConnection(endpointLookupResult.discoveredSecureEndpoints, failures); 069 if (webSocket != null) { 070 return null; 071 } 072 } 073 074 establishWebSocketConnection(endpointLookupResult.discoveredInsecureEndpoints, failures); 075 if (webSocket != null) { 076 return null; 077 } 078 079 StateTransitionResult.Failure failure = FailedToConnectToAnyWebSocketEndpoint.create(failures); 080 return failure; 081 } 082 083 private void establishWebSocketConnection(List<? extends WebSocketRemoteConnectionEndpoint> webSocketEndpoints, 084 List<Exception> failures) throws InterruptedException { 085 final int endpointCount = webSocketEndpoints.size(); 086 087 List<SmackFuture<AbstractWebSocket, Exception>> futures = new ArrayList<>(endpointCount); 088 { 089 List<AbstractWebSocket> webSockets = new ArrayList<>(endpointCount); 090 // First only create the AbstractWebSocket instances, in case a constructor throws. 091 for (WebSocketRemoteConnectionEndpoint endpoint : webSocketEndpoints) { 092 AbstractWebSocket webSocket = webSocketFactory.create(endpoint, connectionInternal); 093 webSockets.add(webSocket); 094 } 095 096 for (AbstractWebSocket webSocket : webSockets) { 097 SmackFuture<AbstractWebSocket, Exception> future = webSocket.getFuture(); 098 futures.add(future); 099 } 100 } 101 102 SmackFuture.await(futures, connectionInternal.connection.getReplyTimeout()); 103 104 for (SmackFuture<AbstractWebSocket, Exception> future : futures) { 105 AbstractWebSocket connectedWebSocket = future.getIfAvailable(); 106 if (connectedWebSocket == null) { 107 Exception exception = future.getExceptionIfAvailable(); 108 assert exception != null; 109 failures.add(exception); 110 continue; 111 } 112 113 if (webSocket == null) { 114 webSocket = connectedWebSocket; 115 // Continue here since we still need to read out the failure exceptions from potential further remaining 116 // futures and close remaining successfully connected ones. 117 continue; 118 } 119 120 connectedWebSocket.disconnect(1000, "Using other connection endpoint at " + webSocket.getEndpoint()); 121 } 122 } 123 124 public AbstractWebSocket getConnectedWebSocket() { 125 return webSocket; 126 } 127 128 public static final class FailedToConnectToAnyWebSocketEndpoint extends StateTransitionResult.Failure { 129 130 private final List<Exception> failures; 131 132 private FailedToConnectToAnyWebSocketEndpoint(String failureMessage, List<Exception> failures) { 133 super(failureMessage); 134 this.failures = failures; 135 } 136 137 public List<Exception> getFailures() { 138 return failures; 139 } 140 141 private static FailedToConnectToAnyWebSocketEndpoint create(List<Exception> failures) { 142 StringBuilder sb = new StringBuilder(256); 143 StringUtils.appendTo(failures, sb, e -> sb.append(e.getMessage())); 144 String message = sb.toString(); 145 return new FailedToConnectToAnyWebSocketEndpoint(message, failures); 146 } 147 } 148}