ModularXmppClientToServerConnection.java

  1. /**
  2.  *
  3.  * Copyright 2018-2022 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.c2s;

  18. import java.io.IOException;
  19. import java.net.InetAddress;
  20. import java.security.cert.CertificateException;
  21. import java.util.ArrayList;
  22. import java.util.Collection;
  23. import java.util.Collections;
  24. import java.util.HashMap;
  25. import java.util.Iterator;
  26. import java.util.List;
  27. import java.util.ListIterator;
  28. import java.util.Map;
  29. import java.util.concurrent.CopyOnWriteArrayList;
  30. import java.util.concurrent.TimeUnit;
  31. import java.util.logging.Level;
  32. import java.util.logging.Logger;

  33. import javax.net.ssl.SSLSession;

  34. import org.jivesoftware.smack.AbstractXMPPConnection;
  35. import org.jivesoftware.smack.SmackException;
  36. import org.jivesoftware.smack.SmackException.NoResponseException;
  37. import org.jivesoftware.smack.SmackException.NotConnectedException;
  38. import org.jivesoftware.smack.SmackException.OutgoingQueueFullException;
  39. import org.jivesoftware.smack.SmackFuture;
  40. import org.jivesoftware.smack.XMPPException;
  41. import org.jivesoftware.smack.XMPPException.FailedNonzaException;
  42. import org.jivesoftware.smack.XMPPException.StreamErrorException;
  43. import org.jivesoftware.smack.XMPPException.XMPPErrorException;
  44. import org.jivesoftware.smack.XmppInputOutputFilter;
  45. import org.jivesoftware.smack.c2s.XmppClientToServerTransport.LookupConnectionEndpointsFailed;
  46. import org.jivesoftware.smack.c2s.XmppClientToServerTransport.LookupConnectionEndpointsResult;
  47. import org.jivesoftware.smack.c2s.XmppClientToServerTransport.LookupConnectionEndpointsSuccess;
  48. import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
  49. import org.jivesoftware.smack.c2s.internal.WalkStateGraphContext;
  50. import org.jivesoftware.smack.fsm.ConnectionStateEvent;
  51. import org.jivesoftware.smack.fsm.ConnectionStateMachineListener;
  52. import org.jivesoftware.smack.fsm.LoginContext;
  53. import org.jivesoftware.smack.fsm.NoOpState;
  54. import org.jivesoftware.smack.fsm.State;
  55. import org.jivesoftware.smack.fsm.StateDescriptor;
  56. import org.jivesoftware.smack.fsm.StateDescriptorGraph;
  57. import org.jivesoftware.smack.fsm.StateDescriptorGraph.GraphVertex;
  58. import org.jivesoftware.smack.fsm.StateMachineException;
  59. import org.jivesoftware.smack.fsm.StateTransitionResult;
  60. import org.jivesoftware.smack.fsm.StateTransitionResult.AttemptResult;
  61. import org.jivesoftware.smack.internal.AbstractStats;
  62. import org.jivesoftware.smack.internal.SmackTlsContext;
  63. import org.jivesoftware.smack.packet.AbstractStreamClose;
  64. import org.jivesoftware.smack.packet.AbstractStreamOpen;
  65. import org.jivesoftware.smack.packet.IQ;
  66. import org.jivesoftware.smack.packet.Message;
  67. import org.jivesoftware.smack.packet.Nonza;
  68. import org.jivesoftware.smack.packet.Presence;
  69. import org.jivesoftware.smack.packet.StreamError;
  70. import org.jivesoftware.smack.packet.TopLevelStreamElement;
  71. import org.jivesoftware.smack.packet.XmlEnvironment;
  72. import org.jivesoftware.smack.parsing.SmackParsingException;
  73. import org.jivesoftware.smack.sasl.SASLErrorException;
  74. import org.jivesoftware.smack.sasl.SASLMechanism;
  75. import org.jivesoftware.smack.util.ArrayBlockingQueueWithShutdown;
  76. import org.jivesoftware.smack.util.ExtendedAppendable;
  77. import org.jivesoftware.smack.util.PacketParserUtils;
  78. import org.jivesoftware.smack.util.StringUtils;
  79. import org.jivesoftware.smack.util.Supplier;
  80. import org.jivesoftware.smack.xml.XmlPullParser;
  81. import org.jivesoftware.smack.xml.XmlPullParserException;

  82. import org.jxmpp.jid.DomainBareJid;
  83. import org.jxmpp.jid.parts.Resourcepart;
  84. import org.jxmpp.util.XmppStringUtils;

  85. /**
  86.  * The superclass of Smack's Modular Connection Architecture.
  87.  * <p>
  88.  * <b>Note:</b> Everything related to the modular connection architecture is currently considered experimental and
  89.  * should not be used in production. Use the mature {@code XMPPTCPConnection} if you do not feel adventurous.
  90.  * </p>
  91.  * <p>
  92.  * Smack's modular connection architecture allows to extend a XMPP c2s (client-to-server) connection with additional
  93.  * functionality by adding modules. Those modules extend the Finite State Machine (FSM) within the connection with new
  94.  * states. Connection modules can either be
  95.  * <ul>
  96.  * <li>Transports</li>
  97.  * <li>Extensions</li>
  98.  * </ul>
  99.  * <p>
  100.  * Transports bind the XMPP XML stream to an underlying transport like TCP, WebSockets, BOSH, and allow for the
  101.  * different particularities of transports like DirectTLS
  102.  * (<a href="https://xmpp.org/extensions/xep-0368.html">XEP-0368</a>). This eventually means that a single transport
  103.  * module can implement multiple transport mechanisms. For example the TCP transport module implements the RFC6120 TCP
  104.  * and the XEP-0368 direct TLS TCP transport bindings.
  105.  * </p>
  106.  * <p>
  107.  * Extensions allow for a richer functionality of the connection. Those include
  108.  * <ul>
  109.  * <li>Compression</li>
  110.  *   <li><ul>
  111.  *   <li>zlib ([XEP-0138](https://xmpp.org/extensions/xep-0138.html))</li>
  112.  *   <li>[Efficient XML Interchange (EXI)](https://www.w3.org/TR/exi/)</li>
  113.  *   </ul></li>
  114.  * <li>Instant Stream Resumption ([XEP-0397](https://xmpp.org/extensions/xep-0397.html)</li>
  115.  * <li>Bind2</li>
  116.  * <li>Stream Management</li>
  117.  * </ul>
  118.  * Note that not all extensions work with every transport. For example compression only works with TCP-based transport
  119.  * bindings.
  120.  * <p>
  121.  * Connection modules are plugged into the the modular connection via their constructor. and they usually declare
  122.  * backwards edges to some common, generic connection state of the FSM.
  123.  * </p>
  124.  * <p>
  125.  * Modules and states always have an accompanying *descriptor* type. `ModuleDescriptor` and `StateDescriptor` exist
  126.  * without an connection instance. They describe the module and state metadata, while their modules and states are
  127.  * Instantiated once a modular connection is instantiated.
  128.  * </p>
  129.  */
  130. public final class ModularXmppClientToServerConnection extends AbstractXMPPConnection {

  131.     private static final Logger LOGGER = Logger.getLogger(
  132.                     ModularXmppClientToServerConnectionConfiguration.class.getName());

  133.     private final ArrayBlockingQueueWithShutdown<TopLevelStreamElement> outgoingElementsQueue = new ArrayBlockingQueueWithShutdown<>(
  134.                     100, true);

  135.     private XmppClientToServerTransport activeTransport;

  136.     private final List<ConnectionStateMachineListener> connectionStateMachineListeners = new CopyOnWriteArrayList<>();

  137.     private boolean featuresReceived;

  138.     private boolean streamResumed;

  139.     private GraphVertex<State> currentStateVertex;

  140.     private List<State> walkFromDisconnectToAuthenticated;

  141.     private final ModularXmppClientToServerConnectionConfiguration configuration;

  142.     private final ModularXmppClientToServerConnectionInternal connectionInternal;

  143.     private final Map<Class<? extends ModularXmppClientToServerConnectionModuleDescriptor>, ModularXmppClientToServerConnectionModule<? extends ModularXmppClientToServerConnectionModuleDescriptor>> connectionModules = new HashMap<>();

  144.     private final Map<Class<? extends ModularXmppClientToServerConnectionModuleDescriptor>, XmppClientToServerTransport> transports = new HashMap<>();
  145.     /**
  146.      * This is one of those cases where the field is modified by one thread and read by another. We currently use
  147.      * CopyOnWriteArrayList but should potentially use a VarHandle once Smack supports them.
  148.      */
  149.     private final List<XmppInputOutputFilter> inputOutputFilters = new CopyOnWriteArrayList<>();

  150.     private List<XmppInputOutputFilter> previousInputOutputFilters;

  151.     public ModularXmppClientToServerConnection(ModularXmppClientToServerConnectionConfiguration configuration) {
  152.         super(configuration);

  153.         this.configuration = configuration;

  154.         // Construct the internal connection API.
  155.         connectionInternal = new ModularXmppClientToServerConnectionInternal(this, getReactor(), debugger,
  156.                         outgoingElementsQueue) {

  157.             @Override
  158.             public void parseAndProcessElement(String wrappedCompleteElement) {
  159.                 ModularXmppClientToServerConnection.this.parseAndProcessElement(wrappedCompleteElement);
  160.             }

  161.             @Override
  162.             public void notifyConnectionError(Exception e) {
  163.                 ModularXmppClientToServerConnection.this.notifyConnectionError(e);
  164.             }

  165.             @Override
  166.             public String onStreamOpen(XmlPullParser parser) {
  167.                 return ModularXmppClientToServerConnection.this.onStreamOpen(parser);
  168.             }

  169.             @Override
  170.             public void onStreamClosed() {
  171.                 ModularXmppClientToServerConnection.this.closingStreamReceived = true;
  172.                 notifyWaitingThreads();
  173.             }

  174.             @Override
  175.             public void fireFirstLevelElementSendListeners(TopLevelStreamElement element) {
  176.                 ModularXmppClientToServerConnection.this.firePacketSendingListeners(element);
  177.             }

  178.             @Override
  179.             public void invokeConnectionStateMachineListener(ConnectionStateEvent connectionStateEvent) {
  180.                 ModularXmppClientToServerConnection.this.invokeConnectionStateMachineListener(connectionStateEvent);
  181.             }

  182.             @Override
  183.             public XmlEnvironment getOutgoingStreamXmlEnvironment() {
  184.                 return outgoingStreamXmlEnvironment;
  185.             }

  186.             @Override
  187.             public void addXmppInputOutputFilter(XmppInputOutputFilter xmppInputOutputFilter) {
  188.                 inputOutputFilters.add(0, xmppInputOutputFilter);
  189.             }

  190.             @Override
  191.             public ListIterator<XmppInputOutputFilter> getXmppInputOutputFilterBeginIterator() {
  192.                 return inputOutputFilters.listIterator();
  193.             }

  194.             @Override
  195.             public ListIterator<XmppInputOutputFilter> getXmppInputOutputFilterEndIterator() {
  196.                 return inputOutputFilters.listIterator(inputOutputFilters.size());
  197.             }

  198.             @Override
  199.             public void waitForFeaturesReceived(String waitFor)
  200.                             throws InterruptedException, SmackException, XMPPException {
  201.                 ModularXmppClientToServerConnection.this.waitForFeaturesReceived(waitFor);
  202.             }

  203.             @Override
  204.             public void newStreamOpenWaitForFeaturesSequence(String waitFor)
  205.                             throws InterruptedException, SmackException, XMPPException {
  206.                 ModularXmppClientToServerConnection.this.newStreamOpenWaitForFeaturesSequence(waitFor);
  207.             }

  208.             @Override
  209.             public SmackTlsContext getSmackTlsContext() {
  210.                 return ModularXmppClientToServerConnection.this.getSmackTlsContext();
  211.             }

  212.             @Override
  213.             public <SN extends Nonza, FN extends Nonza> SN sendAndWaitForResponse(Nonza nonza,
  214.                             Class<SN> successNonzaClass, Class<FN> failedNonzaClass) throws NoResponseException,
  215.                             NotConnectedException, FailedNonzaException, InterruptedException {
  216.                 return ModularXmppClientToServerConnection.this.sendAndWaitForResponse(nonza, successNonzaClass,
  217.                                 failedNonzaClass);
  218.             }

  219.             @Override
  220.             public void asyncGo(Runnable runnable) {
  221.                 AbstractXMPPConnection.asyncGo(runnable);
  222.             }

  223.             @Override
  224.             public void waitForConditionOrThrowConnectionException(Supplier<Boolean> condition, String waitFor)
  225.                             throws InterruptedException, SmackException, XMPPException {
  226.                 ModularXmppClientToServerConnection.this.waitForConditionOrThrowConnectionException(condition, waitFor);
  227.             }

  228.             @Override
  229.             public void notifyWaitingThreads() {
  230.                 ModularXmppClientToServerConnection.this.notifyWaitingThreads();
  231.             }

  232.             @Override
  233.             public void setCompressionEnabled(boolean compressionEnabled) {
  234.                 ModularXmppClientToServerConnection.this.compressionEnabled = compressionEnabled;
  235.             }

  236.             @Override
  237.             public void setTransport(XmppClientToServerTransport xmppTransport) {
  238.                 ModularXmppClientToServerConnection.this.activeTransport = xmppTransport;
  239.                 ModularXmppClientToServerConnection.this.connected = true;
  240.             }

  241.         };

  242.         // Construct the modules from the module descriptor. We do this before constructing the state graph, as the
  243.         // modules are sometimes used to construct the states.
  244.         for (ModularXmppClientToServerConnectionModuleDescriptor moduleDescriptor : configuration.moduleDescriptors) {
  245.             Class<? extends ModularXmppClientToServerConnectionModuleDescriptor> moduleDescriptorClass = moduleDescriptor.getClass();
  246.             ModularXmppClientToServerConnectionModule<? extends ModularXmppClientToServerConnectionModuleDescriptor> connectionModule = moduleDescriptor.constructXmppConnectionModule(
  247.                             connectionInternal);
  248.             connectionModules.put(moduleDescriptorClass, connectionModule);

  249.             XmppClientToServerTransport transport = connectionModule.getTransport();
  250.             // Not every module may provide a transport.
  251.             if (transport != null) {
  252.                 transports.put(moduleDescriptorClass, transport);
  253.             }
  254.         }

  255.         GraphVertex<StateDescriptor> initialStateDescriptorVertex = configuration.initialStateDescriptorVertex;
  256.         // Convert the graph of state descriptors to a graph of states, bound to this very connection.
  257.         currentStateVertex = StateDescriptorGraph.convertToStateGraph(initialStateDescriptorVertex, connectionInternal);
  258.     }

  259.     @SuppressWarnings("unchecked")
  260.     public <CM extends ModularXmppClientToServerConnectionModule<? extends ModularXmppClientToServerConnectionModuleDescriptor>> CM getConnectionModuleFor(
  261.                     Class<? extends ModularXmppClientToServerConnectionModuleDescriptor> descriptorClass) {
  262.         return (CM) connectionModules.get(descriptorClass);
  263.     }

  264.     @Override
  265.     protected void loginInternal(String username, String password, Resourcepart resource)
  266.                     throws XMPPException, SmackException, IOException, InterruptedException {
  267.         WalkStateGraphContext walkStateGraphContext = buildNewWalkTo(
  268.                         AuthenticatedAndResourceBoundStateDescriptor.class).withLoginContext(username, password,
  269.                                         resource).build();
  270.         walkStateGraph(walkStateGraphContext);
  271.     }

  272.     private WalkStateGraphContext.Builder buildNewWalkTo(Class<? extends StateDescriptor> finalStateClass) {
  273.         return WalkStateGraphContext.builder(currentStateVertex.getElement().getStateDescriptor().getClass(),
  274.                         finalStateClass);
  275.     }

  276.     /**
  277.      * Unwind the state. This will revert the effects of the state by calling {@link State#resetState()} prior issuing a
  278.      * connection state event of {@link ConnectionStateEvent#StateRevertBackwardsWalk}.
  279.      *
  280.      * @param revertedState the state which is going to get reverted.
  281.      */
  282.     private void unwindState(State revertedState) {
  283.         invokeConnectionStateMachineListener(new ConnectionStateEvent.StateRevertBackwardsWalk(revertedState));
  284.         revertedState.resetState();
  285.     }

  286.     private void walkStateGraph(WalkStateGraphContext walkStateGraphContext)
  287.                     throws XMPPException, IOException, SmackException, InterruptedException {
  288.         // Save a copy of the current state
  289.         GraphVertex<State> previousStateVertex = currentStateVertex;
  290.         try {
  291.             walkStateGraphInternal(walkStateGraphContext);
  292.         } catch (IOException | SmackException | InterruptedException | XMPPException e) {
  293.             currentStateVertex = previousStateVertex;
  294.             // Unwind the state.
  295.             State revertedState = currentStateVertex.getElement();
  296.             unwindState(revertedState);
  297.             throw e;
  298.         }
  299.     }

  300.     private void walkStateGraphInternal(WalkStateGraphContext walkStateGraphContext)
  301.                     throws IOException, SmackException, InterruptedException, XMPPException {
  302.         // Save a copy of the current state
  303.         final GraphVertex<State> initialStateVertex = currentStateVertex;
  304.         final State initialState = initialStateVertex.getElement();
  305.         final StateDescriptor initialStateDescriptor = initialState.getStateDescriptor();

  306.         walkStateGraphContext.recordWalkTo(initialState);

  307.         // Check if this is the walk's final state.
  308.         if (walkStateGraphContext.isWalksFinalState(initialStateDescriptor)) {
  309.             // If this is used as final state, then it should be marked as such.
  310.             assert initialStateDescriptor.isFinalState();

  311.             // We reached the final state.
  312.             invokeConnectionStateMachineListener(new ConnectionStateEvent.FinalStateReached(initialState));
  313.             return;
  314.         }

  315.         List<GraphVertex<State>> outgoingStateEdges = initialStateVertex.getOutgoingEdges();

  316.         // See if we need to handle mandatory intermediate states.
  317.         GraphVertex<State> mandatoryIntermediateStateVertex = walkStateGraphContext.maybeReturnMandatoryImmediateState(
  318.                         outgoingStateEdges);
  319.         if (mandatoryIntermediateStateVertex != null) {
  320.             StateTransitionResult reason = attemptEnterState(mandatoryIntermediateStateVertex, walkStateGraphContext);

  321.             if (reason instanceof StateTransitionResult.Success) {
  322.                 walkStateGraph(walkStateGraphContext);
  323.                 return;
  324.             }

  325.             // We could not enter a mandatory intermediate state. Throw here.
  326.             throw new StateMachineException.SmackMandatoryStateFailedException(
  327.                             mandatoryIntermediateStateVertex.getElement(), reason);
  328.         }

  329.         for (Iterator<GraphVertex<State>> it = outgoingStateEdges.iterator(); it.hasNext();) {
  330.             GraphVertex<State> successorStateVertex = it.next();
  331.             State successorState = successorStateVertex.getElement();

  332.             // Ignore successorStateVertex if the only way to the final state is via the initial state. This happens
  333.             // typically if we are in the ConnectedButUnauthenticated state on the way to ResourceboundAndAuthenticated,
  334.             // where we do not want to walk via InstantShutdown/Shutdown in a cycle over the initial state towards this
  335.             // state.
  336.             if (walkStateGraphContext.wouldCauseCycle(successorStateVertex)) {
  337.                 // Ignore this successor.
  338.                 invokeConnectionStateMachineListener(new ConnectionStateEvent.TransitionIgnoredDueCycle(
  339.                                 initialStateVertex, successorStateVertex));
  340.             } else {
  341.                 StateTransitionResult result = attemptEnterState(successorStateVertex, walkStateGraphContext);

  342.                 if (result instanceof StateTransitionResult.Success) {
  343.                     break;
  344.                 }

  345.                 // If attemptEnterState did not throw and did not return a value of type TransitionSuccessResult, then
  346.                 // we
  347.                 // just record this value and go on from there. Note that reason may be null, which is returned by
  348.                 // attemptEnterState in case the state was already successfully handled. If this is the case, then we
  349.                 // don't
  350.                 // record it.
  351.                 if (result != null) {
  352.                     walkStateGraphContext.recordFailedState(successorState, result);
  353.                 }
  354.             }

  355.             if (!it.hasNext()) {
  356.                 throw StateMachineException.SmackStateGraphDeadEndException.from(walkStateGraphContext,
  357.                                 initialStateVertex);
  358.             }
  359.         }

  360.         // Walk the state graph by recursion.
  361.         walkStateGraph(walkStateGraphContext);
  362.     }

  363.     /**
  364.      * Attempt to enter a state. Note that this method may return <code>null</code> if this state can be safely ignored.
  365.      *
  366.      * @param successorStateVertex the successor state vertex.
  367.      * @param walkStateGraphContext the "walk state graph" context.
  368.      * @return A state transition result or <code>null</code> if this state can be ignored.
  369.      * @throws SmackException if Smack detected an exceptional situation.
  370.      * @throws XMPPException if an XMPP protocol error was received.
  371.      * @throws IOException if an I/O error occurred.
  372.      * @throws InterruptedException if the calling thread was interrupted.
  373.      */
  374.     private StateTransitionResult attemptEnterState(GraphVertex<State> successorStateVertex,
  375.                     WalkStateGraphContext walkStateGraphContext)
  376.                     throws SmackException, XMPPException, IOException, InterruptedException {
  377.         final GraphVertex<State> initialStateVertex = currentStateVertex;
  378.         final State initialState = initialStateVertex.getElement();
  379.         final State successorState = successorStateVertex.getElement();
  380.         final StateDescriptor successorStateDescriptor = successorState.getStateDescriptor();

  381.         if (!successorStateDescriptor.isMultiVisitState()
  382.                         && walkStateGraphContext.stateAlreadyVisited(successorState)) {
  383.             // This can happen if a state leads back to the state where it originated from. See for example the
  384.             // 'Compression' state. We return 'null' here to signal that the state can safely be ignored.
  385.             return null;
  386.         }

  387.         if (successorStateDescriptor.isNotImplemented()) {
  388.             StateTransitionResult.TransitionImpossibleBecauseNotImplemented transtionImpossibleBecauseNotImplemented = new StateTransitionResult.TransitionImpossibleBecauseNotImplemented(
  389.                             successorStateDescriptor);
  390.             invokeConnectionStateMachineListener(new ConnectionStateEvent.TransitionNotPossible(initialState,
  391.                             successorState, transtionImpossibleBecauseNotImplemented));
  392.             return transtionImpossibleBecauseNotImplemented;
  393.         }

  394.         final StateTransitionResult.AttemptResult transitionAttemptResult;
  395.         try {
  396.             StateTransitionResult.TransitionImpossible transitionImpossible = successorState.isTransitionToPossible(
  397.                             walkStateGraphContext);
  398.             if (transitionImpossible != null) {
  399.                 invokeConnectionStateMachineListener(new ConnectionStateEvent.TransitionNotPossible(initialState,
  400.                                 successorState, transitionImpossible));
  401.                 return transitionImpossible;
  402.             }

  403.             invokeConnectionStateMachineListener(
  404.                             new ConnectionStateEvent.AboutToTransitionInto(initialState, successorState));
  405.             transitionAttemptResult = successorState.transitionInto(walkStateGraphContext);
  406.         } catch (SmackException | IOException | InterruptedException | XMPPException e) {
  407.             // Unwind the state here too, since this state will not be unwound by walkStateGraph(), as it will not
  408.             // become a predecessor state in the walk.
  409.             unwindState(successorState);
  410.             throw e;
  411.         }
  412.         if (transitionAttemptResult instanceof StateTransitionResult.Failure) {
  413.             StateTransitionResult.Failure transitionFailureResult = (StateTransitionResult.Failure) transitionAttemptResult;
  414.             invokeConnectionStateMachineListener(new ConnectionStateEvent.TransitionFailed(initialState, successorState,
  415.                             transitionFailureResult));
  416.             return transitionAttemptResult;
  417.         }

  418.         // If transitionAttemptResult is not an instance of TransitionFailureResult, then it has to be of type
  419.         // TransitionSuccessResult.
  420.         StateTransitionResult.Success transitionSuccessResult = (StateTransitionResult.Success) transitionAttemptResult;

  421.         currentStateVertex = successorStateVertex;
  422.         invokeConnectionStateMachineListener(
  423.                         new ConnectionStateEvent.SuccessfullyTransitionedInto(successorState, transitionSuccessResult));

  424.         return transitionSuccessResult;
  425.     }

  426.     @Override
  427.     protected void sendInternal(TopLevelStreamElement element) throws NotConnectedException, InterruptedException {
  428.         final XmppClientToServerTransport transport = activeTransport;
  429.         if (transport == null) {
  430.             throw new NotConnectedException();
  431.         }

  432.         outgoingElementsQueue.put(element);
  433.         transport.notifyAboutNewOutgoingElements();
  434.     }

  435.     @Override
  436.     protected void sendNonBlockingInternal(TopLevelStreamElement element) throws NotConnectedException, OutgoingQueueFullException {
  437.         final XmppClientToServerTransport transport = activeTransport;
  438.         if (transport == null) {
  439.             throw new NotConnectedException();
  440.         }

  441.         boolean enqueued = outgoingElementsQueue.offer(element);
  442.         if (!enqueued) {
  443.             throw new OutgoingQueueFullException();
  444.         }

  445.         transport.notifyAboutNewOutgoingElements();
  446.     }

  447.     @Override
  448.     protected void shutdown() {
  449.         shutdown(false);
  450.     }

  451.     @Override
  452.     public synchronized void instantShutdown() {
  453.         shutdown(true);
  454.     }

  455.     @Override
  456.     public ModularXmppClientToServerConnectionConfiguration getConfiguration() {
  457.         return configuration;
  458.     }

  459.     private void shutdown(boolean instant) {
  460.         Class<? extends StateDescriptor> mandatoryIntermediateState;
  461.         if (instant) {
  462.             mandatoryIntermediateState = InstantShutdownStateDescriptor.class;
  463.         } else {
  464.             mandatoryIntermediateState = ShutdownStateDescriptor.class;
  465.         }

  466.         WalkStateGraphContext context = buildNewWalkTo(
  467.                         DisconnectedStateDescriptor.class).withMandatoryIntermediateState(
  468.                                         mandatoryIntermediateState).build();

  469.         try {
  470.             walkStateGraph(context);
  471.         } catch (IOException | SmackException | InterruptedException | XMPPException e) {
  472.             throw new IllegalStateException("A walk to disconnected state should never throw", e);
  473.         }
  474.     }

  475.     private SSLSession getSSLSession() {
  476.         final XmppClientToServerTransport transport = activeTransport;
  477.         if (transport == null) {
  478.             return null;
  479.         }
  480.         return transport.getSslSession();
  481.     }

  482.     @Override
  483.     protected void afterFeaturesReceived() {
  484.         featuresReceived = true;
  485.         notifyWaitingThreads();
  486.     }

  487.     private void parseAndProcessElement(String element) {
  488.         try {
  489.             XmlPullParser parser = PacketParserUtils.getParserFor(element);

  490.             // Skip the enclosing stream open what is guaranteed to be there.
  491.             parser.next();

  492.             XmlPullParser.Event event = parser.getEventType();
  493.             outerloop: while (true) {
  494.                 switch (event) {
  495.                 case START_ELEMENT:
  496.                     final String name = parser.getName();
  497.                     // Note that we don't handle "stream" here as it's done in the splitter.
  498.                     switch (name) {
  499.                     case Message.ELEMENT:
  500.                     case IQ.IQ_ELEMENT:
  501.                     case Presence.ELEMENT:
  502.                         try {
  503.                             parseAndProcessStanza(parser);
  504.                         } finally {
  505.                             // TODO: Here would be the following stream management code.
  506.                             // clientHandledStanzasCount = SMUtils.incrementHeight(clientHandledStanzasCount);
  507.                         }
  508.                         break;
  509.                     case "error":
  510.                         StreamError streamError = PacketParserUtils.parseStreamError(parser, null);
  511.                         StreamErrorException streamErrorException = new StreamErrorException(streamError);
  512.                         currentXmppException = streamErrorException;
  513.                         notifyWaitingThreads();
  514.                         throw streamErrorException;
  515.                     case "features":
  516.                         parseFeatures(parser);
  517.                         afterFeaturesReceived();
  518.                         break;
  519.                     default:
  520.                         parseAndProcessNonza(parser);
  521.                         break;
  522.                     }
  523.                     break;
  524.                 case END_DOCUMENT:
  525.                     break outerloop;
  526.                 default: // fall out
  527.                 }
  528.                 event = parser.next();
  529.             }
  530.         } catch (XmlPullParserException | IOException | InterruptedException | StreamErrorException
  531.                         | SmackParsingException e) {
  532.             notifyConnectionError(e);
  533.         }
  534.     }

  535.     private synchronized void prepareToWaitForFeaturesReceived() {
  536.         featuresReceived = false;
  537.     }

  538.     private void waitForFeaturesReceived(String waitFor) throws InterruptedException, SmackException, XMPPException {
  539.         waitForConditionOrThrowConnectionException(() -> featuresReceived, waitFor);
  540.     }

  541.     @Override
  542.     protected AbstractStreamOpen getStreamOpen(DomainBareJid to, CharSequence from, String id, String lang) {
  543.         StreamOpenAndCloseFactory streamOpenAndCloseFactory = activeTransport.getStreamOpenAndCloseFactory();
  544.         return streamOpenAndCloseFactory.createStreamOpen(to, from, id, lang);
  545.     }

  546.     private void newStreamOpenWaitForFeaturesSequence(String waitFor)
  547.                     throws InterruptedException, SmackException, XMPPException {
  548.         prepareToWaitForFeaturesReceived();

  549.         // Create StreamOpen from StreamOpenAndCloseFactory via underlying transport.
  550.         StreamOpenAndCloseFactory streamOpenAndCloseFactory = activeTransport.getStreamOpenAndCloseFactory();
  551.         CharSequence from = null;
  552.         CharSequence localpart = connectionInternal.connection.getConfiguration().getUsername();
  553.         DomainBareJid xmppServiceDomain = getXMPPServiceDomain();
  554.         if (localpart != null) {
  555.             from = XmppStringUtils.completeJidFrom(localpart, xmppServiceDomain);
  556.         }
  557.         AbstractStreamOpen streamOpen = streamOpenAndCloseFactory.createStreamOpen(xmppServiceDomain, from,
  558.                         getStreamId(), getConfiguration().getXmlLang());
  559.         sendStreamOpen(streamOpen);

  560.         waitForFeaturesReceived(waitFor);
  561.     }

  562.     private void sendStreamOpen(AbstractStreamOpen streamOpen) throws NotConnectedException, InterruptedException {
  563.         sendNonza(streamOpen);
  564.         updateOutgoingStreamXmlEnvironmentOnStreamOpen(streamOpen);
  565.     }

  566.     public static class DisconnectedStateDescriptor extends StateDescriptor {
  567.         protected DisconnectedStateDescriptor() {
  568.             super(DisconnectedState.class, StateDescriptor.Property.finalState);
  569.             addSuccessor(LookupRemoteConnectionEndpointsStateDescriptor.class);
  570.         }
  571.     }

  572.     private final class DisconnectedState extends State {

  573.         private DisconnectedState(StateDescriptor stateDescriptor,
  574.                         ModularXmppClientToServerConnectionInternal connectionInternal) {
  575.             super(stateDescriptor, connectionInternal);
  576.         }

  577.         @Override
  578.         public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) {
  579.             synchronized (ModularXmppClientToServerConnection.this) {
  580.                 if (inputOutputFilters.isEmpty()) {
  581.                     previousInputOutputFilters = null;
  582.                 } else {
  583.                     previousInputOutputFilters = new ArrayList<>(inputOutputFilters.size());
  584.                     previousInputOutputFilters.addAll(inputOutputFilters);
  585.                     inputOutputFilters.clear();
  586.                 }
  587.             }

  588.             // Reset all states we have visited when transitioning from disconnected to authenticated. This assumes that
  589.             // every state after authenticated does not need to be reset.
  590.             ListIterator<State> it = walkFromDisconnectToAuthenticated.listIterator(
  591.                             walkFromDisconnectToAuthenticated.size());
  592.             while (it.hasPrevious()) {
  593.                 State stateToReset = it.previous();
  594.                 stateToReset.resetState();
  595.             }
  596.             walkFromDisconnectToAuthenticated = null;

  597.             return StateTransitionResult.Success.EMPTY_INSTANCE;
  598.         }
  599.     }

  600.     public static final class LookupRemoteConnectionEndpointsStateDescriptor extends StateDescriptor {
  601.         private LookupRemoteConnectionEndpointsStateDescriptor() {
  602.             super(LookupRemoteConnectionEndpointsState.class);
  603.         }
  604.     }

  605.     private final class LookupRemoteConnectionEndpointsState extends State {
  606.         boolean outgoingElementsQueueWasShutdown;

  607.         private LookupRemoteConnectionEndpointsState(StateDescriptor stateDescriptor,
  608.                         ModularXmppClientToServerConnectionInternal connectionInternal) {
  609.             super(stateDescriptor, connectionInternal);
  610.         }

  611.         @Override
  612.         public AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) throws XMPPErrorException,
  613.                         SASLErrorException, IOException, SmackException, InterruptedException, FailedNonzaException {
  614.             // There is a challenge here: We are going to trigger the discovery of endpoints which will run
  615.             // asynchronously. After a timeout, all discovered endpoints are collected. To prevent stale results from
  616.             // previous discover runs, the results are communicated via SmackFuture, so that we always handle the most
  617.             // up-to-date results.

  618.             Map<XmppClientToServerTransport, List<SmackFuture<LookupConnectionEndpointsResult, Exception>>> lookupFutures = new HashMap<>(
  619.                             transports.size());

  620.             final int numberOfFutures;
  621.             {
  622.                 List<SmackFuture<?, ?>> allFutures = new ArrayList<>();
  623.                 for (XmppClientToServerTransport transport : transports.values()) {
  624.                     // First we clear the transport of any potentially previously discovered connection endpoints.
  625.                     transport.resetDiscoveredConnectionEndpoints();

  626.                     // Ask the transport to start the discovery of remote connection endpoints asynchronously.
  627.                     List<SmackFuture<LookupConnectionEndpointsResult, Exception>> transportFutures = transport.lookupConnectionEndpoints();

  628.                     lookupFutures.put(transport, transportFutures);
  629.                     allFutures.addAll(transportFutures);
  630.                 }

  631.                 numberOfFutures = allFutures.size();

  632.                 // Wait until all features are ready or if the timeout occurs. Note that we do not inspect and react the
  633.                 // return value of SmackFuture.await() as we want to collect the LookupConnectionEndpointsFailed later.
  634.                 SmackFuture.await(allFutures, getReplyTimeout(), TimeUnit.MILLISECONDS);
  635.             }

  636.             // Note that we do not pass the lookupFailures in case there is at least one successful transport. The
  637.             // lookup failures are also recording in LookupConnectionEndpointsSuccess, e.g. as part of
  638.             // RemoteXmppTcpConnectionEndpoints.Result.
  639.             List<LookupConnectionEndpointsFailed> lookupFailures = new ArrayList<>(numberOfFutures);

  640.             boolean atLeastOneConnectionEndpointDiscovered = false;
  641.             for (Map.Entry<XmppClientToServerTransport, List<SmackFuture<LookupConnectionEndpointsResult, Exception>>> entry : lookupFutures.entrySet()) {
  642.                 XmppClientToServerTransport transport = entry.getKey();

  643.                 for (SmackFuture<LookupConnectionEndpointsResult, Exception> future : entry.getValue()) {
  644.                     LookupConnectionEndpointsResult result = future.getIfAvailable();

  645.                     if (result == null) {
  646.                         continue;
  647.                     }

  648.                     if (result instanceof LookupConnectionEndpointsFailed) {
  649.                         LookupConnectionEndpointsFailed lookupFailure = (LookupConnectionEndpointsFailed) result;
  650.                         lookupFailures.add(lookupFailure);
  651.                         continue;
  652.                     }

  653.                     LookupConnectionEndpointsSuccess successResult = (LookupConnectionEndpointsSuccess) result;

  654.                     // Arm the transport with the success result, so that its information can be used by the transport
  655.                     // to establish the connection.
  656.                     transport.loadConnectionEndpoints(successResult);

  657.                     // Mark that the connection attempt can continue.
  658.                     atLeastOneConnectionEndpointDiscovered = true;
  659.                 }
  660.             }

  661.             if (!atLeastOneConnectionEndpointDiscovered) {
  662.                 throw SmackException.NoEndpointsDiscoveredException.from(lookupFailures);
  663.             }

  664.             if (!lookupFailures.isEmpty()) {
  665.                 // TODO: Put those non-fatal lookup failures into a sink of the connection so that the user is able to
  666.                 // be aware of them.
  667.             }

  668.             // Even though the outgoing elements queue is unrelated to the lookup remote connection endpoints state, we
  669.             // do start the queue at this point. The transports will need it available, and we use the state's reset()
  670.             // function to close the queue again on failure.
  671.             outgoingElementsQueueWasShutdown = outgoingElementsQueue.start();

  672.             return StateTransitionResult.Success.EMPTY_INSTANCE;
  673.         }

  674.         @Override
  675.         public void resetState() {
  676.             for (XmppClientToServerTransport transport : transports.values()) {
  677.                 transport.resetDiscoveredConnectionEndpoints();
  678.             }

  679.             if (outgoingElementsQueueWasShutdown) {
  680.                 // Reset the outgoing elements queue in this state, since we also start it in this state.
  681.                 outgoingElementsQueue.shutdown();
  682.             }
  683.         }
  684.     }

  685.     public static final class ConnectedButUnauthenticatedStateDescriptor extends StateDescriptor {
  686.         private ConnectedButUnauthenticatedStateDescriptor() {
  687.             super(ConnectedButUnauthenticatedState.class, StateDescriptor.Property.finalState);
  688.             addSuccessor(SaslAuthenticationStateDescriptor.class);
  689.             addSuccessor(InstantShutdownStateDescriptor.class);
  690.             addSuccessor(ShutdownStateDescriptor.class);
  691.         }
  692.     }

  693.     private final class ConnectedButUnauthenticatedState extends State {
  694.         private ConnectedButUnauthenticatedState(StateDescriptor stateDescriptor,
  695.                         ModularXmppClientToServerConnectionInternal connectionInternal) {
  696.             super(stateDescriptor, connectionInternal);
  697.         }

  698.         @Override
  699.         public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) {
  700.             assert walkFromDisconnectToAuthenticated == null;

  701.             if (walkStateGraphContext.isWalksFinalState(getStateDescriptor())) {
  702.                 // If this is the final state, then record the walk so far.
  703.                 walkFromDisconnectToAuthenticated = walkStateGraphContext.getWalk();
  704.             }

  705.             connected = true;
  706.             return StateTransitionResult.Success.EMPTY_INSTANCE;
  707.         }

  708.         @Override
  709.         public void resetState() {
  710.             connected = false;
  711.         }
  712.     }

  713.     public static final class SaslAuthenticationStateDescriptor extends StateDescriptor {
  714.         private SaslAuthenticationStateDescriptor() {
  715.             super(SaslAuthenticationState.class, "RFC 6120 § 6");
  716.             addSuccessor(AuthenticatedButUnboundStateDescriptor.class);
  717.         }
  718.     }

  719.     private final class SaslAuthenticationState extends State {
  720.         private SaslAuthenticationState(StateDescriptor stateDescriptor,
  721.                         ModularXmppClientToServerConnectionInternal connectionInternal) {
  722.             super(stateDescriptor, connectionInternal);
  723.         }

  724.         @Override
  725.         public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
  726.                         throws IOException, SmackException, InterruptedException, XMPPException {
  727.             prepareToWaitForFeaturesReceived();

  728.             LoginContext loginContext = walkStateGraphContext.getLoginContext();
  729.             SASLMechanism usedSaslMechanism = authenticate(loginContext.username, loginContext.password,
  730.                             config.getAuthzid(), getSSLSession());
  731.             // authenticate() will only return if the SASL authentication was successful, but we also need to wait for
  732.             // the next round of stream features.

  733.             waitForFeaturesReceived("server stream features after SASL authentication");

  734.             return new SaslAuthenticationSuccessResult(usedSaslMechanism);
  735.         }
  736.     }

  737.     public static final class SaslAuthenticationSuccessResult extends StateTransitionResult.Success {
  738.         private final String saslMechanismName;

  739.         private SaslAuthenticationSuccessResult(SASLMechanism usedSaslMechanism) {
  740.             super("SASL authentication successfull using " + usedSaslMechanism.getName());
  741.             this.saslMechanismName = usedSaslMechanism.getName();
  742.         }

  743.         public String getSaslMechanismName() {
  744.             return saslMechanismName;
  745.         }
  746.     }

  747.     public static final class AuthenticatedButUnboundStateDescriptor extends StateDescriptor {
  748.         private AuthenticatedButUnboundStateDescriptor() {
  749.             super(StateDescriptor.Property.multiVisitState);
  750.             addSuccessor(ResourceBindingStateDescriptor.class);
  751.         }
  752.     }

  753.     public static final class ResourceBindingStateDescriptor extends StateDescriptor {
  754.         private ResourceBindingStateDescriptor() {
  755.             super(ResourceBindingState.class, "RFC 6120 § 7");
  756.             addSuccessor(AuthenticatedAndResourceBoundStateDescriptor.class);
  757.         }
  758.     }

  759.     private final class ResourceBindingState extends State {
  760.         private ResourceBindingState(StateDescriptor stateDescriptor,
  761.                         ModularXmppClientToServerConnectionInternal connectionInternal) {
  762.             super(stateDescriptor, connectionInternal);
  763.         }

  764.         @Override
  765.         public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
  766.                         throws IOException, SmackException, InterruptedException, XMPPException {
  767.             // Calling bindResourceAndEstablishSession() below requires the lastFeaturesReceived sync point to be
  768.             // signaled.
  769.             // Since we entered this state, the FSM has decided that the last features have been received, hence signal
  770.             // the sync point.
  771.             lastFeaturesReceived = true;
  772.             notifyWaitingThreads();

  773.             LoginContext loginContext = walkStateGraphContext.getLoginContext();
  774.             Resourcepart resource = bindResourceAndEstablishSession(loginContext.resource);

  775.             // TODO: This should be a field in the Stream Management (SM) module. Here should be hook which the SM
  776.             // module can use to set the field instead.
  777.             streamResumed = false;

  778.             return new ResourceBoundResult(resource, loginContext.resource);
  779.         }
  780.     }

  781.     public static final class ResourceBoundResult extends StateTransitionResult.Success {
  782.         private final Resourcepart resource;

  783.         private ResourceBoundResult(Resourcepart boundResource, Resourcepart requestedResource) {
  784.             super("Resource '" + boundResource + "' bound (requested: '" + requestedResource + "')");
  785.             this.resource = boundResource;
  786.         }

  787.         public Resourcepart getResource() {
  788.             return resource;
  789.         }
  790.     }

  791.     private boolean compressionEnabled;

  792.     @Override
  793.     public boolean isUsingCompression() {
  794.         return compressionEnabled;
  795.     }

  796.     public static final class AuthenticatedAndResourceBoundStateDescriptor extends StateDescriptor {
  797.         private AuthenticatedAndResourceBoundStateDescriptor() {
  798.             super(AuthenticatedAndResourceBoundState.class, StateDescriptor.Property.finalState);
  799.             addSuccessor(InstantShutdownStateDescriptor.class);
  800.             addSuccessor(ShutdownStateDescriptor.class);
  801.         }
  802.     }

  803.     private final class AuthenticatedAndResourceBoundState extends State {
  804.         private AuthenticatedAndResourceBoundState(StateDescriptor stateDescriptor,
  805.                         ModularXmppClientToServerConnectionInternal connectionInternal) {
  806.             super(stateDescriptor, connectionInternal);
  807.         }

  808.         @Override
  809.         public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
  810.                         throws NotConnectedException, InterruptedException {
  811.             if (walkFromDisconnectToAuthenticated != null) {
  812.                 // If there was already a previous walk to ConnectedButUnauthenticated, then the context of the current
  813.                 // walk must not start from the 'Disconnected' state.
  814.                 assert walkStateGraphContext.getWalk().get(
  815.                                 0).getStateDescriptor().getClass() != DisconnectedStateDescriptor.class;
  816.                 // Append the current walk to the previous one.
  817.                 walkStateGraphContext.appendWalkTo(walkFromDisconnectToAuthenticated);
  818.             } else {
  819.                 walkFromDisconnectToAuthenticated = new ArrayList<>(walkStateGraphContext.getWalkLength() + 1);
  820.                 walkStateGraphContext.appendWalkTo(walkFromDisconnectToAuthenticated);
  821.             }
  822.             walkFromDisconnectToAuthenticated.add(this);

  823.             afterSuccessfulLogin(streamResumed);

  824.             return StateTransitionResult.Success.EMPTY_INSTANCE;
  825.         }

  826.         @Override
  827.         public void resetState() {
  828.             authenticated = false;
  829.         }
  830.     }

  831.     static final class ShutdownStateDescriptor extends StateDescriptor {
  832.         private ShutdownStateDescriptor() {
  833.             super(ShutdownState.class);
  834.             addSuccessor(CloseConnectionStateDescriptor.class);
  835.         }
  836.     }

  837.     private final class ShutdownState extends State {
  838.         private ShutdownState(StateDescriptor stateDescriptor,
  839.                         ModularXmppClientToServerConnectionInternal connectionInternal) {
  840.             super(stateDescriptor, connectionInternal);
  841.         }

  842.         @Override
  843.         public StateTransitionResult.TransitionImpossible isTransitionToPossible(
  844.                         WalkStateGraphContext walkStateGraphContext) {
  845.             ensureNotOnOurWayToAuthenticatedAndResourceBound(walkStateGraphContext);
  846.             return null;
  847.         }

  848.         @Override
  849.         public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) {
  850.             closingStreamReceived = false;

  851.             StreamOpenAndCloseFactory openAndCloseFactory = activeTransport.getStreamOpenAndCloseFactory();
  852.             AbstractStreamClose closeStreamElement = openAndCloseFactory.createStreamClose();
  853.             boolean streamCloseIssued = outgoingElementsQueue.offerAndShutdown(closeStreamElement);

  854.             if (streamCloseIssued) {
  855.                 activeTransport.notifyAboutNewOutgoingElements();

  856.                 boolean successfullyReceivedStreamClose = waitForClosingStreamTagFromServer();

  857.                 if (successfullyReceivedStreamClose) {
  858.                     for (Iterator<XmppInputOutputFilter> it = connectionInternal.getXmppInputOutputFilterBeginIterator(); it.hasNext();) {
  859.                         XmppInputOutputFilter filter = it.next();
  860.                         filter.closeInputOutput();
  861.                     }

  862.                     // Closing the filters may produced new outgoing data. Notify the transport about it.
  863.                     activeTransport.afterFiltersClosed();

  864.                     for (Iterator<XmppInputOutputFilter> it = connectionInternal.getXmppInputOutputFilterBeginIterator(); it.hasNext();) {
  865.                         XmppInputOutputFilter filter = it.next();
  866.                         try {
  867.                             filter.waitUntilInputOutputClosed();
  868.                         } catch (IOException | CertificateException | InterruptedException | SmackException
  869.                                         | XMPPException e) {
  870.                             LOGGER.log(Level.WARNING, "waitUntilInputOutputClosed() threw", e);
  871.                         }
  872.                     }

  873.                     // For correctness we set authenticated to false here, even though we will later again set it to
  874.                     // false in the disconnected state.
  875.                     authenticated = false;
  876.                 }
  877.             }

  878.             return StateTransitionResult.Success.EMPTY_INSTANCE;
  879.         }
  880.     }

  881.     static final class InstantShutdownStateDescriptor extends StateDescriptor {
  882.         private InstantShutdownStateDescriptor() {
  883.             super(InstantShutdownState.class);
  884.             addSuccessor(CloseConnectionStateDescriptor.class);
  885.         }
  886.     }

  887.     private static final class InstantShutdownState extends NoOpState {
  888.         private InstantShutdownState(ModularXmppClientToServerConnection connection, StateDescriptor stateDescriptor,
  889.                         ModularXmppClientToServerConnectionInternal connectionInternal) {
  890.             super(connection, stateDescriptor, connectionInternal);
  891.         }

  892.         @Override
  893.         public StateTransitionResult.TransitionImpossible isTransitionToPossible(
  894.                         WalkStateGraphContext walkStateGraphContext) {
  895.             ensureNotOnOurWayToAuthenticatedAndResourceBound(walkStateGraphContext);
  896.             return null;
  897.         }
  898.     }

  899.     private static final class CloseConnectionStateDescriptor extends StateDescriptor {
  900.         private CloseConnectionStateDescriptor() {
  901.             super(CloseConnectionState.class);
  902.             addSuccessor(DisconnectedStateDescriptor.class);
  903.         }
  904.     }

  905.     private final class CloseConnectionState extends State {
  906.         private CloseConnectionState(StateDescriptor stateDescriptor,
  907.                         ModularXmppClientToServerConnectionInternal connectionInternal) {
  908.             super(stateDescriptor, connectionInternal);
  909.         }

  910.         @Override
  911.         public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) {
  912.             activeTransport.disconnect();
  913.             activeTransport = null;

  914.             authenticated = connected = false;

  915.             return StateTransitionResult.Success.EMPTY_INSTANCE;
  916.         }
  917.     }

  918.     public void addConnectionStateMachineListener(ConnectionStateMachineListener connectionStateMachineListener) {
  919.         connectionStateMachineListeners.add(connectionStateMachineListener);
  920.     }

  921.     public boolean removeConnectionStateMachineListener(ConnectionStateMachineListener connectionStateMachineListener) {
  922.         return connectionStateMachineListeners.remove(connectionStateMachineListener);
  923.     }

  924.     private void invokeConnectionStateMachineListener(ConnectionStateEvent connectionStateEvent) {
  925.         if (connectionStateMachineListeners.isEmpty()) {
  926.             return;
  927.         }

  928.         ASYNC_BUT_ORDERED.performAsyncButOrdered(this, () -> {
  929.             for (ConnectionStateMachineListener connectionStateMachineListener : connectionStateMachineListeners) {
  930.                 connectionStateMachineListener.onConnectionStateEvent(connectionStateEvent, this);
  931.             }
  932.         });
  933.     }

  934.     @Override
  935.     public boolean isSecureConnection() {
  936.         final XmppClientToServerTransport transport = activeTransport;
  937.         if (transport == null) {
  938.             return false;
  939.         }
  940.         return transport.isTransportSecured();
  941.     }

  942.     @Override
  943.     protected void connectInternal() throws SmackException, IOException, XMPPException, InterruptedException {
  944.         WalkStateGraphContext walkStateGraphContext = buildNewWalkTo(
  945.                         ConnectedButUnauthenticatedStateDescriptor.class).build();
  946.         walkStateGraph(walkStateGraphContext);
  947.     }

  948.     @Override
  949.     public InetAddress getLocalAddress() {
  950.         return null;
  951.     }

  952.     private Map<String, Object> getFilterStats() {
  953.         Collection<XmppInputOutputFilter> filters;
  954.         synchronized (this) {
  955.             if (inputOutputFilters.isEmpty() && previousInputOutputFilters != null) {
  956.                 filters = previousInputOutputFilters;
  957.             } else {
  958.                 filters = inputOutputFilters;
  959.             }
  960.         }

  961.         Map<String, Object> filterStats = new HashMap<>(filters.size());
  962.         for (XmppInputOutputFilter xmppInputOutputFilter : filters) {
  963.             Object stats = xmppInputOutputFilter.getStats();
  964.             String filterName = xmppInputOutputFilter.getFilterName();

  965.             filterStats.put(filterName, stats);
  966.         }

  967.         return filterStats;
  968.     }

  969.     public Stats getStats() {
  970.         Map<Class<? extends ModularXmppClientToServerConnectionModuleDescriptor>, XmppClientToServerTransport.Stats> transportsStats = new HashMap<>(
  971.                         transports.size());
  972.         for (Map.Entry<Class<? extends ModularXmppClientToServerConnectionModuleDescriptor>, XmppClientToServerTransport> entry : transports.entrySet()) {
  973.             XmppClientToServerTransport.Stats transportStats = entry.getValue().getStats();

  974.             transportsStats.put(entry.getKey(), transportStats);
  975.         }

  976.         Map<String, Object> filterStats = getFilterStats();

  977.         return new Stats(transportsStats, filterStats);
  978.     }

  979.     public static final class Stats extends AbstractStats {
  980.         public final Map<Class<? extends ModularXmppClientToServerConnectionModuleDescriptor>, XmppClientToServerTransport.Stats> transportsStats;
  981.         public final Map<String, Object> filtersStats;

  982.         private Stats(Map<Class<? extends ModularXmppClientToServerConnectionModuleDescriptor>, XmppClientToServerTransport.Stats> transportsStats,
  983.                         Map<String, Object> filtersStats) {
  984.             this.transportsStats = Collections.unmodifiableMap(transportsStats);
  985.             this.filtersStats = Collections.unmodifiableMap(filtersStats);
  986.         }

  987.         @Override
  988.         public void appendStatsTo(ExtendedAppendable appendable) throws IOException {
  989.             StringUtils.appendHeading(appendable, "Connection stats", '#').append('\n');

  990.             for (Map.Entry<Class<? extends ModularXmppClientToServerConnectionModuleDescriptor>, XmppClientToServerTransport.Stats> entry : transportsStats.entrySet()) {
  991.                 Class<? extends ModularXmppClientToServerConnectionModuleDescriptor> transportClass = entry.getKey();
  992.                 XmppClientToServerTransport.Stats stats = entry.getValue();

  993.                 StringUtils.appendHeading(appendable, transportClass.getName());
  994.                 if (stats != null) {
  995.                     appendable.append(stats.toString());
  996.                 } else {
  997.                     appendable.append("No stats available.");
  998.                 }
  999.                 appendable.append('\n');
  1000.             }

  1001.             for (Map.Entry<String, Object> entry : filtersStats.entrySet()) {
  1002.                 String filterName = entry.getKey();
  1003.                 Object filterStats = entry.getValue();

  1004.                 StringUtils.appendHeading(appendable, filterName);
  1005.                 appendable.append(filterStats.toString()).append('\n');
  1006.             }
  1007.         }

  1008.     }
  1009. }