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