| /* |
| * ==================================================================== |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * ==================================================================== |
| * |
| * This software consists of voluntary contributions made by many |
| * individuals on behalf of the Apache Software Foundation. For more |
| * information on the Apache Software Foundation, please see |
| * <http://www.apache.org/>. |
| * |
| */ |
| |
| package org.apache.http.nio.reactor.ssl; |
| |
| import java.io.IOException; |
| import java.net.Socket; |
| import java.net.SocketAddress; |
| import java.nio.ByteBuffer; |
| import java.nio.channels.ByteChannel; |
| import java.nio.channels.CancelledKeyException; |
| import java.nio.channels.ClosedChannelException; |
| import java.nio.channels.SelectionKey; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| import javax.net.ssl.SSLContext; |
| import javax.net.ssl.SSLEngine; |
| import javax.net.ssl.SSLEngineResult; |
| import javax.net.ssl.SSLEngineResult.HandshakeStatus; |
| import javax.net.ssl.SSLEngineResult.Status; |
| import javax.net.ssl.SSLException; |
| import javax.net.ssl.SSLSession; |
| |
| import org.apache.http.HttpHost; |
| import org.apache.http.annotation.Contract; |
| import org.apache.http.annotation.ThreadingBehavior; |
| import org.apache.http.nio.reactor.EventMask; |
| import org.apache.http.nio.reactor.IOSession; |
| import org.apache.http.nio.reactor.SessionBufferStatus; |
| import org.apache.http.nio.reactor.SocketAccessor; |
| import org.apache.http.util.Args; |
| import org.apache.http.util.Asserts; |
| |
| /** |
| * {@code SSLIOSession} is a decorator class intended to transparently extend |
| * an {@link IOSession} with transport layer security capabilities based on |
| * the SSL/TLS protocol. |
| * <p> |
| * The resultant instance of {@code SSLIOSession} must be added to the original |
| * I/O session as an attribute with the {@link #SESSION_KEY} key. |
| * <pre> |
| * SSLContext sslContext = SSLContext.getInstance("SSL"); |
| * sslContext.init(null, null, null); |
| * SSLIOSession sslsession = new SSLIOSession( |
| * ioSession, SSLMode.CLIENT, sslContext, null); |
| * ioSession.setAttribute(SSLIOSession.SESSION_KEY, sslsession); |
| * </pre> |
| * |
| * @since 4.2 |
| */ |
| @Contract(threading = ThreadingBehavior.SAFE_CONDITIONAL) |
| public class SSLIOSession implements IOSession, SessionBufferStatus, SocketAccessor { |
| |
| /** |
| * Name of the context attribute key, which can be used to obtain the |
| * SSL session. |
| */ |
| public static final String SESSION_KEY = "http.session.ssl"; |
| |
| private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0); |
| |
| private final IOSession session; |
| private final SSLEngine sslEngine; |
| private final SSLBuffer inEncrypted; |
| private final SSLBuffer outEncrypted; |
| private final SSLBuffer inPlain; |
| private final InternalByteChannel channel; |
| private final SSLSetupHandler handler; |
| private final AtomicInteger outboundClosedCount; |
| |
| private int appEventMask; |
| private SessionBufferStatus appBufferStatus; |
| |
| private boolean endOfStream; |
| private volatile SSLMode sslMode; |
| private volatile int status; |
| private volatile boolean initialized; |
| private volatile boolean terminated; |
| |
| /** |
| * Creates new instance of {@code SSLIOSession} class. The instances created uses a |
| * {@link PermanentSSLBufferManagementStrategy} to manage its buffers. |
| * |
| * @param session I/O session to be decorated with the TLS/SSL capabilities. |
| * @param sslMode SSL mode (client or server) |
| * @param host original host (applicable in client mode only) |
| * @param sslContext SSL context to use for this I/O session. |
| * @param handler optional SSL setup handler. May be {@code null}. |
| * |
| * @since 4.4 |
| */ |
| public SSLIOSession( |
| final IOSession session, |
| final SSLMode sslMode, |
| final HttpHost host, |
| final SSLContext sslContext, |
| final SSLSetupHandler handler) { |
| this(session, sslMode, host, sslContext, handler, new PermanentSSLBufferManagementStrategy()); |
| } |
| |
| /** |
| * Creates new instance of {@code SSLIOSession} class. |
| * |
| * @param session I/O session to be decorated with the TLS/SSL capabilities. |
| * @param sslMode SSL mode (client or server) |
| * @param host original host (applicable in client mode only) |
| * @param sslContext SSL context to use for this I/O session. |
| * @param handler optional SSL setup handler. May be {@code null}. |
| * @param bufferManagementStrategy buffer management strategy |
| */ |
| public SSLIOSession( |
| final IOSession session, |
| final SSLMode sslMode, |
| final HttpHost host, |
| final SSLContext sslContext, |
| final SSLSetupHandler handler, |
| final SSLBufferManagementStrategy bufferManagementStrategy) { |
| super(); |
| Args.notNull(session, "IO session"); |
| Args.notNull(sslContext, "SSL context"); |
| Args.notNull(bufferManagementStrategy, "Buffer management strategy"); |
| this.session = session; |
| this.sslMode = sslMode; |
| this.appEventMask = session.getEventMask(); |
| this.channel = new InternalByteChannel(); |
| this.handler = handler; |
| |
| // Override the status buffer interface |
| this.session.setBufferStatus(this); |
| |
| if (this.sslMode == SSLMode.CLIENT && host != null) { |
| this.sslEngine = sslContext.createSSLEngine(host.getHostName(), host.getPort()); |
| } else { |
| this.sslEngine = sslContext.createSSLEngine(); |
| } |
| |
| // Allocate buffers for network (encrypted) data |
| final int netBuffersize = this.sslEngine.getSession().getPacketBufferSize(); |
| this.inEncrypted = bufferManagementStrategy.constructBuffer(netBuffersize); |
| this.outEncrypted = bufferManagementStrategy.constructBuffer(netBuffersize); |
| |
| // Allocate buffers for application (unencrypted) data |
| final int appBuffersize = this.sslEngine.getSession().getApplicationBufferSize(); |
| this.inPlain = bufferManagementStrategy.constructBuffer(appBuffersize); |
| this.outboundClosedCount = new AtomicInteger(0); |
| } |
| |
| /** |
| * Creates new instance of {@code SSLIOSession} class. |
| * |
| * @param session I/O session to be decorated with the TLS/SSL capabilities. |
| * @param sslMode SSL mode (client or server) |
| * @param sslContext SSL context to use for this I/O session. |
| * @param handler optional SSL setup handler. May be {@code null}. |
| */ |
| public SSLIOSession( |
| final IOSession session, |
| final SSLMode sslMode, |
| final SSLContext sslContext, |
| final SSLSetupHandler handler) { |
| this(session, sslMode, null, sslContext, handler); |
| } |
| |
| protected SSLSetupHandler getSSLSetupHandler() { |
| return this.handler; |
| } |
| |
| /** |
| * Returns {@code true} is the session has been fully initialized, |
| * {@code false} otherwise. |
| */ |
| public boolean isInitialized() { |
| return this.initialized; |
| } |
| |
| /** |
| * Initializes the session in the given {@link SSLMode}. This method |
| * invokes the {@link SSLSetupHandler#initalize(SSLEngine)} callback |
| * if an instance of {@link SSLSetupHandler} was specified at |
| * the construction time. |
| * |
| * @deprecated (4.3) SSL mode must be set at construction time. |
| */ |
| @Deprecated |
| public synchronized void initialize(final SSLMode sslMode) throws SSLException { |
| this.sslMode = sslMode; |
| initialize(); |
| } |
| |
| /** |
| * Initializes the session. This method invokes the {@link |
| * SSLSetupHandler#initalize(SSLEngine)} callback if an instance of |
| * {@link SSLSetupHandler} was specified at the construction time. |
| * |
| * @throws SSLException in case of a SSL protocol exception. |
| * @throws IllegalStateException if the session has already been initialized. |
| */ |
| public synchronized void initialize() throws SSLException { |
| Asserts.check(!this.initialized, "SSL I/O session already initialized"); |
| if (this.status >= IOSession.CLOSING) { |
| return; |
| } |
| switch (this.sslMode) { |
| case CLIENT: |
| this.sslEngine.setUseClientMode(true); |
| break; |
| case SERVER: |
| this.sslEngine.setUseClientMode(false); |
| break; |
| } |
| if (this.handler != null) { |
| try { |
| this.handler.initalize(this.sslEngine); |
| } catch (final RuntimeException ex) { |
| throw convert(ex); |
| } |
| } |
| this.initialized = true; |
| this.sslEngine.beginHandshake(); |
| |
| this.inEncrypted.release(); |
| this.outEncrypted.release(); |
| this.inPlain.release(); |
| |
| doHandshake(); |
| } |
| |
| public synchronized SSLSession getSSLSession() { |
| return this.sslEngine.getSession(); |
| } |
| |
| // A works-around for exception handling craziness in Sun/Oracle's SSLEngine |
| // implementation. |
| // |
| // sun.security.pkcs11.wrapper.PKCS11Exception is re-thrown as |
| // plain RuntimeException in sun.security.ssl.Handshaker#checkThrown |
| private SSLException convert(final RuntimeException ex) { |
| Throwable cause = ex.getCause(); |
| if (cause == null) { |
| cause = ex; |
| } |
| return new SSLException(cause); |
| } |
| |
| private SSLEngineResult doWrap(final ByteBuffer src, final ByteBuffer dst) throws SSLException { |
| try { |
| return this.sslEngine.wrap(src, dst); |
| } catch (final RuntimeException ex) { |
| throw convert(ex); |
| } |
| } |
| |
| private SSLEngineResult doUnwrap(final ByteBuffer src, final ByteBuffer dst) throws SSLException { |
| try { |
| return this.sslEngine.unwrap(src, dst); |
| } catch (final RuntimeException ex) { |
| throw convert(ex); |
| } |
| } |
| |
| private void doRunTask() throws SSLException { |
| try { |
| final Runnable r = this.sslEngine.getDelegatedTask(); |
| if (r != null) { |
| r.run(); |
| } |
| } catch (final RuntimeException ex) { |
| throw convert(ex); |
| } |
| } |
| |
| private void doHandshake() throws SSLException { |
| boolean handshaking = true; |
| |
| SSLEngineResult result = null; |
| while (handshaking) { |
| HandshakeStatus handshakeStatus = this.sslEngine.getHandshakeStatus(); |
| |
| // Work-around for what appears to be a bug in Conscrypt SSLEngine that does not |
| // transition into the handshaking state upon #closeOutbound() call but still |
| // has some handshake data stuck in its internal buffer. |
| if (handshakeStatus == HandshakeStatus.NOT_HANDSHAKING && outboundClosedCount.get() > 0) { |
| handshakeStatus = HandshakeStatus.NEED_WRAP; |
| } |
| switch (handshakeStatus) { |
| case NEED_WRAP: |
| // Generate outgoing handshake data |
| |
| // Acquire buffer |
| final ByteBuffer outEncryptedBuf = this.outEncrypted.acquire(); |
| |
| // Just wrap an empty buffer because there is no data to write. |
| result = doWrap(ByteBuffer.allocate(0), outEncryptedBuf); |
| |
| if (result.getStatus() != Status.OK || result.getHandshakeStatus() == HandshakeStatus.NEED_WRAP) { |
| handshaking = false; |
| } |
| break; |
| case NEED_UNWRAP: |
| // Process incoming handshake data |
| |
| // Acquire buffers |
| final ByteBuffer inEncryptedBuf = this.inEncrypted.acquire(); |
| final ByteBuffer inPlainBuf = this.inPlain.acquire(); |
| |
| // Perform operations |
| inEncryptedBuf.flip(); |
| try { |
| result = doUnwrap(inEncryptedBuf, inPlainBuf); |
| } finally { |
| inEncryptedBuf.compact(); |
| } |
| |
| try { |
| if (!inEncryptedBuf.hasRemaining() && result.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP) { |
| throw new SSLException("Input buffer is full"); |
| } |
| } finally { |
| // Release inEncrypted if empty |
| if (inEncryptedBuf.position() == 0) { |
| this.inEncrypted.release(); |
| } |
| } |
| |
| if (this.status >= IOSession.CLOSING) { |
| this.inPlain.release(); |
| } |
| if (result.getStatus() != Status.OK) { |
| handshaking = false; |
| } |
| break; |
| case NEED_TASK: |
| doRunTask(); |
| break; |
| case NOT_HANDSHAKING: |
| handshaking = false; |
| break; |
| case FINISHED: |
| break; |
| } |
| } |
| |
| // The SSLEngine has just finished handshaking. This value is only generated by a call |
| // to SSLEngine.wrap()/unwrap() when that call finishes a handshake. |
| // It is never generated by SSLEngine.getHandshakeStatus(). |
| if (result != null && result.getHandshakeStatus() == HandshakeStatus.FINISHED) { |
| if (this.handler != null) { |
| try { |
| this.handler.verify(this.session, this.sslEngine.getSession()); |
| } catch (final RuntimeException ex) { |
| // Some libraries can throw a plan RuntimeException from #verify method |
| // in case of a hostname verification failure in violation of the API contract |
| throw convert(ex); |
| } |
| } |
| } |
| } |
| |
| private void updateEventMask() { |
| // Graceful session termination |
| if (this.status == ACTIVE |
| && (this.endOfStream || this.sslEngine.isInboundDone())) { |
| this.status = CLOSING; |
| } |
| if (this.status == CLOSING && !this.outEncrypted.hasData()) { |
| this.sslEngine.closeOutbound(); |
| this.outboundClosedCount.incrementAndGet(); |
| } |
| if (this.status == CLOSING && this.sslEngine.isOutboundDone() |
| && (this.endOfStream || this.sslEngine.isInboundDone()) |
| && (this.terminated || (!this.inPlain.hasData() && (this.appBufferStatus == null || !this.appBufferStatus.hasBufferedInput())))) { |
| this.status = CLOSED; |
| } |
| // Abnormal session termination |
| if (this.status <= CLOSING && this.endOfStream |
| && this.sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP) { |
| this.status = CLOSED; |
| } |
| if (this.status == CLOSED) { |
| this.session.close(); |
| return; |
| } |
| // Need to toggle the event mask for this channel? |
| final int oldMask = this.session.getEventMask(); |
| int newMask = oldMask; |
| switch (this.sslEngine.getHandshakeStatus()) { |
| case NEED_WRAP: |
| newMask = EventMask.READ_WRITE; |
| break; |
| case NEED_UNWRAP: |
| newMask = EventMask.READ; |
| break; |
| case NOT_HANDSHAKING: |
| if (this.status >= CLOSING && isOutboundDone() && !isInboundDone()) { |
| // NEED_UNWRAP behavior |
| newMask = EventMask.READ; |
| } else if (this.status >= CLOSING && !isOutboundDone() && isInboundDone()) { |
| // NEED_WRAP behavior |
| newMask = EventMask.READ_WRITE; |
| } else { |
| newMask = this.appEventMask; |
| } |
| break; |
| case NEED_TASK: |
| break; |
| case FINISHED: |
| break; |
| } |
| |
| if (this.endOfStream && |
| !this.inPlain.hasData() && |
| (this.appBufferStatus == null || !this.appBufferStatus.hasBufferedInput())) { |
| newMask = newMask & ~EventMask.READ; |
| } |
| |
| // Do we have encrypted data ready to be sent? |
| if (this.outEncrypted.hasData()) { |
| newMask = newMask | EventMask.WRITE; |
| } else if (this.sslEngine.isOutboundDone()) { |
| newMask = newMask & ~EventMask.WRITE; |
| } |
| |
| // Update the mask if necessary |
| if (oldMask != newMask) { |
| this.session.setEventMask(newMask); |
| } |
| } |
| |
| private int sendEncryptedData() throws IOException { |
| if (!this.outEncrypted.hasData()) { |
| // If the buffer isn't acquired or is empty, call write() with an empty buffer. |
| // This will ensure that tests performed by write() still take place without |
| // having to acquire and release an empty buffer (e.g. connection closed, |
| // interrupted thread, etc..) |
| return this.session.channel().write(EMPTY_BUFFER); |
| } |
| |
| // Acquire buffer |
| final ByteBuffer outEncryptedBuf = this.outEncrypted.acquire(); |
| |
| final int bytesWritten; |
| // Perform operation |
| outEncryptedBuf.flip(); |
| try { |
| bytesWritten = this.session.channel().write(outEncryptedBuf); |
| } finally { |
| outEncryptedBuf.compact(); |
| } |
| |
| // Release if empty |
| if (outEncryptedBuf.position() == 0) { |
| this.outEncrypted.release(); |
| } |
| return bytesWritten; |
| } |
| |
| private int receiveEncryptedData() throws IOException { |
| if (this.endOfStream) { |
| return -1; |
| } |
| |
| // Acquire buffer |
| final ByteBuffer inEncryptedBuf = this.inEncrypted.acquire(); |
| |
| // Perform operation |
| final int bytesRead = this.session.channel().read(inEncryptedBuf); |
| |
| // Release if empty |
| if (inEncryptedBuf.position() == 0) { |
| this.inEncrypted.release(); |
| } |
| if (bytesRead == -1) { |
| this.endOfStream = true; |
| } |
| return bytesRead; |
| } |
| |
| private boolean decryptData() throws SSLException { |
| boolean decrypted = false; |
| while (this.inEncrypted.hasData()) { |
| // Get buffers |
| final ByteBuffer inEncryptedBuf = this.inEncrypted.acquire(); |
| final ByteBuffer inPlainBuf = this.inPlain.acquire(); |
| |
| final SSLEngineResult result; |
| // Perform operations |
| inEncryptedBuf.flip(); |
| try { |
| result = doUnwrap(inEncryptedBuf, inPlainBuf); |
| } finally { |
| inEncryptedBuf.compact(); |
| } |
| |
| try { |
| if (!inEncryptedBuf.hasRemaining() && result.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP) { |
| throw new SSLException("Unable to complete SSL handshake"); |
| } |
| final Status status = result.getStatus(); |
| if (status == Status.OK) { |
| decrypted = true; |
| } else { |
| if (status == Status.BUFFER_UNDERFLOW && this.endOfStream) { |
| throw new SSLException("Unable to decrypt incoming data due to unexpected end of stream"); |
| } |
| break; |
| } |
| } finally { |
| // Release inEncrypted if empty |
| if (this.inEncrypted.acquire().position() == 0) { |
| this.inEncrypted.release(); |
| } |
| } |
| } |
| if (this.sslEngine.isInboundDone()) { |
| this.endOfStream = true; |
| } |
| return decrypted; |
| } |
| |
| /** |
| * Reads encrypted data and returns whether the channel associated with |
| * this session has any decrypted inbound data available for reading. |
| * |
| * @throws IOException in case of an I/O error. |
| */ |
| public synchronized boolean isAppInputReady() throws IOException { |
| do { |
| receiveEncryptedData(); |
| doHandshake(); |
| final HandshakeStatus status = this.sslEngine.getHandshakeStatus(); |
| if (status == HandshakeStatus.NOT_HANDSHAKING || status == HandshakeStatus.FINISHED) { |
| decryptData(); |
| } |
| if (this.terminated) { |
| // Discard any remaining input data |
| final ByteBuffer inPlainBuf = this.inPlain.acquire(); |
| inPlainBuf.clear(); |
| if (inPlainBuf.position() == 0) { |
| this.inPlain.release(); |
| } |
| } |
| } while (this.sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_TASK); |
| // Some decrypted data is available or at the end of stream |
| return (this.appEventMask & SelectionKey.OP_READ) > 0 |
| && (this.inPlain.hasData() |
| || (this.appBufferStatus != null && this.appBufferStatus.hasBufferedInput()) |
| || (this.endOfStream && this.status == ACTIVE)); |
| } |
| |
| /** |
| * Returns whether the channel associated with this session is ready to |
| * accept outbound unecrypted data for writing. |
| * |
| * @throws IOException - not thrown currently |
| */ |
| public synchronized boolean isAppOutputReady() throws IOException { |
| return (this.appEventMask & SelectionKey.OP_WRITE) > 0 |
| && this.status == ACTIVE |
| && this.sslEngine.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING; |
| } |
| |
| /** |
| * Executes inbound SSL transport operations. |
| * |
| * @throws IOException - not thrown currently |
| */ |
| public synchronized void inboundTransport() throws IOException { |
| updateEventMask(); |
| } |
| |
| /** |
| * Sends encrypted data and executes outbound SSL transport operations. |
| * |
| * @throws IOException in case of an I/O error. |
| */ |
| public synchronized void outboundTransport() throws IOException { |
| sendEncryptedData(); |
| doHandshake(); |
| updateEventMask(); |
| } |
| |
| /** |
| * Returns whether the session will produce any more inbound data. |
| */ |
| public synchronized boolean isInboundDone() { |
| return this.sslEngine.isInboundDone(); |
| } |
| |
| /** |
| * Returns whether the session will accept any more outbound data. |
| */ |
| public synchronized boolean isOutboundDone() { |
| return this.sslEngine.isOutboundDone(); |
| } |
| |
| private synchronized int writePlain(final ByteBuffer src) throws IOException { |
| Args.notNull(src, "Byte buffer"); |
| if (this.status != ACTIVE) { |
| throw new ClosedChannelException(); |
| } |
| final ByteBuffer outEncryptedBuf = this.outEncrypted.acquire(); |
| final SSLEngineResult result = doWrap(src, outEncryptedBuf); |
| if (result.getStatus() == Status.CLOSED) { |
| this.status = CLOSED; |
| } |
| return result.bytesConsumed(); |
| } |
| |
| private synchronized int readPlain(final ByteBuffer dst) { |
| Args.notNull(dst, "Byte buffer"); |
| if (this.inPlain.hasData()) { |
| // Acquire buffer |
| final ByteBuffer inPlainBuf = this.inPlain.acquire(); |
| |
| // Perform opertaions |
| inPlainBuf.flip(); |
| final int n = Math.min(inPlainBuf.remaining(), dst.remaining()); |
| for (int i = 0; i < n; i++) { |
| dst.put(inPlainBuf.get()); |
| } |
| inPlainBuf.compact(); |
| |
| // Release if empty |
| if (inPlainBuf.position() == 0) { |
| this.inPlain.release(); |
| } |
| return n; |
| } |
| return this.endOfStream ? -1 : 0; |
| } |
| |
| @Override |
| public synchronized void close() { |
| this.terminated = true; |
| if (this.status >= CLOSING) { |
| return; |
| } |
| this.status = CLOSING; |
| if (this.session.getSocketTimeout() == 0) { |
| this.session.setSocketTimeout(1000); |
| } |
| |
| try { |
| updateEventMask(); |
| } catch (final CancelledKeyException ex) { |
| shutdown(); |
| } |
| } |
| |
| @Override |
| public synchronized void shutdown() { |
| if (this.status == CLOSED) { |
| return; |
| } |
| this.status = CLOSED; |
| this.session.shutdown(); |
| |
| this.inEncrypted.release(); |
| this.outEncrypted.release(); |
| this.inPlain.release(); |
| |
| } |
| |
| @Override |
| public int getStatus() { |
| return this.status; |
| } |
| |
| @Override |
| public boolean isClosed() { |
| return this.status >= CLOSING || this.session.isClosed(); |
| } |
| |
| @Override |
| public ByteChannel channel() { |
| return this.channel; |
| } |
| |
| @Override |
| public SocketAddress getLocalAddress() { |
| return this.session.getLocalAddress(); |
| } |
| |
| @Override |
| public SocketAddress getRemoteAddress() { |
| return this.session.getRemoteAddress(); |
| } |
| |
| @Override |
| public synchronized int getEventMask() { |
| return this.appEventMask; |
| } |
| |
| @Override |
| public synchronized void setEventMask(final int ops) { |
| this.appEventMask = ops; |
| updateEventMask(); |
| } |
| |
| @Override |
| public synchronized void setEvent(final int op) { |
| this.appEventMask = this.appEventMask | op; |
| updateEventMask(); |
| } |
| |
| @Override |
| public synchronized void clearEvent(final int op) { |
| this.appEventMask = this.appEventMask & ~op; |
| updateEventMask(); |
| } |
| |
| @Override |
| public int getSocketTimeout() { |
| return this.session.getSocketTimeout(); |
| } |
| |
| @Override |
| public void setSocketTimeout(final int timeout) { |
| this.session.setSocketTimeout(timeout); |
| } |
| |
| @Override |
| public synchronized boolean hasBufferedInput() { |
| return (this.appBufferStatus != null && this.appBufferStatus.hasBufferedInput()) |
| || this.inEncrypted.hasData() |
| || this.inPlain.hasData(); |
| } |
| |
| @Override |
| public synchronized boolean hasBufferedOutput() { |
| return (this.appBufferStatus != null && this.appBufferStatus.hasBufferedOutput()) |
| || this.outEncrypted.hasData(); |
| } |
| |
| @Override |
| public synchronized void setBufferStatus(final SessionBufferStatus status) { |
| this.appBufferStatus = status; |
| } |
| |
| @Override |
| public Object getAttribute(final String name) { |
| return this.session.getAttribute(name); |
| } |
| |
| @Override |
| public Object removeAttribute(final String name) { |
| return this.session.removeAttribute(name); |
| } |
| |
| @Override |
| public void setAttribute(final String name, final Object obj) { |
| this.session.setAttribute(name, obj); |
| } |
| |
| private static void formatOps(final StringBuilder buffer, final int ops) { |
| if ((ops & SelectionKey.OP_READ) > 0) { |
| buffer.append('r'); |
| } |
| if ((ops & SelectionKey.OP_WRITE) > 0) { |
| buffer.append('w'); |
| } |
| } |
| |
| @Override |
| public String toString() { |
| final StringBuilder buffer = new StringBuilder(); |
| buffer.append(this.session); |
| buffer.append("["); |
| switch (this.status) { |
| case ACTIVE: |
| buffer.append("ACTIVE"); |
| break; |
| case CLOSING: |
| buffer.append("CLOSING"); |
| break; |
| case CLOSED: |
| buffer.append("CLOSED"); |
| break; |
| } |
| buffer.append("]["); |
| formatOps(buffer, this.appEventMask); |
| buffer.append("]["); |
| buffer.append(this.sslEngine.getHandshakeStatus()); |
| if (this.sslEngine.isInboundDone()) { |
| buffer.append("][inbound done]["); |
| } |
| if (this.sslEngine.isOutboundDone()) { |
| buffer.append("][outbound done]["); |
| } |
| if (this.endOfStream) { |
| buffer.append("][EOF]["); |
| } |
| buffer.append("]["); |
| buffer.append(!this.inEncrypted.hasData() ? 0 : inEncrypted.acquire().position()); |
| buffer.append("]["); |
| buffer.append(!this.inPlain.hasData() ? 0 : inPlain.acquire().position()); |
| buffer.append("]["); |
| buffer.append(!this.outEncrypted.hasData() ? 0 : outEncrypted.acquire().position()); |
| buffer.append("]"); |
| return buffer.toString(); |
| } |
| |
| @Override |
| public Socket getSocket(){ |
| return this.session instanceof SocketAccessor ? ((SocketAccessor) this.session).getSocket() : null; |
| } |
| |
| private class InternalByteChannel implements ByteChannel { |
| |
| @Override |
| public int write(final ByteBuffer src) throws IOException { |
| return SSLIOSession.this.writePlain(src); |
| } |
| |
| @Override |
| public int read(final ByteBuffer dst) throws IOException { |
| return SSLIOSession.this.readPlain(dst); |
| } |
| |
| @Override |
| public void close() throws IOException { |
| SSLIOSession.this.close(); |
| } |
| |
| @Override |
| public boolean isOpen() { |
| return !SSLIOSession.this.isClosed(); |
| } |
| |
| } |
| |
| } |