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