All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.xnio.ssl.JsseSslConduitEngine Maven / Gradle / Ivy

There is a newer version: 62
Show newest version
/*
 * JBoss, Home of Professional Open Source.
 *
 * Copyright 2013 Red Hat, Inc. and/or its affiliates, and individual
 * contributors as indicated by the @author tags.
 *
 * Licensed 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.
 */

package org.xnio.ssl;

import static java.lang.Thread.currentThread;
import static java.util.concurrent.locks.LockSupport.park;
import static java.util.concurrent.locks.LockSupport.parkNanos;
import static java.util.concurrent.locks.LockSupport.unpark;
import static org.xnio.Bits.allAreClear;
import static org.xnio.Bits.allAreSet;
import static org.xnio.Bits.anyAreSet;
import static org.xnio.Bits.intBitMask;
import static org.xnio._private.Messages.msg;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSession;

import org.jboss.logging.Logger;

import org.xnio.BufferAllocator;
import org.xnio.Buffers;
import org.xnio.ByteBufferSlicePool;
import org.xnio.Pool;
import org.xnio.Pooled;
import org.xnio.conduits.StreamSinkConduit;
import org.xnio.conduits.StreamSourceConduit;

/**
 * {@link SSLEngine} wrapper, used by Jsse SSL conduits.
 *
 * @author Flavia Rainone
 */
final class JsseSslConduitEngine {

    private static final Logger log = Logger.getLogger("org.xnio.conduits");
    private static final String FQCN = JsseSslConduitEngine.class.getName();

    // read-side
    private static final int NEED_WRAP              = 1 << 0x00; // conduit cannot be read due to pending wrap
    private static final int READ_SHUT_DOWN         = 1 << 0x01; // user shut down reads
    private static final int BUFFER_UNDERFLOW         = 1 << 0x02; // even though there is data in the buffer there is not enough to form a complete packet
    @SuppressWarnings("unused")
    private static final int READ_FLAGS             = intBitMask(0x00, 0x0F);
    // write-side
    private static final int NEED_UNWRAP            = 1 << 0x10; // conduit cannot be written to due to pending unwrap
    private static final int WRITE_SHUT_DOWN        = 1 << 0x11; // user requested shut down of writes
    private static final int WRITE_COMPLETE         = 1 << 0x12; // flush acknowledged full write shutdown

    private static final int FIRST_HANDSHAKE          = 1 << 0x16; // first handshake has not been performed
    private static final int ENGINE_CLOSED          = 1 << 0x17;  // engine is fully closed
     // engine is fully closed
    @SuppressWarnings("unused")
    private static final int WRITE_FLAGS            = intBitMask(0x10, 0x1F);
    // empty buffer
    private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0);

    // final fields
    /** The SSL engine. */
    private final SSLEngine engine;
    /** The buffer into which incoming SSL data is written. */
    private final Pooled receiveBuffer;
    /** The buffer from which outbound SSL data is sent. */
    private final Pooled sendBuffer;
    /** An expanded non-final buffer from which outbound SSL data is sent when
     * large fragments handling is enabled in the underlying SSL Engine. When
     * this happens, we need a specific buffer with expanded capacity. */
    private  ByteBuffer expandedSendBuffer;
    /** The buffer into which inbound clear data is written. */
    private final Pooled readBuffer;

    // the next conduits
    private final StreamSinkConduit sinkConduit;
    private final StreamSourceConduit sourceConduit;
    // the connection
    private final JsseSslStreamConnection connection;

    // state
    private volatile int state;
    private static final AtomicIntegerFieldUpdater stateUpdater = AtomicIntegerFieldUpdater.newUpdater(JsseSslConduitEngine.class, "state");
    // waiters
    @SuppressWarnings("unused")
    private volatile Thread readWaiter;
    @SuppressWarnings("unused")
    private volatile Thread writeWaiter;
    private static final AtomicReferenceFieldUpdater readWaiterUpdater = AtomicReferenceFieldUpdater.newUpdater(JsseSslConduitEngine.class, Thread.class, "readWaiter");
    private static final AtomicReferenceFieldUpdater writeWaiterUpdater = AtomicReferenceFieldUpdater.newUpdater(JsseSslConduitEngine.class, Thread.class, "writeWaiter");

    /**
     * Construct a new instance.
     *
     * @param connection            the ssl connection associated with this engine 
     * @param sinkConduit           the sink channel to use for write operations
     * @param sourceConduit         the source channel to use for read operations
     * @param engine                the SSL engine to use
     * @param socketBufferPool      the socket buffer pool
     * @param applicationBufferPool the application buffer pool
     */
    JsseSslConduitEngine(final JsseSslStreamConnection connection, final StreamSinkConduit sinkConduit, final StreamSourceConduit sourceConduit, final SSLEngine engine, final Pool socketBufferPool, final Pool applicationBufferPool) {
        if (connection == null) {
            throw msg.nullParameter("connection");
        }
        if (sinkConduit == null) {
            throw msg.nullParameter("sinkConduit");
        }
        if (sourceConduit == null) {
            throw msg.nullParameter("sourceConduit");
        }
        if (engine == null) {
            throw msg.nullParameter("engine");
        }
        if (socketBufferPool == null) {
            throw msg.nullParameter("socketBufferPool");
        }
        if (applicationBufferPool == null) {
            throw msg.nullParameter("applicationBufferPool");
        }
        this.connection = connection;
        this.sinkConduit = sinkConduit;
        this.sourceConduit = sourceConduit;
        this.engine = engine;
        this.state = FIRST_HANDSHAKE;
        final SSLSession session = engine.getSession();
        final int packetBufferSize = session.getPacketBufferSize();
        boolean ok = false;
        receiveBuffer = socketBufferPool.allocate();
        try {
            receiveBuffer.getResource().flip();
            sendBuffer = socketBufferPool.allocate();
            try {
                if (receiveBuffer.getResource().capacity() < packetBufferSize || sendBuffer.getResource().capacity() < packetBufferSize) {
                    // create expanded send buffer
                    expandedSendBuffer = ByteBuffer.allocate(packetBufferSize);
                }
                readBuffer = applicationBufferPool.allocate();
                ok = true;
            } finally {
                if (! ok) sendBuffer.free();
            }
        } finally {
            if (! ok) receiveBuffer.free();
        }
    }

    /**
     * Begins handshake.
     * 
     * @throws IOException if an I/O error occurs
     */
    public void beginHandshake() throws IOException {
        engine.beginHandshake();
    }

    /**
     * Returns the engine's session.
     */
    public SSLSession getSession() {
        return engine.getSession();
    }

    /**
     * Attempt to wrap the bytes in {@code src}. The wrapped bytes can be later retrieved by calling
     * {@link #getWrappedBuffer()}.
     * 

* If the engine is performing handshake during this request, not all bytes could be wrapped. In this case, a later * call can be performed to attempt to wrap more bytes. *

* This method must not be invoked inside the {@link #getWrapLock() wrap lock}, or else unexpected behavior * could occur. * @param src the bytes to be wrapped * @return the amount of wrapped bytes. * * @throws IOException if an IO exception occurs during wrapping */ public int wrap(final ByteBuffer src) throws IOException { return wrap(src, false); } /** * Attempt to wrap the bytes in {@code srcs}. The wrapped bytes can be later retrieved by calling * {@link #getWrappedBuffer()}. *

* If the engine is performing handshake during this request, not all bytes could be wrapped. In this case, a later * call can be performed to attempt to wrap more bytes. *

* This method must not be invoked inside the {@link #getWrapLock() wrap lock}, or else unexpected behavior * could occur. * * @param srcs contains the bytes to be wrapped * @param offset offset * @param length length * @return the amount of wrapped bytes. * * @throws IOException if an IO exception occurs during wrapping */ public long wrap(final ByteBuffer[] srcs, final int offset, final int length) throws IOException { assert ! Thread.holdsLock(getWrapLock()); assert ! Thread.holdsLock(getUnwrapLock()); if (length < 1) { return 0L; } if (allAreSet(state, WRITE_COMPLETE)) { // atempted write after shutdown, this is // a workaround for a bug found in SSLEngine throw new ClosedChannelException(); } long bytesConsumed = 0; boolean run; try { do { final SSLEngineResult result; synchronized (getWrapLock()) { run = handleWrapResult(result = engineWrap(srcs, offset, length, getSendBuffer()), false); bytesConsumed += (long) result.bytesConsumed(); } // handshake will tell us whether to keep the loop run = run && (handleHandshake(result, true) || (!isUnwrapNeeded() && Buffers.hasRemaining(srcs, offset, length))); } while (run); } catch (SSLHandshakeException e) { try { synchronized (getWrapLock()) { engine.wrap(EMPTY_BUFFER, getSendBuffer()); doFlush(); } } catch (IOException ignore) {} throw e; } return bytesConsumed; } /** * Returns the buffer that contains the wrapped data. *

* Retrieval and manipulation of this buffer should always be protected by the {@link #getWrapLock() wrap lock}. * * @return the buffer containing wrapped bytes */ public ByteBuffer getWrappedBuffer() { assert Thread.holdsLock(getWrapLock()); assert ! Thread.holdsLock(getUnwrapLock()); return allAreSet(stateUpdater.get(this), ENGINE_CLOSED)? Buffers.EMPTY_BYTE_BUFFER: getSendBuffer(); } /** * Returns the wrap lock, that must be used whenever the {@link #getWrappedBuffer() wrapped buffer} is being * accessed. *

* This lock is also used internally by wrapping operations, thus guaranteeing safe concurrent execution of * both wrap and unwrap operations, specially during handshake handling. * * @return lock for protecting access to the unwrapped buffer */ public Object getWrapLock() { return sendBuffer; } /** * Wrap operation used internally. * * @param src contains the bytes to be wrapped * @param isCloseExpected indicates if close is expected, information that is used to perform special handling * when closing the engine * @return the amount of resulting wrapped bytes * * @throws IOException if an IO exception occurs during wrapping */ private int wrap(final ByteBuffer src, boolean isCloseExpected) throws IOException { assert ! Thread.holdsLock(getWrapLock()); assert ! Thread.holdsLock(getUnwrapLock()); if (allAreSet(state, WRITE_COMPLETE)) { // attempted write after shutdown, this is // a workaround for a bug found in SSLEngine throw new ClosedChannelException(); } clearFlags(FIRST_HANDSHAKE); int bytesConsumed = 0; boolean run; try { do { final SSLEngineResult result; synchronized (getWrapLock()) { run = handleWrapResult(result = engineWrap(src, getSendBuffer()), isCloseExpected); bytesConsumed += result.bytesConsumed(); } // handshake will tell us whether to keep the loop run = run && bytesConsumed == 0 && (handleHandshake(result, true) || (!isUnwrapNeeded() && src.hasRemaining())); } while (run); } catch (SSLHandshakeException e) { try { synchronized (getWrapLock()) { engine.wrap(EMPTY_BUFFER, getSendBuffer()); doFlush(); } } catch (IOException ignore) {} throw e; } return bytesConsumed; } /** * Invoke inner SSL engine to wrap. */ private SSLEngineResult engineWrap(final ByteBuffer[] srcs, final int offset, final int length, final ByteBuffer dest) throws SSLException { assert Thread.holdsLock(getWrapLock()); assert ! Thread.holdsLock(getUnwrapLock()); log.logf(FQCN, Logger.Level.TRACE, null, "Wrapping %s into %s", srcs, dest); try { return engine.wrap(srcs, offset, length, dest); } catch (SSLHandshakeException e) { try { engine.wrap(srcs, offset, length, dest); doFlush(); } catch (IOException ignore) {} throw e; } } /** * Invoke inner SSL engine to wrap. */ private SSLEngineResult engineWrap(final ByteBuffer src, final ByteBuffer dest) throws SSLException { assert Thread.holdsLock(getWrapLock()); assert ! Thread.holdsLock(getUnwrapLock()); log.logf(FQCN, Logger.Level.TRACE, null, "Wrapping %s into %s", src, dest); return engine.wrap(src, dest); } /** * Handles the wrap result, indicating if caller should perform a new attempt to wrap. * * @param result the wrap result * @param closeExpected is a closed engine result expected * @return {@code true} if a new attempt to wrap should be made * * @throws IOException if an IO exception occurs */ private boolean handleWrapResult(SSLEngineResult result, boolean closeExpected) throws IOException { assert Thread.holdsLock(getWrapLock()); assert ! Thread.holdsLock(getUnwrapLock()); log.logf(FQCN, Logger.Level.TRACE, null, "Wrap result is %s", result); switch (result.getStatus()) { case BUFFER_UNDERFLOW: { assert result.bytesConsumed() == 0; assert result.bytesProduced() == 0; // should not be possible but just to be safe... break; } case BUFFER_OVERFLOW: { assert result.bytesConsumed() == 0; assert result.bytesProduced() == 0; final ByteBuffer buffer = getSendBuffer(); if (buffer.position() == 0) { final int bufferSize = engine.getSession().getPacketBufferSize(); if (buffer.capacity() < bufferSize) { msg.expandedSslBufferEnabled(bufferSize); expandedSendBuffer = ByteBuffer.allocate(bufferSize); } else throw msg.wrongBufferExpansion(); } else { // there's some data in there, so send it first buffer.flip(); try { while (buffer.hasRemaining()) { final int res = sinkConduit.write(buffer); if (res == 0) { return false; } } } finally { buffer.compact(); } } break; } case CLOSED: { if (! closeExpected) { // attempted write after shutdown throw new ClosedChannelException(); } // else treat as OK // fall thru!!! } case OK: { if (result.bytesConsumed() == 0) { if (result.bytesProduced() > 0) { if (! doFlush()) { return false; } } } break; } default: { throw msg.unexpectedWrapResult(result.getStatus()); } } return true; } /** * Handle handshake process, after a wrap or an unwrap operation. * * @param result the wrap/unwrap result * @param write if {@code true}, indicates caller executed a {@code wrap} operation; if {@code false}, indicates * caller executed an {@code unwrap} operation * @return {@code true} to indicate that caller should rerun the previous wrap or unwrap operation, hence * producing a new result; {@code false} to indicate otherwise * * @throws IOException if an IO error occurs during handshake handling */ private boolean handleHandshake(SSLEngineResult result, boolean write) throws IOException { assert ! Thread.holdsLock(getUnwrapLock()); // if read needs wrap, the only possible reason is that something went wrong with flushing, try to flush now if (isWrapNeeded()) { synchronized(getWrapLock()) { if (doFlush()) { clearNeedWrap(); } } } boolean newResult = false; for (;;) { switch (result.getHandshakeStatus()) { case FINISHED: { clearNeedUnwrap(); connection.handleHandshakeFinished(); // Operation can continue immediately return true; } case NOT_HANDSHAKING: { // Operation can continue immediately clearNeedUnwrap(); return false; } case NEED_WRAP: { // clear writeRequiresRead clearNeedUnwrap(); // if write, let caller do the wrap if (write) { return true; } final ByteBuffer buffer = getSendBuffer(); // else, trigger a write call // Needs wrap, so we wrap (if possible)... synchronized (getWrapLock()) { // given caller is reading, tell it to continue only if we can move away from NEED_WRAP // and flush any wrapped data we may have left if (doFlush()) { if (result.getStatus() == SSLEngineResult.Status.CLOSED) { closeOutbound(); return false; } if (!handleWrapResult(result = engineWrap(Buffers.EMPTY_BYTE_BUFFER, buffer), true) || !doFlush()) { needWrap(); return false; } // success, clear need wrap and handle the new handshake status newResult = true; clearNeedWrap(); continue; } assert !isUnwrapNeeded(); // else... oops, there is unflushed data, and handshake status is NEED_WRAP needWrap(); // tell read caller to break read loop return false; } } case NEED_UNWRAP: { // if read, let caller do the unwrap if (! write) { return newResult; } synchronized(getWrapLock()) { // there could be unflushed data from a previous wrap, make sure everything is flushed at this point doFlush(); } final ByteBuffer buffer = receiveBuffer.getResource(); final ByteBuffer unwrappedBuffer = readBuffer.getResource(); // FIXME this if block is a workaround for a bug in SSLEngine if (result.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP && engine.isOutboundDone()) { synchronized (getUnwrapLock()) { buffer.compact(); sourceConduit.read(buffer); buffer.flip(); if (buffer.hasRemaining() && sourceConduit.isReadResumed()) { sourceConduit.wakeupReads(); } return false; } } synchronized (getUnwrapLock()) { // attempt to unwrap int unwrapResult = handleUnwrapResult(result = engineUnwrap(buffer, unwrappedBuffer)); if (buffer.hasRemaining() && sourceConduit.isReadResumed()) { sourceConduit.wakeupReads(); } if (unwrapResult >= 0) { // have we made some progress? if(result.getHandshakeStatus() != HandshakeStatus.NEED_UNWRAP || result.bytesConsumed() > 0) { clearNeedUnwrap(); continue; } assert !isWrapNeeded(); // no point in proceeding, we're stuck until the user reads anyway needUnwrap(); return false; } else if (unwrapResult == -1 && result.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP) { if (!allAreSet(state, READ_SHUT_DOWN)) { // connection has been closed by peer prior to handshake finished throw new ClosedChannelException(); } return false; } } continue; } case NEED_TASK: { Runnable task; synchronized (engine) { // run the tasks needed for handshaking while ((task = engine.getDelegatedTask()) != null) { try { task.run(); } catch (Exception e) { throw new IOException(e); } } } // caller should try to wrap/unwrap again return true; } default: throw msg.unexpectedHandshakeStatus(result.getHandshakeStatus()); } } } /** * Unwraps the bytes contained in {@link #getUnwrapBuffer()}, copying the resulting unwrapped bytes into * {@code dst}. *

* If the engine is performing handshake during this request, not all bytes could be unwrapped. In this case, a * later call can be performed to attempt to unwrap more bytes. *

* This method must not be invoked inside the {@link #getUnwrapLock() unwrap lock}, or else unexpected behavior * could occur. * * @param dst where the resulting unwrapped bytes will be copied to * @return the amount of resulting unwrapped bytes * * @throws IOException if an IO exception occurs during unwrapping */ public int unwrap(final ByteBuffer dst) throws IOException { return (int) unwrap(new ByteBuffer[]{dst}, 0, 1); } private int failureCount = 0; /** * Unwraps the bytes contained in {@link #getUnwrapBuffer()}, copying the resulting unwrapped bytes into * {@code dsts}. *

* If the engine is performing handshake during this request, not all bytes could be unwrapped. In this case, a * later call can be performed to attempt to unwrap more bytes. *

* This method must not be invoked inside the {@link #getUnwrapLock() unwrap lock}, or else unexpected behavior * could occur. * * @param dsts where the resulting unwrapped bytes will be copied to * @param offset offset * @param length length * @return the amount of resulting unwrapped bytes * * @throws IOException if an IO exception occurs during unwrapping */ public long unwrap(final ByteBuffer[] dsts, final int offset, final int length) throws IOException { assert ! Thread.holdsLock(getUnwrapLock()); assert ! Thread.holdsLock(getWrapLock()); if (dsts.length == 0 || length == 0 || isClosed()) { return 0L; } clearFlags(FIRST_HANDSHAKE | BUFFER_UNDERFLOW); final ByteBuffer buffer = receiveBuffer.getResource(); final ByteBuffer unwrappedBuffer = readBuffer.getResource(); long total = 0; SSLEngineResult result; synchronized(getUnwrapLock()) { if (unwrappedBuffer.position() > 0) { total += (long) copyUnwrappedData(dsts, offset, length, unwrappedBuffer); } } int res = 0; try { do { synchronized (getUnwrapLock()) { if (! Buffers.hasRemaining(dsts, offset, length)) { if (unwrappedBuffer.hasRemaining() && sourceConduit.isReadResumed()) { sourceConduit.wakeupReads(); } return total; } res = handleUnwrapResult(result = engineUnwrap(buffer, unwrappedBuffer)); if (unwrappedBuffer.position() > 0) { // test the position of the buffer instead of the // the amount of produced bytes, because in a concurrent scenario, during this loop, // another thread could read more bytes as a side effect of a need unwrap total += (long) copyUnwrappedData(dsts, offset, length, unwrappedBuffer); } } } while ((handleHandshake(result, false) || res > 0)); } catch (SSLHandshakeException e) { try { synchronized (getWrapLock()) { engine.wrap(EMPTY_BUFFER, getSendBuffer()); doFlush(); } } catch (IOException ignore) {} throw e; } if (total == 0L) { if (res == -1) { return -1L; } } if(res == 0 && result.getStatus() == SSLEngineResult.Status.BUFFER_UNDERFLOW) { int old; do { old = state; } while(!stateUpdater.compareAndSet(this, old, old | BUFFER_UNDERFLOW)); } return total; } /** * Returns the buffer that contains the data to be unwrapped. *

* Retrieval and manipulation of this buffer should always be protected by the {@link #getUnwrapLock() unwrap lock}. * * @return the buffer containing bytes to be unwrapped */ public ByteBuffer getUnwrapBuffer() { assert Thread.holdsLock(getUnwrapLock()); assert ! Thread.holdsLock(getWrapLock()); return receiveBuffer.getResource(); } /** * Returns the unwrap lock, that must be used whenever the {@link #getUnwrapBuffer() unwrap buffer} is being * accessed. *

* This lock is also used internally by unwrapping operations, thus guaranteeing safe concurrent execution of * both wrap and unwrap operations, specially during handshake handling. * * @return lock for protecting access to the unwrap buffer */ public Object getUnwrapLock() { return receiveBuffer; } /** * Invoke inner SSL engine to unwrap. */ private SSLEngineResult engineUnwrap(final ByteBuffer buffer, final ByteBuffer unwrappedBuffer) throws IOException { assert Thread.holdsLock(getUnwrapLock()); if (!buffer.hasRemaining()) { buffer.compact(); sourceConduit.read(buffer); buffer.flip(); } log.logf(FQCN, Logger.Level.TRACE, null, "Unwrapping %s into %s", buffer, unwrappedBuffer); return engine.unwrap(buffer, unwrappedBuffer); } /** * Copy unwrapped data from {@code unwrappedBuffer} into {@code dsts}. * * @param dsts destine of copy * @param offset offset * @param length length * @param unwrappedBuffer source from where byte will be copied * @return the amount of copied bytes */ private int copyUnwrappedData(final ByteBuffer[] dsts, final int offset, final int length, ByteBuffer unwrappedBuffer) { assert Thread.holdsLock(getUnwrapLock()); unwrappedBuffer.flip(); try { return Buffers.copy(dsts, offset, length, unwrappedBuffer); } finally { unwrappedBuffer.compact(); } } /** * Handles the unwrap result, indicating how many bytes have been consumed. * * @param result the unwrap result * @return the amount of bytes consumed by unwrap. If the engine is closed and no bytes were consumed, * returns {@code -1}. * * @throws IOException if an IO exception occurs */ private int handleUnwrapResult(final SSLEngineResult result) throws IOException { assert Thread.holdsLock(getUnwrapLock()); log.logf(FQCN, Logger.Level.TRACE, null, "Unwrap result is %s", result); switch (result.getStatus()) { case BUFFER_OVERFLOW: { assert result.bytesConsumed() == 0; assert result.bytesProduced() == 0; // not enough space in destination buffer; caller should flush & retry return 0; } case BUFFER_UNDERFLOW: { assert result.bytesConsumed() == 0; assert result.bytesProduced() == 0; // fill the rest of the buffer, then retry! final ByteBuffer buffer = receiveBuffer.getResource(); synchronized (getUnwrapLock()) { buffer.compact(); try { return sourceConduit.read(buffer); } finally { buffer.flip(); } } } case CLOSED: { // if unwrap processed any data, it should return bytes produced instead of -1 if (result.bytesConsumed() > 0) { return result.bytesConsumed(); } return -1; } case OK: { // continue return result.bytesConsumed(); } default: { throw msg.unexpectedUnwrapResult(result.getStatus()); } } } /** * Flush any data needed for handshaking into the {@link #getWrappedBuffer() wrapped buffer}. *

* The flushed data can later be retrieved by reading the buffer. If the engine is closed, this method can be used * to flush all data associated to engine close messages. * * @return {@code true} if there is no left data to be flushed; {@code false} if for some reason the engine was * unable to flush all data * * @throws IOException if an IO exception occurs during attempt to flush */ public boolean flush() throws IOException { int oldState, newState; oldState = stateUpdater.get(this); if (allAreSet(oldState, WRITE_COMPLETE)) { if (engine.isOutboundDone()) { connection.writeClosed(); } return true; } synchronized (getWrapLock()) { if (allAreSet(oldState, WRITE_SHUT_DOWN)) { if (!wrapCloseMessage()) { return false; } } else { if (engine.isOutboundDone()) { connection.writeClosed(); } return true; } } // conclude write newState = oldState | WRITE_COMPLETE; while (! stateUpdater.compareAndSet(this, oldState, newState)) { oldState = stateUpdater.get(this); if (allAreSet(oldState, WRITE_COMPLETE)) { if (engine.isOutboundDone()) { connection.writeClosed(); } return true;//sinkConduit.flush(); } newState = oldState | WRITE_COMPLETE; } // close the engine if read is shut down if (allAreSet(oldState, READ_SHUT_DOWN)) { closeEngine(); } if (engine.isOutboundDone()) { connection.writeClosed(); } return true; } /** * Attempt to finish wrapping close handshake bytes. * * @return {@code true} only if all bytes concerning close handshake messages have been wrapped. * @throws IOException if an unexpected IO exception occurs */ private boolean wrapCloseMessage() throws IOException { assert ! Thread.holdsLock(getUnwrapLock()); assert Thread.holdsLock(getWrapLock()); if (sinkConduit.isWriteShutdown()) { return true; } if (!engine.isOutboundDone() || !engine.isInboundDone()) { SSLEngineResult result; do { if (!handleWrapResult(result = engineWrap(Buffers.EMPTY_BYTE_BUFFER, getSendBuffer()), true)) { return false; } } while (handleHandshake(result, true) && (result.getHandshakeStatus() != HandshakeStatus.NEED_UNWRAP || !engine.isOutboundDone())); handleWrapResult(result = engineWrap(Buffers.EMPTY_BYTE_BUFFER, getSendBuffer()), true); if (!engine.isOutboundDone() || (result.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING && result.getHandshakeStatus() != HandshakeStatus.NEED_UNWRAP)) { return false; } } return true; } /** * Flushes all data in the wrapped bytes buffer. * * @return {@code true} if all data available has been flushed * * @throws IOException if an unexpected IO exception occurs */ private boolean doFlush() throws IOException { assert Thread.holdsLock(getWrapLock()); assert ! Thread.holdsLock(getUnwrapLock()); final ByteBuffer buffer; buffer = getSendBuffer(); buffer.flip(); try { while (buffer.hasRemaining()) { final int res = sinkConduit.write(buffer); if (res == 0) { return false; } } } finally { buffer.compact(); } return sinkConduit.flush(); } /** * Closes this engine for both inbound and outbound, clearing the buffers. * * @throws IOException */ private void closeEngine() throws IOException { int old = setFlags(ENGINE_CLOSED); // idempotent if (allAreSet(old, ENGINE_CLOSED)) { return; } try { synchronized(getWrapLock()) { if (! doFlush()) { throw msg.unflushedData(); } } } finally { readBuffer.free(); receiveBuffer.free(); sendBuffer.free(); } } /** * Signals that no outbound data will be sent. * * @throws IOException if an IO exception occurs */ public void closeOutbound() throws IOException { int old = setFlags(WRITE_SHUT_DOWN); try { if (allAreClear(old, WRITE_SHUT_DOWN)) { engine.closeOutbound(); synchronized (getWrapLock()) { wrapCloseMessage(); flush(); } } if (!allAreClear(old, READ_SHUT_DOWN)) { closeEngine(); } } catch (Exception e) { //if there is an exception on close we immediately close the engine to make sure buffers are freed try { closeEngine(); } catch (Exception closeEngineException) { msg.failedToCloseSSLEngine(e, closeEngineException); } if(e instanceof IOException) { throw (IOException) e; } else { throw (RuntimeException)e; } } } /** * Closes the engine for both inbound and outbound connection, and discards this engine's internal * resources immediately without handshaking if this engine has not been used. * * @throws IOException */ void close() throws IOException { if (isFirstHandshake()) { setFlags(WRITE_SHUT_DOWN|WRITE_COMPLETE|READ_SHUT_DOWN); closeEngine(); } else { try { closeInbound(); } catch (Throwable t) { try { closeOutbound(); } catch (Throwable t2) { t2.addSuppressed(t); throw t2; } throw t; } closeOutbound(); } } /** * Indicates if outbound is closed. * * @throws IOException if an IO exception occurs */ public boolean isOutboundClosed() { return allAreSet(stateUpdater.get(this), WRITE_SHUT_DOWN); } /** * Block until this engine can wrap messages. *

* The engine may be unable to wrap if a handshake message needs to be unwrapped first. In this case, this thread * will be awakened as soon as the message is made available for reading by the internal source conduit. * * @throws IOException if an IO exception occurs during await */ public void awaitCanWrap() throws IOException { int oldState = state; if (anyAreSet(oldState, WRITE_SHUT_DOWN) || !allAreSet(oldState, NEED_UNWRAP)) { return; } final Thread thread = currentThread(); final Thread next = writeWaiterUpdater.getAndSet(this, thread); try { if (anyAreSet(oldState = state, WRITE_SHUT_DOWN)) { return; } if (allAreSet(oldState, NEED_UNWRAP)) { unwrap(Buffers.EMPTY_BYTE_BUFFER); } park(this); if (thread.isInterrupted()) { throw msg.interruptedIO(); } } finally { // always unpark because we cannot know if our awaken was spurious if (next != null) unpark(next); } } /** * Block until this engine can wrap messages. *

* The engine may be unable to wrap if a handshake message needs to be unwrapped first. In this case, this thread * will be awakened as soon as the message is made available for reading by the internal source conduit. * * @param time timeout for blocking * @param timeUnit timeout unit * @throws IOException if an IO exception occurs during await */ public void awaitCanWrap(long time, TimeUnit timeUnit) throws IOException { int oldState = state; if (anyAreSet(oldState, WRITE_SHUT_DOWN) || !allAreSet(oldState, NEED_UNWRAP)) { return; } final Thread thread = currentThread(); final Thread next = writeWaiterUpdater.getAndSet(this, thread); long duration = timeUnit.toNanos(time); try { if (anyAreSet(oldState = state, WRITE_SHUT_DOWN)) { return; } if (allAreSet(oldState, NEED_UNWRAP)) { unwrap(Buffers.EMPTY_BYTE_BUFFER); } parkNanos(this, duration); if (thread.isInterrupted()) { throw msg.interruptedIO(); } } finally { // always unpark because we cannot know if our awaken was spurious if (next != null) unpark(next); } } /** * Signals that no inbound data will be read * * @throws IOException if an IO exception occurs */ public void closeInbound() throws IOException { connection.readClosed(); int old = setFlags(READ_SHUT_DOWN); try { if (allAreClear(old, READ_SHUT_DOWN)) { sourceConduit.terminateReads(); } if (allAreSet(old, WRITE_SHUT_DOWN) && !allAreSet(old, WRITE_COMPLETE)) { synchronized (getWrapLock()) { wrapCloseMessage(); flush(); } } if (allAreSet(old, WRITE_COMPLETE)) { closeEngine(); } } catch (Exception e) { //if there is an exception on close we immediately close the engine to make sure buffers are freed closeEngine(); if(e instanceof IOException) { throw (IOException)e; } else { throw (RuntimeException)e; } } } /** * Indicates if inbound is closed. * * @throws IOException if an IO exception occurs */ public boolean isInboundClosed() { return allAreSet(state, READ_SHUT_DOWN); } /** * Indicates if engine is closed. * */ public boolean isClosed() { return allAreSet(state, ENGINE_CLOSED); } /** * Block until this engine can unwrap messages. * * @throws IOException if an IO exception occurs during await */ public void awaitCanUnwrap() throws IOException { int oldState = state; if (anyAreSet(oldState, READ_SHUT_DOWN) || ! anyAreSet(oldState, NEED_WRAP)) { return; } final Thread thread = currentThread(); final Thread next = readWaiterUpdater.getAndSet(this, thread); try { if (anyAreSet(oldState = state, READ_SHUT_DOWN)) { return; } if (allAreSet(oldState, NEED_WRAP)) { wrap(Buffers.EMPTY_BYTE_BUFFER); } park(this); if (thread.isInterrupted()) { throw msg.interruptedIO(); } } finally { // always unpark because we cannot know if our awaken was spurious if (next != null) unpark(next); } } /** * Block until this engine can unwrap messages. * * @param time timeout for blocking * @param timeUnit timeout unit * @throws IOException if an IO exception occurs during await */ public void awaitCanUnwrap(long time, TimeUnit timeUnit) throws IOException { int oldState = state; if (anyAreSet(oldState, READ_SHUT_DOWN) || ! anyAreSet(oldState, NEED_WRAP)) { return; } final Thread thread = currentThread(); final Thread next = readWaiterUpdater.getAndSet(this, thread); long duration = timeUnit.toNanos(time); try { if (anyAreSet(oldState = state, READ_SHUT_DOWN)) { return; } if (allAreSet(oldState, NEED_WRAP)) { wrap(Buffers.EMPTY_BYTE_BUFFER); } parkNanos(this, duration); if (thread.isInterrupted()) { throw msg.interruptedIO(); } } finally { // always unpark because we cannot know if our awaken was spurious if (next != null) unpark(next); } } public boolean isFirstHandshake() { return allAreSet(state, FIRST_HANDSHAKE); } SSLEngine getEngine() { return engine; } /** * Indicate that the engine will not be able unwrap before a successful wrap is performed. */ private void needWrap() { setFlags(NEED_WRAP); } /** * Indicate if the engine can unwrap. */ private boolean isWrapNeeded() { return allAreSet(state, NEED_WRAP); } /** * Indicate that the engine no longer requires a successful wrap to proceed with unwrap operations. */ private void clearNeedWrap() { clearFlags(NEED_WRAP); } /** * Indicate that the engine will not be able wrap before a successful unwrap is performed. */ private void needUnwrap() { setFlags(NEED_UNWRAP); } /** * Indicate if the engine can wrap. */ private boolean isUnwrapNeeded() { return allAreSet(state, NEED_UNWRAP); } /** * Indicates that even though there is data available there is not enough to form a complete packet */ private boolean isUnderflow() { return allAreSet(state, BUFFER_UNDERFLOW); } /** * Indicate that the engine no longer requires a successful unwrap to proceed with wrap operations. */ private void clearNeedUnwrap() { clearFlags(NEED_UNWRAP); } private int setFlags(int flags) { int oldState; do { oldState = state; if ((oldState & flags) == flags) { return oldState; } } while (! stateUpdater.compareAndSet(this, oldState, oldState | flags)); return oldState; } private int clearFlags(int flags) { int oldState; do { oldState = state; if ((oldState & flags) == 0) { return oldState; } } while (! stateUpdater.compareAndSet(this, oldState, oldState & ~flags)); return oldState; } public boolean isDataAvailable() { synchronized (getUnwrapLock()) { try { return readBuffer.getResource().hasRemaining() || (receiveBuffer.getResource().hasRemaining() && !isUnderflow()); } catch (IllegalStateException ignored) { return false; } } } private final ByteBuffer getSendBuffer() { return expandedSendBuffer != null? expandedSendBuffer : sendBuffer.getResource(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy