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 <= CLOSING && 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         if (this.endOfStream) {
397             newMask = newMask & ~EventMask.READ;
398         }
399 
400         // Do we have encrypted data ready to be sent?
401         if (this.outEncrypted.hasData()) {
402             newMask = newMask | EventMask.WRITE;
403         }
404 
405         // Update the mask if necessary
406         if (oldMask != newMask) {
407             this.session.setEventMask(newMask);
408         }
409     }
410 
411     private int sendEncryptedData() throws IOException {
412         if (!this.outEncrypted.hasData()) {
413             // If the buffer isn't acquired or is empty, call write() with an empty buffer.
414             // This will ensure that tests performed by write() still take place without
415             // having to acquire and release an empty buffer (e.g. connection closed,
416             // interrupted thread, etc..)
417             return this.session.channel().write(EMPTY_BUFFER);
418         }
419 
420         // Acquire buffer
421         final ByteBuffer outEncryptedBuf = this.outEncrypted.acquire();
422 
423         // Perform operation
424         outEncryptedBuf.flip();
425         final int bytesWritten = this.session.channel().write(outEncryptedBuf);
426         outEncryptedBuf.compact();
427 
428         // Release if empty
429         if (outEncryptedBuf.position() == 0) {
430             this.outEncrypted.release();
431         }
432         return bytesWritten;
433     }
434 
435     private int receiveEncryptedData() throws IOException {
436         if (this.endOfStream) {
437             return -1;
438         }
439 
440         // Acquire buffer
441         final ByteBuffer inEncryptedBuf = this.inEncrypted.acquire();
442 
443         // Perform operation
444         final int ret = this.session.channel().read(inEncryptedBuf);
445 
446         // Release if empty
447         if (inEncryptedBuf.position() == 0) {
448             this.inEncrypted.release();
449         }
450         return ret;
451     }
452 
453     private boolean decryptData() throws SSLException {
454         boolean decrypted = false;
455         while (this.inEncrypted.hasData()) {
456             // Get buffers
457             final ByteBuffer inEncryptedBuf = this.inEncrypted.acquire();
458             final ByteBuffer inPlainBuf = this.inPlain.acquire();
459 
460             // Perform operations
461             inEncryptedBuf.flip();
462             final SSLEngineResult result = doUnwrap(inEncryptedBuf, inPlainBuf);
463             inEncryptedBuf.compact();
464 
465             try {
466                 if (!inEncryptedBuf.hasRemaining() && result.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP) {
467                     throw new SSLException("Unable to complete SSL handshake");
468                 }
469                 final Status status = result.getStatus();
470                 if (status == Status.OK) {
471                     decrypted = true;
472                 } else {
473                     if (status == Status.BUFFER_UNDERFLOW && this.endOfStream) {
474                         throw new SSLException("Unable to decrypt incoming data due to unexpected end of stream");
475                     }
476                     break;
477                 }
478                 if (result.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING) {
479                     break;
480                 }
481             } finally {
482                 // Release inEncrypted if empty
483                 if (this.inEncrypted.acquire().position() == 0) {
484                     this.inEncrypted.release();
485                 }
486             }
487         }
488         return decrypted;
489     }
490 
491     /**
492      * Reads encrypted data and returns whether the channel associated with
493      * this session has any decrypted inbound data available for reading.
494      *
495      * @throws IOException in case of an I/O error.
496      */
497     public synchronized boolean isAppInputReady() throws IOException {
498         do {
499             final int bytesRead = receiveEncryptedData();
500             if (bytesRead == -1) {
501                 this.endOfStream = true;
502             }
503             doHandshake();
504             final HandshakeStatus status = this.sslEngine.getHandshakeStatus();
505             if (status == HandshakeStatus.NOT_HANDSHAKING || status == HandshakeStatus.FINISHED) {
506                 decryptData();
507             }
508         } while (this.sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_TASK);
509         // Some decrypted data is available or at the end of stream
510         return (this.appEventMask & SelectionKey.OP_READ) > 0
511             && (this.inPlain.hasData()
512                     || (this.appBufferStatus != null && this.appBufferStatus.hasBufferedInput())
513                     || (this.endOfStream && this.status == ACTIVE));
514     }
515 
516     /**
517      * Returns whether the channel associated with this session is ready to
518      * accept outbound unecrypted data for writing.
519      *
520      * @throws IOException - not thrown currently
521      */
522     public synchronized boolean isAppOutputReady() throws IOException {
523         return (this.appEventMask & SelectionKey.OP_WRITE) > 0
524             && this.status == ACTIVE
525             && this.sslEngine.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING;
526     }
527 
528     /**
529      * Executes inbound SSL transport operations.
530      *
531      * @throws IOException - not thrown currently
532      */
533     public synchronized void inboundTransport() throws IOException {
534         updateEventMask();
535     }
536 
537     /**
538      * Sends encrypted data and executes outbound SSL transport operations.
539      *
540      * @throws IOException in case of an I/O error.
541      */
542     public synchronized void outboundTransport() throws IOException {
543         sendEncryptedData();
544         doHandshake();
545         updateEventMask();
546     }
547 
548     /**
549      * Returns whether the session will produce any more inbound data.
550      */
551     public synchronized boolean isInboundDone() {
552         return this.sslEngine.isInboundDone();
553     }
554 
555     /**
556      * Returns whether the session will accept any more outbound data.
557      */
558     public synchronized boolean isOutboundDone() {
559         return this.sslEngine.isOutboundDone();
560     }
561 
562     private synchronized int writePlain(final ByteBuffer src) throws IOException {
563         Args.notNull(src, "Byte buffer");
564         if (this.status != ACTIVE) {
565             throw new ClosedChannelException();
566         }
567         final ByteBuffer outEncryptedBuf = this.outEncrypted.acquire();
568         final SSLEngineResult result = doWrap(src, outEncryptedBuf);
569         if (result.getStatus() == Status.CLOSED) {
570            this.status = CLOSED;
571         }
572         return result.bytesConsumed();
573     }
574 
575     private synchronized int readPlain(final ByteBuffer dst) {
576         Args.notNull(dst, "Byte buffer");
577         if (this.inPlain.hasData()) {
578             // Acquire buffer
579             final ByteBuffer inPlainBuf = this.inPlain.acquire();
580 
581             // Perform opertaions
582             inPlainBuf.flip();
583             final int n = Math.min(inPlainBuf.remaining(), dst.remaining());
584             for (int i = 0; i < n; i++) {
585                 dst.put(inPlainBuf.get());
586             }
587             inPlainBuf.compact();
588 
589             // Release if empty
590             if (inPlainBuf.position() == 0) {
591                 this.inPlain.release();
592             }
593             return n;
594         } else {
595             if (this.endOfStream) {
596                 return -1;
597             } else {
598                 return 0;
599             }
600         }
601     }
602 
603     @Override
604     public synchronized void close() {
605         if (this.status >= CLOSING) {
606             return;
607         }
608         this.status = CLOSING;
609         if (this.session.getSocketTimeout() == 0) {
610             this.session.setSocketTimeout(1000);
611         }
612         this.sslEngine.closeOutbound();
613         try {
614             updateEventMask();
615         } catch (final CancelledKeyException ex) {
616             shutdown();
617         }
618     }
619 
620     @Override
621     public synchronized void shutdown() {
622         if (this.status == CLOSED) {
623             return;
624         }
625         this.status = CLOSED;
626         this.session.shutdown();
627 
628         this.inEncrypted.release();
629         this.outEncrypted.release();
630         this.inPlain.release();
631 
632     }
633 
634     @Override
635     public int getStatus() {
636         return this.status;
637     }
638 
639     @Override
640     public boolean isClosed() {
641         return this.status >= CLOSING || this.session.isClosed();
642     }
643 
644     @Override
645     public ByteChannel channel() {
646         return this.channel;
647     }
648 
649     @Override
650     public SocketAddress getLocalAddress() {
651         return this.session.getLocalAddress();
652     }
653 
654     @Override
655     public SocketAddress getRemoteAddress() {
656         return this.session.getRemoteAddress();
657     }
658 
659     @Override
660     public synchronized int getEventMask() {
661         return this.appEventMask;
662     }
663 
664     @Override
665     public synchronized void setEventMask(final int ops) {
666         this.appEventMask = ops;
667         updateEventMask();
668     }
669 
670     @Override
671     public synchronized void setEvent(final int op) {
672         this.appEventMask = this.appEventMask | op;
673         updateEventMask();
674     }
675 
676     @Override
677     public synchronized void clearEvent(final int op) {
678         this.appEventMask = this.appEventMask & ~op;
679         updateEventMask();
680     }
681 
682     @Override
683     public int getSocketTimeout() {
684         return this.session.getSocketTimeout();
685     }
686 
687     @Override
688     public void setSocketTimeout(final int timeout) {
689         this.session.setSocketTimeout(timeout);
690     }
691 
692     @Override
693     public synchronized boolean hasBufferedInput() {
694         return (this.appBufferStatus != null && this.appBufferStatus.hasBufferedInput())
695             || this.inEncrypted.hasData()
696             || this.inPlain.hasData();
697     }
698 
699     @Override
700     public synchronized boolean hasBufferedOutput() {
701         return (this.appBufferStatus != null && this.appBufferStatus.hasBufferedOutput())
702             || this.outEncrypted.hasData();
703     }
704 
705     @Override
706     public synchronized void setBufferStatus(final SessionBufferStatus status) {
707         this.appBufferStatus = status;
708     }
709 
710     @Override
711     public Object getAttribute(final String name) {
712         return this.session.getAttribute(name);
713     }
714 
715     @Override
716     public Object removeAttribute(final String name) {
717         return this.session.removeAttribute(name);
718     }
719 
720     @Override
721     public void setAttribute(final String name, final Object obj) {
722         this.session.setAttribute(name, obj);
723     }
724 
725     private static void formatOps(final StringBuilder buffer, final int ops) {
726         if ((ops & SelectionKey.OP_READ) > 0) {
727             buffer.append('r');
728         }
729         if ((ops & SelectionKey.OP_WRITE) > 0) {
730             buffer.append('w');
731         }
732     }
733 
734     @Override
735     public String toString() {
736         final StringBuilder buffer = new StringBuilder();
737         buffer.append(this.session);
738         buffer.append("[");
739         switch (this.status) {
740         case ACTIVE:
741             buffer.append("ACTIVE");
742             break;
743         case CLOSING:
744             buffer.append("CLOSING");
745             break;
746         case CLOSED:
747             buffer.append("CLOSED");
748             break;
749         }
750         buffer.append("][");
751         formatOps(buffer, this.appEventMask);
752         buffer.append("][");
753         buffer.append(this.sslEngine.getHandshakeStatus());
754         if (this.sslEngine.isInboundDone()) {
755             buffer.append("][inbound done][");
756         }
757         if (this.sslEngine.isOutboundDone()) {
758             buffer.append("][outbound done][");
759         }
760         if (this.endOfStream) {
761             buffer.append("][EOF][");
762         }
763         buffer.append("][");
764         buffer.append(!this.inEncrypted.hasData() ? 0 : inEncrypted.acquire().position());
765         buffer.append("][");
766         buffer.append(!this.inPlain.hasData() ? 0 : inPlain.acquire().position());
767         buffer.append("][");
768         buffer.append(!this.outEncrypted.hasData() ? 0 : outEncrypted.acquire().position());
769         buffer.append("]");
770         return buffer.toString();
771     }
772 
773     @Override
774     public Socket getSocket(){
775         if (this.session instanceof SocketAccessor){
776             return ((SocketAccessor) this.session).getSocket();
777         } else {
778             return null;
779         }
780     }
781 
782     private class InternalByteChannel implements ByteChannel {
783 
784         @Override
785         public int write(final ByteBuffer src) throws IOException {
786             return SSLIOSession.this.writePlain(src);
787         }
788 
789         @Override
790         public int read(final ByteBuffer dst) throws IOException {
791             return SSLIOSession.this.readPlain(dst);
792         }
793 
794         @Override
795         public void close() throws IOException {
796             SSLIOSession.this.close();
797         }
798 
799         @Override
800         public boolean isOpen() {
801             return !SSLIOSession.this.isClosed();
802         }
803 
804     }
805 
806 }