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             if (this.tlsDetails == null) {
428                 final SSLSession sslSession = this.sslEngine.getSession();
429                 final String applicationProtocol = this.sslEngine.getApplicationProtocol();
430                 this.tlsDetails = new TlsDetails(sslSession, applicationProtocol);
431             }
432 
433             ensureHandler().connected(protocolSession);
434 
435             if (this.sessionStartCallback != null) {
436                 this.sessionStartCallback.execute(this);
437             }
438             final FutureCallback<SSLSession> resultCallback = handshakeCallbackRef.getAndSet(null);
439             if (resultCallback != null) {
440                 resultCallback.completed(sslEngine.getSession());
441             }
442         }
443     }
444 
445     private void updateEventMask() {
446         this.session.getLock().lock();
447         try {
448             // Graceful session termination
449             if (this.status == Status.ACTIVE
450                     && (this.endOfStream || this.sslEngine.isInboundDone())) {
451                 this.status = Status.CLOSING;
452                 final FutureCallback<SSLSession> resultCallback = handshakeCallbackRef.getAndSet(null);
453                 if (resultCallback != null) {
454                     resultCallback.failed(new SSLHandshakeException("TLS handshake failed"));
455                 }
456             }
457             if (this.status == Status.CLOSING && !this.outEncrypted.hasData()) {
458                 this.sslEngine.closeOutbound();
459                 this.outboundClosedCount.incrementAndGet();
460             }
461             if (this.status == Status.CLOSING && this.sslEngine.isOutboundDone()
462                     && (this.endOfStream || this.sslEngine.isInboundDone())) {
463                 this.status = Status.CLOSED;
464             }
465             // Abnormal session termination
466             if (this.status.compareTo(Status.CLOSING) <= 0 && this.endOfStream
467                     && this.sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP) {
468                 this.status = Status.CLOSED;
469             }
470             if (this.status == Status.CLOSED) {
471                 this.session.close();
472                 if (sessionEndCallback != null) {
473                     sessionEndCallback.execute(this);
474                 }
475                 return;
476             }
477             // Is there a task pending?
478             if (this.sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
479                 doRunTask();
480             }
481             // Need to toggle the event mask for this channel?
482             final int oldMask = this.session.getEventMask();
483             int newMask = oldMask;
484             switch (this.sslEngine.getHandshakeStatus()) {
485                 case NEED_WRAP:
486                     newMask = EventMask.READ_WRITE;
487                     break;
488                 case NEED_UNWRAP:
489                     newMask = EventMask.READ;
490                     break;
491                 case NOT_HANDSHAKING:
492                     newMask = this.appEventMask;
493                     break;
494             }
495 
496             if (this.endOfStream && !this.inPlain.hasData()) {
497                 newMask = newMask & ~EventMask.READ;
498             } else if (this.status == Status.CLOSING) {
499                 newMask = newMask | EventMask.READ;
500             }
501 
502             // Do we have encrypted data ready to be sent?
503             if (this.outEncrypted.hasData()) {
504                 newMask = newMask | EventMask.WRITE;
505             } else if (this.sslEngine.isOutboundDone()) {
506                 newMask = newMask & ~EventMask.WRITE;
507             }
508 
509             // Update the mask if necessary
510             if (oldMask != newMask) {
511                 this.session.setEventMask(newMask);
512             }
513         } finally {
514             this.session.getLock().unlock();
515         }
516     }
517 
518     private int sendEncryptedData() throws IOException {
519         this.session.getLock().lock();
520         try {
521             if (!this.outEncrypted.hasData()) {
522                 // If the buffer isn't acquired or is empty, call write() with an empty buffer.
523                 // This will ensure that tests performed by write() still take place without
524                 // having to acquire and release an empty buffer (e.g. connection closed,
525                 // interrupted thread, etc..)
526                 return this.session.write(EMPTY_BUFFER);
527             }
528 
529             // Acquire buffer
530             final ByteBuffer outEncryptedBuf = this.outEncrypted.acquire();
531 
532             // Clear output buffer if the session has been closed
533             // in case there is still `close_notify` data stuck in it
534             if (this.status == Status.CLOSED) {
535                 outEncryptedBuf.clear();
536             }
537 
538             // Perform operation
539             int bytesWritten = 0;
540             if (outEncryptedBuf.position() > 0) {
541                 outEncryptedBuf.flip();
542                 try {
543                     bytesWritten = this.session.write(outEncryptedBuf);
544                 } finally {
545                     outEncryptedBuf.compact();
546                 }
547             }
548 
549             // Release if empty
550             if (outEncryptedBuf.position() == 0) {
551                 this.outEncrypted.release();
552             }
553             return bytesWritten;
554         } finally {
555             this.session.getLock().unlock();
556         }
557     }
558 
559     private int receiveEncryptedData() throws IOException {
560         if (this.endOfStream) {
561             return -1;
562         }
563 
564         // Acquire buffer
565         final ByteBuffer inEncryptedBuf = this.inEncrypted.acquire();
566 
567         // Perform operation
568         final int bytesRead = this.session.read(inEncryptedBuf);
569 
570         // Release if empty
571         if (inEncryptedBuf.position() == 0) {
572             this.inEncrypted.release();
573         }
574         if (bytesRead == -1) {
575             this.endOfStream = true;
576         }
577         return bytesRead;
578     }
579 
580     private void decryptData(final IOSession protocolSession) throws IOException {
581         final HandshakeStatus handshakeStatus = sslEngine.getHandshakeStatus();
582         if ((handshakeStatus == HandshakeStatus.NOT_HANDSHAKING || handshakeStatus == HandshakeStatus.FINISHED)
583                 && inEncrypted.hasData()) {
584             final ByteBuffer inEncryptedBuf = inEncrypted.acquire();
585             inEncryptedBuf.flip();
586             try {
587                 while (inEncryptedBuf.hasRemaining()) {
588                     final ByteBuffer inPlainBuf = inPlain.acquire();
589                     try {
590                         final SSLEngineResult result = doUnwrap(inEncryptedBuf, inPlainBuf);
591                         if (!inEncryptedBuf.hasRemaining() && result.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP) {
592                             throw new SSLException("Unable to complete SSL handshake");
593                         }
594                         if (sslEngine.isInboundDone()) {
595                             endOfStream = true;
596                         }
597                         if(inPlainBuf.position() > 0) {
598                             inPlainBuf.flip();
599                             try {
600                                 ensureHandler().inputReady(protocolSession, inPlainBuf.hasRemaining() ? inPlainBuf : null);
601                             } finally {
602                                 inPlainBuf.clear();
603                             }
604                         }
605                         if (result.getStatus() != SSLEngineResult.Status.OK) {
606                             if (result.getStatus() == SSLEngineResult.Status.BUFFER_UNDERFLOW && endOfStream) {
607                                 throw new SSLException("Unable to decrypt incoming data due to unexpected end of stream");
608                             }
609                             break;
610                         }
611                     } finally {
612                         inPlain.release();
613                     }
614                 }
615             } finally {
616                 inEncryptedBuf.compact();
617                 // Release inEncrypted if empty
618                 if (inEncryptedBuf.position() == 0) {
619                     inEncrypted.release();
620                 }
621             }
622         }
623         if (endOfStream && !inEncrypted.hasData()) {
624             ensureHandler().inputReady(protocolSession, null);
625         }
626     }
627 
628     private void encryptData(final IOSession protocolSession) throws IOException {
629         final boolean appReady;
630         this.session.getLock().lock();
631         try {
632             appReady = (this.appEventMask & SelectionKey.OP_WRITE) > 0
633                     && this.status == Status.ACTIVE
634                     && this.sslEngine.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING;
635         } finally {
636             this.session.getLock().unlock();
637         }
638         if (appReady) {
639             ensureHandler().outputReady(protocolSession);
640         }
641     }
642 
643     @Override
644     public int write(final ByteBuffer src) throws IOException {
645         Args.notNull(src, "Byte buffer");
646         this.session.getLock().lock();
647         try {
648             if (this.status != Status.ACTIVE) {
649                 throw new ClosedChannelException();
650             }
651             if (this.handshakeStateRef.get() == TLSHandShakeState.READY) {
652                 return 0;
653             }
654             final ByteBuffer outEncryptedBuf = this.outEncrypted.acquire();
655             final SSLEngineResult result = doWrap(src, outEncryptedBuf);
656             return result.bytesConsumed();
657         } finally {
658             this.session.getLock().unlock();
659         }
660     }
661 
662     @Override
663     public int read(final ByteBuffer dst) {
664         return endOfStream ? -1 : 0;
665     }
666 
667     @Override
668     public String getId() {
669         return session.getId();
670     }
671 
672     @Override
673     public Lock getLock() {
674         return this.session.getLock();
675     }
676 
677     @Override
678     public void upgrade(final IOEventHandler handler) {
679         this.session.upgrade(handler);
680     }
681 
682     public TlsDetails getTlsDetails() {
683         return tlsDetails;
684     }
685 
686     @Override
687     public boolean isOpen() {
688         return this.status == Status.ACTIVE && this.session.isOpen();
689     }
690 
691     @Override
692     public void close() {
693         close(CloseMode.GRACEFUL);
694     }
695 
696     @Override
697     public void close(final CloseMode closeMode) {
698         this.session.getLock().lock();
699         try {
700             if (closeMode == CloseMode.GRACEFUL) {
701                 if (this.status.compareTo(Status.CLOSING) >= 0) {
702                     return;
703                 }
704                 this.status = Status.CLOSING;
705                 if (this.session.getSocketTimeout().isDisabled()) {
706                     this.session.setSocketTimeout(Timeout.ofMilliseconds(1000));
707                 }
708                 try {
709                     // Catch all unchecked exceptions in case something goes wrong
710                     // in the JSSE provider. For instance
711                     // com.android.org.conscrypt.NativeCrypto#SSL_get_shutdown can
712                     // throw NPE at this point
713                     updateEventMask();
714                 } catch (final CancelledKeyException ex) {
715                     this.session.close(CloseMode.GRACEFUL);
716                 } catch (final Exception ex) {
717                     this.session.close(CloseMode.IMMEDIATE);
718                 }
719             } else {
720                 if (this.status == Status.CLOSED) {
721                     return;
722                 }
723                 this.inEncrypted.release();
724                 this.outEncrypted.release();
725                 this.inPlain.release();
726 
727                 this.status = Status.CLOSED;
728                 this.session.close(closeMode);
729             }
730         } finally {
731             this.session.getLock().unlock();
732         }
733     }
734 
735     @Override
736     public Status getStatus() {
737         return this.status;
738     }
739 
740     @Override
741     public void enqueue(final Command command, final Command.Priority priority) {
742         this.session.getLock().lock();
743         try {
744             this.session.enqueue(command, priority);
745             setEvent(SelectionKey.OP_WRITE);
746         } finally {
747             this.session.getLock().unlock();
748         }
749     }
750 
751     @Override
752     public boolean hasCommands() {
753         return this.session.hasCommands();
754     }
755 
756     @Override
757     public Command poll() {
758         return this.session.poll();
759     }
760 
761     @Override
762     public ByteChannel channel() {
763         return this.session.channel();
764     }
765 
766     @Override
767     public SocketAddress getLocalAddress() {
768         return this.session.getLocalAddress();
769     }
770 
771     @Override
772     public SocketAddress getRemoteAddress() {
773         return this.session.getRemoteAddress();
774     }
775 
776     @Override
777     public int getEventMask() {
778         this.session.getLock().lock();
779         try {
780             return this.appEventMask;
781         } finally {
782             this.session.getLock().unlock();
783         }
784     }
785 
786     @Override
787     public void setEventMask(final int ops) {
788         this.session.getLock().lock();
789         try {
790             this.appEventMask = ops;
791             updateEventMask();
792         } finally {
793             this.session.getLock().unlock();
794         }
795     }
796 
797     @Override
798     public void setEvent(final int op) {
799         this.session.getLock().lock();
800         try {
801             this.appEventMask = this.appEventMask | op;
802             updateEventMask();
803         } finally {
804             this.session.getLock().unlock();
805         }
806     }
807 
808     @Override
809     public void clearEvent(final int op) {
810         this.session.getLock().lock();
811         try {
812             this.appEventMask = this.appEventMask & ~op;
813             updateEventMask();
814         } finally {
815             this.session.getLock().unlock();
816         }
817     }
818 
819     @Override
820     public Timeout getSocketTimeout() {
821         return this.session.getSocketTimeout();
822     }
823 
824     @Override
825     public void setSocketTimeout(final Timeout timeout) {
826         this.socketTimeout = timeout;
827         if (this.sslEngine.getHandshakeStatus() == HandshakeStatus.FINISHED) {
828             this.session.setSocketTimeout(timeout);
829         }
830     }
831 
832     @Override
833     public void updateReadTime() {
834         this.session.updateReadTime();
835     }
836 
837     @Override
838     public void updateWriteTime() {
839         this.session.updateWriteTime();
840     }
841 
842     @Override
843     public long getLastReadTime() {
844         return this.session.getLastReadTime();
845     }
846 
847     @Override
848     public long getLastWriteTime() {
849         return this.session.getLastWriteTime();
850     }
851 
852     @Override
853     public long getLastEventTime() {
854         return this.session.getLastEventTime();
855     }
856 
857     private static void formatOps(final StringBuilder buffer, final int ops) {
858         if ((ops & SelectionKey.OP_READ) > 0) {
859             buffer.append('r');
860         }
861         if ((ops & SelectionKey.OP_WRITE) > 0) {
862             buffer.append('w');
863         }
864     }
865 
866     @Override
867     public String toString() {
868         this.session.getLock().lock();
869         try {
870             final StringBuilder buffer = new StringBuilder();
871             buffer.append(this.session);
872             buffer.append("[");
873             buffer.append(this.status);
874             buffer.append("][");
875             formatOps(buffer, this.appEventMask);
876             buffer.append("][");
877             buffer.append(this.sslEngine.getHandshakeStatus());
878             if (this.sslEngine.isInboundDone()) {
879                 buffer.append("][inbound done][");
880             }
881             if (this.sslEngine.isOutboundDone()) {
882                 buffer.append("][outbound done][");
883             }
884             if (this.endOfStream) {
885                 buffer.append("][EOF][");
886             }
887             buffer.append("][");
888             buffer.append(!this.inEncrypted.hasData() ? 0 : inEncrypted.acquire().position());
889             buffer.append("][");
890             buffer.append(!this.inPlain.hasData() ? 0 : inPlain.acquire().position());
891             buffer.append("][");
892             buffer.append(!this.outEncrypted.hasData() ? 0 : outEncrypted.acquire().position());
893             buffer.append("]");
894             return buffer.toString();
895         } finally {
896             this.session.getLock().unlock();
897         }
898     }
899 
900 }