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