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