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