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