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         if (endOfStream && !inEncrypted.hasData()) {
573             ensureHandler().inputReady(protocolSession, null);
574         }
575     }
576 
577     private void encryptData(final IOSession protocolSession) throws IOException {
578         final boolean appReady;
579         this.session.getLock().lock();
580         try {
581             appReady = (this.appEventMask & SelectionKey.OP_WRITE) > 0
582                     && this.status == Status.ACTIVE
583                     && this.sslEngine.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING;
584         } finally {
585             this.session.getLock().unlock();
586         }
587         if (appReady) {
588             ensureHandler().outputReady(protocolSession);
589         }
590     }
591 
592     @Override
593     public int write(final ByteBuffer src) throws IOException {
594         Args.notNull(src, "Byte buffer");
595         this.session.getLock().lock();
596         try {
597             if (this.status != Status.ACTIVE) {
598                 throw new ClosedChannelException();
599             }
600             if (this.handshakeStateRef.get() == TLSHandShakeState.READY) {
601                 return 0;
602             }
603             final ByteBuffer outEncryptedBuf = this.outEncrypted.acquire();
604             final SSLEngineResult result = doWrap(src, outEncryptedBuf);
605             return result.bytesConsumed();
606         } finally {
607             this.session.getLock().unlock();
608         }
609     }
610 
611     @Override
612     public int read(final ByteBuffer dst) {
613         return endOfStream ? -1 : 0;
614     }
615 
616     @Override
617     public String getId() {
618         return session.getId();
619     }
620 
621     @Override
622     public Lock getLock() {
623         return this.session.getLock();
624     }
625 
626     @Override
627     public void upgrade(final IOEventHandler handler) {
628         this.session.upgrade(handler);
629     }
630 
631     public TlsDetails getTlsDetails() {
632         return tlsDetails;
633     }
634 
635     @Override
636     public boolean isOpen() {
637         return this.status == Status.ACTIVE && this.session.isOpen();
638     }
639 
640     @Override
641     public void close() {
642         close(CloseMode.GRACEFUL);
643     }
644 
645     @Override
646     public void close(final CloseMode closeMode) {
647         this.session.getLock().lock();
648         try {
649             if (closeMode == CloseMode.GRACEFUL) {
650                 if (this.status.compareTo(Status.CLOSING) >= 0) {
651                     return;
652                 }
653                 this.status = Status.CLOSING;
654                 if (this.session.getSocketTimeout().isDisabled()) {
655                     this.session.setSocketTimeout(Timeout.ofMilliseconds(1000));
656                 }
657                 try {
658                     // Catch all unchecked exceptions in case something goes wrong
659                     // in the JSSE provider. For instance
660                     // com.android.org.conscrypt.NativeCrypto#SSL_get_shutdown can
661                     // throw NPE at this point
662                     updateEventMask();
663                 } catch (final CancelledKeyException ex) {
664                     this.session.close(CloseMode.GRACEFUL);
665                 } catch (final Exception ex) {
666                     this.session.close(CloseMode.IMMEDIATE);
667                 }
668             } else {
669                 if (this.status == Status.CLOSED) {
670                     return;
671                 }
672                 this.inEncrypted.release();
673                 this.outEncrypted.release();
674                 this.inPlain.release();
675 
676                 this.status = Status.CLOSED;
677                 this.session.close(closeMode);
678             }
679         } finally {
680             this.session.getLock().unlock();
681         }
682     }
683 
684     @Override
685     public Status getStatus() {
686         return this.status;
687     }
688 
689     @Override
690     public void enqueue(final Command command, final Command.Priority priority) {
691         this.session.getLock().lock();
692         try {
693             this.session.enqueue(command, priority);
694             setEvent(SelectionKey.OP_WRITE);
695         } finally {
696             this.session.getLock().unlock();
697         }
698     }
699 
700     @Override
701     public boolean hasCommands() {
702         return this.session.hasCommands();
703     }
704 
705     @Override
706     public Command poll() {
707         return this.session.poll();
708     }
709 
710     @Override
711     public ByteChannel channel() {
712         return this.session.channel();
713     }
714 
715     @Override
716     public SocketAddress getLocalAddress() {
717         return this.session.getLocalAddress();
718     }
719 
720     @Override
721     public SocketAddress getRemoteAddress() {
722         return this.session.getRemoteAddress();
723     }
724 
725     @Override
726     public int getEventMask() {
727         this.session.getLock().lock();
728         try {
729             return this.appEventMask;
730         } finally {
731             this.session.getLock().unlock();
732         }
733     }
734 
735     @Override
736     public void setEventMask(final int ops) {
737         this.session.getLock().lock();
738         try {
739             this.appEventMask = ops;
740             updateEventMask();
741         } finally {
742             this.session.getLock().unlock();
743         }
744     }
745 
746     @Override
747     public void setEvent(final int op) {
748         this.session.getLock().lock();
749         try {
750             this.appEventMask = this.appEventMask | op;
751             updateEventMask();
752         } finally {
753             this.session.getLock().unlock();
754         }
755     }
756 
757     @Override
758     public void clearEvent(final int op) {
759         this.session.getLock().lock();
760         try {
761             this.appEventMask = this.appEventMask & ~op;
762             updateEventMask();
763         } finally {
764             this.session.getLock().unlock();
765         }
766     }
767 
768     @Override
769     public Timeout getSocketTimeout() {
770         return this.session.getSocketTimeout();
771     }
772 
773     @Override
774     public void setSocketTimeout(final Timeout timeout) {
775         this.socketTimeout = timeout;
776         if (this.sslEngine.getHandshakeStatus() == HandshakeStatus.FINISHED) {
777             this.session.setSocketTimeout(timeout);
778         }
779     }
780 
781     @Override
782     public void updateReadTime() {
783         this.session.updateReadTime();
784     }
785 
786     @Override
787     public void updateWriteTime() {
788         this.session.updateWriteTime();
789     }
790 
791     @Override
792     public long getLastReadTime() {
793         return this.session.getLastReadTime();
794     }
795 
796     @Override
797     public long getLastWriteTime() {
798         return this.session.getLastWriteTime();
799     }
800 
801     @Override
802     public long getLastEventTime() {
803         return this.session.getLastEventTime();
804     }
805 
806     private static void formatOps(final StringBuilder buffer, final int ops) {
807         if ((ops & SelectionKey.OP_READ) > 0) {
808             buffer.append('r');
809         }
810         if ((ops & SelectionKey.OP_WRITE) > 0) {
811             buffer.append('w');
812         }
813     }
814 
815     @Override
816     public String toString() {
817         this.session.getLock().lock();
818         try {
819             final StringBuilder buffer = new StringBuilder();
820             buffer.append(this.session);
821             buffer.append("[");
822             buffer.append(this.status);
823             buffer.append("][");
824             formatOps(buffer, this.appEventMask);
825             buffer.append("][");
826             buffer.append(this.sslEngine.getHandshakeStatus());
827             if (this.sslEngine.isInboundDone()) {
828                 buffer.append("][inbound done][");
829             }
830             if (this.sslEngine.isOutboundDone()) {
831                 buffer.append("][outbound done][");
832             }
833             if (this.endOfStream) {
834                 buffer.append("][EOF][");
835             }
836             buffer.append("][");
837             buffer.append(!this.inEncrypted.hasData() ? 0 : inEncrypted.acquire().position());
838             buffer.append("][");
839             buffer.append(!this.inPlain.hasData() ? 0 : inPlain.acquire().position());
840             buffer.append("][");
841             buffer.append(!this.outEncrypted.hasData() ? 0 : outEncrypted.acquire().position());
842             buffer.append("]");
843             return buffer.toString();
844         } finally {
845             this.session.getLock().unlock();
846         }
847     }
848 
849 }