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