• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

igniterealtime / Smack / #2868

pending completion
#2868

push

github-actions

web-flow
Merge pull request #537 from MF1-MS/mf1-ms/use_xmpp_connection_as_local_socks5_address

Use XMPP connection as local socks5 address

10 of 10 new or added lines in 3 files covered. (100.0%)

16378 of 41845 relevant lines covered (39.14%)

0.39 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

18.14
/smack-core/src/main/java/org/jivesoftware/smack/c2s/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

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

35
import javax.net.ssl.SSLSession;
36

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

86
import org.jxmpp.jid.DomainBareJid;
87
import org.jxmpp.jid.parts.Resourcepart;
88
import org.jxmpp.util.XmppStringUtils;
89

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

137
    private static final Logger LOGGER = Logger.getLogger(
1✔
138
                    ModularXmppClientToServerConnectionConfiguration.class.getName());
1✔
139

140
    private final ArrayBlockingQueueWithShutdown<TopLevelStreamElement> outgoingElementsQueue = new ArrayBlockingQueueWithShutdown<>(
1✔
141
                    100, true);
142

143
    private XmppClientToServerTransport activeTransport;
144

145
    private final List<ConnectionStateMachineListener> connectionStateMachineListeners = new CopyOnWriteArrayList<>();
1✔
146

147
    private boolean featuresReceived;
148

149
    private boolean streamResumed;
150

151
    private GraphVertex<State> currentStateVertex;
152

153
    private List<State> walkFromDisconnectToAuthenticated;
154

155
    private final ModularXmppClientToServerConnectionConfiguration configuration;
156

157
    private final ModularXmppClientToServerConnectionInternal connectionInternal;
158

159
    private final Map<Class<? extends ModularXmppClientToServerConnectionModuleDescriptor>, ModularXmppClientToServerConnectionModule<? extends ModularXmppClientToServerConnectionModuleDescriptor>> connectionModules = new HashMap<>();
1✔
160

161
    private final Map<Class<? extends ModularXmppClientToServerConnectionModuleDescriptor>, XmppClientToServerTransport> transports = new HashMap<>();
1✔
162
    /**
163
     * This is one of those cases where the field is modified by one thread and read by another. We currently use
164
     * CopyOnWriteArrayList but should potentially use a VarHandle once Smack supports them.
165
     */
166
    private final List<XmppInputOutputFilter> inputOutputFilters = new CopyOnWriteArrayList<>();
1✔
167

168
    private List<XmppInputOutputFilter> previousInputOutputFilters;
169

170
    public ModularXmppClientToServerConnection(ModularXmppClientToServerConnectionConfiguration configuration) {
171
        super(configuration);
1✔
172

173
        this.configuration = configuration;
1✔
174

175
        // Construct the internal connection API.
176
        connectionInternal = new ModularXmppClientToServerConnectionInternal(this, getReactor(), debugger,
1✔
177
                        outgoingElementsQueue) {
1✔
178

179
            @Override
180
            public void parseAndProcessElement(String wrappedCompleteElement) {
181
                ModularXmppClientToServerConnection.this.parseAndProcessElement(wrappedCompleteElement);
×
182
            }
×
183

184
            @Override
185
            public void notifyConnectionError(Exception e) {
186
                ModularXmppClientToServerConnection.this.notifyConnectionError(e);
×
187
            }
×
188

189
            @Override
190
            public String onStreamOpen(XmlPullParser parser) {
191
                return ModularXmppClientToServerConnection.this.onStreamOpen(parser);
×
192
            }
193

194
            @Override
195
            public void onStreamClosed() {
196
                ModularXmppClientToServerConnection.this.closingStreamReceived = true;
×
197
                notifyWaitingThreads();
×
198
            }
×
199

200
            @Override
201
            public void fireFirstLevelElementSendListeners(TopLevelStreamElement element) {
202
                ModularXmppClientToServerConnection.this.firePacketSendingListeners(element);
×
203
            }
×
204

205
            @Override
206
            public void invokeConnectionStateMachineListener(ConnectionStateEvent connectionStateEvent) {
207
                ModularXmppClientToServerConnection.this.invokeConnectionStateMachineListener(connectionStateEvent);
×
208
            }
×
209

210
            @Override
211
            public XmlEnvironment getOutgoingStreamXmlEnvironment() {
212
                return outgoingStreamXmlEnvironment;
×
213
            }
214

215
            @Override
216
            public void addXmppInputOutputFilter(XmppInputOutputFilter xmppInputOutputFilter) {
217
                inputOutputFilters.add(0, xmppInputOutputFilter);
×
218
            }
×
219

220
            @Override
221
            public ListIterator<XmppInputOutputFilter> getXmppInputOutputFilterBeginIterator() {
222
                return inputOutputFilters.listIterator();
×
223
            }
224

225
            @Override
226
            public ListIterator<XmppInputOutputFilter> getXmppInputOutputFilterEndIterator() {
227
                return inputOutputFilters.listIterator(inputOutputFilters.size());
×
228
            }
229

230
            @Override
231
            public void waitForFeaturesReceived(String waitFor)
232
                            throws InterruptedException, SmackException, XMPPException {
233
                ModularXmppClientToServerConnection.this.waitForFeaturesReceived(waitFor);
×
234
            }
×
235

236
            @Override
237
            public void newStreamOpenWaitForFeaturesSequence(String waitFor)
238
                            throws InterruptedException, SmackException, XMPPException {
239
                ModularXmppClientToServerConnection.this.newStreamOpenWaitForFeaturesSequence(waitFor);
×
240
            }
×
241

242
            @Override
243
            public SmackTlsContext getSmackTlsContext() {
244
                return ModularXmppClientToServerConnection.this.getSmackTlsContext();
×
245
            }
246

247
            @Override
248
            public <SN extends Nonza, FN extends Nonza> SN sendAndWaitForResponse(Nonza nonza,
249
                            Class<SN> successNonzaClass, Class<FN> failedNonzaClass) throws NoResponseException,
250
                            NotConnectedException, FailedNonzaException, InterruptedException {
251
                return ModularXmppClientToServerConnection.this.sendAndWaitForResponse(nonza, successNonzaClass,
×
252
                                failedNonzaClass);
253
            }
254

255
            @Override
256
            public void asyncGo(Runnable runnable) {
257
                AbstractXMPPConnection.asyncGo(runnable);
×
258
            }
×
259

260
            @Override
261
            public void waitForConditionOrThrowConnectionException(Supplier<Boolean> condition, String waitFor)
262
                            throws InterruptedException, SmackException, XMPPException {
263
                ModularXmppClientToServerConnection.this.waitForConditionOrThrowConnectionException(condition, waitFor);
×
264
            }
×
265

266
            @Override
267
            public void notifyWaitingThreads() {
268
                ModularXmppClientToServerConnection.this.notifyWaitingThreads();
×
269
            }
×
270

271
            @Override
272
            public void setCompressionEnabled(boolean compressionEnabled) {
273
                ModularXmppClientToServerConnection.this.compressionEnabled = compressionEnabled;
×
274
            }
×
275

276
            @Override
277
            public void setTransport(XmppClientToServerTransport xmppTransport) {
278
                ModularXmppClientToServerConnection.this.activeTransport = xmppTransport;
×
279
                ModularXmppClientToServerConnection.this.connected = true;
×
280
            }
×
281

282
        };
283

284
        // Construct the modules from the module descriptor. We do this before constructing the state graph, as the
285
        // modules are sometimes used to construct the states.
286
        for (ModularXmppClientToServerConnectionModuleDescriptor moduleDescriptor : configuration.moduleDescriptors) {
1✔
287
            Class<? extends ModularXmppClientToServerConnectionModuleDescriptor> moduleDescriptorClass = moduleDescriptor.getClass();
1✔
288
            ModularXmppClientToServerConnectionModule<? extends ModularXmppClientToServerConnectionModuleDescriptor> connectionModule = moduleDescriptor.constructXmppConnectionModule(
1✔
289
                            connectionInternal);
290
            connectionModules.put(moduleDescriptorClass, connectionModule);
1✔
291

292
            XmppClientToServerTransport transport = connectionModule.getTransport();
1✔
293
            // Not every module may provide a transport.
294
            if (transport != null) {
1✔
295
                transports.put(moduleDescriptorClass, transport);
1✔
296
            }
297
        }
1✔
298

299
        GraphVertex<StateDescriptor> initialStateDescriptorVertex = configuration.initialStateDescriptorVertex;
1✔
300
        // Convert the graph of state descriptors to a graph of states, bound to this very connection.
301
        currentStateVertex = StateDescriptorGraph.convertToStateGraph(initialStateDescriptorVertex, connectionInternal);
1✔
302
    }
1✔
303

304
    @SuppressWarnings("unchecked")
305
    public <CM extends ModularXmppClientToServerConnectionModule<? extends ModularXmppClientToServerConnectionModuleDescriptor>> CM getConnectionModuleFor(
306
                    Class<? extends ModularXmppClientToServerConnectionModuleDescriptor> descriptorClass) {
307
        return (CM) connectionModules.get(descriptorClass);
1✔
308
    }
309

310
    @Override
311
    protected void loginInternal(String username, String password, Resourcepart resource)
312
                    throws XMPPException, SmackException, IOException, InterruptedException {
313
        WalkStateGraphContext walkStateGraphContext = buildNewWalkTo(
×
314
                        AuthenticatedAndResourceBoundStateDescriptor.class).withLoginContext(username, password,
×
315
                                        resource).build();
×
316
        walkStateGraph(walkStateGraphContext);
×
317
    }
×
318

319
    private WalkStateGraphContext.Builder buildNewWalkTo(Class<? extends StateDescriptor> finalStateClass) {
320
        return WalkStateGraphContext.builder(currentStateVertex.getElement().getStateDescriptor().getClass(),
×
321
                        finalStateClass);
322
    }
323

324
    /**
325
     * Unwind the state. This will revert the effects of the state by calling {@link State#resetState()} prior issuing a
326
     * connection state event of {@link ConnectionStateEvent#StateRevertBackwardsWalk}.
327
     *
328
     * @param revertedState the state which is going to get reverted.
329
     */
330
    private void unwindState(State revertedState) {
331
        invokeConnectionStateMachineListener(new ConnectionStateEvent.StateRevertBackwardsWalk(revertedState));
×
332
        revertedState.resetState();
×
333
    }
×
334

335
    private void walkStateGraph(WalkStateGraphContext walkStateGraphContext)
336
                    throws XMPPException, IOException, SmackException, InterruptedException {
337
        // Save a copy of the current state
338
        GraphVertex<State> previousStateVertex = currentStateVertex;
×
339
        try {
340
            walkStateGraphInternal(walkStateGraphContext);
×
341
        } catch (IOException | SmackException | InterruptedException | XMPPException e) {
×
342
            currentStateVertex = previousStateVertex;
×
343
            // Unwind the state.
344
            State revertedState = currentStateVertex.getElement();
×
345
            unwindState(revertedState);
×
346
            throw e;
×
347
        }
×
348
    }
×
349

350
    private void walkStateGraphInternal(WalkStateGraphContext walkStateGraphContext)
351
                    throws IOException, SmackException, InterruptedException, XMPPException {
352
        // Save a copy of the current state
353
        final GraphVertex<State> initialStateVertex = currentStateVertex;
×
354
        final State initialState = initialStateVertex.getElement();
×
355
        final StateDescriptor initialStateDescriptor = initialState.getStateDescriptor();
×
356

357
        walkStateGraphContext.recordWalkTo(initialState);
×
358

359
        // Check if this is the walk's final state.
360
        if (walkStateGraphContext.isWalksFinalState(initialStateDescriptor)) {
×
361
            // If this is used as final state, then it should be marked as such.
362
            assert initialStateDescriptor.isFinalState();
×
363

364
            // We reached the final state.
365
            invokeConnectionStateMachineListener(new ConnectionStateEvent.FinalStateReached(initialState));
×
366
            return;
×
367
        }
368

369
        List<GraphVertex<State>> outgoingStateEdges = initialStateVertex.getOutgoingEdges();
×
370

371
        // See if we need to handle mandatory intermediate states.
372
        GraphVertex<State> mandatoryIntermediateStateVertex = walkStateGraphContext.maybeReturnMandatoryImmediateState(
×
373
                        outgoingStateEdges);
374
        if (mandatoryIntermediateStateVertex != null) {
×
375
            StateTransitionResult reason = attemptEnterState(mandatoryIntermediateStateVertex, walkStateGraphContext);
×
376

377
            if (reason instanceof StateTransitionResult.Success) {
×
378
                walkStateGraph(walkStateGraphContext);
×
379
                return;
×
380
            }
381

382
            // We could not enter a mandatory intermediate state. Throw here.
383
            throw new StateMachineException.SmackMandatoryStateFailedException(
×
384
                            mandatoryIntermediateStateVertex.getElement(), reason);
×
385
        }
386

387
        for (Iterator<GraphVertex<State>> it = outgoingStateEdges.iterator(); it.hasNext();) {
×
388
            GraphVertex<State> successorStateVertex = it.next();
×
389
            State successorState = successorStateVertex.getElement();
×
390

391
            // Ignore successorStateVertex if the only way to the final state is via the initial state. This happens
392
            // typically if we are in the ConnectedButUnauthenticated state on the way to ResourceboundAndAuthenticated,
393
            // where we do not want to walk via InstantShutdown/Shtudown in a cycle over the initial state towards this
394
            // state.
395
            if (walkStateGraphContext.wouldCauseCycle(successorStateVertex)) {
×
396
                // Ignore this successor.
397
                invokeConnectionStateMachineListener(new ConnectionStateEvent.TransitionIgnoredDueCycle(
×
398
                                initialStateVertex, successorStateVertex));
399
            } else {
400
                StateTransitionResult result = attemptEnterState(successorStateVertex, walkStateGraphContext);
×
401

402
                if (result instanceof StateTransitionResult.Success) {
×
403
                    break;
×
404
                }
405

406
                // If attemptEnterState did not throw and did not return a value of type TransitionSuccessResult, then
407
                // we
408
                // just record this value and go on from there. Note that reason may be null, which is returned by
409
                // attemptEnterState in case the state was already successfully handled. If this is the case, then we
410
                // don't
411
                // record it.
412
                if (result != null) {
×
413
                    walkStateGraphContext.recordFailedState(successorState, result);
×
414
                }
415
            }
416

417
            if (!it.hasNext()) {
×
418
                throw StateMachineException.SmackStateGraphDeadEndException.from(walkStateGraphContext,
×
419
                                initialStateVertex);
420
            }
421
        }
×
422

423
        // Walk the state graph by recursion.
424
        walkStateGraph(walkStateGraphContext);
×
425
    }
×
426

427
    /**
428
     * Attempt to enter a state. Note that this method may return <code>null</code> if this state can be safely ignored.
429
     *
430
     * @param successorStateVertex the successor state vertex.
431
     * @param walkStateGraphContext the "walk state graph" context.
432
     * @return A state transition result or <code>null</code> if this state can be ignored.
433
     * @throws SmackException if Smack detected an exceptional situation.
434
     * @throws XMPPException if an XMPP protocol error was received.
435
     * @throws IOException if an I/O error occurred.
436
     * @throws InterruptedException if the calling thread was interrupted.
437
     */
438
    private StateTransitionResult attemptEnterState(GraphVertex<State> successorStateVertex,
439
                    WalkStateGraphContext walkStateGraphContext)
440
                    throws SmackException, XMPPException, IOException, InterruptedException {
441
        final GraphVertex<State> initialStateVertex = currentStateVertex;
×
442
        final State initialState = initialStateVertex.getElement();
×
443
        final State successorState = successorStateVertex.getElement();
×
444
        final StateDescriptor successorStateDescriptor = successorState.getStateDescriptor();
×
445

446
        if (!successorStateDescriptor.isMultiVisitState()
×
447
                        && walkStateGraphContext.stateAlreadyVisited(successorState)) {
×
448
            // This can happen if a state leads back to the state where it originated from. See for example the
449
            // 'Compression' state. We return 'null' here to signal that the state can safely be ignored.
450
            return null;
×
451
        }
452

453
        if (successorStateDescriptor.isNotImplemented()) {
×
454
            StateTransitionResult.TransitionImpossibleBecauseNotImplemented transtionImpossibleBecauseNotImplemented = new StateTransitionResult.TransitionImpossibleBecauseNotImplemented(
×
455
                            successorStateDescriptor);
456
            invokeConnectionStateMachineListener(new ConnectionStateEvent.TransitionNotPossible(initialState,
×
457
                            successorState, transtionImpossibleBecauseNotImplemented));
458
            return transtionImpossibleBecauseNotImplemented;
×
459
        }
460

461
        final StateTransitionResult.AttemptResult transitionAttemptResult;
462
        try {
463
            StateTransitionResult.TransitionImpossible transitionImpossible = successorState.isTransitionToPossible(
×
464
                            walkStateGraphContext);
465
            if (transitionImpossible != null) {
×
466
                invokeConnectionStateMachineListener(new ConnectionStateEvent.TransitionNotPossible(initialState,
×
467
                                successorState, transitionImpossible));
468
                return transitionImpossible;
×
469
            }
470

471
            invokeConnectionStateMachineListener(
×
472
                            new ConnectionStateEvent.AboutToTransitionInto(initialState, successorState));
473
            transitionAttemptResult = successorState.transitionInto(walkStateGraphContext);
×
474
        } catch (SmackException | IOException | InterruptedException | XMPPException e) {
×
475
            // Unwind the state here too, since this state will not be unwound by walkStateGraph(), as it will not
476
            // become a predecessor state in the walk.
477
            unwindState(successorState);
×
478
            throw e;
×
479
        }
×
480
        if (transitionAttemptResult instanceof StateTransitionResult.Failure) {
×
481
            StateTransitionResult.Failure transitionFailureResult = (StateTransitionResult.Failure) transitionAttemptResult;
×
482
            invokeConnectionStateMachineListener(new ConnectionStateEvent.TransitionFailed(initialState, successorState,
×
483
                            transitionFailureResult));
484
            return transitionAttemptResult;
×
485
        }
486

487
        // If transitionAttemptResult is not an instance of TransitionFailureResult, then it has to be of type
488
        // TransitionSuccessResult.
489
        StateTransitionResult.Success transitionSuccessResult = (StateTransitionResult.Success) transitionAttemptResult;
×
490

491
        currentStateVertex = successorStateVertex;
×
492
        invokeConnectionStateMachineListener(
×
493
                        new ConnectionStateEvent.SuccessfullyTransitionedInto(successorState, transitionSuccessResult));
494

495
        return transitionSuccessResult;
×
496
    }
497

498
    @Override
499
    protected void sendInternal(TopLevelStreamElement element) throws NotConnectedException, InterruptedException {
500
        final XmppClientToServerTransport transport = activeTransport;
×
501
        if (transport == null) {
×
502
            throw new NotConnectedException();
×
503
        }
504

505
        outgoingElementsQueue.put(element);
×
506
        transport.notifyAboutNewOutgoingElements();
×
507
    }
×
508

509
    @Override
510
    protected void sendNonBlockingInternal(TopLevelStreamElement element) throws NotConnectedException, OutgoingQueueFullException {
511
        final XmppClientToServerTransport transport = activeTransport;
×
512
        if (transport == null) {
×
513
            throw new NotConnectedException();
×
514
        }
515

516
        boolean enqueued = outgoingElementsQueue.offer(element);
×
517
        if (!enqueued) {
×
518
            throw new OutgoingQueueFullException();
×
519
        }
520

521
        transport.notifyAboutNewOutgoingElements();
×
522
    }
×
523

524
    @Override
525
    protected void shutdown() {
526
        shutdown(false);
×
527
    }
×
528

529
    @Override
530
    public synchronized void instantShutdown() {
531
        shutdown(true);
×
532
    }
×
533

534
    @Override
535
    public ModularXmppClientToServerConnectionConfiguration getConfiguration() {
536
        return configuration;
×
537
    }
538

539
    private void shutdown(boolean instant) {
540
        Class<? extends StateDescriptor> mandatoryIntermediateState;
541
        if (instant) {
×
542
            mandatoryIntermediateState = InstantShutdownStateDescriptor.class;
×
543
        } else {
544
            mandatoryIntermediateState = ShutdownStateDescriptor.class;
×
545
        }
546

547
        WalkStateGraphContext context = buildNewWalkTo(
×
548
                        DisconnectedStateDescriptor.class).withMandatoryIntermediateState(
×
549
                                        mandatoryIntermediateState).build();
×
550

551
        try {
552
            walkStateGraph(context);
×
553
        } catch (IOException | SmackException | InterruptedException | XMPPException e) {
×
554
            throw new IllegalStateException("A walk to disconnected state should never throw", e);
×
555
        }
×
556
    }
×
557

558
    private SSLSession getSSLSession() {
559
        final XmppClientToServerTransport transport = activeTransport;
×
560
        if (transport == null) {
×
561
            return null;
×
562
        }
563
        return transport.getSslSession();
×
564
    }
565

566
    @Override
567
    protected void afterFeaturesReceived() {
568
        featuresReceived = true;
×
569
        notifyWaitingThreads();
×
570
    }
×
571

572
    private void parseAndProcessElement(String element) {
573
        try {
574
            XmlPullParser parser = PacketParserUtils.getParserFor(element);
×
575

576
            // Skip the enclosing stream open what is guaranteed to be there.
577
            parser.next();
×
578

579
            XmlPullParser.Event event = parser.getEventType();
×
580
            outerloop: while (true) {
581
                switch (event) {
×
582
                case START_ELEMENT:
583
                    final String name = parser.getName();
×
584
                    // Note that we don't handle "stream" here as it's done in the splitter.
585
                    switch (name) {
×
586
                    case Message.ELEMENT:
587
                    case IQ.IQ_ELEMENT:
588
                    case Presence.ELEMENT:
589
                        try {
590
                            parseAndProcessStanza(parser);
×
591
                        } finally {
592
                            // TODO: Here would be the following stream management code.
593
                            // clientHandledStanzasCount = SMUtils.incrementHeight(clientHandledStanzasCount);
594
                        }
595
                        break;
×
596
                    case "error":
597
                        StreamError streamError = PacketParserUtils.parseStreamError(parser, null);
×
598
                        StreamErrorException streamErrorException = new StreamErrorException(streamError);
×
599
                        currentXmppException = streamErrorException;
×
600
                        notifyWaitingThreads();
×
601
                        throw streamErrorException;
×
602
                    case "features":
603
                        parseFeatures(parser);
×
604
                        afterFeaturesReceived();
×
605
                        break;
×
606
                    default:
607
                        parseAndProcessNonza(parser);
×
608
                        break;
609
                    }
610
                    break;
×
611
                case END_DOCUMENT:
612
                    break outerloop;
×
613
                default: // fall out
614
                }
615
                event = parser.next();
×
616
            }
617
        } catch (XmlPullParserException | IOException | InterruptedException | StreamErrorException
×
618
                        | SmackParsingException e) {
619
            notifyConnectionError(e);
×
620
        }
×
621
    }
×
622

623
    private synchronized void prepareToWaitForFeaturesReceived() {
624
        featuresReceived = false;
×
625
    }
×
626

627
    private void waitForFeaturesReceived(String waitFor) throws InterruptedException, SmackException, XMPPException {
628
        waitForConditionOrThrowConnectionException(() -> featuresReceived, waitFor);
×
629
    }
×
630

631
    @Override
632
    protected AbstractStreamOpen getStreamOpen(DomainBareJid to, CharSequence from, String id, String lang) {
633
        StreamOpenAndCloseFactory streamOpenAndCloseFactory = activeTransport.getStreamOpenAndCloseFactory();
×
634
        return streamOpenAndCloseFactory.createStreamOpen(to, from, id, lang);
×
635
    }
636

637
    private void newStreamOpenWaitForFeaturesSequence(String waitFor)
638
                    throws InterruptedException, SmackException, XMPPException {
639
        prepareToWaitForFeaturesReceived();
×
640

641
        // Create StreamOpen from StreamOpenAndCloseFactory via underlying transport.
642
        StreamOpenAndCloseFactory streamOpenAndCloseFactory = activeTransport.getStreamOpenAndCloseFactory();
×
643
        CharSequence from = null;
×
644
        CharSequence localpart = connectionInternal.connection.getConfiguration().getUsername();
×
645
        DomainBareJid xmppServiceDomain = getXMPPServiceDomain();
×
646
        if (localpart != null) {
×
647
            from = XmppStringUtils.completeJidFrom(localpart, xmppServiceDomain);
×
648
        }
649
        AbstractStreamOpen streamOpen = streamOpenAndCloseFactory.createStreamOpen(xmppServiceDomain, from,
×
650
                        getStreamId(), getConfiguration().getXmlLang());
×
651
        sendStreamOpen(streamOpen);
×
652

653
        waitForFeaturesReceived(waitFor);
×
654
    }
×
655

656
    private void sendStreamOpen(AbstractStreamOpen streamOpen) throws NotConnectedException, InterruptedException {
657
        sendNonza(streamOpen);
×
658
        updateOutgoingStreamXmlEnvironmentOnStreamOpen(streamOpen);
×
659
    }
×
660

661
    public static class DisconnectedStateDescriptor extends StateDescriptor {
662
        protected DisconnectedStateDescriptor() {
663
            super(DisconnectedState.class, StateDescriptor.Property.finalState);
1✔
664
            addSuccessor(LookupRemoteConnectionEndpointsStateDescriptor.class);
1✔
665
        }
1✔
666
    }
667

668
    private final class DisconnectedState extends State {
669

670
        private DisconnectedState(StateDescriptor stateDescriptor,
671
                        ModularXmppClientToServerConnectionInternal connectionInternal) {
1✔
672
            super(stateDescriptor, connectionInternal);
1✔
673
        }
1✔
674

675
        @Override
676
        public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) {
677
            synchronized (ModularXmppClientToServerConnection.this) {
×
678
                if (inputOutputFilters.isEmpty()) {
×
679
                    previousInputOutputFilters = null;
×
680
                } else {
681
                    previousInputOutputFilters = new ArrayList<>(inputOutputFilters.size());
×
682
                    previousInputOutputFilters.addAll(inputOutputFilters);
×
683
                    inputOutputFilters.clear();
×
684
                }
685
            }
×
686

687
            // Reset all states we have visited when transitioning from disconnected to authenticated. This assumes that
688
            // every state after authenticated does not need to be reset.
689
            ListIterator<State> it = walkFromDisconnectToAuthenticated.listIterator(
×
690
                            walkFromDisconnectToAuthenticated.size());
×
691
            while (it.hasPrevious()) {
×
692
                State stateToReset = it.previous();
×
693
                stateToReset.resetState();
×
694
            }
×
695
            walkFromDisconnectToAuthenticated = null;
×
696

697
            return StateTransitionResult.Success.EMPTY_INSTANCE;
×
698
        }
699
    }
700

701
    public static final class LookupRemoteConnectionEndpointsStateDescriptor extends StateDescriptor {
702
        private LookupRemoteConnectionEndpointsStateDescriptor() {
703
            super(LookupRemoteConnectionEndpointsState.class);
1✔
704
        }
1✔
705
    }
706

707
    private final class LookupRemoteConnectionEndpointsState extends State {
708
        boolean outgoingElementsQueueWasShutdown;
709

710
        private LookupRemoteConnectionEndpointsState(StateDescriptor stateDescriptor,
711
                        ModularXmppClientToServerConnectionInternal connectionInternal) {
1✔
712
            super(stateDescriptor, connectionInternal);
1✔
713
        }
1✔
714

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

723
            Map<XmppClientToServerTransport, List<SmackFuture<LookupConnectionEndpointsResult, Exception>>> lookupFutures = new HashMap<>(
×
724
                            transports.size());
×
725

726
            final int numberOfFutures;
727
            {
728
                List<SmackFuture<?, ?>> allFutures = new ArrayList<>();
×
729
                for (XmppClientToServerTransport transport : transports.values()) {
×
730
                    // First we clear the transport of any potentially previously discovered connection endpoints.
731
                    transport.resetDiscoveredConnectionEndpoints();
×
732

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

736
                    lookupFutures.put(transport, transportFutures);
×
737
                    allFutures.addAll(transportFutures);
×
738
                }
×
739

740
                numberOfFutures = allFutures.size();
×
741

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

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

752
            boolean atLeastOneConnectionEndpointDiscovered = false;
×
753
            for (Map.Entry<XmppClientToServerTransport, List<SmackFuture<LookupConnectionEndpointsResult, Exception>>> entry : lookupFutures.entrySet()) {
×
754
                XmppClientToServerTransport transport = entry.getKey();
×
755

756
                for (SmackFuture<LookupConnectionEndpointsResult, Exception> future : entry.getValue()) {
×
757
                    LookupConnectionEndpointsResult result = future.getIfAvailable();
×
758

759
                    if (result == null) {
×
760
                        continue;
×
761
                    }
762

763
                    if (result instanceof LookupConnectionEndpointsFailed) {
×
764
                        LookupConnectionEndpointsFailed lookupFailure = (LookupConnectionEndpointsFailed) result;
×
765
                        lookupFailures.add(lookupFailure);
×
766
                        continue;
×
767
                    }
768

769
                    LookupConnectionEndpointsSuccess successResult = (LookupConnectionEndpointsSuccess) result;
×
770

771
                    // Arm the transport with the success result, so that its information can be used by the transport
772
                    // to establish the connection.
773
                    transport.loadConnectionEndpoints(successResult);
×
774

775
                    // Mark that the connection attempt can continue.
776
                    atLeastOneConnectionEndpointDiscovered = true;
×
777
                }
×
778
            }
×
779

780
            if (!atLeastOneConnectionEndpointDiscovered) {
×
781
                throw SmackException.NoEndpointsDiscoveredException.from(lookupFailures);
×
782
            }
783

784
            if (!lookupFailures.isEmpty()) {
×
785
                // TODO: Put those non-fatal lookup failures into a sink of the connection so that the user is able to
786
                // be aware of them.
787
            }
788

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

794
            return StateTransitionResult.Success.EMPTY_INSTANCE;
×
795
        }
796

797
        @Override
798
        public void resetState() {
799
            for (XmppClientToServerTransport transport : transports.values()) {
×
800
                transport.resetDiscoveredConnectionEndpoints();
×
801
            }
×
802

803
            if (outgoingElementsQueueWasShutdown) {
×
804
                // Reset the outgoing elements queue in this state, since we also start it in this state.
805
                outgoingElementsQueue.shutdown();
×
806
            }
807
        }
×
808
    }
809

810
    public static final class ConnectedButUnauthenticatedStateDescriptor extends StateDescriptor {
811
        private ConnectedButUnauthenticatedStateDescriptor() {
812
            super(ConnectedButUnauthenticatedState.class, StateDescriptor.Property.finalState);
1✔
813
            addSuccessor(SaslAuthenticationStateDescriptor.class);
1✔
814
            addSuccessor(InstantShutdownStateDescriptor.class);
1✔
815
            addSuccessor(ShutdownStateDescriptor.class);
1✔
816
        }
1✔
817
    }
818

819
    private final class ConnectedButUnauthenticatedState extends State {
1✔
820
        private ConnectedButUnauthenticatedState(StateDescriptor stateDescriptor,
821
                        ModularXmppClientToServerConnectionInternal connectionInternal) {
1✔
822
            super(stateDescriptor, connectionInternal);
1✔
823
        }
1✔
824

825
        @Override
826
        public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) {
827
            assert walkFromDisconnectToAuthenticated == null;
×
828

829
            if (walkStateGraphContext.isWalksFinalState(getStateDescriptor())) {
×
830
                // If this is the final state, then record the walk so far.
831
                walkFromDisconnectToAuthenticated = walkStateGraphContext.getWalk();
×
832
            }
833

834
            connected = true;
×
835
            return StateTransitionResult.Success.EMPTY_INSTANCE;
×
836
        }
837

838
        @Override
839
        public void resetState() {
840
            connected = false;
×
841
        }
×
842
    }
843

844
    public static final class SaslAuthenticationStateDescriptor extends StateDescriptor {
845
        private SaslAuthenticationStateDescriptor() {
846
            super(SaslAuthenticationState.class, "RFC 6120 § 6");
1✔
847
            addSuccessor(AuthenticatedButUnboundStateDescriptor.class);
1✔
848
        }
1✔
849
    }
850

851
    private final class SaslAuthenticationState extends State {
852
        private SaslAuthenticationState(StateDescriptor stateDescriptor,
853
                        ModularXmppClientToServerConnectionInternal connectionInternal) {
1✔
854
            super(stateDescriptor, connectionInternal);
1✔
855
        }
1✔
856

857
        @Override
858
        public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
859
                        throws IOException, SmackException, InterruptedException, XMPPException {
860
            prepareToWaitForFeaturesReceived();
×
861

862
            LoginContext loginContext = walkStateGraphContext.getLoginContext();
×
863
            SASLMechanism usedSaslMechanism = authenticate(loginContext.username, loginContext.password,
×
864
                            config.getAuthzid(), getSSLSession());
×
865
            // authenticate() will only return if the SASL authentication was successful, but we also need to wait for
866
            // the next round of stream features.
867

868
            waitForFeaturesReceived("server stream features after SASL authentication");
×
869

870
            return new SaslAuthenticationSuccessResult(usedSaslMechanism);
×
871
        }
872
    }
873

874
    public static final class SaslAuthenticationSuccessResult extends StateTransitionResult.Success {
875
        private final String saslMechanismName;
876

877
        private SaslAuthenticationSuccessResult(SASLMechanism usedSaslMechanism) {
878
            super("SASL authentication successfull using " + usedSaslMechanism.getName());
×
879
            this.saslMechanismName = usedSaslMechanism.getName();
×
880
        }
×
881

882
        public String getSaslMechanismName() {
883
            return saslMechanismName;
×
884
        }
885
    }
886

887
    public static final class AuthenticatedButUnboundStateDescriptor extends StateDescriptor {
888
        private AuthenticatedButUnboundStateDescriptor() {
889
            super(StateDescriptor.Property.multiVisitState);
1✔
890
            addSuccessor(ResourceBindingStateDescriptor.class);
1✔
891
        }
1✔
892
    }
893

894
    public static final class ResourceBindingStateDescriptor extends StateDescriptor {
895
        private ResourceBindingStateDescriptor() {
896
            super(ResourceBindingState.class, "RFC 6120 § 7");
1✔
897
            addSuccessor(AuthenticatedAndResourceBoundStateDescriptor.class);
1✔
898
        }
1✔
899
    }
900

901
    private final class ResourceBindingState extends State {
902
        private ResourceBindingState(StateDescriptor stateDescriptor,
903
                        ModularXmppClientToServerConnectionInternal connectionInternal) {
1✔
904
            super(stateDescriptor, connectionInternal);
1✔
905
        }
1✔
906

907
        @Override
908
        public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
909
                        throws IOException, SmackException, InterruptedException, XMPPException {
910
            // Calling bindResourceAndEstablishSession() below requires the lastFeaturesReceived sync point to be
911
            // signaled.
912
            // Since we entered this state, the FSM has decided that the last features have been received, hence signal
913
            // the sync point.
914
            lastFeaturesReceived = true;
×
915
            notifyWaitingThreads();
×
916

917
            LoginContext loginContext = walkStateGraphContext.getLoginContext();
×
918
            Resourcepart resource = bindResourceAndEstablishSession(loginContext.resource);
×
919

920
            // TODO: This should be a field in the Stream Management (SM) module. Here should be hook which the SM
921
            // module can use to set the field instead.
922
            streamResumed = false;
×
923

924
            return new ResourceBoundResult(resource, loginContext.resource);
×
925
        }
926
    }
927

928
    public static final class ResourceBoundResult extends StateTransitionResult.Success {
929
        private final Resourcepart resource;
930

931
        private ResourceBoundResult(Resourcepart boundResource, Resourcepart requestedResource) {
932
            super("Resource '" + boundResource + "' bound (requested: '" + requestedResource + "')");
×
933
            this.resource = boundResource;
×
934
        }
×
935

936
        public Resourcepart getResource() {
937
            return resource;
×
938
        }
939
    }
940

941
    private boolean compressionEnabled;
942

943
    @Override
944
    public boolean isUsingCompression() {
945
        return compressionEnabled;
×
946
    }
947

948
    public static final class AuthenticatedAndResourceBoundStateDescriptor extends StateDescriptor {
949
        private AuthenticatedAndResourceBoundStateDescriptor() {
950
            super(AuthenticatedAndResourceBoundState.class, StateDescriptor.Property.finalState);
1✔
951
            addSuccessor(InstantShutdownStateDescriptor.class);
1✔
952
            addSuccessor(ShutdownStateDescriptor.class);
1✔
953
        }
1✔
954
    }
955

956
    private final class AuthenticatedAndResourceBoundState extends State {
1✔
957
        private AuthenticatedAndResourceBoundState(StateDescriptor stateDescriptor,
958
                        ModularXmppClientToServerConnectionInternal connectionInternal) {
1✔
959
            super(stateDescriptor, connectionInternal);
1✔
960
        }
1✔
961

962
        @Override
963
        public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
964
                        throws NotConnectedException, InterruptedException {
965
            if (walkFromDisconnectToAuthenticated != null) {
×
966
                // If there was already a previous walk to ConnectedButUnauthenticated, then the context of the current
967
                // walk must not start from the 'Disconnected' state.
968
                assert walkStateGraphContext.getWalk().get(
×
969
                                0).getStateDescriptor().getClass() != DisconnectedStateDescriptor.class;
×
970
                // Append the current walk to the previous one.
971
                walkStateGraphContext.appendWalkTo(walkFromDisconnectToAuthenticated);
×
972
            } else {
973
                walkFromDisconnectToAuthenticated = new ArrayList<>(walkStateGraphContext.getWalkLength() + 1);
×
974
                walkStateGraphContext.appendWalkTo(walkFromDisconnectToAuthenticated);
×
975
            }
976
            walkFromDisconnectToAuthenticated.add(this);
×
977

978
            afterSuccessfulLogin(streamResumed);
×
979

980
            return StateTransitionResult.Success.EMPTY_INSTANCE;
×
981
        }
982

983
        @Override
984
        public void resetState() {
985
            authenticated = false;
×
986
        }
×
987
    }
988

989
    static final class ShutdownStateDescriptor extends StateDescriptor {
990
        private ShutdownStateDescriptor() {
991
            super(ShutdownState.class);
1✔
992
            addSuccessor(CloseConnectionStateDescriptor.class);
1✔
993
        }
1✔
994
    }
995

996
    private final class ShutdownState extends State {
997
        private ShutdownState(StateDescriptor stateDescriptor,
998
                        ModularXmppClientToServerConnectionInternal connectionInternal) {
1✔
999
            super(stateDescriptor, connectionInternal);
1✔
1000
        }
1✔
1001

1002
        @Override
1003
        public StateTransitionResult.TransitionImpossible isTransitionToPossible(
1004
                        WalkStateGraphContext walkStateGraphContext) {
1005
            ensureNotOnOurWayToAuthenticatedAndResourceBound(walkStateGraphContext);
×
1006
            return null;
×
1007
        }
1008

1009
        @Override
1010
        public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) {
1011
            closingStreamReceived = false;
×
1012

1013
            StreamOpenAndCloseFactory openAndCloseFactory = activeTransport.getStreamOpenAndCloseFactory();
×
1014
            AbstractStreamClose closeStreamElement = openAndCloseFactory.createStreamClose();
×
1015
            boolean streamCloseIssued = outgoingElementsQueue.offerAndShutdown(closeStreamElement);
×
1016

1017
            if (streamCloseIssued) {
×
1018
                activeTransport.notifyAboutNewOutgoingElements();
×
1019

1020
                boolean successfullyReceivedStreamClose = waitForClosingStreamTagFromServer();
×
1021

1022
                if (successfullyReceivedStreamClose) {
×
1023
                    for (Iterator<XmppInputOutputFilter> it = connectionInternal.getXmppInputOutputFilterBeginIterator(); it.hasNext();) {
×
1024
                        XmppInputOutputFilter filter = it.next();
×
1025
                        filter.closeInputOutput();
×
1026
                    }
×
1027

1028
                    // Closing the filters may produced new outgoing data. Notify the transport about it.
1029
                    activeTransport.afterFiltersClosed();
×
1030

1031
                    for (Iterator<XmppInputOutputFilter> it = connectionInternal.getXmppInputOutputFilterBeginIterator(); it.hasNext();) {
×
1032
                        XmppInputOutputFilter filter = it.next();
×
1033
                        try {
1034
                            filter.waitUntilInputOutputClosed();
×
1035
                        } catch (IOException | CertificateException | InterruptedException | SmackException
×
1036
                                        | XMPPException e) {
1037
                            LOGGER.log(Level.WARNING, "waitUntilInputOutputClosed() threw", e);
×
1038
                        }
×
1039
                    }
×
1040

1041
                    // For correctness we set authenticated to false here, even though we will later again set it to
1042
                    // false in the disconnected state.
1043
                    authenticated = false;
×
1044
                }
1045
            }
1046

1047
            return StateTransitionResult.Success.EMPTY_INSTANCE;
×
1048
        }
1049
    }
1050

1051
    static final class InstantShutdownStateDescriptor extends StateDescriptor {
1052
        private InstantShutdownStateDescriptor() {
1053
            super(InstantShutdownState.class);
1✔
1054
            addSuccessor(CloseConnectionStateDescriptor.class);
1✔
1055
        }
1✔
1056
    }
1057

1058
    private static final class InstantShutdownState extends NoOpState {
1059
        private InstantShutdownState(ModularXmppClientToServerConnection connection, StateDescriptor stateDescriptor,
1060
                        ModularXmppClientToServerConnectionInternal connectionInternal) {
1061
            super(connection, stateDescriptor, connectionInternal);
1✔
1062
        }
1✔
1063

1064
        @Override
1065
        public StateTransitionResult.TransitionImpossible isTransitionToPossible(
1066
                        WalkStateGraphContext walkStateGraphContext) {
1067
            ensureNotOnOurWayToAuthenticatedAndResourceBound(walkStateGraphContext);
×
1068
            return null;
×
1069
        }
1070
    }
1071

1072
    private static final class CloseConnectionStateDescriptor extends StateDescriptor {
1073
        private CloseConnectionStateDescriptor() {
1074
            super(CloseConnectionState.class);
1✔
1075
            addSuccessor(DisconnectedStateDescriptor.class);
1✔
1076
        }
1✔
1077
    }
1078

1079
    private final class CloseConnectionState extends State {
1080
        private CloseConnectionState(StateDescriptor stateDescriptor,
1081
                        ModularXmppClientToServerConnectionInternal connectionInternal) {
1✔
1082
            super(stateDescriptor, connectionInternal);
1✔
1083
        }
1✔
1084

1085
        @Override
1086
        public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) {
1087
            activeTransport.disconnect();
×
1088
            activeTransport = null;
×
1089

1090
            authenticated = connected = false;
×
1091

1092
            return StateTransitionResult.Success.EMPTY_INSTANCE;
×
1093
        }
1094
    }
1095

1096
    public void addConnectionStateMachineListener(ConnectionStateMachineListener connectionStateMachineListener) {
1097
        connectionStateMachineListeners.add(connectionStateMachineListener);
×
1098
    }
×
1099

1100
    public boolean removeConnectionStateMachineListener(ConnectionStateMachineListener connectionStateMachineListener) {
1101
        return connectionStateMachineListeners.remove(connectionStateMachineListener);
×
1102
    }
1103

1104
    private void invokeConnectionStateMachineListener(ConnectionStateEvent connectionStateEvent) {
1105
        if (connectionStateMachineListeners.isEmpty()) {
×
1106
            return;
×
1107
        }
1108

1109
        ASYNC_BUT_ORDERED.performAsyncButOrdered(this, () -> {
×
1110
            for (ConnectionStateMachineListener connectionStateMachineListener : connectionStateMachineListeners) {
×
1111
                connectionStateMachineListener.onConnectionStateEvent(connectionStateEvent, this);
×
1112
            }
×
1113
        });
×
1114
    }
×
1115

1116
    @Override
1117
    public boolean isSecureConnection() {
1118
        final XmppClientToServerTransport transport = activeTransport;
×
1119
        if (transport == null) {
×
1120
            return false;
×
1121
        }
1122
        return transport.isTransportSecured();
×
1123
    }
1124

1125
    @Override
1126
    protected void connectInternal() throws SmackException, IOException, XMPPException, InterruptedException {
1127
        WalkStateGraphContext walkStateGraphContext = buildNewWalkTo(
×
1128
                        ConnectedButUnauthenticatedStateDescriptor.class).build();
×
1129
        walkStateGraph(walkStateGraphContext);
×
1130
    }
×
1131

1132
    @Override
1133
    public InetAddress getLocalAddress() {
1134
        return null;
×
1135
    }
1136

1137
    private Map<String, Object> getFilterStats() {
1138
        Collection<XmppInputOutputFilter> filters;
1139
        synchronized (this) {
×
1140
            if (inputOutputFilters.isEmpty() && previousInputOutputFilters != null) {
×
1141
                filters = previousInputOutputFilters;
×
1142
            } else {
1143
                filters = inputOutputFilters;
×
1144
            }
1145
        }
×
1146

1147
        Map<String, Object> filterStats = new HashMap<>(filters.size());
×
1148
        for (XmppInputOutputFilter xmppInputOutputFilter : filters) {
×
1149
            Object stats = xmppInputOutputFilter.getStats();
×
1150
            String filterName = xmppInputOutputFilter.getFilterName();
×
1151

1152
            filterStats.put(filterName, stats);
×
1153
        }
×
1154

1155
        return filterStats;
×
1156
    }
1157

1158
    public Stats getStats() {
1159
        Map<Class<? extends ModularXmppClientToServerConnectionModuleDescriptor>, XmppClientToServerTransport.Stats> transportsStats = new HashMap<>(
×
1160
                        transports.size());
×
1161
        for (Map.Entry<Class<? extends ModularXmppClientToServerConnectionModuleDescriptor>, XmppClientToServerTransport> entry : transports.entrySet()) {
×
1162
            XmppClientToServerTransport.Stats transportStats = entry.getValue().getStats();
×
1163

1164
            transportsStats.put(entry.getKey(), transportStats);
×
1165
        }
×
1166

1167
        Map<String, Object> filterStats = getFilterStats();
×
1168

1169
        return new Stats(transportsStats, filterStats);
×
1170
    }
1171

1172
    public static final class Stats extends AbstractStats {
1173
        public final Map<Class<? extends ModularXmppClientToServerConnectionModuleDescriptor>, XmppClientToServerTransport.Stats> transportsStats;
1174
        public final Map<String, Object> filtersStats;
1175

1176
        private Stats(Map<Class<? extends ModularXmppClientToServerConnectionModuleDescriptor>, XmppClientToServerTransport.Stats> transportsStats,
1177
                        Map<String, Object> filtersStats) {
×
1178
            this.transportsStats = Collections.unmodifiableMap(transportsStats);
×
1179
            this.filtersStats = Collections.unmodifiableMap(filtersStats);
×
1180
        }
×
1181

1182
        @Override
1183
        public void appendStatsTo(ExtendedAppendable appendable) throws IOException {
1184
            StringUtils.appendHeading(appendable, "Connection stats", '#').append('\n');
×
1185

1186
            for (Map.Entry<Class<? extends ModularXmppClientToServerConnectionModuleDescriptor>, XmppClientToServerTransport.Stats> entry : transportsStats.entrySet()) {
×
1187
                Class<? extends ModularXmppClientToServerConnectionModuleDescriptor> transportClass = entry.getKey();
×
1188
                XmppClientToServerTransport.Stats stats = entry.getValue();
×
1189

1190
                StringUtils.appendHeading(appendable, transportClass.getName());
×
1191
                if (stats != null) {
×
1192
                    appendable.append(stats.toString());
×
1193
                } else {
1194
                    appendable.append("No stats available.");
×
1195
                }
1196
                appendable.append('\n');
×
1197
            }
×
1198

1199
            for (Map.Entry<String, Object> entry : filtersStats.entrySet()) {
×
1200
                String filterName = entry.getKey();
×
1201
                Object filterStats = entry.getValue();
×
1202

1203
                StringUtils.appendHeading(appendable, filterName);
×
1204
                appendable.append(filterStats.toString()).append('\n');
×
1205
            }
×
1206
        }
×
1207

1208
    }
1209
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc