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