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