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