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