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