View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.hc.core5.reactor.ssl;
29  
30  import java.io.IOException;
31  import java.net.SocketAddress;
32  import java.nio.ByteBuffer;
33  import java.nio.channels.ByteChannel;
34  import java.nio.channels.CancelledKeyException;
35  import java.nio.channels.ClosedChannelException;
36  import java.nio.channels.SelectionKey;
37  import java.util.concurrent.atomic.AtomicInteger;
38  import java.util.concurrent.atomic.AtomicReference;
39  import java.util.concurrent.locks.Lock;
40  
41  import javax.net.ssl.SSLContext;
42  import javax.net.ssl.SSLEngine;
43  import javax.net.ssl.SSLEngineResult;
44  import javax.net.ssl.SSLEngineResult.HandshakeStatus;
45  import javax.net.ssl.SSLException;
46  import javax.net.ssl.SSLHandshakeException;
47  import javax.net.ssl.SSLSession;
48  
49  import org.apache.hc.core5.annotation.Contract;
50  import org.apache.hc.core5.annotation.Internal;
51  import org.apache.hc.core5.annotation.ThreadingBehavior;
52  import org.apache.hc.core5.concurrent.FutureCallback;
53  import org.apache.hc.core5.function.Callback;
54  import org.apache.hc.core5.io.CloseMode;
55  import org.apache.hc.core5.io.SocketTimeoutExceptionFactory;
56  import org.apache.hc.core5.net.NamedEndpoint;
57  import org.apache.hc.core5.reactor.Command;
58  import org.apache.hc.core5.reactor.EventMask;
59  import org.apache.hc.core5.reactor.IOEventHandler;
60  import org.apache.hc.core5.reactor.IOSession;
61  import org.apache.hc.core5.util.Args;
62  import org.apache.hc.core5.util.Asserts;
63  import org.apache.hc.core5.util.Timeout;
64  
65  /**
66   * {@code SSLIOSession} is a decorator class intended to transparently extend
67   * an {@link IOSession} with transport layer security capabilities based on
68   * the SSL/TLS protocol.
69   *
70   * @since 4.2
71   */
72  @Contract(threading = ThreadingBehavior.SAFE_CONDITIONAL)
73  @Internal
74  public class SSLIOSession implements IOSession {
75  
76      enum TLSHandShakeState { READY, INITIALIZED, HANDSHAKING, COMPLETE }
77  
78      private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0);
79  
80      private final NamedEndpoint targetEndpoint;
81      private final IOSession session;
82      private final SSLEngine sslEngine;
83      private final SSLManagedBuffer inEncrypted;
84      private final SSLManagedBuffer outEncrypted;
85      private final SSLManagedBuffer inPlain;
86      private final SSLSessionInitializer initializer;
87      private final SSLSessionVerifier verifier;
88      private final Callback<SSLIOSession> sessionStartCallback;
89      private final Callback<SSLIOSession> sessionEndCallback;
90      private final AtomicReference<FutureCallback<SSLSession>> handshakeCallbackRef;
91      private final Timeout handshakeTimeout;
92      private final SSLMode sslMode;
93      private final AtomicInteger outboundClosedCount;
94      private final AtomicReference<TLSHandShakeState> handshakeStateRef;
95      private final IOEventHandler internalEventHandler;
96  
97      private int appEventMask;
98  
99      private volatile boolean endOfStream;
100     private volatile Status status = Status.ACTIVE;
101     private volatile Timeout socketTimeout;
102     private volatile TlsDetails tlsDetails;
103 
104     /**
105      * Creates new instance of {@code SSLIOSession} class.
106      *
107      * @param session I/O session to be decorated with the TLS/SSL capabilities.
108      * @param sslMode SSL mode (client or server)
109      * @param targetEndpoint target endpoint (applicable in client mode only). May be {@code null}.
110      * @param sslContext SSL context to use for this I/O session.
111      * @param sslBufferMode buffer management mode
112      * @param initializer optional SSL session initializer. May be {@code null}.
113      * @param verifier optional SSL session verifier. May be {@code null}.
114      * @param connectTimeout timeout to apply for the TLS/SSL handshake. May be {@code null}.
115      *
116      * @since 5.0
117      */
118     public SSLIOSession(
119             final NamedEndpoint targetEndpoint,
120             final IOSession session,
121             final SSLMode sslMode,
122             final SSLContext sslContext,
123             final SSLBufferMode sslBufferMode,
124             final SSLSessionInitializer initializer,
125             final SSLSessionVerifier verifier,
126             final Callback<SSLIOSession> sessionStartCallback,
127             final Callback<SSLIOSession> sessionEndCallback,
128             final Timeout connectTimeout) {
129         this(targetEndpoint, session, sslMode, sslContext, sslBufferMode, initializer, verifier, connectTimeout,
130                 sessionStartCallback, sessionEndCallback, null);
131     }
132 
133     /**
134      * Creates new instance of {@code SSLIOSession} class.
135      *
136      * @param session I/O session to be decorated with the TLS/SSL capabilities.
137      * @param sslMode SSL mode (client or server)
138      * @param targetEndpoint target endpoint (applicable in client mode only). May be {@code null}.
139      * @param sslContext SSL context to use for this I/O session.
140      * @param sslBufferMode buffer management mode
141      * @param initializer optional SSL session initializer. May be {@code null}.
142      * @param verifier optional SSL session verifier. May be {@code null}.
143      * @param handshakeTimeout timeout to apply for the TLS/SSL handshake. May be {@code null}.
144      * @param resultCallback result callback. May be {@code null}.
145      *
146      * @since 5.2
147      */
148     public SSLIOSession(
149             final NamedEndpoint targetEndpoint,
150             final IOSession session,
151             final SSLMode sslMode,
152             final SSLContext sslContext,
153             final SSLBufferMode sslBufferMode,
154             final SSLSessionInitializer initializer,
155             final SSLSessionVerifier verifier,
156             final Timeout handshakeTimeout,
157             final Callback<SSLIOSession> sessionStartCallback,
158             final Callback<SSLIOSession> sessionEndCallback,
159             final FutureCallback<SSLSession> resultCallback) {
160         super();
161         Args.notNull(session, "IO session");
162         Args.notNull(sslContext, "SSL context");
163         this.targetEndpoint = targetEndpoint;
164         this.session = session;
165         this.sslMode = sslMode;
166         this.initializer = initializer;
167         this.verifier = verifier;
168         this.sessionStartCallback = sessionStartCallback;
169         this.sessionEndCallback = sessionEndCallback;
170         this.handshakeCallbackRef = new AtomicReference<>(resultCallback);
171 
172         this.appEventMask = session.getEventMask();
173         if (this.sslMode == SSLMode.CLIENT && targetEndpoint != null) {
174             this.sslEngine = sslContext.createSSLEngine(targetEndpoint.getHostName(), targetEndpoint.getPort());
175         } else {
176             this.sslEngine = sslContext.createSSLEngine();
177         }
178 
179         final SSLSession sslSession = this.sslEngine.getSession();
180         // Allocate buffers for network (encrypted) data
181         final int netBufferSize = sslSession.getPacketBufferSize();
182         this.inEncrypted = SSLManagedBuffer.create(sslBufferMode, netBufferSize);
183         this.outEncrypted = SSLManagedBuffer.create(sslBufferMode, netBufferSize);
184 
185         // Allocate buffers for application (unencrypted) data
186         final int appBufferSize = sslSession.getApplicationBufferSize();
187         this.inPlain = SSLManagedBuffer.create(sslBufferMode, appBufferSize);
188         this.outboundClosedCount = new AtomicInteger(0);
189         this.handshakeStateRef = new AtomicReference<>(TLSHandShakeState.READY);
190         this.handshakeTimeout = handshakeTimeout;
191         this.internalEventHandler = new IOEventHandler() {
192 
193             @Override
194             public void connected(final IOSession protocolSession) throws IOException {
195                 beginHandshake(protocolSession);
196             }
197 
198             @Override
199             public void inputReady(final IOSession protocolSession, final ByteBuffer src) throws IOException {
200                 receiveEncryptedData();
201                 doHandshake(protocolSession);
202                 decryptData(protocolSession);
203                 updateEventMask();
204             }
205 
206             @Override
207             public void outputReady(final IOSession protocolSession) throws IOException {
208                 encryptData(protocolSession);
209                 sendEncryptedData();
210                 doHandshake(protocolSession);
211                 updateEventMask();
212             }
213 
214             @Override
215             public void timeout(final IOSession protocolSession, final Timeout timeout) throws IOException {
216                 if (sslEngine.isInboundDone() && !sslEngine.isInboundDone()) {
217                     // The session failed to terminate cleanly
218                     close(CloseMode.IMMEDIATE);
219                 }
220                 if (handshakeStateRef.get() != TLSHandShakeState.COMPLETE) {
221                     exception(protocolSession, SocketTimeoutExceptionFactory.create(handshakeTimeout));
222                 } else {
223                     ensureHandler().timeout(protocolSession, timeout);
224                 }
225             }
226 
227             @Override
228             public void exception(final IOSession protocolSession, final Exception cause) {
229                 final FutureCallback<SSLSession> resultCallback = handshakeCallbackRef.getAndSet(null);
230                 if (resultCallback != null) {
231                     resultCallback.failed(cause);
232                 }
233                 final IOEventHandler handler = session.getHandler();
234                 if (handshakeStateRef.get() != TLSHandShakeState.COMPLETE) {
235                     session.close(CloseMode.GRACEFUL);
236                     close(CloseMode.IMMEDIATE);
237                 }
238                 if (handler != null) {
239                     handler.exception(protocolSession, cause);
240                 }
241             }
242 
243             @Override
244             public void disconnected(final IOSession protocolSession) {
245                 final IOEventHandler handler = session.getHandler();
246                 if (handler != null) {
247                     handler.disconnected(protocolSession);
248                 }
249             }
250 
251         };
252 
253     }
254 
255     private IOEventHandler ensureHandler() {
256         final IOEventHandler handler = session.getHandler();
257         Asserts.notNull(handler, "IO event handler");
258         return handler;
259     }
260 
261     @Override
262     public IOEventHandler getHandler() {
263         return internalEventHandler;
264     }
265 
266     public void beginHandshake(final IOSession protocolSession) throws IOException {
267         if (handshakeStateRef.compareAndSet(TLSHandShakeState.READY, TLSHandShakeState.INITIALIZED)) {
268             initialize(protocolSession);
269         }
270     }
271 
272     private void initialize(final IOSession protocolSession) throws IOException {
273         // Save the initial socketTimeout of the underlying IOSession, to be restored after the handshake is finished
274         this.socketTimeout = this.session.getSocketTimeout();
275         if (handshakeTimeout != null) {
276             this.session.setSocketTimeout(handshakeTimeout);
277         }
278 
279         this.session.getLock().lock();
280         try {
281             if (this.status.compareTo(Status.CLOSING) >= 0) {
282                 return;
283             }
284             switch (this.sslMode) {
285                 case CLIENT:
286                     this.sslEngine.setUseClientMode(true);
287                     break;
288                 case SERVER:
289                     this.sslEngine.setUseClientMode(false);
290                     break;
291             }
292             if (this.initializer != null) {
293                 this.initializer.initialize(this.targetEndpoint, this.sslEngine);
294             }
295             this.handshakeStateRef.set(TLSHandShakeState.HANDSHAKING);
296             this.sslEngine.beginHandshake();
297 
298             this.inEncrypted.release();
299             this.outEncrypted.release();
300             doHandshake(protocolSession);
301             updateEventMask();
302         } finally {
303             this.session.getLock().unlock();
304         }
305     }
306 
307     // A works-around for exception handling craziness in Sun/Oracle's SSLEngine
308     // implementation.
309     //
310     // sun.security.pkcs11.wrapper.PKCS11Exception is re-thrown as
311     // plain RuntimeException in sun.security.ssl.Handshaker#checkThrown
312     private SSLException convert(final RuntimeException ex) {
313         Throwable cause = ex.getCause();
314         if (cause == null) {
315             cause = ex;
316         }
317         return new SSLException(cause);
318     }
319 
320     private SSLEngineResult doWrap(final ByteBuffer src, final ByteBuffer dst) throws SSLException {
321         try {
322             return this.sslEngine.wrap(src, dst);
323         } catch (final RuntimeException ex) {
324             throw convert(ex);
325         }
326     }
327 
328     private SSLEngineResult doUnwrap(final ByteBuffer src, final ByteBuffer dst) throws SSLException {
329         try {
330             return this.sslEngine.unwrap(src, dst);
331         } catch (final RuntimeException ex) {
332             throw convert(ex);
333         }
334     }
335 
336     private void doRunTask() {
337         final Runnable r = this.sslEngine.getDelegatedTask();
338         if (r != null) {
339             r.run();
340         }
341     }
342 
343     private void doHandshake(final IOSession protocolSession) throws IOException {
344         boolean handshaking = true;
345 
346         SSLEngineResult result = null;
347         while (handshaking) {
348              HandshakeStatus handshakeStatus = this.sslEngine.getHandshakeStatus();
349 
350             // Work-around for what appears to be a bug in Conscrypt SSLEngine that does not
351             // transition into the handshaking state upon #closeOutbound() call but still
352             // has some handshake data stuck in its internal buffer.
353             if (handshakeStatus == HandshakeStatus.NOT_HANDSHAKING && outboundClosedCount.get() > 0) {
354                 handshakeStatus = HandshakeStatus.NEED_WRAP;
355             }
356 
357             switch (handshakeStatus) {
358             case NEED_WRAP:
359                 // Generate outgoing handshake data
360 
361                 this.session.getLock().lock();
362                 try {
363                     // Acquire buffers
364                     final ByteBuffer outEncryptedBuf = this.outEncrypted.acquire();
365 
366                     // Just wrap an empty buffer because there is no data to write.
367                     result = doWrap(EMPTY_BUFFER, outEncryptedBuf);
368 
369                     if (result.getStatus() != SSLEngineResult.Status.OK || result.getHandshakeStatus() == HandshakeStatus.NEED_WRAP) {
370                         handshaking = false;
371                     }
372                     break;
373                 } finally {
374                     this.session.getLock().unlock();
375                 }
376             case NEED_UNWRAP:
377                 // Process incoming handshake data
378 
379                 // Acquire buffers
380                 final ByteBuffer inEncryptedBuf = this.inEncrypted.acquire();
381                 final ByteBuffer inPlainBuf = this.inPlain.acquire();
382 
383                 // Perform operations
384                 inEncryptedBuf.flip();
385                 try {
386                     result = doUnwrap(inEncryptedBuf, inPlainBuf);
387                 } finally {
388                     inEncryptedBuf.compact();
389                 }
390 
391                 try {
392                     if (!inEncryptedBuf.hasRemaining() && result.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP) {
393                         throw new SSLException("Input buffer is full");
394                     }
395                 } finally {
396                     // Release inEncrypted if empty
397                     if (inEncryptedBuf.position() == 0) {
398                         this.inEncrypted.release();
399                     }
400                 }
401 
402                 if (this.status.compareTo(Status.CLOSING) >= 0) {
403                     this.inPlain.release();
404                 }
405                 if (result.getStatus() != SSLEngineResult.Status.OK) {
406                     handshaking = false;
407                 }
408                 break;
409             case NEED_TASK:
410                 doRunTask();
411                 break;
412             case NOT_HANDSHAKING:
413                 handshaking = false;
414                 break;
415             }
416         }
417 
418         // The SSLEngine has just finished handshaking. This value is only generated by a call
419         // to SSLEngine.wrap()/unwrap() when that call finishes a handshake.
420         // It is never generated by SSLEngine.getHandshakeStatus().
421         if (result != null && result.getHandshakeStatus() == HandshakeStatus.FINISHED) {
422             this.handshakeStateRef.set(TLSHandShakeState.COMPLETE);
423             this.session.setSocketTimeout(this.socketTimeout);
424             if (this.verifier != null) {
425                 this.tlsDetails = this.verifier.verify(this.targetEndpoint, this.sslEngine);
426             }
427             String applicationProtocol;
428             if (this.tlsDetails == null) {
429                 final SSLSession sslSession = this.sslEngine.getSession();
430                 try {
431                     applicationProtocol = this.sslEngine.getApplicationProtocol();
432                 } catch (final UnsupportedOperationException e) {
433                     // If the underlying provider does not support the operation, the getApplicationProtocol() method throws an UnsupportedOperationException.
434                     // In this case, we fall back to "http/1.1" as the application protocol.
435                     // This is a workaround to allow older applications that do not support the getApplicationProtocol() method to continue working.
436                     // This workaround is temporary and is meant to maintain compatibility with older systems.
437                     applicationProtocol = "http/1.1";
438                 }
439                 this.tlsDetails = new TlsDetails(sslSession, applicationProtocol);
440             }
441 
442             ensureHandler().connected(protocolSession);
443 
444             if (this.sessionStartCallback != null) {
445                 this.sessionStartCallback.execute(this);
446             }
447             final FutureCallback<SSLSession> resultCallback = handshakeCallbackRef.getAndSet(null);
448             if (resultCallback != null) {
449                 resultCallback.completed(sslEngine.getSession());
450             }
451         }
452     }
453 
454     private void updateEventMask() {
455         this.session.getLock().lock();
456         try {
457             // Graceful session termination
458             if (this.status == Status.ACTIVE
459                     && (this.endOfStream || this.sslEngine.isInboundDone())) {
460                 this.status = Status.CLOSING;
461                 final FutureCallback<SSLSession> resultCallback = handshakeCallbackRef.getAndSet(null);
462                 if (resultCallback != null) {
463                     resultCallback.failed(new SSLHandshakeException("TLS handshake failed"));
464                 }
465             }
466             if (this.status == Status.CLOSING && !this.outEncrypted.hasData()) {
467                 this.sslEngine.closeOutbound();
468                 this.outboundClosedCount.incrementAndGet();
469             }
470             if (this.status == Status.CLOSING && this.sslEngine.isOutboundDone()
471                     && (this.endOfStream || this.sslEngine.isInboundDone())) {
472                 this.status = Status.CLOSED;
473             }
474             // Abnormal session termination
475             if (this.status.compareTo(Status.CLOSING) <= 0 && this.endOfStream
476                     && this.sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP) {
477                 this.status = Status.CLOSED;
478             }
479             if (this.status == Status.CLOSED) {
480                 this.session.close();
481                 if (sessionEndCallback != null) {
482                     sessionEndCallback.execute(this);
483                 }
484                 return;
485             }
486             // Is there a task pending?
487             if (this.sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
488                 doRunTask();
489             }
490             // Need to toggle the event mask for this channel?
491             final int oldMask = this.session.getEventMask();
492             int newMask = oldMask;
493             switch (this.sslEngine.getHandshakeStatus()) {
494                 case NEED_WRAP:
495                     newMask = EventMask.READ_WRITE;
496                     break;
497                 case NEED_UNWRAP:
498                     newMask = EventMask.READ;
499                     break;
500                 case NOT_HANDSHAKING:
501                     newMask = this.appEventMask;
502                     break;
503             }
504 
505             if (this.endOfStream && !this.inPlain.hasData()) {
506                 newMask = newMask & ~EventMask.READ;
507             } else if (this.status == Status.CLOSING) {
508                 newMask = newMask | EventMask.READ;
509             }
510 
511             // Do we have encrypted data ready to be sent?
512             if (this.outEncrypted.hasData()) {
513                 newMask = newMask | EventMask.WRITE;
514             } else if (this.sslEngine.isOutboundDone()) {
515                 newMask = newMask & ~EventMask.WRITE;
516             }
517 
518             // Update the mask if necessary
519             if (oldMask != newMask) {
520                 this.session.setEventMask(newMask);
521             }
522         } finally {
523             this.session.getLock().unlock();
524         }
525     }
526 
527     private int sendEncryptedData() throws IOException {
528         this.session.getLock().lock();
529         try {
530             if (!this.outEncrypted.hasData()) {
531                 // If the buffer isn't acquired or is empty, call write() with an empty buffer.
532                 // This will ensure that tests performed by write() still take place without
533                 // having to acquire and release an empty buffer (e.g. connection closed,
534                 // interrupted thread, etc..)
535                 return this.session.write(EMPTY_BUFFER);
536             }
537 
538             // Acquire buffer
539             final ByteBuffer outEncryptedBuf = this.outEncrypted.acquire();
540 
541             // Clear output buffer if the session has been closed
542             // in case there is still `close_notify` data stuck in it
543             if (this.status == Status.CLOSED) {
544                 outEncryptedBuf.clear();
545             }
546 
547             // Perform operation
548             int bytesWritten = 0;
549             if (outEncryptedBuf.position() > 0) {
550                 outEncryptedBuf.flip();
551                 try {
552                     bytesWritten = this.session.write(outEncryptedBuf);
553                 } finally {
554                     outEncryptedBuf.compact();
555                 }
556             }
557 
558             // Release if empty
559             if (outEncryptedBuf.position() == 0) {
560                 this.outEncrypted.release();
561             }
562             return bytesWritten;
563         } finally {
564             this.session.getLock().unlock();
565         }
566     }
567 
568     private int receiveEncryptedData() throws IOException {
569         if (this.endOfStream) {
570             return -1;
571         }
572 
573         // Acquire buffer
574         final ByteBuffer inEncryptedBuf = this.inEncrypted.acquire();
575 
576         // Perform operation
577         final int bytesRead = this.session.read(inEncryptedBuf);
578 
579         // Release if empty
580         if (inEncryptedBuf.position() == 0) {
581             this.inEncrypted.release();
582         }
583         if (bytesRead == -1) {
584             this.endOfStream = true;
585         }
586         return bytesRead;
587     }
588 
589     private void decryptData(final IOSession protocolSession) throws IOException {
590         final HandshakeStatus handshakeStatus = sslEngine.getHandshakeStatus();
591         if ((handshakeStatus == HandshakeStatus.NOT_HANDSHAKING || handshakeStatus == HandshakeStatus.FINISHED)
592                 && inEncrypted.hasData()) {
593             final ByteBuffer inEncryptedBuf = inEncrypted.acquire();
594             inEncryptedBuf.flip();
595             try {
596                 while (inEncryptedBuf.hasRemaining()) {
597                     final ByteBuffer inPlainBuf = inPlain.acquire();
598                     try {
599                         final SSLEngineResult result = doUnwrap(inEncryptedBuf, inPlainBuf);
600                         if (!inEncryptedBuf.hasRemaining() && result.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP) {
601                             throw new SSLException("Unable to complete SSL handshake");
602                         }
603                         if (sslEngine.isInboundDone()) {
604                             endOfStream = true;
605                         }
606                         if(inPlainBuf.position() > 0) {
607                             inPlainBuf.flip();
608                             try {
609                                 ensureHandler().inputReady(protocolSession, inPlainBuf.hasRemaining() ? inPlainBuf : null);
610                             } finally {
611                                 inPlainBuf.clear();
612                             }
613                         }
614                         if (result.getStatus() != SSLEngineResult.Status.OK) {
615                             if (result.getStatus() == SSLEngineResult.Status.BUFFER_UNDERFLOW && endOfStream) {
616                                 throw new SSLException("Unable to decrypt incoming data due to unexpected end of stream");
617                             }
618                             break;
619                         }
620                     } finally {
621                         inPlain.release();
622                     }
623                 }
624             } finally {
625                 inEncryptedBuf.compact();
626                 // Release inEncrypted if empty
627                 if (inEncryptedBuf.position() == 0) {
628                     inEncrypted.release();
629                 }
630             }
631         }
632         if (endOfStream && !inEncrypted.hasData()) {
633             ensureHandler().inputReady(protocolSession, null);
634         }
635     }
636 
637     private void encryptData(final IOSession protocolSession) throws IOException {
638         final boolean appReady;
639         this.session.getLock().lock();
640         try {
641             appReady = (this.appEventMask & SelectionKey.OP_WRITE) > 0
642                     && this.status == Status.ACTIVE
643                     && this.sslEngine.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING;
644         } finally {
645             this.session.getLock().unlock();
646         }
647         if (appReady) {
648             ensureHandler().outputReady(protocolSession);
649         }
650     }
651 
652     @Override
653     public int write(final ByteBuffer src) throws IOException {
654         Args.notNull(src, "Byte buffer");
655         this.session.getLock().lock();
656         try {
657             if (this.status != Status.ACTIVE) {
658                 throw new ClosedChannelException();
659             }
660             if (this.handshakeStateRef.get() == TLSHandShakeState.READY) {
661                 return 0;
662             }
663             final ByteBuffer outEncryptedBuf = this.outEncrypted.acquire();
664             final SSLEngineResult result = doWrap(src, outEncryptedBuf);
665             return result.bytesConsumed();
666         } finally {
667             this.session.getLock().unlock();
668         }
669     }
670 
671     @Override
672     public int read(final ByteBuffer dst) {
673         return endOfStream ? -1 : 0;
674     }
675 
676     @Override
677     public String getId() {
678         return session.getId();
679     }
680 
681     @Override
682     public Lock getLock() {
683         return this.session.getLock();
684     }
685 
686     @Override
687     public void upgrade(final IOEventHandler handler) {
688         this.session.upgrade(handler);
689     }
690 
691     public TlsDetails getTlsDetails() {
692         return tlsDetails;
693     }
694 
695     @Override
696     public boolean isOpen() {
697         return this.status == Status.ACTIVE && this.session.isOpen();
698     }
699 
700     @Override
701     public void close() {
702         close(CloseMode.GRACEFUL);
703     }
704 
705     @Override
706     public void close(final CloseMode closeMode) {
707         this.session.getLock().lock();
708         try {
709             if (closeMode == CloseMode.GRACEFUL) {
710                 if (this.status.compareTo(Status.CLOSING) >= 0) {
711                     return;
712                 }
713                 this.status = Status.CLOSING;
714                 if (this.session.getSocketTimeout().isDisabled()) {
715                     this.session.setSocketTimeout(Timeout.ofMilliseconds(1000));
716                 }
717                 try {
718                     // Catch all unchecked exceptions in case something goes wrong
719                     // in the JSSE provider. For instance
720                     // com.android.org.conscrypt.NativeCrypto#SSL_get_shutdown can
721                     // throw NPE at this point
722                     updateEventMask();
723                 } catch (final CancelledKeyException ex) {
724                     this.session.close(CloseMode.GRACEFUL);
725                 } catch (final Exception ex) {
726                     this.session.close(CloseMode.IMMEDIATE);
727                 }
728             } else {
729                 if (this.status == Status.CLOSED) {
730                     return;
731                 }
732                 this.inEncrypted.release();
733                 this.outEncrypted.release();
734                 this.inPlain.release();
735 
736                 this.status = Status.CLOSED;
737                 this.session.close(closeMode);
738             }
739         } finally {
740             this.session.getLock().unlock();
741         }
742     }
743 
744     @Override
745     public Status getStatus() {
746         return this.status;
747     }
748 
749     @Override
750     public void enqueue(final Command command, final Command.Priority priority) {
751         this.session.getLock().lock();
752         try {
753             this.session.enqueue(command, priority);
754             setEvent(SelectionKey.OP_WRITE);
755         } finally {
756             this.session.getLock().unlock();
757         }
758     }
759 
760     @Override
761     public boolean hasCommands() {
762         return this.session.hasCommands();
763     }
764 
765     @Override
766     public Command poll() {
767         return this.session.poll();
768     }
769 
770     @Override
771     public ByteChannel channel() {
772         return this.session.channel();
773     }
774 
775     @Override
776     public SocketAddress getLocalAddress() {
777         return this.session.getLocalAddress();
778     }
779 
780     @Override
781     public SocketAddress getRemoteAddress() {
782         return this.session.getRemoteAddress();
783     }
784 
785     @Override
786     public int getEventMask() {
787         this.session.getLock().lock();
788         try {
789             return this.appEventMask;
790         } finally {
791             this.session.getLock().unlock();
792         }
793     }
794 
795     @Override
796     public void setEventMask(final int ops) {
797         this.session.getLock().lock();
798         try {
799             this.appEventMask = ops;
800             updateEventMask();
801         } finally {
802             this.session.getLock().unlock();
803         }
804     }
805 
806     @Override
807     public void setEvent(final int op) {
808         this.session.getLock().lock();
809         try {
810             this.appEventMask = this.appEventMask | op;
811             updateEventMask();
812         } finally {
813             this.session.getLock().unlock();
814         }
815     }
816 
817     @Override
818     public void clearEvent(final int op) {
819         this.session.getLock().lock();
820         try {
821             this.appEventMask = this.appEventMask & ~op;
822             updateEventMask();
823         } finally {
824             this.session.getLock().unlock();
825         }
826     }
827 
828     @Override
829     public Timeout getSocketTimeout() {
830         return this.session.getSocketTimeout();
831     }
832 
833     @Override
834     public void setSocketTimeout(final Timeout timeout) {
835         this.socketTimeout = timeout;
836         if (this.sslEngine.getHandshakeStatus() == HandshakeStatus.FINISHED) {
837             this.session.setSocketTimeout(timeout);
838         }
839     }
840 
841     @Override
842     public void updateReadTime() {
843         this.session.updateReadTime();
844     }
845 
846     @Override
847     public void updateWriteTime() {
848         this.session.updateWriteTime();
849     }
850 
851     @Override
852     public long getLastReadTime() {
853         return this.session.getLastReadTime();
854     }
855 
856     @Override
857     public long getLastWriteTime() {
858         return this.session.getLastWriteTime();
859     }
860 
861     @Override
862     public long getLastEventTime() {
863         return this.session.getLastEventTime();
864     }
865 
866     private static void formatOps(final StringBuilder buffer, final int ops) {
867         if ((ops & SelectionKey.OP_READ) > 0) {
868             buffer.append('r');
869         }
870         if ((ops & SelectionKey.OP_WRITE) > 0) {
871             buffer.append('w');
872         }
873     }
874 
875     @Override
876     public String toString() {
877         this.session.getLock().lock();
878         try {
879             final StringBuilder buffer = new StringBuilder();
880             buffer.append(this.session);
881             buffer.append("[");
882             buffer.append(this.status);
883             buffer.append("][");
884             formatOps(buffer, this.appEventMask);
885             buffer.append("][");
886             buffer.append(this.sslEngine.getHandshakeStatus());
887             if (this.sslEngine.isInboundDone()) {
888                 buffer.append("][inbound done][");
889             }
890             if (this.sslEngine.isOutboundDone()) {
891                 buffer.append("][outbound done][");
892             }
893             if (this.endOfStream) {
894                 buffer.append("][EOF][");
895             }
896             buffer.append("][");
897             buffer.append(!this.inEncrypted.hasData() ? 0 : inEncrypted.acquire().position());
898             buffer.append("][");
899             buffer.append(!this.inPlain.hasData() ? 0 : inPlain.acquire().position());
900             buffer.append("][");
901             buffer.append(!this.outEncrypted.hasData() ? 0 : outEncrypted.acquire().position());
902             buffer.append("]");
903             return buffer.toString();
904         } finally {
905             this.session.getLock().unlock();
906         }
907     }
908 
909 }