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