001/**
002 *
003 * Copyright 2018 Florian Schmaus
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smack.tcp;
018
019import java.io.IOException;
020import java.io.UnsupportedEncodingException;
021import java.lang.reflect.InvocationTargetException;
022import java.net.InetAddress;
023import java.net.InetSocketAddress;
024import java.nio.Buffer;
025import java.nio.ByteBuffer;
026import java.nio.channels.ClosedChannelException;
027import java.nio.channels.SelectionKey;
028import java.nio.channels.SocketChannel;
029import java.security.KeyManagementException;
030import java.security.KeyStoreException;
031import java.security.NoSuchAlgorithmException;
032import java.security.NoSuchProviderException;
033import java.security.UnrecoverableKeyException;
034import java.security.cert.CertificateException;
035import java.util.ArrayList;
036import java.util.Collection;
037import java.util.Collections;
038import java.util.HashSet;
039import java.util.IdentityHashMap;
040import java.util.Iterator;
041import java.util.List;
042import java.util.ListIterator;
043import java.util.Map;
044import java.util.Map.Entry;
045import java.util.Set;
046import java.util.concurrent.atomic.AtomicInteger;
047import java.util.concurrent.atomic.AtomicLong;
048import java.util.concurrent.locks.ReentrantLock;
049import java.util.logging.Level;
050import java.util.logging.Logger;
051
052import javax.net.ssl.SSLEngine;
053import javax.net.ssl.SSLEngineResult;
054import javax.net.ssl.SSLException;
055import javax.net.ssl.SSLSession;
056
057import org.jivesoftware.smack.AbstractXmppNioConnection;
058import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
059import org.jivesoftware.smack.SmackException;
060import org.jivesoftware.smack.SmackException.ConnectionException;
061import org.jivesoftware.smack.SmackException.NoResponseException;
062import org.jivesoftware.smack.SmackException.NotConnectedException;
063import org.jivesoftware.smack.SmackException.SecurityRequiredByClientException;
064import org.jivesoftware.smack.SmackException.SecurityRequiredByServerException;
065import org.jivesoftware.smack.SmackReactor.ChannelSelectedCallback;
066import org.jivesoftware.smack.SmackReactor.SelectionKeyAttachment;
067import org.jivesoftware.smack.SynchronizationPointWithSmackException;
068import org.jivesoftware.smack.XMPPException;
069import org.jivesoftware.smack.XMPPException.FailedNonzaException;
070import org.jivesoftware.smack.XMPPException.XMPPErrorException;
071import org.jivesoftware.smack.XmppInputOutputFilter;
072import org.jivesoftware.smack.fsm.LoginContext;
073import org.jivesoftware.smack.fsm.StateDescriptor;
074import org.jivesoftware.smack.fsm.StateDescriptorGraph;
075import org.jivesoftware.smack.fsm.StateDescriptorGraph.GraphVertex;
076import org.jivesoftware.smack.packet.Nonza;
077import org.jivesoftware.smack.packet.Stanza;
078import org.jivesoftware.smack.packet.StartTls;
079import org.jivesoftware.smack.packet.StreamClose;
080import org.jivesoftware.smack.packet.StreamOpen;
081import org.jivesoftware.smack.packet.TlsFailure;
082import org.jivesoftware.smack.packet.TlsProceed;
083import org.jivesoftware.smack.packet.TopLevelStreamElement;
084import org.jivesoftware.smack.sasl.SASLErrorException;
085import org.jivesoftware.smack.util.ArrayBlockingQueueWithShutdown;
086import org.jivesoftware.smack.util.CollectionUtil;
087import org.jivesoftware.smack.util.StringUtils;
088import org.jivesoftware.smack.util.UTF8;
089import org.jivesoftware.smack.util.XmlStringBuilder;
090import org.jivesoftware.smack.util.dns.HostAddress;
091
092import org.jxmpp.jid.DomainBareJid;
093import org.jxmpp.jid.Jid;
094import org.jxmpp.jid.impl.JidCreate;
095import org.jxmpp.stringprep.XmppStringprepException;
096import org.jxmpp.xml.splitter.Utf8ByteXmppXmlSplitter;
097import org.jxmpp.xml.splitter.XmppElementCallback;
098
099/**
100 * Test sentence.
101 *
102 * <h2>Smack XMPP TCP NIO connection states</h2>
103 * <img src="doc-files/XmppNioTcpConnectionStateGraph.png" alt="TODO">
104 * Another one.
105 */
106public class XmppNioTcpConnection extends AbstractXmppNioConnection {
107
108    private static final Logger LOGGER = Logger.getLogger(XmppNioTcpConnection.class.getName());
109
110    private static final Set<Class<? extends StateDescriptor>> BACKWARD_EDGES_STATE_DESCRIPTORS = new HashSet<>();
111
112    static final GraphVertex<StateDescriptor> INITIAL_STATE_DESCRIPTOR_VERTEX;
113
114    static {
115        BACKWARD_EDGES_STATE_DESCRIPTORS.add(LookupHostAddressesStateDescriptor.class);
116        BACKWARD_EDGES_STATE_DESCRIPTORS.add(EnableStreamManagementStateDescriptor.class);
117        BACKWARD_EDGES_STATE_DESCRIPTORS.add(ResumeStreamStateDescriptor.class);
118        BACKWARD_EDGES_STATE_DESCRIPTORS.add(InstantStreamResumptionStateDescriptor.class);
119        BACKWARD_EDGES_STATE_DESCRIPTORS.add(Bind2StateDescriptor.class);
120        BACKWARD_EDGES_STATE_DESCRIPTORS.add(InstantShutdownStateDescriptor.class);
121        BACKWARD_EDGES_STATE_DESCRIPTORS.add(ShutdownStateDescriptor.class);
122
123        try {
124            INITIAL_STATE_DESCRIPTOR_VERTEX = StateDescriptorGraph.constructStateDescriptorGraph(BACKWARD_EDGES_STATE_DESCRIPTORS);
125        } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException
126                        | NoSuchMethodException | SecurityException e) {
127            throw new IllegalStateException(e);
128        }
129    }
130
131    public static Set<Class<? extends StateDescriptor>> getBackwardEdgesStateDescriptors() {
132        return Collections.unmodifiableSet(BACKWARD_EDGES_STATE_DESCRIPTORS);
133    }
134
135    private static final int CALLBACK_MAX_BYTES_READ = 10 * 1024 * 1024;
136    private static final int CALLBACK_MAX_BYTES_WRITEN = CALLBACK_MAX_BYTES_READ;
137
138    private SelectionKey selectionKey;
139    private SelectionKeyAttachment selectionKeyAttachment;
140    private SocketChannel socketChannel;
141    private InetSocketAddress remoteAddress;
142    private TlsState tlsState;
143
144    private final Utf8ByteXmppXmlSplitter splitter = new Utf8ByteXmppXmlSplitter(new XmppElementCallback() {
145        private String streamOpen;
146        private String streamClose;
147
148        @Override
149        public void onCompleteElement(String completeElement) {
150            // TODO invoke debugger that we just received an element?
151            assert streamOpen != null;
152            assert streamClose != null;
153
154            String wrappedCompleteElement = streamOpen + completeElement + streamClose;
155            try {
156                parseAndProcessElement(wrappedCompleteElement);
157            } catch (Exception e) {
158                LOGGER.log(Level.WARNING, "exception", e);
159                // TODO connection closed on error
160            }
161        }
162
163        @Override
164        public void streamOpened(String prefix, Map<String, String> attributes) {
165            LOGGER.info("Stream opened. prefix=" + prefix + " attributes=" + attributes);
166
167            final String prefixXmlns = "xmlns:" + prefix;
168            final StringBuilder streamClose = new StringBuilder(32);
169            final StringBuilder streamOpen = new StringBuilder(256);
170
171            streamOpen.append('<');
172            streamClose.append("</");
173            if (StringUtils.isNotEmpty(prefix)) {
174                streamOpen.append(prefix).append(':');
175                streamClose.append(prefix).append(':');
176            }
177            streamOpen.append("stream");
178            streamClose.append("stream>");
179            for (Entry<String, String> entry : attributes.entrySet()) {
180                String attributeName = entry.getKey();
181                String attributeValue = entry.getValue();
182                switch (attributeName) {
183                case "id":
184                    streamId = attributeValue;
185                    break;
186                case "version":
187                    break;
188                case "xml:lang":
189                    streamOpen.append(" xml:lang='").append(attributeValue).append('\'');
190                    break;
191                case "to":
192                    break;
193                case "from":
194                    DomainBareJid reportedServerDomain;
195                    try {
196                        reportedServerDomain = JidCreate.domainBareFrom(attributeValue);
197                    } catch (XmppStringprepException e) {
198                        // TODO: handle this
199                        throw new AssertionError(e);
200                    }
201                    assert (config.getXMPPServiceDomain().equals(reportedServerDomain));
202                    break;
203                case "xmlns":
204                    streamOpen.append(" xmlns='").append(attributeValue).append('\'');
205                    break;
206                default:
207                    if (attributeName.equals(prefixXmlns)) {
208                        streamOpen.append(' ').append(prefixXmlns).append("='").append(attributeValue).append('\'');
209                        break;
210                    }
211                    LOGGER.info("Unknown <stream/> attribute: " + attributeName);
212                    break;
213                }
214            }
215            streamOpen.append('>');
216
217            this.streamOpen = streamOpen.toString();
218            this.streamClose = streamClose.toString();
219        }
220
221        @Override
222        public void streamClosed() {
223            LOGGER.info("Stream closed");
224            closingStreamReceived.reportSuccess();
225        }
226    });
227
228    private final ArrayBlockingQueueWithShutdown<TopLevelStreamElement> outgoingElementsQueue = new ArrayBlockingQueueWithShutdown<>(
229                    100, true);
230
231    private Iterator<CharSequence> outgoingCharSequenceIterator;
232
233    private final List<TopLevelStreamElement> currentlyOutgoingElements = new ArrayList<>();
234    private final Map<ByteBuffer, List<TopLevelStreamElement>> bufferToElementMap = new IdentityHashMap<>();
235
236    private ByteBuffer outgoingBuffer;
237    private ByteBuffer filteredOutgoingBuffer;
238    private final List<ByteBuffer> networkOutgoingBuffers = new ArrayList<>();
239    private long networkOutgoingBuffersBytes;
240
241    // TODO: Make the size of the incomingBuffer configurable.
242    private final ByteBuffer incomingBuffer = ByteBuffer.allocateDirect(2 * 4096);
243
244    private final ReentrantLock channelSelectedCallbackLock = new ReentrantLock();
245
246    private long totalBytesRead;
247    private long totalBytesWritten;
248    private long totalBytesReadAfterFilter;
249    private long totalBytesWrittenBeforeFilter;
250    private long handledChannelSelectedCallbacks;
251    private long callbackPreemtBecauseBytesWritten;
252    private long callbackPreemtBecauseBytesRead;
253    private int sslEngineDelegatedTasks;
254    private int maxPendingSslEngineDelegatedTasks;
255
256    // TODO: Use LongAdder once Smack's minimum Android API level is 24 or higher.
257    private final AtomicLong setWriteInterestAfterChannelSelectedCallback = new AtomicLong();
258    private final AtomicLong reactorThreadAlreadyRacing = new AtomicLong();
259    private final AtomicLong afterOutgoingElementsQueueModifiedSetInterestOps = new AtomicLong();
260    private final AtomicLong rejectedChannelSelectedCallbacks = new AtomicLong();
261
262    private Jid lastDestinationAddress;
263
264    private boolean pendingInputFilterData;
265    private boolean pendingOutputFilterData;
266
267    private boolean pendingWriteInterestAfterRead;
268
269    private boolean useDirectTls = false;
270
271    private boolean useSm = false;
272    private boolean useSmResumption = false;
273    private boolean useIsr = false;
274
275    private boolean useBind2 = false;
276
277    public XmppNioTcpConnection(XMPPTCPConnectionConfiguration configuration) {
278        super(configuration, INITIAL_STATE_DESCRIPTOR_VERTEX);
279    }
280
281    private final ChannelSelectedCallback channelSelectedCallback =
282            (selectedChannel, selectedSelectionKey) -> {
283        assert selectionKey == selectedSelectionKey;
284        SocketChannel selectedSocketChannel = (SocketChannel) selectedChannel;
285        // We are *always* interested in OP_READ.
286        int newInterestedOps = SelectionKey.OP_READ;
287        boolean newPendingOutputFilterData = false;
288
289        if (!channelSelectedCallbackLock.tryLock()) {
290            rejectedChannelSelectedCallbacks.incrementAndGet();
291            LOGGER.info("Rejected channel selected callback");
292            return;
293        }
294
295        // LOGGER.info("Accepted channel selected callback");
296
297        handledChannelSelectedCallbacks++;
298
299        long callbackBytesRead = 0;
300        long callbackBytesWritten = 0;
301
302        try {
303            boolean destinationAddressChanged = false;
304            boolean isLastPartOfElement = false;
305            TopLevelStreamElement currentlyOutgonigTopLevelStreamElement = null;
306
307            while (true) {
308                // Prevent one callback from dominating the reactor thread. Break out of the write-loop if we have
309                // written a certain amount.
310                if (callbackBytesWritten > CALLBACK_MAX_BYTES_WRITEN) {
311                    newInterestedOps |= SelectionKey.OP_WRITE;
312                    callbackPreemtBecauseBytesWritten++;
313                    break;
314                }
315
316                final boolean moreDataAvailable = !isLastPartOfElement || !outgoingElementsQueue.isEmpty();
317
318                if (filteredOutgoingBuffer != null || !networkOutgoingBuffers.isEmpty()) {
319                    if (filteredOutgoingBuffer != null) {
320                        networkOutgoingBuffers.add(filteredOutgoingBuffer);
321                        networkOutgoingBuffersBytes += filteredOutgoingBuffer.remaining();
322
323                        filteredOutgoingBuffer = null;
324                        if (moreDataAvailable && networkOutgoingBuffersBytes < 8096) {
325                            continue;
326                        }
327                    }
328
329                    ByteBuffer[] output = networkOutgoingBuffers.toArray(new ByteBuffer[networkOutgoingBuffers.size()]);
330                    long bytesWritten;
331                    try {
332                        bytesWritten = selectedSocketChannel.write(output);
333                    } catch (IOException e) {
334                        // TODO Auto-generated catch block
335                        // We have seen here so far
336                        // - IOException "Broken pipe"
337                        throw new AssertionError(e);
338                    }
339
340                    if (bytesWritten == 0) {
341                        newInterestedOps |= SelectionKey.OP_WRITE;
342                        break;
343                    }
344
345                    callbackBytesWritten += bytesWritten;
346
347                    networkOutgoingBuffersBytes -= bytesWritten;
348
349                    List<? extends Buffer> prunedBuffers = pruneBufferList(networkOutgoingBuffers);
350
351                    for (Buffer prunedBuffer : prunedBuffers) {
352                        List<TopLevelStreamElement> sendElements = bufferToElementMap.remove(prunedBuffer);
353                        if (sendElements == null) {
354                            continue;
355                        }
356                        for (TopLevelStreamElement elementJustSend : sendElements) {
357                            LOGGER.info("NIO SEND: " + elementJustSend.toXML(null));
358                        }
359                    }
360                } else if (outgoingBuffer != null || pendingOutputFilterData) {
361                    pendingOutputFilterData = false;
362
363                    if (outgoingBuffer != null) {
364                        totalBytesWrittenBeforeFilter += outgoingBuffer.remaining();
365                        if (isLastPartOfElement) {
366                            assert currentlyOutgonigTopLevelStreamElement != null;
367                            currentlyOutgoingElements.add(currentlyOutgonigTopLevelStreamElement);
368                        }
369                    }
370
371                    ByteBuffer outputFilterInputData = outgoingBuffer;
372                    // We can now null the outgoingBuffer since the filter step will take care of it from now on.
373                    outgoingBuffer = null;
374
375                    for (ListIterator<XmppInputOutputFilter> it = getXmppInputOutputFilterBeginIterator(); it.hasNext();) {
376                        XmppInputOutputFilter inputOutputFilter = it.next();
377                        XmppInputOutputFilter.OutputResult outputResult;
378                        try {
379                            outputResult = inputOutputFilter.output(outputFilterInputData, isLastPartOfElement,
380                                    destinationAddressChanged, moreDataAvailable);
381                        } catch (IOException e) {
382                            // TODO Auto-generated catch block
383                            throw new AssertionError(e);
384                        }
385                        newPendingOutputFilterData |= outputResult.pendingFilterData;
386                        outputFilterInputData = outputResult.filteredOutputData;
387                        if (outputFilterInputData != null) {
388                            outputFilterInputData.flip();
389                        }
390                    }
391
392                    // It is ok if outpuFilterInputData is 'null' here, this is expected behavior.
393                    if (outputFilterInputData != null && outputFilterInputData.hasRemaining()) {
394                        filteredOutgoingBuffer = outputFilterInputData;
395                    } else {
396                        filteredOutgoingBuffer = null;
397                    }
398
399                    // If the filters did eventually not produce any output data but if there is
400                    // pending output data then we have a pending write request after read.
401                    if (filteredOutgoingBuffer == null && newPendingOutputFilterData) {
402                        pendingWriteInterestAfterRead = true;
403                    }
404
405                    if (filteredOutgoingBuffer != null && isLastPartOfElement) {
406                        bufferToElementMap.put(filteredOutgoingBuffer, new ArrayList<>(currentlyOutgoingElements));
407                        currentlyOutgoingElements.clear();
408                    }
409                } else if (outgoingCharSequenceIterator != null) {
410                    CharSequence nextCharSequence = outgoingCharSequenceIterator.next();
411                    outgoingBuffer = UTF8.encode(nextCharSequence);
412                    if (!outgoingCharSequenceIterator.hasNext()) {
413                        outgoingCharSequenceIterator = null;
414                        isLastPartOfElement = true;
415                    } else {
416                        isLastPartOfElement = false;
417                    }
418                } else if (!outgoingElementsQueue.isEmpty()) {
419                    currentlyOutgonigTopLevelStreamElement = outgoingElementsQueue.poll();
420                    if (currentlyOutgonigTopLevelStreamElement instanceof Stanza) {
421                        Stanza currentlyOutgoingStanza = (Stanza) currentlyOutgonigTopLevelStreamElement;
422                        Jid currentDestinationAddress = currentlyOutgoingStanza.getTo();
423                        // TODO replace with JidUtil.equals(Jid, Jid) once jxmpp's version is newer.
424                        destinationAddressChanged = !equals(lastDestinationAddress, currentDestinationAddress);
425                        lastDestinationAddress = currentDestinationAddress;
426                    }
427                    CharSequence nextCharSequence = currentlyOutgonigTopLevelStreamElement.toXML(StreamOpen.CLIENT_NAMESPACE);
428                    if (nextCharSequence instanceof XmlStringBuilder) {
429                        XmlStringBuilder xmlStringBuilder = (XmlStringBuilder) nextCharSequence;
430                        outgoingCharSequenceIterator = xmlStringBuilder.getCharSequenceIterator();
431                    } else {
432                        outgoingCharSequenceIterator = Collections.singletonList(nextCharSequence).iterator();
433                    }
434                    assert (outgoingCharSequenceIterator != null);
435                } else {
436                    // There is nothing more to write.
437                    break;
438                }
439            }
440
441            pendingOutputFilterData = newPendingOutputFilterData;
442            if (!pendingWriteInterestAfterRead && pendingOutputFilterData) {
443                newInterestedOps |= SelectionKey.OP_WRITE;
444            }
445
446            outerloop: while (true) {
447                // Prevent one callback from dominating the reactor thread. Break out of the read-loop if we have
448                // read a certain amount.
449                if (callbackBytesRead > CALLBACK_MAX_BYTES_READ) {
450                    callbackPreemtBecauseBytesRead++;
451                    break;
452                }
453
454                int bytesRead;
455                incomingBuffer.clear();
456                try {
457                    bytesRead = selectedSocketChannel.read(incomingBuffer);
458                } catch (IOException e) {
459                    // TODO Auto-generated catch block
460                    throw new AssertionError(e);
461                }
462
463                if (bytesRead < 0) {
464                    LOGGER.severe("NIO read() returned " + bytesRead
465                            + ". This probably means that the TCP connection was terminated. TODO: We need to handle that.");
466                    // According to the socket channel javadoc section about "asynchronous reads" a socket channel's
467                    // read() may return -1 if the input side of a socket is shut down.
468                    // TODO: handle socket closed
469                    return;
470                }
471
472                if (!pendingInputFilterData) {
473                    if (bytesRead == 0) {
474                        // Nothing more to read.
475                        break;
476                    }
477                } else {
478                    pendingInputFilterData = false;
479                }
480
481                // We have successfully read something. It is now possible that a filter is now also able to write
482                // additional data (for example SSLEngine).
483                if (pendingWriteInterestAfterRead) {
484                    pendingWriteInterestAfterRead = false;
485                    newInterestedOps |= SelectionKey.OP_WRITE;
486                }
487
488                callbackBytesRead += bytesRead;
489
490                ByteBuffer filteredIncomingBuffer = incomingBuffer;
491                for (ListIterator<XmppInputOutputFilter> it = getXmppInputOutputFilterEndIterator(); it.hasPrevious();) {
492                    filteredIncomingBuffer.flip();
493
494                    ByteBuffer newFilteredIncomingBuffer;
495                    try {
496                        newFilteredIncomingBuffer = it.previous().input(filteredIncomingBuffer);
497                    } catch (IOException e) {
498                        // TODO Auto-generated catch block
499                        throw new AssertionError(e);
500                    }
501                    if (newFilteredIncomingBuffer == null) {
502                        break outerloop;
503                    }
504                    filteredIncomingBuffer = newFilteredIncomingBuffer;
505                }
506
507                final int bytesReadAfterFilter = filteredIncomingBuffer.flip().remaining();
508
509                totalBytesReadAfterFilter += bytesReadAfterFilter;
510
511                // TODO Remove this complete block once XmppNioTcpConnection has proper debug facilities.
512                String incomingString = byteBufferToString(filteredIncomingBuffer);
513                LOGGER.info("NIO RECV: " + incomingString);
514
515                for (int i = 0; i < bytesReadAfterFilter; i++) {
516                    try {
517                        splitter.write(filteredIncomingBuffer.get(i));
518                    } catch (IOException e) {
519                        // TODO Auto-generated catch block
520                        throw new AssertionError(e);
521                    }
522                }
523            }
524        } finally {
525            totalBytesWritten += callbackBytesWritten;
526            totalBytesRead += callbackBytesRead;
527
528            channelSelectedCallbackLock.unlock();
529        }
530
531        // Indicate that there is no reactor thread racing towards handling this selection key by attaching 'null'.
532        final SelectionKeyAttachment selectionKeyAttachment = this.selectionKeyAttachment;
533        if (selectionKeyAttachment != null) {
534            selectionKeyAttachment.resetReactorThreadRacing();
535        }
536
537        // Check the queue again. TODO: Document why this is necessary.
538        if (!outgoingElementsQueue.isEmpty()) {
539            setWriteInterestAfterChannelSelectedCallback.incrementAndGet();
540            newInterestedOps |= SelectionKey.OP_WRITE;
541        }
542
543        setInterestOps(selectionKey, newInterestedOps);
544    };
545
546    private void callCahnnelSelectedCallback(boolean setPendingInputFilterData, boolean setPendingOutputFilterData) {
547        final SocketChannel channel = socketChannel;
548        final SelectionKey key = selectionKey;
549        if (channel == null || key == null) {
550            LOGGER.info("Not calling channel selected callback because the connection was eventually disconnected");
551            return;
552        }
553
554        channelSelectedCallbackLock.lock();
555        // Note that it is important that we send the pending(Input|Output)FilterData flags while holding the lock.
556        if (setPendingInputFilterData) {
557            pendingInputFilterData = true;
558        }
559        if (setPendingOutputFilterData) {
560            pendingOutputFilterData = true;
561        }
562
563        try {
564            channelSelectedCallback.onChannelSelected(channel, key);
565        } finally {
566            channelSelectedCallbackLock.unlock();
567        }
568    }
569
570    private final class ConnectionAttemptState {
571        InetSocketAddress inetSocketAddress;
572        final SocketChannel socketChannel;
573        final Iterator<InetSocketAddress> remainingAddresses;
574        final List<HostAddress> failedAddresses;
575        final SynchronizationPointWithSmackException<IOException, SocketChannel> tcpConnectionEstablishedSyncPoint;
576
577        private ConnectionAttemptState(List<InetSocketAddress> inetSocketAddresses, List<HostAddress> failedAddresses) throws IOException {
578            socketChannel = SocketChannel.open();
579            socketChannel.configureBlocking(false);
580            remainingAddresses = inetSocketAddresses.iterator();
581            inetSocketAddress = remainingAddresses.next();
582            this.failedAddresses = failedAddresses;
583
584            tcpConnectionEstablishedSyncPoint = new SynchronizationPointWithSmackException<IOException, SocketChannel>(XmppNioTcpConnection.this, "TCP");
585        }
586
587        private void establishTcpConnection() {
588            // TODO: Notify about connection attempt to inetSocketAddress.
589            boolean connected;
590            try {
591                connected = socketChannel.connect(inetSocketAddress);
592            } catch (IOException e) {
593                onIOExceptionWhenEstablishingTcpConnection(e);
594                return;
595            }
596
597            if (connected) {
598                tcpConnectionEstablishedSyncPoint.reportSuccess(socketChannel);
599                return;
600            }
601
602            try {
603                registerWithSelector(socketChannel, SelectionKey.OP_CONNECT,
604                        (selectedChannel, selectedSelectionKey) -> {
605                            SocketChannel selectedSocketChannel = (SocketChannel) selectedChannel;
606
607                            boolean finishConnected;
608                            try {
609                                finishConnected = selectedSocketChannel.finishConnect();
610                            } catch (IOException e) {
611                                onIOExceptionWhenEstablishingTcpConnection(e);
612                                return;
613                            }
614
615                            if (!finishConnected) {
616                                onIOExceptionWhenEstablishingTcpConnection(new IOException("finishConnect() failed"));
617                                return;
618                            }
619
620                            LOGGER.info("Successfully connected via " + selectedSocketChannel);
621                            // Do not set 'state' here, since this is processed by a reactor thread, which doesn't hold
622                            // the objects lock.
623                            tcpConnectionEstablishedSyncPoint.reportSuccess(selectedSocketChannel);
624                        });
625            } catch (ClosedChannelException e) {
626                onIOExceptionWhenEstablishingTcpConnection(e);
627            }
628        }
629
630        private void onIOExceptionWhenEstablishingTcpConnection(IOException exception) {
631            if (!remainingAddresses.hasNext()) {
632                // TODO failed exception
633                ConnectionException connectionException = ConnectionException.from(failedAddresses);
634                tcpConnectionEstablishedSyncPoint.reportFailure(connectionException);
635                return;
636            }
637
638            tcpConnectionEstablishedSyncPoint.resetTimeout();
639
640            HostAddress failedHostAddress = new HostAddress(inetSocketAddress, exception);
641            failedAddresses.add(failedHostAddress);
642
643            inetSocketAddress = remainingAddresses.next();
644
645            establishTcpConnection();
646        }
647    }
648
649    @Override
650    protected void connectInternal() throws SmackException, IOException, XMPPException, InterruptedException {
651        // TODO: Check if those initialization methods can be invoked later.
652        outgoingElementsQueue.start();
653        closingStreamReceived.init();
654
655        WalkStateGraphContext walkStateGraphContext = buildNewWalkTo(ConnectedButUnauthenticatedStateDescriptor.class)
656                .build();
657        walkStateGraph(walkStateGraphContext);
658    }
659
660    // TODO: clear on shutdown.
661    private List<HostAddress> failedAddresses;
662    private List<InetSocketAddress> inetSocketAddresses;
663
664    public static class LookupHostAddressesStateDescriptor extends StateDescriptor {
665        public LookupHostAddressesStateDescriptor() {
666            super(LookupHostAddressesState.class);
667            addPredeccessor(DisconnectedStateDescriptor.class);
668            addSuccessor(ConnectingToHostStateDescriptor.class);
669            addSuccessor(DirectTlsConnectionToHostStateDescriptor.class);
670        }
671    }
672
673    public class LookupHostAddressesState extends State {
674        protected LookupHostAddressesState(StateDescriptor stateDescriptor) {
675            super(stateDescriptor);
676        }
677
678        @Override
679        protected TransitionFailedReason transitionInto(LoginContext loginContext) throws XMPPErrorException,
680                        SASLErrorException, IOException, SmackException, InterruptedException {
681            failedAddresses = populateHostAddresses();
682            if (hostAddresses.isEmpty()) {
683                // TODO throw! This is probably also the best case to test the yet to get implemented connect()/login() exception handling mechanism.
684            }
685            inetSocketAddresses = new ArrayList<>();
686            for (HostAddress hostAddress : XmppNioTcpConnection.this.hostAddresses) {
687                List<InetAddress> inetAddresses = hostAddress.getInetAddresses();
688                for (InetAddress inetAddress : inetAddresses) {
689                    InetSocketAddress inetSocketAddress = new InetSocketAddress(inetAddress, hostAddress.getPort());
690                    inetSocketAddresses.add(inetSocketAddress);
691                }
692            }
693            return null;
694        }
695    }
696
697    public static final class DirectTlsConnectionToHostStateDescriptor extends StateDescriptor {
698        private DirectTlsConnectionToHostStateDescriptor() {
699            super(DirectTlsConnectionToHostState.class, 368);
700            addPredeccessor(LookupHostAddressesStateDescriptor.class);
701            addSuccessor(ConnectedButUnauthenticatedStateDescriptor.class);
702            declarePrecedenceOver(ConnectingToHostStateDescriptor.class);
703        }
704    }
705
706    public final class DirectTlsConnectionToHostState extends State {
707        private DirectTlsConnectionToHostState(StateDescriptor stateDescriptor) {
708            super(stateDescriptor);
709        }
710
711        @Override
712        protected TransitionImpossibleReason isTransitionToPossible() {
713            if (!useDirectTls) {
714                return new TransitionImpossibleReason("Direct TLS not enabled");
715            }
716
717            // TODO: Check if lookup yielded any xmpps SRV RRs
718
719            throw new IllegalStateException("Direct TLS not implemented");
720        }
721
722        @Override
723        protected TransitionFailedReason transitionInto(LoginContext loginContext) throws XMPPErrorException,
724                SASLErrorException, IOException, SmackException, InterruptedException, FailedNonzaException {
725            // TODO Auto-generated method stub
726            return null;
727        }
728    }
729
730    public static class ConnectingToHostStateDescriptor extends StateDescriptor {
731        public ConnectingToHostStateDescriptor() {
732            super(ConnectingToHostState.class);
733            addSuccessor(EstablishTlsStateDescriptor.class);
734            addSuccessor(ConnectedButUnauthenticatedStateDescriptor.class);
735        }
736    }
737
738    public class ConnectingToHostState extends State {
739        protected ConnectingToHostState(StateDescriptor stateDescriptor) {
740            super(stateDescriptor);
741        }
742
743        @Override
744        protected TransitionFailedReason transitionInto(LoginContext loginContext) throws XMPPErrorException,
745                        SASLErrorException, IOException, SmackException, InterruptedException {
746            // TODO: The fields inetSocketAddress and failedAddresses are used as handover from LookupHostAddresses to
747            // ConnectingToHost. There is possibly a better way to perform the handover.
748            ConnectionAttemptState connectionAttemptState = new ConnectionAttemptState(inetSocketAddresses, failedAddresses);
749            connectionAttemptState.establishTcpConnection();
750
751            socketChannel = connectionAttemptState.tcpConnectionEstablishedSyncPoint.checkIfSuccessOrWaitOrThrow();
752            remoteAddress = (InetSocketAddress) socketChannel.socket().getRemoteSocketAddress();
753
754            selectionKey = registerWithSelector(socketChannel, SelectionKey.OP_READ, channelSelectedCallback);
755            selectionKeyAttachment = (SelectionKeyAttachment) selectionKey.attachment();
756
757            newStreamOpenWaitForFeaturesSequence("stream features after initial connection");
758
759            // TODO: It would be probably better if we would return here a TransitionSuccess class, which contains meta
760            // information, like which host was used, etc.
761            return null;
762        }
763    }
764
765    public static class EstablishTlsStateDescriptor extends StateDescriptor {
766        public EstablishTlsStateDescriptor() {
767            super(EstablishTlsState.class, "RFC 6120 § 5");
768            addSuccessor(ConnectedButUnauthenticatedStateDescriptor.class);
769            declarePrecedenceOver(ConnectedButUnauthenticatedStateDescriptor.class);
770        }
771    }
772
773    public class EstablishTlsState extends State {
774        protected EstablishTlsState(StateDescriptor stateDescriptor) {
775            super(stateDescriptor);
776        }
777
778        @Override
779        protected TransitionImpossibleReason isTransitionToPossible() throws SecurityRequiredByClientException, SecurityRequiredByServerException {
780            StartTls startTlsFeature = getFeature(StartTls.ELEMENT, StartTls.NAMESPACE);
781            SecurityMode securityMode = config.getSecurityMode();
782
783            switch (securityMode) {
784            case required:
785            case ifpossible:
786                if (startTlsFeature == null) {
787                    if (securityMode == SecurityMode.ifpossible) {
788                        return new TransitionImpossibleReason("Server does not announce support for TLS and we do not required it");
789                    }
790                    throw new SecurityRequiredByClientException();
791                }
792                // Allows transition by returning null.
793                return null;
794            case disabled:
795                if (startTlsFeature != null && startTlsFeature.required()) {
796                    // TODO disconnect
797                    throw new SecurityRequiredByServerException();
798                }
799                return new TransitionImpossibleReason("TLS disabled in client settings and server does not require it");
800            default:
801                throw new AssertionError("Unknown security mode: " + securityMode);
802            }
803        }
804
805        @Override
806        protected TransitionFailedReason transitionInto(LoginContext loginContext) throws XMPPErrorException,
807                        SASLErrorException, IOException, SmackException, InterruptedException, FailedNonzaException {
808            sendAndWaitForResponse(StartTls.INSTANCE, TlsProceed.class, TlsFailure.class);
809
810            // TODO: Implement TLS.
811            SmackTlsContext smackTlsContext;
812            try {
813                smackTlsContext = getSmackTlsContext();
814            } catch (KeyManagementException | UnrecoverableKeyException | NoSuchAlgorithmException
815                    | CertificateException | KeyStoreException | NoSuchProviderException e) {
816                throw new SmackException(e);
817            }
818
819            tlsState = new TlsState(smackTlsContext);
820            addXmppInputOutputFilter(tlsState);
821
822            channelSelectedCallbackLock.lock();
823            try {
824                pendingOutputFilterData = true;
825                // TODO: It is not clear if this beginHandshake() is required here. Try to remove it and see if still
826                // works.
827                tlsState.engine.beginHandshake();
828                tlsState.handshakeStatus = TlsHandshakeStatus.initiated;
829            } finally {
830                channelSelectedCallbackLock.unlock();
831            }
832            setInterestOps(selectionKey, SelectionKey.OP_WRITE | SelectionKey.OP_READ);
833
834            try {
835                tlsState.waitForHandshakeFinished();
836            } catch (CertificateException e) {
837                throw new SmackException(e);
838            }
839
840            newStreamOpenWaitForFeaturesSequence("stream features after TLS established");
841
842            return null;
843        }
844    }
845
846    private enum TlsHandshakeStatus {
847        initial,
848        initiated,
849        successful,
850        failed,
851        ;
852    }
853
854    private final class TlsState implements XmppInputOutputFilter {
855
856        private static final int MAX_PENDING_OUTPUT_BYTES = 8096;
857
858        private final SmackTlsContext smackTlsContext;
859        private final SSLEngine engine;
860
861        private TlsHandshakeStatus handshakeStatus = TlsHandshakeStatus.initial;
862        private SSLException handshakeException;
863
864        private ByteBuffer myNetData;
865        private ByteBuffer peerAppData;
866
867        private final List<ByteBuffer> pendingOutputData = new ArrayList<>();
868        private int pendingOutputBytes;
869        private ByteBuffer pendingInputData;
870
871        private final AtomicInteger pendingDelegatedTasks = new AtomicInteger();
872
873        private TlsState(SmackTlsContext smackTlsContext) throws IOException {
874            this.smackTlsContext = smackTlsContext;
875
876            engine = smackTlsContext.sslContext.createSSLEngine(config.getXMPPServiceDomain().toString(), remoteAddress.getPort());
877            engine.setUseClientMode(true);
878
879            SSLSession session = engine.getSession();
880            int applicationBufferSize = session.getApplicationBufferSize();
881            int packetBufferSize = session.getPacketBufferSize();
882
883            myNetData = ByteBuffer.allocateDirect(packetBufferSize);
884            peerAppData = ByteBuffer.allocate(applicationBufferSize);
885        }
886
887        @Override
888        public OutputResult output(ByteBuffer outputData, boolean isFinalDataOfElement, boolean destinationAddressChanged,
889                boolean moreDataAvailable) throws SSLException {
890            if (outputData != null) {
891                pendingOutputData.add(outputData);
892                pendingOutputBytes += outputData.remaining();
893                if (moreDataAvailable && pendingOutputBytes < MAX_PENDING_OUTPUT_BYTES) {
894                    return OutputResult.NO_OUTPUT;
895                }
896            }
897
898            ByteBuffer[] outputDataArray = pendingOutputData.toArray(new ByteBuffer[pendingOutputData.size()]);
899
900            myNetData.clear();
901
902            while (true) {
903                SSLEngineResult result;
904                try {
905                    result = engine.wrap(outputDataArray, myNetData);
906                } catch (SSLException e) {
907                    handleSslException(e);
908                    throw e;
909                }
910
911                LOGGER.info("SSLEngineResult of wrap(): " + result);
912
913                SSLEngineResult.Status engineResultStatus = result.getStatus();
914
915                pendingOutputBytes -= result.bytesConsumed();
916
917                if (engineResultStatus == SSLEngineResult.Status.OK) {
918                    SSLEngineResult.HandshakeStatus handshakeStatus = handleHandshakeStatus(result);
919                    switch (handshakeStatus) {
920                        case NEED_UNWRAP:
921                            // NEED_UNWRAP means that we need to receive something in order to continue the handshake. The
922                            // standard channelSelectedCallback logic will take care of this, as there is eventually always
923                            // a interest to read from the socket.
924                            break;
925                        case NEED_WRAP:
926                            // Same as need task: Cycle the reactor.
927                        case NEED_TASK:
928                            // Note that we also set pendingOutputFilterData in the OutputResult in the NEED_TASK case, as
929                            // we also want to retry the wrap() operation above in this case.
930                            return new OutputResult(true, myNetData);
931                        default:
932                            break;
933                    }
934                }
935
936                switch (engineResultStatus) {
937                case OK:
938                    // No need to outputData.compact() here, since we do not reuse the buffer.
939                    // Clean up the pending output data.
940                    pruneBufferList(pendingOutputData);
941                    return new OutputResult(!pendingOutputData.isEmpty(), myNetData);
942                case CLOSED:
943                    LOGGER.info("SSLEngine wrap() returned CLOSED");
944                    pendingOutputData.clear();
945                    return OutputResult.NO_OUTPUT;
946                case BUFFER_OVERFLOW:
947                    LOGGER.warning("SSLEngine status BUFFER_OVERFLOW, this is hopefully uncommon");
948                    int outputDataRemaining = outputData != null ? outputData.remaining() : 0;
949                    int newCapacity = (int) (1.3 * outputDataRemaining);
950                    // If newCapacity would not increase myNetData, then double it.
951                    if (newCapacity <= myNetData.capacity()) {
952                        newCapacity = 2 * myNetData.capacity();
953                    }
954                    ByteBuffer newMyNetData = ByteBuffer.allocateDirect(newCapacity);
955                    myNetData.flip();
956                    newMyNetData.put(myNetData);
957                    myNetData = newMyNetData;
958                    continue;
959                case BUFFER_UNDERFLOW:
960                    throw new IllegalStateException(
961                            "Buffer underflow as result of SSLEngine.wrap() should never happen");
962                }
963            }
964        }
965
966        @Override
967        public ByteBuffer input(ByteBuffer inputData) throws SSLException {
968            ByteBuffer accumulatedData;
969            if (pendingInputData == null) {
970                accumulatedData = inputData;
971            } else {
972                int accumulatedDataBytes = pendingInputData.remaining() + inputData.remaining();
973                accumulatedData = ByteBuffer.allocate(accumulatedDataBytes);
974                accumulatedData.put(pendingInputData)
975                               .put(inputData)
976                               .flip();
977                pendingInputData = null;
978            }
979
980            peerAppData.clear();
981
982            while (true) {
983                SSLEngineResult result;
984                try {
985                    result = engine.unwrap(accumulatedData, peerAppData);
986                } catch (SSLException e) {
987                    handleSslException(e);
988                    throw e;
989                }
990
991                LOGGER.info("SSLEngineResult of unwrap(): " + result);
992                SSLEngineResult.Status engineResultStatus = result.getStatus();
993
994                if (engineResultStatus == SSLEngineResult.Status.OK) {
995                    SSLEngineResult.HandshakeStatus handshakeStatus = handleHandshakeStatus(result);
996                    switch (handshakeStatus) {
997                    case NEED_TASK:
998                        addAsPendingInputData(accumulatedData);
999                        // TODO: A delegated task is asynchronously running. Signal that there is pending input data and
1000                        // cycle again through the smack reactor.
1001                        break;
1002                    case NEED_UNWRAP:
1003                        continue;
1004                    case NEED_WRAP:
1005                        asyncGo(() -> callCahnnelSelectedCallback(false, true));
1006                        // NEED_WRAP means that the SSLEngine needs to send data, probably without consuming data.
1007                        // We exploit here the fact that the channelSelectedCallback is single threaded and that the
1008                        // input processing is after the output processing.
1009                        // setInterestOps(selectionKey, SelectionKey.OP_WRITE | SelectionKey.OP_READ);
1010                        break;
1011                    default:
1012                        break;
1013                    }
1014                }
1015
1016                switch (engineResultStatus) {
1017                case OK:
1018                    // SSLEngine's unwrap() may not consume all bytes from the source buffer. If this is the case, then
1019                    // simply perform another unwrap until accumlatedData has no remaining bytes.
1020                    if (accumulatedData.hasRemaining()) {
1021                        continue;
1022                    }
1023                    return peerAppData;
1024                case CLOSED:
1025                    LOGGER.info("SSLEngine wrap() returned CLOSED");
1026                    return null;
1027                case BUFFER_UNDERFLOW:
1028                    // There were not enough source bytes available to make a complete packet. Let it in
1029                    // pendingInputData. Note that we do not resize SSLEngine's source buffer - inputData in our case -
1030                    // as it is not possible.
1031                    addAsPendingInputData(accumulatedData);
1032                    return null;
1033                case BUFFER_OVERFLOW:
1034                    int applicationBufferSize = engine.getSession().getApplicationBufferSize();
1035                    assert (peerAppData.remaining() < applicationBufferSize);
1036                    peerAppData = ByteBuffer.allocate(applicationBufferSize);
1037                    continue;
1038                }
1039            }
1040        }
1041
1042        private void addAsPendingInputData(ByteBuffer byteBuffer) {
1043            pendingInputData = ByteBuffer.allocate(byteBuffer.remaining());
1044            pendingInputData.put(byteBuffer).flip();
1045        }
1046
1047        private SSLEngineResult.HandshakeStatus handleHandshakeStatus(SSLEngineResult sslEngineResult) {
1048            SSLEngineResult.HandshakeStatus handshakeStatus = sslEngineResult.getHandshakeStatus();
1049            switch (handshakeStatus) {
1050            case NEED_TASK:
1051                while (true) {
1052                    final Runnable delegatedTask = engine.getDelegatedTask();
1053                    if (delegatedTask == null) {
1054                        break;
1055                    }
1056                    sslEngineDelegatedTasks++;
1057                    int currentPendingDelegatedTasks = pendingDelegatedTasks.incrementAndGet();
1058                    if (currentPendingDelegatedTasks > maxPendingSslEngineDelegatedTasks) {
1059                        maxPendingSslEngineDelegatedTasks = currentPendingDelegatedTasks;
1060                    }
1061
1062                    Runnable wrappedDelegatedTask = () -> {
1063                        delegatedTask.run();
1064                        int wrappedCurrentPendingDelegatedTasks = pendingDelegatedTasks.decrementAndGet();
1065                        if (wrappedCurrentPendingDelegatedTasks == 0) {
1066                            callCahnnelSelectedCallback(true, true);
1067                        }
1068                    };
1069                    asyncGo(wrappedDelegatedTask);
1070                }
1071                break;
1072            case FINISHED:
1073                onHandshakeFinished();
1074                break;
1075            default:
1076                break;
1077            }
1078
1079            SSLEngineResult.HandshakeStatus afterHandshakeStatus = engine.getHandshakeStatus();
1080            return afterHandshakeStatus;
1081        }
1082
1083        private void handleSslException(SSLException e) {
1084            handshakeException = e;
1085            handshakeStatus = TlsHandshakeStatus.failed;
1086            synchronized (this) {
1087                notifyAll();
1088            }
1089        }
1090
1091        private void onHandshakeFinished() {
1092            handshakeStatus = TlsHandshakeStatus.successful;
1093            synchronized (this) {
1094                notifyAll();
1095            }
1096        }
1097
1098        private boolean isHandshakeFinished() {
1099            return handshakeStatus == TlsHandshakeStatus.successful || handshakeStatus == TlsHandshakeStatus.failed;
1100        }
1101
1102        private void waitForHandshakeFinished() throws NoResponseException, InterruptedException, CertificateException, SSLException {
1103            final long deadline = System.currentTimeMillis() + getReplyTimeout();
1104
1105            synchronized (this) {
1106                while (!isHandshakeFinished()) {
1107                    final long now = System.currentTimeMillis();
1108                    if (now >= deadline) break;
1109                    wait(deadline - now);
1110                }
1111            }
1112
1113            if (!isHandshakeFinished()) {
1114                throw NoResponseException.newWith(XmppNioTcpConnection.this, "TLS Handshake finsih");
1115            }
1116
1117            if (handshakeStatus == TlsHandshakeStatus.failed) {
1118                throw handshakeException;
1119            }
1120
1121            assert handshakeStatus == TlsHandshakeStatus.successful;
1122
1123            if (smackTlsContext.daneVerifier != null) {
1124                smackTlsContext.daneVerifier.finish(engine.getSession());
1125            }
1126        }
1127    }
1128
1129    public static final class EnableStreamManagementStateDescriptor extends StateDescriptor {
1130        private EnableStreamManagementStateDescriptor() {
1131            super(EnableStreamManagementState.class, 198);
1132            addPredeccessor(ResourceBindingStateDescriptor.class);
1133            addSuccessor(AuthenticatedAndResourceBoundStateDescriptor.class);
1134            declarePrecedenceOver(AuthenticatedAndResourceBoundStateDescriptor.class);
1135        }
1136    }
1137
1138    public final class EnableStreamManagementState extends State {
1139        private EnableStreamManagementState(StateDescriptor stateDescriptor) {
1140            super(stateDescriptor);
1141        }
1142
1143        @Override
1144        protected TransitionImpossibleReason isTransitionToPossible() {
1145            if (!useSm) {
1146                return new TransitionImpossibleReason("Stream management not enabled");
1147            }
1148
1149            throw new IllegalStateException("SM not implemented");
1150        }
1151
1152        @Override
1153        protected TransitionFailedReason transitionInto(LoginContext loginContext) throws XMPPErrorException,
1154                SASLErrorException, IOException, SmackException, InterruptedException, FailedNonzaException {
1155            // TODO Auto-generated method stub
1156            return null;
1157        }
1158    }
1159
1160    public static final class ResumeStreamStateDescriptor extends StateDescriptor {
1161        private ResumeStreamStateDescriptor() {
1162            super(ResumeStreamState.class, 198);
1163            addPredeccessor(AuthenticatedButUnboundStateDescriptor.class);
1164            addSuccessor(AuthenticatedAndResourceBoundStateDescriptor.class);
1165            declarePrecedenceOver(ResourceBindingStateDescriptor.class);
1166            declareInferiortyTo(CompressionStateDescriptor.class);
1167        }
1168    }
1169
1170    public final class ResumeStreamState extends State {
1171        private ResumeStreamState(StateDescriptor stateDescriptor) {
1172            super(stateDescriptor);
1173        }
1174
1175        @Override
1176        protected TransitionImpossibleReason isTransitionToPossible() {
1177            if (!useSmResumption) {
1178                return new TransitionImpossibleReason("Stream resumption not enabled");
1179            }
1180
1181            throw new IllegalStateException("Stream resumptionimplemented");
1182        }
1183
1184        @Override
1185        protected TransitionFailedReason transitionInto(LoginContext loginContext) throws XMPPErrorException,
1186                SASLErrorException, IOException, SmackException, InterruptedException, FailedNonzaException {
1187            // TODO Auto-generated method stub
1188            return null;
1189        }
1190    }
1191
1192    public static final class InstantStreamResumptionStateDescriptor extends StateDescriptor {
1193        private InstantStreamResumptionStateDescriptor() {
1194            super(InstantStreamResumptionState.class, 397);
1195            addSuccessor(AuthenticatedAndResourceBoundStateDescriptor.class);
1196            addPredeccessor(ConnectedButUnauthenticatedStateDescriptor.class);
1197            declarePrecedenceOver(SaslAuthenticationStateDescriptor.class);
1198        }
1199    }
1200
1201    public final class InstantStreamResumptionState extends State {
1202        private InstantStreamResumptionState(StateDescriptor stateDescriptor) {
1203            super(stateDescriptor);
1204        }
1205
1206        @Override
1207        protected TransitionImpossibleReason isTransitionToPossible() {
1208            if (!useIsr) {
1209                return new TransitionImpossibleReason("Instant stream resumption not enabled");
1210            }
1211
1212            throw new IllegalStateException("Instant stream resumption not implemented");
1213        }
1214
1215        @Override
1216        protected TransitionFailedReason transitionInto(LoginContext loginContext) throws XMPPErrorException,
1217                SASLErrorException, IOException, SmackException, InterruptedException, FailedNonzaException {
1218            // TODO Auto-generated method stub
1219            return null;
1220        }
1221    }
1222
1223    public static final class Bind2StateDescriptor extends StateDescriptor {
1224        private Bind2StateDescriptor() {
1225            super(Bind2State.class, 386);
1226            addPredeccessor(ConnectedButUnauthenticatedStateDescriptor.class);
1227            addSuccessor(AuthenticatedAndResourceBoundStateDescriptor.class);
1228            declarePrecedenceOver(SaslAuthenticationStateDescriptor.class);
1229        }
1230    }
1231
1232    public final class Bind2State extends State {
1233        private Bind2State(StateDescriptor stateDescriptor) {
1234            super(stateDescriptor);
1235        }
1236
1237        @Override
1238        protected TransitionImpossibleReason isTransitionToPossible() {
1239            if (!useBind2) {
1240                return new TransitionImpossibleReason("Instant stream resumption not enabled");
1241            }
1242
1243            throw new IllegalStateException("Instant stream resumption not implemented");
1244        }
1245
1246        @Override
1247        protected TransitionFailedReason transitionInto(LoginContext loginContext) throws XMPPErrorException,
1248                SASLErrorException, IOException, SmackException, InterruptedException, FailedNonzaException {
1249            // TODO Auto-generated method stub
1250            return null;
1251        }
1252    }
1253
1254    public static final class InstantShutdownStateDescriptor extends StateDescriptor {
1255        private InstantShutdownStateDescriptor() {
1256            super(InstantShutdownState.class);
1257            addSuccessor(CloseConnectionStateDescriptor.class);
1258            addPredeccessor(AuthenticatedAndResourceBoundStateDescriptor.class);
1259        }
1260    }
1261
1262    public class InstantShutdownState extends State {
1263        public InstantShutdownState(StateDescriptor stateDescriptor) {
1264            super(stateDescriptor);
1265        }
1266
1267        @Override
1268        protected TransitionFailedReason transitionInto(LoginContext loginContext) {
1269            outgoingElementsQueue.shutdown();
1270            afterOutgoingElementsQueueModified();
1271            return null;
1272        }
1273    }
1274
1275    public static final class ShutdownStateDescriptor extends StateDescriptor {
1276        private ShutdownStateDescriptor() {
1277            super(ShutdownState.class);
1278            addSuccessor(CloseConnectionStateDescriptor.class);
1279            addPredeccessor(AuthenticatedAndResourceBoundStateDescriptor.class);
1280        }
1281    }
1282
1283    public class ShutdownState extends State {
1284        public ShutdownState(StateDescriptor stateDescriptor) {
1285            super(stateDescriptor);
1286        }
1287
1288        @Override
1289        protected TransitionFailedReason transitionInto(LoginContext loginContext) {
1290            boolean streamCloseIssued = false;
1291
1292            closingStreamReceived.init();
1293            streamCloseIssued = outgoingElementsQueue.offer(StreamClose.INSTANCE);
1294
1295            outgoingElementsQueue.shutdown();
1296            afterOutgoingElementsQueueModified();
1297
1298            if (streamCloseIssued) {
1299                try {
1300                    // After we send the closing stream element, check if there was already a
1301                    // closing stream element sent by the server or wait with a timeout for a
1302                    // closing stream element to be received from the server.
1303                    @SuppressWarnings("unused")
1304                    Exception res = closingStreamReceived.checkIfSuccessOrWait();
1305                } catch (InterruptedException | NoResponseException e) {
1306                    LOGGER.log(Level.INFO, "Exception while waiting for closing stream element from the server " + this, e);
1307                }
1308            }
1309
1310            return null;
1311        }
1312    }
1313
1314    public static final class CloseConnectionStateDescriptor extends StateDescriptor {
1315        private CloseConnectionStateDescriptor() {
1316            super(ShutdownState.class);
1317            addSuccessor(DisconnectedStateDescriptor.class);
1318        }
1319    }
1320
1321    public class CloseConnectionState extends State {
1322        public CloseConnectionState(StateDescriptor stateDescriptor) {
1323            super(stateDescriptor);
1324        }
1325
1326        @Override
1327        protected TransitionFailedReason transitionInto(LoginContext loginContext) {
1328            selectionKey.cancel();
1329
1330            try {
1331                selectionKey.channel().close();
1332            } catch (IOException e) {
1333                LOGGER.log(Level.WARNING, "Exception closing NIO socket channel", e);
1334            }
1335
1336            selectionKey = null;
1337            selectionKeyAttachment = null;
1338            socketChannel = null;
1339            remoteAddress = null;
1340            tlsState = null;
1341            // TODO Release more resource here.
1342
1343            return null;
1344        }
1345    }
1346
1347    @Override
1348    public boolean isSecureConnection() {
1349        return tlsState != null && tlsState.handshakeStatus == TlsHandshakeStatus.successful;
1350    }
1351
1352    private void sendTopLevelStreamElement(TopLevelStreamElement topLevelStreamElement)
1353                    throws InterruptedException {
1354        outgoingElementsQueue.put(topLevelStreamElement);
1355        afterOutgoingElementsQueueModified();
1356    }
1357
1358    private void afterOutgoingElementsQueueModified() {
1359        final SelectionKeyAttachment selectionKeyAttachment = this.selectionKeyAttachment;
1360        if (selectionKeyAttachment != null && selectionKeyAttachment.isReactorThreadRacing()) {
1361            // A reactor thread is already racing to the channel selected callback and will take care of this.
1362            reactorThreadAlreadyRacing.incrementAndGet();
1363            return;
1364        }
1365
1366        afterOutgoingElementsQueueModifiedSetInterestOps.incrementAndGet();
1367
1368        // Add OP_WRITE to the interested Ops, since we have now new things to write. Note that this may cause
1369        // multiple reactor threads to race to the channel selected callback in case we perform this right after
1370        // a select() returned with this selection key in the selected-key set. Hence we use tryLock() in the
1371        // channel selected callback to keep the invariant that only exactly one thread is performing the
1372        // callback.
1373        // Note that we need to perform setInterestedOps() *without* holding the channelSelectedCallbackLock, as
1374        // otherwise the reactor thread racing to the channel selected callback may found the lock still locked, which
1375        // would result in the outgoingElementsQueue not being handled.
1376        setInterestOps(selectionKey, SelectionKey.OP_WRITE | SelectionKey.OP_READ);
1377    }
1378
1379    @Override
1380    protected void throwNotConnectedExceptionIfAppropriate() {
1381        // TODO
1382    }
1383
1384    @Override
1385    protected void sendStanzaInternal(Stanza stanza) throws NotConnectedException, InterruptedException {
1386        sendTopLevelStreamElement(stanza);
1387        // TODO stream management code here
1388    }
1389
1390    @Override
1391    public void sendNonza(Nonza nonza) throws NotConnectedException, InterruptedException {
1392        sendTopLevelStreamElement(nonza);
1393    }
1394
1395    @Override
1396    public boolean isUsingCompression() {
1397        // TODO Auto-generated method stub
1398        return false;
1399    }
1400
1401    @Override
1402    protected void shutdown() {
1403        shutdown(false);
1404    }
1405
1406    public void instantShutdown() {
1407        shutdown(true);
1408    }
1409
1410    private void shutdown(boolean instant) {
1411        Class<? extends StateDescriptor> mandatoryIntermediateState;
1412        if (instant) {
1413            mandatoryIntermediateState = InstantShutdownStateDescriptor.class;
1414        } else {
1415            mandatoryIntermediateState = ShutdownStateDescriptor.class;
1416        }
1417
1418        WalkStateGraphContext context = buildNewWalkTo(DisconnectedStateDescriptor.class)
1419                .withMandatoryIntermediateState(mandatoryIntermediateState)
1420                .build();
1421
1422        try {
1423            walkStateGraph(context);
1424        } catch (XMPPErrorException | SASLErrorException | IOException | SmackException | InterruptedException | FailedNonzaException e) {
1425            throw new IllegalStateException("A walk to disconnected state should never throw", e);
1426        }
1427    }
1428
1429    public Stats getStats() {
1430        return new Stats(this);
1431    }
1432
1433    public static final class Stats {
1434        public final long totalBytesRead;
1435        public final long totalBytesWritten;
1436        public final long totalBytesReadAfterFilter;
1437        public final long totalBytesWrittenBeforeFilter;
1438        public final long handledChannelSelectedCallbacks;
1439        public final long setWriteInterestAfterChannelSelectedCallback;
1440        public final long reactorThreadAlreadyRacing;
1441        public final long afterOutgoingElementsQueueModifiedSetInterestOps;
1442        public final long rejectedChannelSelectedCallbacks;
1443        public final long totalCallbackRequests;
1444        public final long callbackPreemtBecauseBytesWritten;
1445        public final long callbackPreemtBecauseBytesRead;
1446        public final int sslEngineDelegatedTasks;
1447        public final int maxPendingSslEngineDelegatedTasks;
1448
1449        private Stats(XmppNioTcpConnection connection) {
1450            totalBytesRead = connection.totalBytesRead;
1451            totalBytesWritten = connection.totalBytesWritten;
1452            totalBytesReadAfterFilter = connection.totalBytesReadAfterFilter;
1453            totalBytesWrittenBeforeFilter = connection.totalBytesWrittenBeforeFilter;
1454            handledChannelSelectedCallbacks = connection.handledChannelSelectedCallbacks;
1455            setWriteInterestAfterChannelSelectedCallback = connection.setWriteInterestAfterChannelSelectedCallback.get();
1456            reactorThreadAlreadyRacing = connection.reactorThreadAlreadyRacing.get();
1457            afterOutgoingElementsQueueModifiedSetInterestOps = connection.afterOutgoingElementsQueueModifiedSetInterestOps
1458                    .get();
1459            rejectedChannelSelectedCallbacks = connection.rejectedChannelSelectedCallbacks.get();
1460
1461            totalCallbackRequests = handledChannelSelectedCallbacks + rejectedChannelSelectedCallbacks;
1462
1463            callbackPreemtBecauseBytesRead = connection.callbackPreemtBecauseBytesRead;
1464            callbackPreemtBecauseBytesWritten = connection.callbackPreemtBecauseBytesWritten;
1465
1466            sslEngineDelegatedTasks = connection.sslEngineDelegatedTasks;
1467            maxPendingSslEngineDelegatedTasks = connection.maxPendingSslEngineDelegatedTasks;
1468        }
1469
1470        private transient String toStringCache;
1471
1472        @Override
1473        public String toString() {
1474            if (toStringCache != null) {
1475                return toStringCache;
1476            }
1477
1478            toStringCache =
1479              "Total bytes\n"
1480            + "recv: " + totalBytesRead + '\n'
1481            + "send: " + totalBytesWritten + '\n'
1482            + "recv-aft-filter: " + totalBytesReadAfterFilter + '\n'
1483            + "send-bef-filter: " + totalBytesWrittenBeforeFilter + '\n'
1484            + "Events\n"
1485            + "total-callback-requests: " + totalCallbackRequests + '\n'
1486            + "handled-channel-selected-callbacks: " + handledChannelSelectedCallbacks + '\n'
1487            + "rejected-channel-selected-callbacks: " + rejectedChannelSelectedCallbacks + '\n'
1488            + "set-write-interest-after-callback: " + setWriteInterestAfterChannelSelectedCallback + '\n'
1489            + "reactor-thread-already-racing: " + reactorThreadAlreadyRacing + '\n'
1490            + "after-queue-modified-set-interest-ops: " + afterOutgoingElementsQueueModifiedSetInterestOps + '\n'
1491            + "callback-preemt-because-bytes-read: " + callbackPreemtBecauseBytesRead + '\n'
1492            + "callback-preemt-because-bytes-written: " + callbackPreemtBecauseBytesWritten + '\n'
1493            + "ssl-engine-delegated-tasks: " + sslEngineDelegatedTasks + '\n'
1494            + "max-pending-ssl-engine-delegated-tasks: " + maxPendingSslEngineDelegatedTasks + '\n'
1495            ;
1496
1497            return toStringCache;
1498        }
1499    }
1500
1501    // TODO replace with JidUtil.equals(Jid, Jid) once jxmpp's version is newer.
1502    private static boolean equals(Jid jidOne, Jid jidTwo) {
1503        if (jidOne == null && jidTwo == null) {
1504            return true;
1505        }
1506        if (jidOne != null) {
1507            return jidOne.equals(jidTwo);
1508        }
1509        return jidTwo.equals(jidOne);
1510    }
1511
1512    private static List<? extends Buffer> pruneBufferList(Collection<? extends Buffer> buffers) {
1513        return CollectionUtil.removeUntil(buffers, b -> b.hasRemaining());
1514    }
1515
1516    private static String byteBufferToString(ByteBuffer byteBuffer) {
1517        final byte[] bufferBytes = new byte[byteBuffer.remaining()];
1518        byteBuffer.get(bufferBytes).rewind();
1519        try {
1520            return new String(bufferBytes, "UTF-8");
1521        } catch (UnsupportedEncodingException e) {
1522            // TODO Auto-generated catch block
1523            throw new AssertionError(e);
1524        }
1525    }
1526
1527    @Override
1528    protected SSLSession getSSLSession() {
1529        if (tlsState == null) {
1530            return null;
1531        }
1532        return tlsState.engine.getSession();
1533    }
1534}