001/**
002 *
003 * Copyright 2003-2007 Jive Software.
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smack.tcp;
018
019import org.jivesoftware.smack.AbstractConnectionListener;
020import org.jivesoftware.smack.AbstractXMPPConnection;
021import org.jivesoftware.smack.ConnectionConfiguration;
022import org.jivesoftware.smack.ConnectionConfiguration.DnssecMode;
023import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
024import org.jivesoftware.smack.StanzaListener;
025import org.jivesoftware.smack.SmackConfiguration;
026import org.jivesoftware.smack.SmackException;
027import org.jivesoftware.smack.SmackException.AlreadyConnectedException;
028import org.jivesoftware.smack.SmackException.AlreadyLoggedInException;
029import org.jivesoftware.smack.SmackException.NoResponseException;
030import org.jivesoftware.smack.SmackException.NotConnectedException;
031import org.jivesoftware.smack.SmackException.ConnectionException;
032import org.jivesoftware.smack.SmackException.SecurityRequiredByServerException;
033import org.jivesoftware.smack.SynchronizationPoint;
034import org.jivesoftware.smack.XMPPException.FailedNonzaException;
035import org.jivesoftware.smack.XMPPException.StreamErrorException;
036import org.jivesoftware.smack.XMPPConnection;
037import org.jivesoftware.smack.XMPPException;
038import org.jivesoftware.smack.compress.packet.Compressed;
039import org.jivesoftware.smack.compression.XMPPInputOutputStream;
040import org.jivesoftware.smack.filter.StanzaFilter;
041import org.jivesoftware.smack.compress.packet.Compress;
042import org.jivesoftware.smack.packet.Element;
043import org.jivesoftware.smack.packet.IQ;
044import org.jivesoftware.smack.packet.Message;
045import org.jivesoftware.smack.packet.StreamOpen;
046import org.jivesoftware.smack.packet.Stanza;
047import org.jivesoftware.smack.packet.Presence;
048import org.jivesoftware.smack.packet.StartTls;
049import org.jivesoftware.smack.packet.StreamError;
050import org.jivesoftware.smack.sasl.packet.SaslStreamElements;
051import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Challenge;
052import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure;
053import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Success;
054import org.jivesoftware.smack.sm.SMUtils;
055import org.jivesoftware.smack.sm.StreamManagementException;
056import org.jivesoftware.smack.sm.StreamManagementException.StreamIdDoesNotMatchException;
057import org.jivesoftware.smack.sm.StreamManagementException.StreamManagementCounterError;
058import org.jivesoftware.smack.sm.StreamManagementException.StreamManagementNotEnabledException;
059import org.jivesoftware.smack.sm.packet.StreamManagement;
060import org.jivesoftware.smack.sm.packet.StreamManagement.AckAnswer;
061import org.jivesoftware.smack.sm.packet.StreamManagement.AckRequest;
062import org.jivesoftware.smack.sm.packet.StreamManagement.Enable;
063import org.jivesoftware.smack.sm.packet.StreamManagement.Enabled;
064import org.jivesoftware.smack.sm.packet.StreamManagement.Failed;
065import org.jivesoftware.smack.sm.packet.StreamManagement.Resume;
066import org.jivesoftware.smack.sm.packet.StreamManagement.Resumed;
067import org.jivesoftware.smack.sm.packet.StreamManagement.StreamManagementFeature;
068import org.jivesoftware.smack.sm.predicates.Predicate;
069import org.jivesoftware.smack.sm.provider.ParseStreamManagement;
070import org.jivesoftware.smack.packet.Nonza;
071import org.jivesoftware.smack.proxy.ProxyInfo;
072import org.jivesoftware.smack.util.ArrayBlockingQueueWithShutdown;
073import org.jivesoftware.smack.util.Async;
074import org.jivesoftware.smack.util.DNSUtil;
075import org.jivesoftware.smack.util.PacketParserUtils;
076import org.jivesoftware.smack.util.StringUtils;
077import org.jivesoftware.smack.util.TLSUtils;
078import org.jivesoftware.smack.util.XmlStringBuilder;
079import org.jivesoftware.smack.util.dns.HostAddress;
080import org.jivesoftware.smack.util.dns.SmackDaneProvider;
081import org.jivesoftware.smack.util.dns.SmackDaneVerifier;
082import org.jxmpp.jid.impl.JidCreate;
083import org.jxmpp.jid.parts.Resourcepart;
084import org.jxmpp.stringprep.XmppStringprepException;
085import org.jxmpp.util.XmppStringUtils;
086import org.xmlpull.v1.XmlPullParser;
087import org.xmlpull.v1.XmlPullParserException;
088
089import javax.net.SocketFactory;
090import javax.net.ssl.HostnameVerifier;
091import javax.net.ssl.KeyManager;
092import javax.net.ssl.KeyManagerFactory;
093import javax.net.ssl.SSLContext;
094import javax.net.ssl.SSLSession;
095import javax.net.ssl.SSLSocket;
096import javax.net.ssl.TrustManager;
097import javax.net.ssl.X509TrustManager;
098import javax.security.auth.callback.Callback;
099import javax.security.auth.callback.CallbackHandler;
100import javax.security.auth.callback.PasswordCallback;
101
102import java.io.BufferedReader;
103import java.io.ByteArrayInputStream;
104import java.io.FileInputStream;
105import java.io.IOException;
106import java.io.InputStream;
107import java.io.InputStreamReader;
108import java.io.OutputStream;
109import java.io.OutputStreamWriter;
110import java.io.Writer;
111import java.lang.reflect.Constructor;
112import java.net.InetAddress;
113import java.net.InetSocketAddress;
114import java.net.Socket;
115import java.security.KeyManagementException;
116import java.security.KeyStore;
117import java.security.KeyStoreException;
118import java.security.NoSuchAlgorithmException;
119import java.security.NoSuchProviderException;
120import java.security.Provider;
121import java.security.SecureRandom;
122import java.security.Security;
123import java.security.UnrecoverableKeyException;
124import java.security.cert.CertificateException;
125import java.util.ArrayList;
126import java.util.Collection;
127import java.util.Iterator;
128import java.util.LinkedHashSet;
129import java.util.LinkedList;
130import java.util.List;
131import java.util.Map;
132import java.util.Set;
133import java.util.concurrent.ArrayBlockingQueue;
134import java.util.concurrent.BlockingQueue;
135import java.util.concurrent.ConcurrentHashMap;
136import java.util.concurrent.ConcurrentLinkedQueue;
137import java.util.concurrent.TimeUnit;
138import java.util.concurrent.atomic.AtomicBoolean;
139import java.util.logging.Level;
140import java.util.logging.Logger;
141
142/**
143 * Creates a socket connection to an XMPP server. This is the default connection
144 * to an XMPP server and is specified in the XMPP Core (RFC 6120).
145 * 
146 * @see XMPPConnection
147 * @author Matt Tucker
148 */
149public class XMPPTCPConnection extends AbstractXMPPConnection {
150
151    private static final int QUEUE_SIZE = 500;
152    private static final Logger LOGGER = Logger.getLogger(XMPPTCPConnection.class.getName());
153
154    /**
155     * The socket which is used for this connection.
156     */
157    private Socket socket;
158
159    /**
160     * 
161     */
162    private boolean disconnectedButResumeable = false;
163
164    private SSLSocket secureSocket;
165
166    /**
167     * Protected access level because of unit test purposes
168     */
169    protected PacketWriter packetWriter;
170
171    /**
172     * Protected access level because of unit test purposes
173     */
174    protected PacketReader packetReader;
175
176    private final SynchronizationPoint<Exception> initalOpenStreamSend = new SynchronizationPoint<>(
177                    this, "initial open stream element send to server");
178
179    /**
180     * 
181     */
182    private final SynchronizationPoint<XMPPException> maybeCompressFeaturesReceived = new SynchronizationPoint<XMPPException>(
183                    this, "stream compression feature");
184
185    /**
186     * 
187     */
188    private final SynchronizationPoint<SmackException> compressSyncPoint = new SynchronizationPoint<>(
189                    this, "stream compression");
190
191    /**
192     * A synchronization point which is successful if this connection has received the closing
193     * stream element from the remote end-point, i.e. the server.
194     */
195    private final SynchronizationPoint<Exception> closingStreamReceived = new SynchronizationPoint<>(
196                    this, "stream closing element received");
197
198    /**
199     * The default bundle and defer callback, used for new connections.
200     * @see bundleAndDeferCallback
201     */
202    private static BundleAndDeferCallback defaultBundleAndDeferCallback;
203
204    /**
205     * The used bundle and defer callback.
206     * <p>
207     * Although this field may be set concurrently, the 'volatile' keyword was deliberately not added, in order to avoid
208     * having a 'volatile' read within the writer threads loop.
209     * </p>
210     */
211    private BundleAndDeferCallback bundleAndDeferCallback = defaultBundleAndDeferCallback;
212
213    private static boolean useSmDefault = true;
214
215    private static boolean useSmResumptionDefault = true;
216
217    /**
218     * The stream ID of the stream that is currently resumable, ie. the stream we hold the state
219     * for in {@link #clientHandledStanzasCount}, {@link #serverHandledStanzasCount} and
220     * {@link #unacknowledgedStanzas}.
221     */
222    private String smSessionId;
223
224    private final SynchronizationPoint<FailedNonzaException> smResumedSyncPoint = new SynchronizationPoint<>(
225                    this, "stream resumed element");
226
227    private final SynchronizationPoint<SmackException> smEnabledSyncPoint = new SynchronizationPoint<>(
228                    this, "stream enabled element");
229
230    /**
231     * The client's preferred maximum resumption time in seconds.
232     */
233    private int smClientMaxResumptionTime = -1;
234
235    /**
236     * The server's preferred maximum resumption time in seconds.
237     */
238    private int smServerMaxResumptimTime = -1;
239
240    /**
241     * Indicates whether Stream Management (XEP-198) should be used if it's supported by the server.
242     */
243    private boolean useSm = useSmDefault;
244    private boolean useSmResumption = useSmResumptionDefault;
245
246    /**
247     * The counter that the server sends the client about it's current height. For example, if the server sends
248     * {@code <a h='42'/>}, then this will be set to 42 (while also handling the {@link #unacknowledgedStanzas} queue).
249     */
250    private long serverHandledStanzasCount = 0;
251
252    /**
253     * The counter for stanzas handled ("received") by the client.
254     * <p>
255     * Note that we don't need to synchronize this counter. Although JLS 17.7 states that reads and writes to longs are
256     * not atomic, it guarantees that there are at most 2 separate writes, one to each 32-bit half. And since
257     * {@link SMUtils#incrementHeight(long)} masks the lower 32 bit, we only operate on one half of the long and
258     * therefore have no concurrency problem because the read/write operations on one half are guaranteed to be atomic.
259     * </p>
260     */
261    private long clientHandledStanzasCount = 0;
262
263    private BlockingQueue<Stanza> unacknowledgedStanzas;
264
265    /**
266     * Set to true if Stream Management was at least once enabled for this connection.
267     */
268    private boolean smWasEnabledAtLeastOnce = false;
269
270    /**
271     * This listeners are invoked for every stanza that got acknowledged.
272     * <p>
273     * We use a {@link ConccurrentLinkedQueue} here in order to allow the listeners to remove
274     * themselves after they have been invoked.
275     * </p>
276     */
277    private final Collection<StanzaListener> stanzaAcknowledgedListeners = new ConcurrentLinkedQueue<StanzaListener>();
278
279    /**
280     * This listeners are invoked for a acknowledged stanza that has the given stanza ID. They will
281     * only be invoked once and automatically removed after that.
282     */
283    private final Map<String, StanzaListener> stanzaIdAcknowledgedListeners = new ConcurrentHashMap<String, StanzaListener>();
284
285    /**
286     * Predicates that determine if an stream management ack should be requested from the server.
287     * <p>
288     * We use a linked hash set here, so that the order how the predicates are added matches the
289     * order in which they are invoked in order to determine if an ack request should be send or not.
290     * </p>
291     */
292    private final Set<StanzaFilter> requestAckPredicates = new LinkedHashSet<StanzaFilter>();
293
294    private final XMPPTCPConnectionConfiguration config;
295
296    /**
297     * Creates a new XMPP connection over TCP (optionally using proxies).
298     * <p>
299     * Note that XMPPTCPConnection constructors do not establish a connection to the server
300     * and you must call {@link #connect()}.
301     * </p>
302     *
303     * @param config the connection configuration.
304     */
305    public XMPPTCPConnection(XMPPTCPConnectionConfiguration config) {
306        super(config);
307        this.config = config;
308        addConnectionListener(new AbstractConnectionListener() {
309            @Override
310            public void connectionClosedOnError(Exception e) {
311                if (e instanceof XMPPException.StreamErrorException) {
312                    dropSmState();
313                }
314            }
315        });
316    }
317
318    /**
319     * Creates a new XMPP connection over TCP.
320     * <p>
321     * Note that {@code jid} must be the bare JID, e.g. "user@example.org". More fine-grained control over the
322     * connection settings is available using the {@link #XMPPTCPConnection(XMPPTCPConnectionConfiguration)}
323     * constructor.
324     * </p>
325     * 
326     * @param jid the bare JID used by the client.
327     * @param password the password or authentication token.
328     * @throws XmppStringprepException 
329     */
330    public XMPPTCPConnection(CharSequence jid, String password) throws XmppStringprepException {
331        this(XmppStringUtils.parseLocalpart(jid.toString()), password, XmppStringUtils.parseDomain(jid.toString()));
332    }
333
334    /**
335     * Creates a new XMPP connection over TCP.
336     * <p>
337     * This is the simplest constructor for connecting to an XMPP server. Alternatively,
338     * you can get fine-grained control over connection settings using the
339     * {@link #XMPPTCPConnection(XMPPTCPConnectionConfiguration)} constructor.
340     * </p>
341     * @param username
342     * @param password
343     * @param serviceName
344     * @throws XmppStringprepException 
345     */
346    public XMPPTCPConnection(CharSequence username, String password, String serviceName) throws XmppStringprepException {
347        this(XMPPTCPConnectionConfiguration.builder().setUsernameAndPassword(username, password).setXmppDomain(
348                                        JidCreate.domainBareFrom(serviceName)).build());
349    }
350
351    @Override
352    protected void throwNotConnectedExceptionIfAppropriate() throws NotConnectedException {
353        if (packetWriter == null) {
354            throw new NotConnectedException();
355        }
356        packetWriter.throwNotConnectedExceptionIfDoneAndResumptionNotPossible();
357    }
358
359    @Override
360    protected void throwAlreadyConnectedExceptionIfAppropriate() throws AlreadyConnectedException {
361        if (isConnected() && !disconnectedButResumeable) {
362            throw new AlreadyConnectedException();
363        }
364    }
365
366    @Override
367    protected void throwAlreadyLoggedInExceptionIfAppropriate() throws AlreadyLoggedInException {
368        if (isAuthenticated() && !disconnectedButResumeable) {
369            throw new AlreadyLoggedInException();
370        }
371    }
372
373    @Override
374    protected void afterSuccessfulLogin(final boolean resumed) throws NotConnectedException, InterruptedException {
375        // Reset the flag in case it was set
376        disconnectedButResumeable = false;
377        super.afterSuccessfulLogin(resumed);
378    }
379
380    @Override
381    protected synchronized void loginInternal(String username, String password, Resourcepart resource) throws XMPPException,
382                    SmackException, IOException, InterruptedException {
383        // Authenticate using SASL
384        SSLSession sslSession = secureSocket != null ? secureSocket.getSession() : null;
385        saslAuthentication.authenticate(username, password, config.getAuthzid(), sslSession);
386
387        // If compression is enabled then request the server to use stream compression. XEP-170
388        // recommends to perform stream compression before resource binding.
389        maybeEnableCompression();
390
391        if (isSmResumptionPossible()) {
392            smResumedSyncPoint.sendAndWaitForResponse(new Resume(clientHandledStanzasCount, smSessionId));
393            if (smResumedSyncPoint.wasSuccessful()) {
394                // We successfully resumed the stream, be done here
395                afterSuccessfulLogin(true);
396                return;
397            }
398            // SM resumption failed, what Smack does here is to report success of
399            // lastFeaturesReceived in case of sm resumption was answered with 'failed' so that
400            // normal resource binding can be tried.
401            LOGGER.fine("Stream resumption failed, continuing with normal stream establishment process");
402        }
403
404        List<Stanza> previouslyUnackedStanzas = new LinkedList<Stanza>();
405        if (unacknowledgedStanzas != null) {
406            // There was a previous connection with SM enabled but that was either not resumable or
407            // failed to resume. Make sure that we (re-)send the unacknowledged stanzas.
408            unacknowledgedStanzas.drainTo(previouslyUnackedStanzas);
409            // Reset unacknowledged stanzas to 'null' to signal that we never send 'enable' in this
410            // XMPP session (There maybe was an enabled in a previous XMPP session of this
411            // connection instance though). This is used in writePackets to decide if stanzas should
412            // be added to the unacknowledged stanzas queue, because they have to be added right
413            // after the 'enable' stream element has been sent.
414            dropSmState();
415        }
416
417        // Now bind the resource. It is important to do this *after* we dropped an eventually
418        // existing Stream Management state. As otherwise <bind/> and <session/> may end up in
419        // unacknowledgedStanzas and become duplicated on reconnect. See SMACK-706.
420        bindResourceAndEstablishSession(resource);
421
422        if (isSmAvailable() && useSm) {
423            // Remove what is maybe left from previously stream managed sessions
424            serverHandledStanzasCount = 0;
425            // XEP-198 3. Enabling Stream Management. If the server response to 'Enable' is 'Failed'
426            // then this is a non recoverable error and we therefore throw an exception.
427            smEnabledSyncPoint.sendAndWaitForResponseOrThrow(new Enable(useSmResumption, smClientMaxResumptionTime));
428            synchronized (requestAckPredicates) {
429                if (requestAckPredicates.isEmpty()) {
430                    // Assure that we have at lest one predicate set up that so that we request acks
431                    // for the server and eventually flush some stanzas from the unacknowledged
432                    // stanza queue
433                    requestAckPredicates.add(Predicate.forMessagesOrAfter5Stanzas());
434                }
435            }
436        }
437        // (Re-)send the stanzas *after* we tried to enable SM
438        for (Stanza stanza : previouslyUnackedStanzas) {
439            sendStanzaInternal(stanza);
440        }
441
442        afterSuccessfulLogin(false);
443    }
444
445    @Override
446    public boolean isSecureConnection() {
447        return secureSocket != null;
448    }
449
450    /**
451     * Shuts the current connection down. After this method returns, the connection must be ready
452     * for re-use by connect.
453     */
454    @Override
455    protected void shutdown() {
456        if (isSmEnabled()) {
457            try {
458                // Try to send a last SM Acknowledgement. Most servers won't find this information helpful, as the SM
459                // state is dropped after a clean disconnect anyways. OTOH it doesn't hurt much either.
460                sendSmAcknowledgementInternal();
461            } catch (InterruptedException | NotConnectedException e) {
462                LOGGER.log(Level.FINE, "Can not send final SM ack as connection is not connected", e);
463            }
464        }
465        shutdown(false);
466    }
467
468    /**
469     * Performs an unclean disconnect and shutdown of the connection. Does not send a closing stream stanza.
470     */
471    public synchronized void instantShutdown() {
472        shutdown(true);
473    }
474
475    private void shutdown(boolean instant) {
476        if (disconnectedButResumeable) {
477            return;
478        }
479
480        // First shutdown the writer, this will result in a closing stream element getting send to
481        // the server
482        if (packetWriter != null) {
483            LOGGER.finer("PacketWriter shutdown()");
484            packetWriter.shutdown(instant);
485        }
486        LOGGER.finer("PacketWriter has been shut down");
487
488        if (!instant) {
489            try {
490                // After we send the closing stream element, check if there was already a
491                // closing stream element sent by the server or wait with a timeout for a
492                // closing stream element to be received from the server.
493                @SuppressWarnings("unused")
494                Exception res = closingStreamReceived.checkIfSuccessOrWait();
495            } catch (InterruptedException | NoResponseException e) {
496                LOGGER.log(Level.INFO, "Exception while waiting for closing stream element from the server " + this, e);
497            }
498        }
499
500        if (packetReader != null) {
501            LOGGER.finer("PacketReader shutdown()");
502                packetReader.shutdown();
503        }
504        LOGGER.finer("PacketReader has been shut down");
505
506        try {
507                socket.close();
508        } catch (Exception e) {
509                LOGGER.log(Level.WARNING, "shutdown", e);
510        }
511
512        setWasAuthenticated();
513        // If we are able to resume the stream, then don't set
514        // connected/authenticated/usingTLS to false since we like behave like we are still
515        // connected (e.g. sendStanza should not throw a NotConnectedException).
516        if (isSmResumptionPossible() && instant) {
517            disconnectedButResumeable = true;
518        } else {
519            disconnectedButResumeable = false;
520            // Reset the stream management session id to null, since if the stream is cleanly closed, i.e. sending a closing
521            // stream tag, there is no longer a stream to resume.
522            smSessionId = null;
523        }
524        authenticated = false;
525        connected = false;
526        secureSocket = null;
527        reader = null;
528        writer = null;
529
530        maybeCompressFeaturesReceived.init();
531        compressSyncPoint.init();
532        smResumedSyncPoint.init();
533        smEnabledSyncPoint.init();
534        initalOpenStreamSend.init();
535    }
536
537    @Override
538    public void sendNonza(Nonza element) throws NotConnectedException, InterruptedException {
539        packetWriter.sendStreamElement(element);
540    }
541
542    @Override
543    protected void sendStanzaInternal(Stanza packet) throws NotConnectedException, InterruptedException {
544        packetWriter.sendStreamElement(packet);
545        if (isSmEnabled()) {
546            for (StanzaFilter requestAckPredicate : requestAckPredicates) {
547                if (requestAckPredicate.accept(packet)) {
548                    requestSmAcknowledgementInternal();
549                    break;
550                }
551            }
552        }
553    }
554
555    private void connectUsingConfiguration() throws ConnectionException, IOException {
556        List<HostAddress> failedAddresses = populateHostAddresses();
557        SocketFactory socketFactory = config.getSocketFactory();
558        ProxyInfo proxyInfo = config.getProxyInfo();
559        int timeout = config.getConnectTimeout();
560        if (socketFactory == null) {
561            socketFactory = SocketFactory.getDefault();
562        }
563        for (HostAddress hostAddress : hostAddresses) {
564            Iterator<InetAddress> inetAddresses = null;
565            String host = hostAddress.getFQDN();
566            int port = hostAddress.getPort();
567            if (proxyInfo == null) {
568                inetAddresses = hostAddress.getInetAddresses().iterator();
569                assert(inetAddresses.hasNext());
570
571                innerloop: while (inetAddresses.hasNext()) {
572                    // Create a *new* Socket before every connection attempt, i.e. connect() call, since Sockets are not
573                    // re-usable after a failed connection attempt. See also SMACK-724.
574                    socket = socketFactory.createSocket();
575
576                    final InetAddress inetAddress = inetAddresses.next();
577                    final String inetAddressAndPort = inetAddress + " at port " + port;
578                    LOGGER.finer("Trying to establish TCP connection to " + inetAddressAndPort);
579                    try {
580                        socket.connect(new InetSocketAddress(inetAddress, port), timeout);
581                    } catch (Exception e) {
582                        hostAddress.setException(inetAddress, e);
583                        if (inetAddresses.hasNext()) {
584                            continue innerloop;
585                        } else {
586                            break innerloop;
587                        }
588                    }
589                    LOGGER.finer("Established TCP connection to " + inetAddressAndPort);
590                    // We found a host to connect to, return here
591                    this.host = host;
592                    this.port = port;
593                    return;
594                }
595                failedAddresses.add(hostAddress);
596            } else {
597                socket = socketFactory.createSocket();
598                StringUtils.requireNotNullOrEmpty(host, "Host of HostAddress " + hostAddress + " must not be null when using a Proxy");
599                final String hostAndPort = host + " at port " + port;
600                LOGGER.finer("Trying to establish TCP connection via Proxy to " + hostAndPort);
601                try {
602                    proxyInfo.getProxySocketConnection().connect(socket, host, port, timeout);
603                } catch (IOException e) {
604                    hostAddress.setException(e);
605                    continue;
606                }
607                LOGGER.finer("Established TCP connection to " + hostAndPort);
608                // We found a host to connect to, return here
609                this.host = host;
610                this.port = port;
611                return;
612            }
613        }
614        // There are no more host addresses to try
615        // throw an exception and report all tried
616        // HostAddresses in the exception
617        throw ConnectionException.from(failedAddresses);
618    }
619
620    /**
621     * Initializes the connection by creating a stanza(/packet) reader and writer and opening a
622     * XMPP stream to the server.
623     *
624     * @throws XMPPException if establishing a connection to the server fails.
625     * @throws SmackException if the server failes to respond back or if there is anther error.
626     * @throws IOException 
627     */
628    private void initConnection() throws IOException {
629        boolean isFirstInitialization = packetReader == null || packetWriter == null;
630        compressionHandler = null;
631
632        // Set the reader and writer instance variables
633        initReaderAndWriter();
634
635        if (isFirstInitialization) {
636            packetWriter = new PacketWriter();
637            packetReader = new PacketReader();
638
639            // If debugging is enabled, we should start the thread that will listen for
640            // all packets and then log them.
641            if (config.isDebuggerEnabled()) {
642                addAsyncStanzaListener(debugger.getReaderListener(), null);
643                if (debugger.getWriterListener() != null) {
644                    addPacketSendingListener(debugger.getWriterListener(), null);
645                }
646            }
647        }
648        // Start the packet writer. This will open an XMPP stream to the server
649        packetWriter.init();
650        // Start the packet reader. The startup() method will block until we
651        // get an opening stream packet back from server
652        packetReader.init();
653    }
654
655    private void initReaderAndWriter() throws IOException {
656        InputStream is = socket.getInputStream();
657        OutputStream os = socket.getOutputStream();
658        if (compressionHandler != null) {
659            is = compressionHandler.getInputStream(is);
660            os = compressionHandler.getOutputStream(os);
661        }
662        // OutputStreamWriter is already buffered, no need to wrap it into a BufferedWriter
663        writer = new OutputStreamWriter(os, "UTF-8");
664        reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
665
666        // If debugging is enabled, we open a window and write out all network traffic.
667        initDebugger();
668    }
669
670    /**
671     * The server has indicated that TLS negotiation can start. We now need to secure the
672     * existing plain connection and perform a handshake. This method won't return until the
673     * connection has finished the handshake or an error occurred while securing the connection.
674     * @throws IOException 
675     * @throws CertificateException 
676     * @throws NoSuchAlgorithmException 
677     * @throws NoSuchProviderException 
678     * @throws KeyStoreException 
679     * @throws UnrecoverableKeyException 
680     * @throws KeyManagementException 
681     * @throws SmackException 
682     * @throws Exception if an exception occurs.
683     */
684    private void proceedTLSReceived() throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException, NoSuchProviderException, UnrecoverableKeyException, KeyManagementException, SmackException {
685        SSLContext context = this.config.getCustomSSLContext();
686        KeyStore ks = null;
687        KeyManager[] kms = null;
688        PasswordCallback pcb = null;
689        SmackDaneVerifier daneVerifier = null;
690
691        if (config.getDnssecMode() == DnssecMode.needsDnssecAndDane) {
692            SmackDaneProvider daneProvider = DNSUtil.getDaneProvider();
693            if (daneProvider == null) {
694                throw new UnsupportedOperationException("DANE enabled but no SmackDaneProvider configured");
695            }
696            daneVerifier = daneProvider.newInstance();
697            if (daneVerifier == null) {
698                throw new IllegalStateException("DANE requested but DANE provider did not return a DANE verifier");
699            }
700        }
701
702        if (context == null) {
703            final String keyStoreType = config.getKeystoreType();
704            final CallbackHandler callbackHandler = config.getCallbackHandler();
705            final String keystorePath = config.getKeystorePath();
706            if ("PKCS11".equals(keyStoreType)) {
707                try {
708                    Constructor<?> c = Class.forName("sun.security.pkcs11.SunPKCS11").getConstructor(InputStream.class);
709                    String pkcs11Config = "name = SmartCard\nlibrary = "+config.getPKCS11Library();
710                    ByteArrayInputStream config = new ByteArrayInputStream(pkcs11Config.getBytes(StringUtils.UTF8));
711                    Provider p = (Provider)c.newInstance(config);
712                    Security.addProvider(p);
713                    ks = KeyStore.getInstance("PKCS11",p);
714                    pcb = new PasswordCallback("PKCS11 Password: ",false);
715                    callbackHandler.handle(new Callback[]{pcb});
716                    ks.load(null,pcb.getPassword());
717                }
718                catch (Exception e) {
719                    LOGGER.log(Level.WARNING, "Exception", e);
720                    ks = null;
721                }
722            }
723            else if ("Apple".equals(keyStoreType)) {
724                ks = KeyStore.getInstance("KeychainStore","Apple");
725                ks.load(null,null);
726                //pcb = new PasswordCallback("Apple Keychain",false);
727                //pcb.setPassword(null);
728            }
729            else if (keyStoreType != null){
730                ks = KeyStore.getInstance(keyStoreType);
731                if (callbackHandler != null && StringUtils.isNotEmpty(keystorePath)) {
732                    try {
733                        pcb = new PasswordCallback("Keystore Password: ", false);
734                        callbackHandler.handle(new Callback[] { pcb });
735                        ks.load(new FileInputStream(keystorePath), pcb.getPassword());
736                    }
737                    catch (Exception e) {
738                        LOGGER.log(Level.WARNING, "Exception", e);
739                        ks = null;
740                    }
741                } else {
742                    ks.load(null, null);
743                }
744            }
745
746            if (ks != null) {
747                KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
748                try {
749                    if (pcb == null) {
750                        kmf.init(ks, null);
751                    }
752                    else {
753                        kmf.init(ks, pcb.getPassword());
754                        pcb.clearPassword();
755                    }
756                    kms = kmf.getKeyManagers();
757                }
758                catch (NullPointerException npe) {
759                    LOGGER.log(Level.WARNING, "NullPointerException", npe);
760                }
761            }
762
763            // If the user didn't specify a SSLContext, use the default one
764            context = SSLContext.getInstance("TLS");
765
766            final SecureRandom secureRandom = new java.security.SecureRandom();
767            X509TrustManager customTrustManager = config.getCustomX509TrustManager();
768
769            if (daneVerifier != null) {
770                // User requested DANE verification.
771                daneVerifier.init(context, kms, customTrustManager, secureRandom);
772            } else {
773                TrustManager[] customTrustManagers = null;
774                if (customTrustManager != null) {
775                    customTrustManagers = new TrustManager[] { customTrustManager };
776                }
777                context.init(kms, customTrustManagers, secureRandom);
778            }
779        }
780
781        Socket plain = socket;
782        // Secure the plain connection
783        socket = context.getSocketFactory().createSocket(plain,
784                host, plain.getPort(), true);
785
786        final SSLSocket sslSocket = (SSLSocket) socket;
787        // Immediately set the enabled SSL protocols and ciphers. See SMACK-712 why this is
788        // important (at least on certain platforms) and it seems to be a good idea anyways to
789        // prevent an accidental implicit handshake.
790        TLSUtils.setEnabledProtocolsAndCiphers(sslSocket, config.getEnabledSSLProtocols(), config.getEnabledSSLCiphers());
791
792        // Initialize the reader and writer with the new secured version
793        initReaderAndWriter();
794
795        // Proceed to do the handshake
796        sslSocket.startHandshake();
797
798        if (daneVerifier != null) {
799            daneVerifier.finish(sslSocket);
800        }
801
802        final HostnameVerifier verifier = getConfiguration().getHostnameVerifier();
803        if (verifier == null) {
804                throw new IllegalStateException("No HostnameVerifier set. Use connectionConfiguration.setHostnameVerifier() to configure.");
805        } else if (!verifier.verify(getXMPPServiceDomain().toString(), sslSocket.getSession())) {
806            throw new CertificateException("Hostname verification of certificate failed. Certificate does not authenticate " + getXMPPServiceDomain());
807        }
808
809        // Set that TLS was successful
810        secureSocket = sslSocket;
811    }
812
813    /**
814     * Returns the compression handler that can be used for one compression methods offered by the server.
815     * 
816     * @return a instance of XMPPInputOutputStream or null if no suitable instance was found
817     * 
818     */
819    private static XMPPInputOutputStream maybeGetCompressionHandler(Compress.Feature compression) {
820        for (XMPPInputOutputStream handler : SmackConfiguration.getCompresionHandlers()) {
821                String method = handler.getCompressionMethod();
822                if (compression.getMethods().contains(method))
823                    return handler;
824        }
825        return null;
826    }
827
828    @Override
829    public boolean isUsingCompression() {
830        return compressionHandler != null && compressSyncPoint.wasSuccessful();
831    }
832
833    /**
834     * <p>
835     * Starts using stream compression that will compress network traffic. Traffic can be
836     * reduced up to 90%. Therefore, stream compression is ideal when using a slow speed network
837     * connection. However, the server and the client will need to use more CPU time in order to
838     * un/compress network data so under high load the server performance might be affected.
839     * </p>
840     * <p>
841     * Stream compression has to have been previously offered by the server. Currently only the
842     * zlib method is supported by the client. Stream compression negotiation has to be done
843     * before authentication took place.
844     * </p>
845     *
846     * @throws NotConnectedException 
847     * @throws SmackException
848     * @throws NoResponseException 
849     * @throws InterruptedException 
850     */
851    private void maybeEnableCompression() throws NotConnectedException, NoResponseException, SmackException, InterruptedException {
852        if (!config.isCompressionEnabled()) {
853            return;
854        }
855        maybeCompressFeaturesReceived.checkIfSuccessOrWait();
856        Compress.Feature compression = getFeature(Compress.Feature.ELEMENT, Compress.NAMESPACE);
857        if (compression == null) {
858            // Server does not support compression
859            return;
860        }
861        // If stream compression was offered by the server and we want to use
862        // compression then send compression request to the server
863        if ((compressionHandler = maybeGetCompressionHandler(compression)) != null) {
864            compressSyncPoint.sendAndWaitForResponseOrThrow(new Compress(compressionHandler.getCompressionMethod()));
865        } else {
866            LOGGER.warning("Could not enable compression because no matching handler/method pair was found");
867        }
868    }
869
870    /**
871     * Establishes a connection to the XMPP server. It basically
872     * creates and maintains a socket connection to the server.
873     * <p>
874     * Listeners will be preserved from a previous connection if the reconnection
875     * occurs after an abrupt termination.
876     * </p>
877     *
878     * @throws XMPPException if an error occurs while trying to establish the connection.
879     * @throws SmackException 
880     * @throws IOException 
881     * @throws InterruptedException 
882     */
883    @Override
884    protected void connectInternal() throws SmackException, IOException, XMPPException, InterruptedException {
885        closingStreamReceived.init();
886        // Establishes the TCP connection to the server and does setup the reader and writer. Throws an exception if
887        // there is an error establishing the connection
888        connectUsingConfiguration();
889
890        // We connected successfully to the servers TCP port
891        initConnection();
892    }
893
894    /**
895     * Sends out a notification that there was an error with the connection
896     * and closes the connection. Also prints the stack trace of the given exception
897     *
898     * @param e the exception that causes the connection close event.
899     */
900    private synchronized void notifyConnectionError(Exception e) {
901        // Listeners were already notified of the exception, return right here.
902        if ((packetReader == null || packetReader.done) &&
903                (packetWriter == null || packetWriter.done())) return;
904
905        // Closes the connection temporary. A reconnection is possible
906        // Note that a connection listener of XMPPTCPConnection will drop the SM state in
907        // case the Exception is a StreamErrorException.
908        instantShutdown();
909
910        // Notify connection listeners of the error.
911        callConnectionClosedOnErrorListener(e);
912    }
913
914    /**
915     * For unit testing purposes
916     *
917     * @param writer
918     */
919    protected void setWriter(Writer writer) {
920        this.writer = writer;
921    }
922
923    @Override
924    protected void afterFeaturesReceived() throws NotConnectedException, InterruptedException {
925        StartTls startTlsFeature = getFeature(StartTls.ELEMENT, StartTls.NAMESPACE);
926        if (startTlsFeature != null) {
927            if (startTlsFeature.required() && config.getSecurityMode() == SecurityMode.disabled) {
928                SmackException smackException = new SecurityRequiredByServerException();
929                tlsHandled.reportFailure(smackException);
930                notifyConnectionError(smackException);
931                return;
932            }
933
934            if (config.getSecurityMode() != ConnectionConfiguration.SecurityMode.disabled) {
935                sendNonza(new StartTls());
936            } else {
937                tlsHandled.reportSuccess();
938            }
939        } else {
940            tlsHandled.reportSuccess();
941        }
942
943        if (getSASLAuthentication().authenticationSuccessful()) {
944            // If we have received features after the SASL has been successfully completed, then we
945            // have also *maybe* received, as it is an optional feature, the compression feature
946            // from the server.
947            maybeCompressFeaturesReceived.reportSuccess();
948        }
949    }
950
951    /**
952     * Resets the parser using the latest connection's reader. Reseting the parser is necessary
953     * when the plain connection has been secured or when a new opening stream element is going
954     * to be sent by the server.
955     *
956     * @throws SmackException if the parser could not be reset.
957     * @throws InterruptedException 
958     */
959    void openStream() throws SmackException, InterruptedException {
960        // If possible, provide the receiving entity of the stream open tag, i.e. the server, as much information as
961        // possible. The 'to' attribute is *always* available. The 'from' attribute if set by the user and no external
962        // mechanism is used to determine the local entity (user). And the 'id' attribute is available after the first
963        // response from the server (see e.g. RFC 6120 ยง 9.1.1 Step 2.)
964        CharSequence to = getXMPPServiceDomain();
965        CharSequence from = null;
966        CharSequence localpart = config.getUsername();
967        if (localpart != null) {
968            from = XmppStringUtils.completeJidFrom(localpart, to);
969        }
970        String id = getStreamId();
971        sendNonza(new StreamOpen(to, from, id));
972        try {
973            packetReader.parser = PacketParserUtils.newXmppParser(reader);
974        }
975        catch (XmlPullParserException e) {
976            throw new SmackException(e);
977        }
978    }
979
980    protected class PacketReader {
981
982        XmlPullParser parser;
983
984        private volatile boolean done;
985
986        /**
987         * Initializes the reader in order to be used. The reader is initialized during the
988         * first connection and when reconnecting due to an abruptly disconnection.
989         */
990        void init() {
991            done = false;
992
993            Async.go(new Runnable() {
994                @Override
995                public void run() {
996                    parsePackets();
997                }
998            }, "Smack Packet Reader (" + getConnectionCounter() + ")");
999         }
1000
1001        /**
1002         * Shuts the stanza(/packet) reader down. This method simply sets the 'done' flag to true.
1003         */
1004        void shutdown() {
1005            done = true;
1006        }
1007
1008        /**
1009         * Parse top-level packets in order to process them further.
1010         *
1011         * @param thread the thread that is being used by the reader to parse incoming packets.
1012         */
1013        private void parsePackets() {
1014            try {
1015                initalOpenStreamSend.checkIfSuccessOrWait();
1016                int eventType = parser.getEventType();
1017                while (!done) {
1018                    switch (eventType) {
1019                    case XmlPullParser.START_TAG:
1020                        final String name = parser.getName();
1021                        switch (name) {
1022                        case Message.ELEMENT:
1023                        case IQ.IQ_ELEMENT:
1024                        case Presence.ELEMENT:
1025                            try {
1026                                parseAndProcessStanza(parser);
1027                            } finally {
1028                                clientHandledStanzasCount = SMUtils.incrementHeight(clientHandledStanzasCount);
1029                            }
1030                            break;
1031                        case "stream":
1032                            // We found an opening stream.
1033                            if ("jabber:client".equals(parser.getNamespace(null))) {
1034                                streamId = parser.getAttributeValue("", "id");
1035                                String reportedServerDomain = parser.getAttributeValue("", "from");
1036                                assert(config.getXMPPServiceDomain().equals(reportedServerDomain));
1037                            }
1038                            break;
1039                        case "error":
1040                            StreamError streamError = PacketParserUtils.parseStreamError(parser);
1041                            saslFeatureReceived.reportFailure(new StreamErrorException(streamError));
1042                            // Mark the tlsHandled sync point as success, we will use the saslFeatureReceived sync
1043                            // point to report the error, which is checked immediately after tlsHandled in
1044                            // connectInternal().
1045                            tlsHandled.reportSuccess();
1046                            throw new StreamErrorException(streamError);
1047                        case "features":
1048                            parseFeatures(parser);
1049                            break;
1050                        case "proceed":
1051                            try {
1052                                // Secure the connection by negotiating TLS
1053                                proceedTLSReceived();
1054                                // Send a new opening stream to the server
1055                                openStream();
1056                            }
1057                            catch (Exception e) {
1058                                SmackException smackException = new SmackException(e);
1059                                tlsHandled.reportFailure(smackException);
1060                                throw e;
1061                            }
1062                            break;
1063                        case "failure":
1064                            String namespace = parser.getNamespace(null);
1065                            switch (namespace) {
1066                            case "urn:ietf:params:xml:ns:xmpp-tls":
1067                                // TLS negotiation has failed. The server will close the connection
1068                                // TODO Parse failure stanza
1069                                throw new SmackException("TLS negotiation has failed");
1070                            case "http://jabber.org/protocol/compress":
1071                                // Stream compression has been denied. This is a recoverable
1072                                // situation. It is still possible to authenticate and
1073                                // use the connection but using an uncompressed connection
1074                                // TODO Parse failure stanza
1075                                compressSyncPoint.reportFailure(new SmackException(
1076                                                "Could not establish compression"));
1077                                break;
1078                            case SaslStreamElements.NAMESPACE:
1079                                // SASL authentication has failed. The server may close the connection
1080                                // depending on the number of retries
1081                                final SASLFailure failure = PacketParserUtils.parseSASLFailure(parser);
1082                                getSASLAuthentication().authenticationFailed(failure);
1083                                break;
1084                            }
1085                            break;
1086                        case Challenge.ELEMENT:
1087                            // The server is challenging the SASL authentication made by the client
1088                            String challengeData = parser.nextText();
1089                            getSASLAuthentication().challengeReceived(challengeData);
1090                            break;
1091                        case Success.ELEMENT:
1092                            Success success = new Success(parser.nextText());
1093                            // We now need to bind a resource for the connection
1094                            // Open a new stream and wait for the response
1095                            openStream();
1096                            // The SASL authentication with the server was successful. The next step
1097                            // will be to bind the resource
1098                            getSASLAuthentication().authenticated(success);
1099                            break;
1100                        case Compressed.ELEMENT:
1101                            // Server confirmed that it's possible to use stream compression. Start
1102                            // stream compression
1103                            // Initialize the reader and writer with the new compressed version
1104                            initReaderAndWriter();
1105                            // Send a new opening stream to the server
1106                            openStream();
1107                            // Notify that compression is being used
1108                            compressSyncPoint.reportSuccess();
1109                            break;
1110                        case Enabled.ELEMENT:
1111                            Enabled enabled = ParseStreamManagement.enabled(parser);
1112                            if (enabled.isResumeSet()) {
1113                                smSessionId = enabled.getId();
1114                                if (StringUtils.isNullOrEmpty(smSessionId)) {
1115                                    SmackException xmppException = new SmackException("Stream Management 'enabled' element with resume attribute but without session id received");
1116                                    smEnabledSyncPoint.reportFailure(xmppException);
1117                                    throw xmppException;
1118                                }
1119                                smServerMaxResumptimTime = enabled.getMaxResumptionTime();
1120                            } else {
1121                                // Mark this a non-resumable stream by setting smSessionId to null
1122                                smSessionId = null;
1123                            }
1124                            clientHandledStanzasCount = 0;
1125                            smWasEnabledAtLeastOnce = true;
1126                            smEnabledSyncPoint.reportSuccess();
1127                            LOGGER.fine("Stream Management (XEP-198): succesfully enabled");
1128                            break;
1129                        case Failed.ELEMENT:
1130                            Failed failed = ParseStreamManagement.failed(parser);
1131                            FailedNonzaException xmppException = new FailedNonzaException(failed, failed.getXMPPErrorCondition());
1132                            // If only XEP-198 would specify different failure elements for the SM
1133                            // enable and SM resume failure case. But this is not the case, so we
1134                            // need to determine if this is a 'Failed' response for either 'Enable'
1135                            // or 'Resume'.
1136                            if (smResumedSyncPoint.requestSent()) {
1137                                smResumedSyncPoint.reportFailure(xmppException);
1138                            }
1139                            else {
1140                                if (!smEnabledSyncPoint.requestSent()) {
1141                                    throw new IllegalStateException("Failed element received but SM was not previously enabled");
1142                                }
1143                                smEnabledSyncPoint.reportFailure(new SmackException(xmppException));
1144                                // Report success for last lastFeaturesReceived so that in case a
1145                                // failed resumption, we can continue with normal resource binding.
1146                                // See text of XEP-198 5. below Example 11.
1147                                lastFeaturesReceived.reportSuccess();
1148                            }
1149                            break;
1150                        case Resumed.ELEMENT:
1151                            Resumed resumed = ParseStreamManagement.resumed(parser);
1152                            if (!smSessionId.equals(resumed.getPrevId())) {
1153                                throw new StreamIdDoesNotMatchException(smSessionId, resumed.getPrevId());
1154                            }
1155                            // Mark SM as enabled and resumption as successful.
1156                            smResumedSyncPoint.reportSuccess();
1157                            smEnabledSyncPoint.reportSuccess();
1158                            // First, drop the stanzas already handled by the server
1159                            processHandledCount(resumed.getHandledCount());
1160                            // Then re-send what is left in the unacknowledged queue
1161                            List<Stanza> stanzasToResend = new ArrayList<>(unacknowledgedStanzas.size());
1162                            unacknowledgedStanzas.drainTo(stanzasToResend);
1163                            for (Stanza stanza : stanzasToResend) {
1164                                sendStanzaInternal(stanza);
1165                            }
1166                            // If there where stanzas resent, then request a SM ack for them.
1167                            // Writer's sendStreamElement() won't do it automatically based on
1168                            // predicates.
1169                            if (!stanzasToResend.isEmpty()) {
1170                                requestSmAcknowledgementInternal();
1171                            }
1172                            LOGGER.fine("Stream Management (XEP-198): Stream resumed");
1173                            break;
1174                        case AckAnswer.ELEMENT:
1175                            AckAnswer ackAnswer = ParseStreamManagement.ackAnswer(parser);
1176                            processHandledCount(ackAnswer.getHandledCount());
1177                            break;
1178                        case AckRequest.ELEMENT:
1179                            ParseStreamManagement.ackRequest(parser);
1180                            if (smEnabledSyncPoint.wasSuccessful()) {
1181                                sendSmAcknowledgementInternal();
1182                            } else {
1183                                LOGGER.warning("SM Ack Request received while SM is not enabled");
1184                            }
1185                            break;
1186                         default:
1187                             LOGGER.warning("Unknown top level stream element: " + name);
1188                             break;
1189                        }
1190                        break;
1191                    case XmlPullParser.END_TAG:
1192                        if (parser.getName().equals("stream")) {
1193                            if (!parser.getNamespace().equals("http://etherx.jabber.org/streams")) {
1194                                LOGGER.warning(XMPPTCPConnection.this +  " </stream> but different namespace " + parser.getNamespace());
1195                                break;
1196                            }
1197
1198                            // Check if the queue was already shut down before reporting success on closing stream tag
1199                            // received. This avoids a race if there is a disconnect(), followed by a connect(), which
1200                            // did re-start the queue again, causing this writer to assume that the queue is not
1201                            // shutdown, which results in a call to disconnect().
1202                            final boolean queueWasShutdown = packetWriter.queue.isShutdown();
1203                            closingStreamReceived.reportSuccess();
1204
1205                            if (queueWasShutdown) {
1206                                // We received a closing stream element *after* we initiated the
1207                                // termination of the session by sending a closing stream element to
1208                                // the server first
1209                                return;
1210                            } else {
1211                                // We received a closing stream element from the server without us
1212                                // sending a closing stream element first. This means that the
1213                                // server wants to terminate the session, therefore disconnect
1214                                // the connection
1215                                LOGGER.info(XMPPTCPConnection.this
1216                                                + " received closing </stream> element."
1217                                                + " Server wants to terminate the connection, calling disconnect()");
1218                                disconnect();
1219                            }
1220                        }
1221                        break;
1222                    case XmlPullParser.END_DOCUMENT:
1223                        // END_DOCUMENT only happens in an error case, as otherwise we would see a
1224                        // closing stream element before.
1225                        throw new SmackException(
1226                                        "Parser got END_DOCUMENT event. This could happen e.g. if the server closed the connection without sending a closing stream element");
1227                    }
1228                    eventType = parser.next();
1229                }
1230            }
1231            catch (Exception e) {
1232                closingStreamReceived.reportFailure(e);
1233                // The exception can be ignored if the the connection is 'done'
1234                // or if the it was caused because the socket got closed
1235                if (!(done || packetWriter.queue.isShutdown())) {
1236                    // Close the connection and notify connection listeners of the
1237                    // error.
1238                    notifyConnectionError(e);
1239                }
1240            }
1241        }
1242    }
1243
1244    protected class PacketWriter {
1245        public static final int QUEUE_SIZE = XMPPTCPConnection.QUEUE_SIZE;
1246
1247        private final ArrayBlockingQueueWithShutdown<Element> queue = new ArrayBlockingQueueWithShutdown<Element>(
1248                        QUEUE_SIZE, true);
1249
1250        /**
1251         * Needs to be protected for unit testing purposes.
1252         */
1253        protected SynchronizationPoint<NoResponseException> shutdownDone = new SynchronizationPoint<NoResponseException>(
1254                        XMPPTCPConnection.this, "shutdown completed");
1255
1256        /**
1257         * If set, the stanza(/packet) writer is shut down
1258         */
1259        protected volatile Long shutdownTimestamp = null;
1260
1261        private volatile boolean instantShutdown;
1262
1263        /**
1264         * True if some preconditions are given to start the bundle and defer mechanism.
1265         * <p>
1266         * This will likely get set to true right after the start of the writer thread, because
1267         * {@link #nextStreamElement()} will check if {@link queue} is empty, which is probably the case, and then set
1268         * this field to true.
1269         * </p>
1270         */
1271        private boolean shouldBundleAndDefer;
1272
1273        /** 
1274        * Initializes the writer in order to be used. It is called at the first connection and also 
1275        * is invoked if the connection is disconnected by an error.
1276        */ 
1277        void init() {
1278            shutdownDone.init();
1279            shutdownTimestamp = null;
1280
1281            if (unacknowledgedStanzas != null) {
1282                // It's possible that there are new stanzas in the writer queue that
1283                // came in while we were disconnected but resumable, drain those into
1284                // the unacknowledged queue so that they get resent now
1285                drainWriterQueueToUnacknowledgedStanzas();
1286            }
1287
1288            queue.start();
1289            Async.go(new Runnable() {
1290                @Override
1291                public void run() {
1292                    writePackets();
1293                }
1294            }, "Smack Packet Writer (" + getConnectionCounter() + ")");
1295        }
1296
1297        private boolean done() {
1298            return shutdownTimestamp != null;
1299        }
1300
1301        protected void throwNotConnectedExceptionIfDoneAndResumptionNotPossible() throws NotConnectedException {
1302            final boolean done = done();
1303            if (done) {
1304                final boolean smResumptionPossbile = isSmResumptionPossible();
1305                // Don't throw a NotConnectedException is there is an resumable stream available
1306                if (!smResumptionPossbile) {
1307                    throw new NotConnectedException(XMPPTCPConnection.this, "done=" + done
1308                                    + " smResumptionPossible=" + smResumptionPossbile);
1309                }
1310            }
1311        }
1312
1313        /**
1314         * Sends the specified element to the server.
1315         *
1316         * @param element the element to send.
1317         * @throws NotConnectedException 
1318         * @throws InterruptedException 
1319         */
1320        protected void sendStreamElement(Element element) throws NotConnectedException, InterruptedException {
1321            throwNotConnectedExceptionIfDoneAndResumptionNotPossible();
1322            try {
1323                queue.put(element);
1324            }
1325            catch (InterruptedException e) {
1326                // put() may throw an InterruptedException for two reasons:
1327                // 1. If the queue was shut down
1328                // 2. If the thread was interrupted
1329                // so we have to check which is the case
1330                throwNotConnectedExceptionIfDoneAndResumptionNotPossible();
1331                // If the method above did not throw, then the sending thread was interrupted
1332                throw e;
1333            }
1334        }
1335
1336        /**
1337         * Shuts down the stanza(/packet) writer. Once this method has been called, no further
1338         * packets will be written to the server.
1339         * @throws InterruptedException 
1340         */
1341        void shutdown(boolean instant) {
1342            instantShutdown = instant;
1343            queue.shutdown();
1344            shutdownTimestamp = System.currentTimeMillis();
1345            try {
1346                shutdownDone.checkIfSuccessOrWait();
1347            }
1348            catch (NoResponseException | InterruptedException e) {
1349                LOGGER.log(Level.WARNING, "shutdownDone was not marked as successful by the writer thread", e);
1350            }
1351        }
1352
1353        /**
1354         * Maybe return the next available element from the queue for writing. If the queue is shut down <b>or</b> a
1355         * spurious interrupt occurs, <code>null</code> is returned. So it is important to check the 'done' condition in
1356         * that case.
1357         *
1358         * @return the next element for writing or null.
1359         */
1360        private Element nextStreamElement() {
1361            // It is important the we check if the queue is empty before removing an element from it
1362            if (queue.isEmpty()) {
1363                shouldBundleAndDefer = true;
1364            }
1365            Element packet = null;
1366            try {
1367                packet = queue.take();
1368            }
1369            catch (InterruptedException e) {
1370                if (!queue.isShutdown()) {
1371                    // Users shouldn't try to interrupt the packet writer thread
1372                    LOGGER.log(Level.WARNING, "Packet writer thread was interrupted. Don't do that. Use disconnect() instead.", e);
1373                }
1374            }
1375            return packet;
1376        }
1377
1378        private void writePackets() {
1379            Exception writerException = null;
1380            try {
1381                openStream();
1382                initalOpenStreamSend.reportSuccess();
1383                // Write out packets from the queue.
1384                while (!done()) {
1385                    Element element = nextStreamElement();
1386                    if (element == null) {
1387                        continue;
1388                    }
1389
1390                    // Get a local version of the bundle and defer callback, in case it's unset
1391                    // between the null check and the method invocation
1392                    final BundleAndDeferCallback localBundleAndDeferCallback = bundleAndDeferCallback;
1393                    // If the preconditions are given (e.g. bundleAndDefer callback is set, queue is
1394                    // empty), then we could wait a bit for further stanzas attempting to decrease
1395                    // our energy consumption
1396                    if (localBundleAndDeferCallback != null && isAuthenticated() && shouldBundleAndDefer) {
1397                        // Reset shouldBundleAndDefer to false, nextStreamElement() will set it to true once the
1398                        // queue is empty again.
1399                        shouldBundleAndDefer = false;
1400                        final AtomicBoolean bundlingAndDeferringStopped = new AtomicBoolean();
1401                        final int bundleAndDeferMillis = localBundleAndDeferCallback.getBundleAndDeferMillis(new BundleAndDefer(
1402                                        bundlingAndDeferringStopped));
1403                        if (bundleAndDeferMillis > 0) {
1404                            long remainingWait = bundleAndDeferMillis;
1405                            final long waitStart = System.currentTimeMillis();
1406                            synchronized (bundlingAndDeferringStopped) {
1407                                while (!bundlingAndDeferringStopped.get() && remainingWait > 0) {
1408                                    bundlingAndDeferringStopped.wait(remainingWait);
1409                                    remainingWait = bundleAndDeferMillis
1410                                                    - (System.currentTimeMillis() - waitStart);
1411                                }
1412                            }
1413                        }
1414                    }
1415
1416                    Stanza packet = null;
1417                    if (element instanceof Stanza) {
1418                        packet = (Stanza) element;
1419                    }
1420                    else if (element instanceof Enable) {
1421                        // The client needs to add messages to the unacknowledged stanzas queue
1422                        // right after it sent 'enabled'. Stanza will be added once
1423                        // unacknowledgedStanzas is not null.
1424                        unacknowledgedStanzas = new ArrayBlockingQueue<>(QUEUE_SIZE);
1425                    }
1426                    // Check if the stream element should be put to the unacknowledgedStanza
1427                    // queue. Note that we can not do the put() in sendStanzaInternal() and the
1428                    // packet order is not stable at this point (sendStanzaInternal() can be
1429                    // called concurrently).
1430                    if (unacknowledgedStanzas != null && packet != null) {
1431                        // If the unacknowledgedStanza queue is nearly full, request an new ack
1432                        // from the server in order to drain it
1433                        if (unacknowledgedStanzas.size() == 0.8 * XMPPTCPConnection.QUEUE_SIZE) {
1434                            writer.write(AckRequest.INSTANCE.toXML().toString());
1435                            writer.flush();
1436                        }
1437                        try {
1438                            // It is important the we put the stanza in the unacknowledged stanza
1439                            // queue before we put it on the wire
1440                            unacknowledgedStanzas.put(packet);
1441                        }
1442                        catch (InterruptedException e) {
1443                            throw new IllegalStateException(e);
1444                        }
1445                    }
1446
1447                    CharSequence elementXml = element.toXML();
1448                    if (elementXml instanceof XmlStringBuilder) {
1449                        ((XmlStringBuilder) elementXml).write(writer);
1450                    }
1451                    else {
1452                        writer.write(elementXml.toString());
1453                    }
1454
1455                    if (queue.isEmpty()) {
1456                        writer.flush();
1457                    }
1458                    if (packet != null) {
1459                        firePacketSendingListeners(packet);
1460                    }
1461                }
1462                if (!instantShutdown) {
1463                    // Flush out the rest of the queue.
1464                    try {
1465                        while (!queue.isEmpty()) {
1466                            Element packet = queue.remove();
1467                            writer.write(packet.toXML().toString());
1468                        }
1469                        writer.flush();
1470                    }
1471                    catch (Exception e) {
1472                        LOGGER.log(Level.WARNING,
1473                                        "Exception flushing queue during shutdown, ignore and continue",
1474                                        e);
1475                    }
1476
1477                    // Close the stream.
1478                    try {
1479                        writer.write("</stream:stream>");
1480                        writer.flush();
1481                    }
1482                    catch (Exception e) {
1483                        LOGGER.log(Level.WARNING, "Exception writing closing stream element", e);
1484                    }
1485
1486                    // Delete the queue contents (hopefully nothing is left).
1487                    queue.clear();
1488                } else if (instantShutdown && isSmEnabled()) {
1489                    // This was an instantShutdown and SM is enabled, drain all remaining stanzas
1490                    // into the unacknowledgedStanzas queue
1491                    drainWriterQueueToUnacknowledgedStanzas();
1492                }
1493                // Do *not* close the writer here, as it will cause the socket
1494                // to get closed. But we may want to receive further stanzas
1495                // until the closing stream tag is received. The socket will be
1496                // closed in shutdown().
1497            }
1498            catch (Exception e) {
1499                // The exception can be ignored if the the connection is 'done'
1500                // or if the it was caused because the socket got closed
1501                if (!(done() || queue.isShutdown())) {
1502                    writerException = e;
1503                } else {
1504                    LOGGER.log(Level.FINE, "Ignoring Exception in writePackets()", e);
1505                }
1506            } finally {
1507                LOGGER.fine("Reporting shutdownDone success in writer thread");
1508                shutdownDone.reportSuccess();
1509            }
1510            // Delay notifyConnectionError after shutdownDone has been reported in the finally block.
1511            if (writerException != null) {
1512                notifyConnectionError(writerException);
1513            }
1514        }
1515
1516        private void drainWriterQueueToUnacknowledgedStanzas() {
1517            List<Element> elements = new ArrayList<Element>(queue.size());
1518            queue.drainTo(elements);
1519            for (Element element : elements) {
1520                if (element instanceof Stanza) {
1521                    unacknowledgedStanzas.add((Stanza) element);
1522                }
1523            }
1524        }
1525    }
1526
1527    /**
1528     * Set if Stream Management should be used by default for new connections.
1529     * 
1530     * @param useSmDefault true to use Stream Management for new connections.
1531     */
1532    public static void setUseStreamManagementDefault(boolean useSmDefault) {
1533        XMPPTCPConnection.useSmDefault = useSmDefault;
1534    }
1535
1536    /**
1537     * Set if Stream Management resumption should be used by default for new connections.
1538     * 
1539     * @param useSmResumptionDefault true to use Stream Management resumption for new connections.
1540     * @deprecated use {@link #setUseStreamManagementResumptionDefault(boolean)} instead.
1541     */
1542    @Deprecated
1543    public static void setUseStreamManagementResumptiodDefault(boolean useSmResumptionDefault) {
1544        setUseStreamManagementResumptionDefault(useSmResumptionDefault);
1545    }
1546
1547    /**
1548     * Set if Stream Management resumption should be used by default for new connections.
1549     *
1550     * @param useSmResumptionDefault true to use Stream Management resumption for new connections.
1551     */
1552    public static void setUseStreamManagementResumptionDefault(boolean useSmResumptionDefault) {
1553        if (useSmResumptionDefault) {
1554            // Also enable SM is resumption is enabled
1555            setUseStreamManagementDefault(useSmResumptionDefault);
1556        }
1557        XMPPTCPConnection.useSmResumptionDefault = useSmResumptionDefault;
1558    }
1559
1560    /**
1561     * Set if Stream Management should be used if supported by the server.
1562     * 
1563     * @param useSm true to use Stream Management.
1564     */
1565    public void setUseStreamManagement(boolean useSm) {
1566        this.useSm = useSm;
1567    }
1568
1569    /**
1570     * Set if Stream Management resumption should be used if supported by the server.
1571     *
1572     * @param useSmResumption true to use Stream Management resumption.
1573     */
1574    public void setUseStreamManagementResumption(boolean useSmResumption) {
1575        if (useSmResumption) {
1576            // Also enable SM is resumption is enabled
1577            setUseStreamManagement(useSmResumption);
1578        }
1579        this.useSmResumption = useSmResumption;
1580    }
1581
1582    /**
1583     * Set the preferred resumption time in seconds.
1584     * @param resumptionTime the preferred resumption time in seconds
1585     */
1586    public void setPreferredResumptionTime(int resumptionTime) {
1587        smClientMaxResumptionTime = resumptionTime;
1588    }
1589
1590    /**
1591     * Add a predicate for Stream Management acknowledgment requests.
1592     * <p>
1593     * Those predicates are used to determine when a Stream Management acknowledgement request is send to the server.
1594     * Some pre-defined predicates are found in the <code>org.jivesoftware.smack.sm.predicates</code> package.
1595     * </p>
1596     * <p>
1597     * If not predicate is configured, the {@link Predicate#forMessagesOrAfter5Stanzas()} will be used.
1598     * </p>
1599     * 
1600     * @param predicate the predicate to add.
1601     * @return if the predicate was not already active.
1602     */
1603    public boolean addRequestAckPredicate(StanzaFilter predicate) {
1604        synchronized (requestAckPredicates) {
1605            return requestAckPredicates.add(predicate);
1606        }
1607    }
1608
1609    /**
1610     * Remove the given predicate for Stream Management acknowledgment request.
1611     * @param predicate the predicate to remove.
1612     * @return true if the predicate was removed.
1613     */
1614    public boolean removeRequestAckPredicate(StanzaFilter predicate) {
1615        synchronized (requestAckPredicates) {
1616            return requestAckPredicates.remove(predicate);
1617        }
1618    }
1619
1620    /**
1621     * Remove all predicates for Stream Management acknowledgment requests.
1622     */
1623    public void removeAllRequestAckPredicates() {
1624        synchronized (requestAckPredicates) {
1625            requestAckPredicates.clear();
1626        }
1627    }
1628
1629    /**
1630     * Send an unconditional Stream Management acknowledgement request to the server.
1631     *
1632     * @throws StreamManagementNotEnabledException if Stream Mangement is not enabled.
1633     * @throws NotConnectedException if the connection is not connected.
1634     * @throws InterruptedException 
1635     */
1636    public void requestSmAcknowledgement() throws StreamManagementNotEnabledException, NotConnectedException, InterruptedException {
1637        if (!isSmEnabled()) {
1638            throw new StreamManagementException.StreamManagementNotEnabledException();
1639        }
1640        requestSmAcknowledgementInternal();
1641    }
1642
1643    private void requestSmAcknowledgementInternal() throws NotConnectedException, InterruptedException {
1644        packetWriter.sendStreamElement(AckRequest.INSTANCE);
1645    }
1646
1647    /**
1648     * Send a unconditional Stream Management acknowledgment to the server.
1649     * <p>
1650     * See <a href="http://xmpp.org/extensions/xep-0198.html#acking">XEP-198: Stream Management ยง 4. Acks</a>:
1651     * "Either party MAY send an <a/> element at any time (e.g., after it has received a certain number of stanzas,
1652     * or after a certain period of time), even if it has not received an <r/> element from the other party."
1653     * </p>
1654     * 
1655     * @throws StreamManagementNotEnabledException if Stream Management is not enabled.
1656     * @throws NotConnectedException if the connection is not connected.
1657     * @throws InterruptedException 
1658     */
1659    public void sendSmAcknowledgement() throws StreamManagementNotEnabledException, NotConnectedException, InterruptedException {
1660        if (!isSmEnabled()) {
1661            throw new StreamManagementException.StreamManagementNotEnabledException();
1662        }
1663        sendSmAcknowledgementInternal();
1664    }
1665
1666    private void sendSmAcknowledgementInternal() throws NotConnectedException, InterruptedException {
1667        packetWriter.sendStreamElement(new AckAnswer(clientHandledStanzasCount));
1668    }
1669
1670    /**
1671     * Add a Stanza acknowledged listener.
1672     * <p>
1673     * Those listeners will be invoked every time a Stanza has been acknowledged by the server. The will not get
1674     * automatically removed. Consider using {@link #addStanzaIdAcknowledgedListener(String, StanzaListener)} when
1675     * possible.
1676     * </p>
1677     * 
1678     * @param listener the listener to add.
1679     */
1680    public void addStanzaAcknowledgedListener(StanzaListener listener) {
1681        stanzaAcknowledgedListeners.add(listener);
1682    }
1683
1684    /**
1685     * Remove the given Stanza acknowledged listener.
1686     *
1687     * @param listener the listener.
1688     * @return true if the listener was removed.
1689     */
1690    public boolean removeStanzaAcknowledgedListener(StanzaListener listener) {
1691        return stanzaAcknowledgedListeners.remove(listener);
1692    }
1693
1694    /**
1695     * Remove all stanza acknowledged listeners.
1696     */
1697    public void removeAllStanzaAcknowledgedListeners() {
1698        stanzaAcknowledgedListeners.clear();
1699    }
1700
1701    /**
1702     * Add a new Stanza ID acknowledged listener for the given ID.
1703     * <p>
1704     * The listener will be invoked if the stanza with the given ID was acknowledged by the server. It will
1705     * automatically be removed after the listener was run.
1706     * </p>
1707     * 
1708     * @param id the stanza ID.
1709     * @param listener the listener to invoke.
1710     * @return the previous listener for this stanza ID or null.
1711     * @throws StreamManagementNotEnabledException if Stream Management is not enabled.
1712     */
1713    public StanzaListener addStanzaIdAcknowledgedListener(final String id, StanzaListener listener) throws StreamManagementNotEnabledException {
1714        // Prevent users from adding callbacks that will never get removed
1715        if (!smWasEnabledAtLeastOnce) {
1716            throw new StreamManagementException.StreamManagementNotEnabledException();
1717        }
1718        // Remove the listener after max. 12 hours
1719        final int removeAfterSeconds = Math.min(getMaxSmResumptionTime(), 12 * 60 * 60);
1720        schedule(new Runnable() {
1721            @Override
1722            public void run() {
1723                stanzaIdAcknowledgedListeners.remove(id);
1724            }
1725        }, removeAfterSeconds, TimeUnit.SECONDS);
1726        return stanzaIdAcknowledgedListeners.put(id, listener);
1727    }
1728
1729    /**
1730     * Remove the Stanza ID acknowledged listener for the given ID.
1731     * 
1732     * @param id the stanza ID.
1733     * @return true if the listener was found and removed, false otherwise.
1734     */
1735    public StanzaListener removeStanzaIdAcknowledgedListener(String id) {
1736        return stanzaIdAcknowledgedListeners.remove(id);
1737    }
1738
1739    /**
1740     * Removes all Stanza ID acknowledged listeners.
1741     */
1742    public void removeAllStanzaIdAcknowledgedListeners() {
1743        stanzaIdAcknowledgedListeners.clear();
1744    }
1745
1746    /**
1747     * Returns true if Stream Management is supported by the server.
1748     *
1749     * @return true if Stream Management is supported by the server.
1750     */
1751    public boolean isSmAvailable() {
1752        return hasFeature(StreamManagementFeature.ELEMENT, StreamManagement.NAMESPACE);
1753    }
1754
1755    /**
1756     * Returns true if Stream Management was successfully negotiated with the server.
1757     *
1758     * @return true if Stream Management was negotiated.
1759     */
1760    public boolean isSmEnabled() {
1761        return smEnabledSyncPoint.wasSuccessful();
1762    }
1763
1764    /**
1765     * Returns true if the stream was successfully resumed with help of Stream Management.
1766     * 
1767     * @return true if the stream was resumed.
1768     */
1769    public boolean streamWasResumed() {
1770        return smResumedSyncPoint.wasSuccessful();
1771    }
1772
1773    /**
1774     * Returns true if the connection is disconnected by a Stream resumption via Stream Management is possible.
1775     * 
1776     * @return true if disconnected but resumption possible.
1777     */
1778    public boolean isDisconnectedButSmResumptionPossible() {
1779        return disconnectedButResumeable && isSmResumptionPossible();
1780    }
1781
1782    /**
1783     * Returns true if the stream is resumable.
1784     *
1785     * @return true if the stream is resumable.
1786     */
1787    public boolean isSmResumptionPossible() {
1788        // There is no resumable stream available
1789        if (smSessionId == null)
1790            return false;
1791
1792        final Long shutdownTimestamp = packetWriter.shutdownTimestamp;
1793        // Seems like we are already reconnected, report true
1794        if (shutdownTimestamp == null) {
1795            return true;
1796        }
1797
1798        // See if resumption time is over
1799        long current = System.currentTimeMillis();
1800        long maxResumptionMillies = ((long) getMaxSmResumptionTime()) * 1000;
1801        if (current > shutdownTimestamp + maxResumptionMillies) {
1802            // Stream resumption is *not* possible if the current timestamp is greater then the greatest timestamp where
1803            // resumption is possible
1804            return false;
1805        } else {
1806            return true;
1807        }
1808    }
1809
1810    /**
1811     * Drop the stream management state. Sets {@link #smSessionId} and
1812     * {@link #unacknowledgedStanzas} to <code>null</code>.
1813     */
1814    private void dropSmState() {
1815        // clientHandledCount and serverHandledCount will be reset on <enable/> and <enabled/>
1816        // respective. No need to reset them here.
1817        smSessionId = null;
1818        unacknowledgedStanzas = null;
1819    }
1820
1821    /**
1822     * Get the maximum resumption time in seconds after which a managed stream can be resumed.
1823     * <p>
1824     * This method will return {@link Integer#MAX_VALUE} if neither the client nor the server specify a maximum
1825     * resumption time. Be aware of integer overflows when using this value, e.g. do not add arbitrary values to it
1826     * without checking for overflows before.
1827     * </p>
1828     *
1829     * @return the maximum resumption time in seconds or {@link Integer#MAX_VALUE} if none set.
1830     */
1831    public int getMaxSmResumptionTime() {
1832        int clientResumptionTime = smClientMaxResumptionTime > 0 ? smClientMaxResumptionTime : Integer.MAX_VALUE;
1833        int serverResumptionTime = smServerMaxResumptimTime > 0 ? smServerMaxResumptimTime : Integer.MAX_VALUE;
1834        return Math.min(clientResumptionTime, serverResumptionTime);
1835    }
1836
1837    private void processHandledCount(long handledCount) throws StreamManagementCounterError {
1838        long ackedStanzasCount = SMUtils.calculateDelta(handledCount, serverHandledStanzasCount);
1839        final List<Stanza> ackedStanzas = new ArrayList<Stanza>(
1840                        ackedStanzasCount <= Integer.MAX_VALUE ? (int) ackedStanzasCount
1841                                        : Integer.MAX_VALUE);
1842        for (long i = 0; i < ackedStanzasCount; i++) {
1843            Stanza ackedStanza = unacknowledgedStanzas.poll();
1844            // If the server ack'ed a stanza, then it must be in the
1845            // unacknowledged stanza queue. There can be no exception.
1846            if (ackedStanza == null) {
1847                throw new StreamManagementCounterError(handledCount, serverHandledStanzasCount,
1848                                ackedStanzasCount, ackedStanzas);
1849            }
1850            ackedStanzas.add(ackedStanza);
1851        }
1852
1853        boolean atLeastOneStanzaAcknowledgedListener = false;
1854        if (!stanzaAcknowledgedListeners.isEmpty()) {
1855            // If stanzaAcknowledgedListeners is not empty, the we have at least one
1856            atLeastOneStanzaAcknowledgedListener = true;
1857        }
1858        else {
1859            // Otherwise we look for a matching id in the stanza *id* acknowledged listeners
1860            for (Stanza ackedStanza : ackedStanzas) {
1861                String id = ackedStanza.getStanzaId();
1862                if (id != null && stanzaIdAcknowledgedListeners.containsKey(id)) {
1863                    atLeastOneStanzaAcknowledgedListener = true;
1864                    break;
1865                }
1866            }
1867        }
1868
1869        // Only spawn a new thread if there is a chance that some listener is invoked
1870        if (atLeastOneStanzaAcknowledgedListener) {
1871            asyncGo(new Runnable() {
1872                @Override
1873                public void run() {
1874                    for (Stanza ackedStanza : ackedStanzas) {
1875                        for (StanzaListener listener : stanzaAcknowledgedListeners) {
1876                            try {
1877                                listener.processStanza(ackedStanza);
1878                            }
1879                            catch (InterruptedException | NotConnectedException e) {
1880                                LOGGER.log(Level.FINER, "Received exception", e);
1881                            }
1882                        }
1883                        String id = ackedStanza.getStanzaId();
1884                        if (StringUtils.isNullOrEmpty(id)) {
1885                            continue;
1886                        }
1887                        StanzaListener listener = stanzaIdAcknowledgedListeners.remove(id);
1888                        if (listener != null) {
1889                            try {
1890                                listener.processStanza(ackedStanza);
1891                            }
1892                            catch (InterruptedException | NotConnectedException e) {
1893                                LOGGER.log(Level.FINER, "Received exception", e);
1894                            }
1895                        }
1896                    }
1897                }
1898            });
1899        }
1900
1901        serverHandledStanzasCount = handledCount;
1902    }
1903
1904    /**
1905     * Set the default bundle and defer callback used for new connections.
1906     *
1907     * @param defaultBundleAndDeferCallback
1908     * @see BundleAndDeferCallback
1909     * @since 4.1
1910     */
1911    public static void setDefaultBundleAndDeferCallback(BundleAndDeferCallback defaultBundleAndDeferCallback) {
1912        XMPPTCPConnection.defaultBundleAndDeferCallback = defaultBundleAndDeferCallback;
1913    }
1914
1915    /**
1916     * Set the bundle and defer callback used for this connection.
1917     * <p>
1918     * You can use <code>null</code> as argument to reset the callback. Outgoing stanzas will then
1919     * no longer get deferred.
1920     * </p>
1921     *
1922     * @param bundleAndDeferCallback the callback or <code>null</code>.
1923     * @see BundleAndDeferCallback
1924     * @since 4.1
1925     */
1926    public void setBundleandDeferCallback(BundleAndDeferCallback bundleAndDeferCallback) {
1927        this.bundleAndDeferCallback = bundleAndDeferCallback;
1928    }
1929
1930}