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}