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 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     }
167 
168     /**
169      * Creates new instance of {@code SSLIOSession} class.
170      *
171      * @param session I/O session to be decorated with the TLS/SSL capabilities.
172      * @param sslMode SSL mode (client or server)
173      * @param sslContext SSL context to use for this I/O session.
174      * @param handler optional SSL setup handler. May be {@code null}.
175      */
176     public SSLIOSession(
177             final IOSession session,
178             final SSLMode sslMode,
179             final SSLContext sslContext,
180             final SSLSetupHandler handler) {
181         this(session, sslMode, null, sslContext, handler);
182     }
183 
184     protected SSLSetupHandler getSSLSetupHandler() {
185         return this.handler;
186     }
187 
188     /**
189      * Returns {@code true} is the session has been fully initialized,
190      * {@code false} otherwise.
191      */
192     public boolean isInitialized() {
193         return this.initialized;
194     }
195 
196     /**
197      * Initializes the session in the given {@link SSLMode}. This method
198      * invokes the {@link SSLSetupHandler#initalize(SSLEngine)} callback
199      * if an instance of {@link SSLSetupHandler} was specified at
200      * the construction time.
201      *
202      * @deprecated (4.3) SSL mode must be set at construction time.
203      */
204     @Deprecated
205     public synchronized void initialize(final SSLMode sslMode) throws SSLException {
206         this.sslMode = sslMode;
207         initialize();
208     }
209 
210     /**
211      * Initializes the session. This method invokes the {@link
212      * SSLSetupHandler#initalize(SSLEngine)} callback if an instance of
213      * {@link SSLSetupHandler} was specified at the construction time.
214      *
215      * @throws SSLException in case of a SSL protocol exception.
216      * @throws IllegalStateException if the session has already been initialized.
217      */
218     public synchronized void initialize() throws SSLException {
219         Asserts.check(!this.initialized, "SSL I/O session already initialized");
220         if (this.status >= IOSession.CLOSING) {
221             return;
222         }
223         switch (this.sslMode) {
224         case CLIENT:
225             this.sslEngine.setUseClientMode(true);
226             break;
227         case SERVER:
228             this.sslEngine.setUseClientMode(false);
229             break;
230         }
231         if (this.handler != null) {
232             this.handler.initalize(this.sslEngine);
233         }
234         this.initialized = true;
235         this.sslEngine.beginHandshake();
236 
237         this.inEncrypted.release();
238         this.outEncrypted.release();
239         this.inPlain.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 buffer
298                final ByteBuffer outEncryptedBuf = this.outEncrypted.acquire();
299 
300                // Just wrap an empty buffer because there is no data to write.
301                result = doWrap(ByteBuffer.allocate(0), outEncryptedBuf);
302 
303                if (result.getStatus() != Status.OK) {
304                    handshaking = false;
305                }
306                break;
307             case NEED_UNWRAP:
308                 // Process incoming handshake data
309 
310                 // Acquire buffers
311                 final ByteBuffer inEncryptedBuf = this.inEncrypted.acquire();
312                 final ByteBuffer inPlainBuf = this.inPlain.acquire();
313 
314                 // Perform operations
315                 inEncryptedBuf.flip();
316                 result = doUnwrap(inEncryptedBuf, inPlainBuf);
317                 inEncryptedBuf.compact();
318 
319 
320                 try {
321                     if (!inEncryptedBuf.hasRemaining() && result.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP) {
322                         throw new SSLException("Input buffer is full");
323                     }
324                 } finally {
325                     // Release inEncrypted if empty
326                     if (inEncryptedBuf.position() == 0) {
327                         this.inEncrypted.release();
328                     }
329                 }
330 
331                 if (this.status >= IOSession.CLOSING) {
332                     this.inPlain.release();
333                 }
334                 if (result.getStatus() != Status.OK) {
335                     handshaking = false;
336                 }
337                 break;
338             case NEED_TASK:
339                 doRunTask();
340                 break;
341             case NOT_HANDSHAKING:
342                 handshaking = false;
343                 break;
344             case FINISHED:
345                 break;
346             }
347         }
348 
349         // The SSLEngine has just finished handshaking. This value is only generated by a call
350         // to SSLEngine.wrap()/unwrap() when that call finishes a handshake.
351         // It is never generated by SSLEngine.getHandshakeStatus().
352         if (result != null && result.getHandshakeStatus() == HandshakeStatus.FINISHED) {
353             if (this.handler != null) {
354                 this.handler.verify(this.session, this.sslEngine.getSession());
355             }
356         }
357     }
358 
359     private void updateEventMask() {
360         // Graceful session termination
361         if (this.status == CLOSING && !this.outEncrypted.hasData()) {
362             this.sslEngine.closeOutbound();
363         }
364         if (this.status == CLOSING && this.sslEngine.isOutboundDone()
365                 && (this.endOfStream || this.sslEngine.isInboundDone())) {
366             this.status = CLOSED;
367         }
368         // Abnormal session termination
369         if (this.status == ACTIVE && this.endOfStream
370                 && this.sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP) {
371             this.status = CLOSED;
372         }
373         if (this.status == CLOSED) {
374             this.session.close();
375             return;
376         }
377         // Need to toggle the event mask for this channel?
378         final int oldMask = this.session.getEventMask();
379         int newMask = oldMask;
380         switch (this.sslEngine.getHandshakeStatus()) {
381         case NEED_WRAP:
382             newMask = EventMask.READ_WRITE;
383             break;
384         case NEED_UNWRAP:
385             newMask = EventMask.READ;
386             break;
387         case NOT_HANDSHAKING:
388             newMask = this.appEventMask;
389             break;
390         case NEED_TASK:
391             break;
392         case FINISHED:
393             break;
394         }
395 
396         // Do we have encrypted data ready to be sent?
397         if (this.outEncrypted.hasData()) {
398             newMask = newMask | EventMask.WRITE;
399         }
400 
401         // Update the mask if necessary
402         if (oldMask != newMask) {
403             this.session.setEventMask(newMask);
404         }
405     }
406 
407     private int sendEncryptedData() throws IOException {
408         if (!this.outEncrypted.hasData()) {
409             // If the buffer isn't acquired or is empty, call write() with an empty buffer.
410             // This will ensure that tests performed by write() still take place without
411             // having to acquire and release an empty buffer (e.g. connection closed,
412             // interrupted thread, etc..)
413             return this.session.channel().write(EMPTY_BUFFER);
414         }
415 
416         // Acquire buffer
417         final ByteBuffer outEncryptedBuf = this.outEncrypted.acquire();
418 
419         // Perform operation
420         outEncryptedBuf.flip();
421         final int bytesWritten = this.session.channel().write(outEncryptedBuf);
422         outEncryptedBuf.compact();
423 
424         // Release if empty
425         if (outEncryptedBuf.position() == 0) {
426             this.outEncrypted.release();
427         }
428         return bytesWritten;
429     }
430 
431     private int receiveEncryptedData() throws IOException {
432         if (this.endOfStream) {
433             return -1;
434         }
435 
436         // Acquire buffer
437         final ByteBuffer inEncryptedBuf = this.inEncrypted.acquire();
438 
439         // Perform operation
440         final int ret = this.session.channel().read(inEncryptedBuf);
441 
442         // Release if empty
443         if (inEncryptedBuf.position() == 0) {
444             this.inEncrypted.release();
445         }
446         return ret;
447     }
448 
449     private boolean decryptData() throws SSLException {
450         boolean decrypted = false;
451         while (this.inEncrypted.hasData()) {
452             // Get buffers
453             final ByteBuffer inEncryptedBuf = this.inEncrypted.acquire();
454             final ByteBuffer inPlainBuf = this.inPlain.acquire();
455 
456             // Perform operations
457             inEncryptedBuf.flip();
458             final SSLEngineResult result = doUnwrap(inEncryptedBuf, inPlainBuf);
459             inEncryptedBuf.compact();
460 
461             try {
462                 if (!inEncryptedBuf.hasRemaining() && result.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP) {
463                     throw new SSLException("Unable to complete SSL handshake");
464                 }
465                 final Status status = result.getStatus();
466                 if (status == Status.OK) {
467                     decrypted = true;
468                 } else {
469                     if (status == Status.BUFFER_UNDERFLOW && this.endOfStream) {
470                         throw new SSLException("Unable to decrypt incoming data due to unexpected end of stream");
471                     }
472                     break;
473                 }
474                 if (result.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING) {
475                     break;
476                 }
477             } finally {
478                 // Release inEncrypted if empty
479                 if (this.inEncrypted.acquire().position() == 0) {
480                     this.inEncrypted.release();
481                 }
482             }
483         }
484         return decrypted;
485     }
486 
487     /**
488      * Reads encrypted data and returns whether the channel associated with
489      * this session has any decrypted inbound data available for reading.
490      *
491      * @throws IOException in case of an I/O error.
492      */
493     public synchronized boolean isAppInputReady() throws IOException {
494         do {
495             final int bytesRead = receiveEncryptedData();
496             if (bytesRead == -1) {
497                 this.endOfStream = true;
498             }
499             doHandshake();
500             final HandshakeStatus status = this.sslEngine.getHandshakeStatus();
501             if (status == HandshakeStatus.NOT_HANDSHAKING || status == HandshakeStatus.FINISHED) {
502                 decryptData();
503             }
504         } while (this.sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_TASK);
505         // Some decrypted data is available or at the end of stream
506         return (this.appEventMask & SelectionKey.OP_READ) > 0
507             && (this.inPlain.hasData()
508                     || (this.appBufferStatus != null && this.appBufferStatus.hasBufferedInput())
509                     || (this.endOfStream && this.status == ACTIVE));
510     }
511 
512     /**
513      * Returns whether the channel associated with this session is ready to
514      * accept outbound unecrypted data for writing.
515      *
516      * @throws IOException - not thrown currently
517      */
518     public synchronized boolean isAppOutputReady() throws IOException {
519         return (this.appEventMask & SelectionKey.OP_WRITE) > 0
520             && this.status == ACTIVE
521             && this.sslEngine.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING;
522     }
523 
524     /**
525      * Executes inbound SSL transport operations.
526      *
527      * @throws IOException - not thrown currently
528      */
529     public synchronized void inboundTransport() throws IOException {
530         updateEventMask();
531     }
532 
533     /**
534      * Sends encrypted data and executes outbound SSL transport operations.
535      *
536      * @throws IOException in case of an I/O error.
537      */
538     public synchronized void outboundTransport() throws IOException {
539         sendEncryptedData();
540         doHandshake();
541         updateEventMask();
542     }
543 
544     /**
545      * Returns whether the session will produce any more inbound data.
546      */
547     public synchronized boolean isInboundDone() {
548         return this.sslEngine.isInboundDone();
549     }
550 
551     /**
552      * Returns whether the session will accept any more outbound data.
553      */
554     public synchronized boolean isOutboundDone() {
555         return this.sslEngine.isOutboundDone();
556     }
557 
558     private synchronized int writePlain(final ByteBuffer src) throws IOException {
559         Args.notNull(src, "Byte buffer");
560         if (this.status != ACTIVE) {
561             throw new ClosedChannelException();
562         }
563         final ByteBuffer outEncryptedBuf = this.outEncrypted.acquire();
564         final SSLEngineResult result = doWrap(src, outEncryptedBuf);
565         if (result.getStatus() == Status.CLOSED) {
566            this.status = CLOSED;
567         }
568         return result.bytesConsumed();
569     }
570 
571     private synchronized int readPlain(final ByteBuffer dst) {
572         Args.notNull(dst, "Byte buffer");
573         if (this.inPlain.hasData()) {
574             // Acquire buffer
575             final ByteBuffer inPlainBuf = this.inPlain.acquire();
576 
577             // Perform opertaions
578             inPlainBuf.flip();
579             final int n = Math.min(inPlainBuf.remaining(), dst.remaining());
580             for (int i = 0; i < n; i++) {
581                 dst.put(inPlainBuf.get());
582             }
583             inPlainBuf.compact();
584 
585             // Release if empty
586             if (inPlainBuf.position() == 0) {
587                 this.inPlain.release();
588             }
589             return n;
590         } else {
591             if (this.endOfStream) {
592                 return -1;
593             } else {
594                 return 0;
595             }
596         }
597     }
598 
599     @Override
600     public synchronized void close() {
601         if (this.status >= CLOSING) {
602             return;
603         }
604         this.status = CLOSING;
605         if (this.session.getSocketTimeout() == 0) {
606             this.session.setSocketTimeout(1000);
607         }
608         this.sslEngine.closeOutbound();
609         try {
610             updateEventMask();
611         } catch (final CancelledKeyException ex) {
612             shutdown();
613         }
614     }
615 
616     @Override
617     public synchronized void shutdown() {
618         if (this.status == CLOSED) {
619             return;
620         }
621         this.status = CLOSED;
622         this.session.shutdown();
623 
624         this.inEncrypted.release();
625         this.outEncrypted.release();
626         this.inPlain.release();
627 
628     }
629 
630     @Override
631     public int getStatus() {
632         return this.status;
633     }
634 
635     @Override
636     public boolean isClosed() {
637         return this.status >= CLOSING || this.session.isClosed();
638     }
639 
640     @Override
641     public ByteChannel channel() {
642         return this.channel;
643     }
644 
645     @Override
646     public SocketAddress getLocalAddress() {
647         return this.session.getLocalAddress();
648     }
649 
650     @Override
651     public SocketAddress getRemoteAddress() {
652         return this.session.getRemoteAddress();
653     }
654 
655     @Override
656     public synchronized int getEventMask() {
657         return this.appEventMask;
658     }
659 
660     @Override
661     public synchronized void setEventMask(final int ops) {
662         this.appEventMask = ops;
663         updateEventMask();
664     }
665 
666     @Override
667     public synchronized void setEvent(final int op) {
668         this.appEventMask = this.appEventMask | op;
669         updateEventMask();
670     }
671 
672     @Override
673     public synchronized void clearEvent(final int op) {
674         this.appEventMask = this.appEventMask & ~op;
675         updateEventMask();
676     }
677 
678     @Override
679     public int getSocketTimeout() {
680         return this.session.getSocketTimeout();
681     }
682 
683     @Override
684     public void setSocketTimeout(final int timeout) {
685         this.session.setSocketTimeout(timeout);
686     }
687 
688     @Override
689     public synchronized boolean hasBufferedInput() {
690         return (this.appBufferStatus != null && this.appBufferStatus.hasBufferedInput())
691             || this.inEncrypted.hasData()
692             || this.inPlain.hasData();
693     }
694 
695     @Override
696     public synchronized boolean hasBufferedOutput() {
697         return (this.appBufferStatus != null && this.appBufferStatus.hasBufferedOutput())
698             || this.outEncrypted.hasData();
699     }
700 
701     @Override
702     public synchronized void setBufferStatus(final SessionBufferStatus status) {
703         this.appBufferStatus = status;
704     }
705 
706     @Override
707     public Object getAttribute(final String name) {
708         return this.session.getAttribute(name);
709     }
710 
711     @Override
712     public Object removeAttribute(final String name) {
713         return this.session.removeAttribute(name);
714     }
715 
716     @Override
717     public void setAttribute(final String name, final Object obj) {
718         this.session.setAttribute(name, obj);
719     }
720 
721     private static void formatOps(final StringBuilder buffer, final int ops) {
722         if ((ops & SelectionKey.OP_READ) > 0) {
723             buffer.append('r');
724         }
725         if ((ops & SelectionKey.OP_WRITE) > 0) {
726             buffer.append('w');
727         }
728     }
729 
730     @Override
731     public String toString() {
732         final StringBuilder buffer = new StringBuilder();
733         buffer.append(this.session);
734         buffer.append("[");
735         switch (this.status) {
736         case ACTIVE:
737             buffer.append("ACTIVE");
738             break;
739         case CLOSING:
740             buffer.append("CLOSING");
741             break;
742         case CLOSED:
743             buffer.append("CLOSED");
744             break;
745         }
746         buffer.append("][");
747         formatOps(buffer, this.appEventMask);
748         buffer.append("][");
749         buffer.append(this.sslEngine.getHandshakeStatus());
750         if (this.sslEngine.isInboundDone()) {
751             buffer.append("][inbound done][");
752         }
753         if (this.sslEngine.isOutboundDone()) {
754             buffer.append("][outbound done][");
755         }
756         if (this.endOfStream) {
757             buffer.append("][EOF][");
758         }
759         buffer.append("][");
760         buffer.append(!this.inEncrypted.hasData() ? 0 : inEncrypted.acquire().position());
761         buffer.append("][");
762         buffer.append(!this.inPlain.hasData() ? 0 : inPlain.acquire().position());
763         buffer.append("][");
764         buffer.append(!this.outEncrypted.hasData() ? 0 : outEncrypted.acquire().position());
765         buffer.append("]");
766         return buffer.toString();
767     }
768 
769     @Override
770     public Socket getSocket(){
771         if (this.session instanceof SocketAccessor){
772             return ((SocketAccessor) this.session).getSocket();
773         } else {
774             return null;
775         }
776     }
777 
778     private class InternalByteChannel implements ByteChannel {
779 
780         @Override
781         public int write(final ByteBuffer src) throws IOException {
782             return SSLIOSession.this.writePlain(src);
783         }
784 
785         @Override
786         public int read(final ByteBuffer dst) throws IOException {
787             return SSLIOSession.this.readPlain(dst);
788         }
789 
790         @Override
791         public void close() throws IOException {
792             SSLIOSession.this.close();
793         }
794 
795         @Override
796         public boolean isOpen() {
797             return !SSLIOSession.this.isClosed();
798         }
799 
800     }
801 
802 }