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