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