/* * JBoss, Home of Professional Open Source * * Copyright 2013 Red Hat, Inc. and/or its affiliates. * * 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.Math.max; import static java.lang.Thread.currentThread; import static org.xnio.Bits.allAreClear; import static org.xnio.Bits.allAreSet; import static org.xnio.Bits.anyAreClear; import static org.xnio.Bits.anyAreSet; import static org.xnio._private.Messages.msg; import java.io.EOFException; import java.io.IOException; import java.io.InterruptedIOException; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.Arrays; import java.util.concurrent.TimeUnit; 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.SSLSession; import org.xnio.Buffers; import org.xnio.Pool; import org.xnio.Pooled; import org.xnio.XnioIoThread; import org.xnio.XnioWorker; import org.xnio.channels.StreamSinkChannel; import org.xnio.channels.StreamSourceChannel; import org.xnio.conduits.ConduitReadableByteChannel; import org.xnio.conduits.ConduitWritableByteChannel; import org.xnio.conduits.Conduits; import org.xnio.conduits.ReadReadyHandler; import org.xnio.conduits.StreamSinkConduit; import org.xnio.conduits.StreamSourceConduit; import org.xnio.conduits.WriteReadyHandler; final class JsseStreamConduit implements StreamSourceConduit, StreamSinkConduit, Runnable { private static final boolean TRACE_SSL = Boolean.getBoolean("org.xnio.ssl.TRACE_SSL"); //================================================================ // // Immutable state // //================================================================ private final JsseSslConnection connection; private final SSLEngine engine; private final StreamSourceConduit sourceConduit; private final StreamSinkConduit sinkConduit; /** The buffer into which incoming SSL data is written. */ private final Pooled<ByteBuffer> receiveBuffer; /** The buffer from which outbound SSL data is sent. */ private final Pooled<ByteBuffer> sendBuffer; /** The buffer into which inbound clear data is written. */ private final Pooled<ByteBuffer> readBuffer; //================================================================ // // Mutable state // //================================================================ // always inline tasks, for now private int state = FLAG_INLINE_TASKS; // tasks counter - protected by {@code this} private int tasks; private ReadReadyHandler readReadyHandler; private WriteReadyHandler writeReadyHandler; //================================================================ // // Constructors // //================================================================ JsseStreamConduit(final JsseSslConnection connection, final SSLEngine engine, final StreamSourceConduit sourceConduit, final StreamSinkConduit sinkConduit, final Pool<ByteBuffer> socketBufferPool, final Pool<ByteBuffer> applicationBufferPool) { Pooled<ByteBuffer> receiveBuffer; Pooled<ByteBuffer> sendBuffer; Pooled<ByteBuffer> readBuffer; boolean ok = false; final SSLSession session = engine.getSession(); final int packetBufferSize = session.getPacketBufferSize(); receiveBuffer = socketBufferPool.allocate(); try { receiveBuffer.getResource().flip(); sendBuffer = socketBufferPool.allocate(); try { if (receiveBuffer.getResource().capacity() < packetBufferSize || sendBuffer.getResource().capacity() < packetBufferSize) { throw msg.socketBufferTooSmall(); } final int applicationBufferSize = session.getApplicationBufferSize(); readBuffer = applicationBufferPool.allocate(); try { if (readBuffer.getResource().capacity() < applicationBufferSize) { throw msg.appBufferTooSmall(); } ok = true; } finally { if (! ok) readBuffer.free(); } } finally { if (! ok) sendBuffer.free(); } } finally { if (! ok) receiveBuffer.free(); } this.receiveBuffer = receiveBuffer; this.sendBuffer = sendBuffer; this.readBuffer = readBuffer; receiveBuffer.getResource().clear().limit(0); if (sourceConduit.getReadThread() != sinkConduit.getWriteThread()) { throw new IllegalArgumentException("Source and sink thread mismatch"); } this.connection = connection; this.engine = engine; this.sourceConduit = sourceConduit; this.sinkConduit = sinkConduit; sourceConduit.setReadReadyHandler(readReady); sinkConduit.setWriteReadyHandler(writeReady); } //================================================================ // // Flags // //================================================================ // global flags // global-_write----_read----; /** TLS is enabled */ private static final int FLAG_TLS = 0b00001_000000000_00000000; /** Run tasks immediately rather than in the worker */ private static final int FLAG_INLINE_TASKS = 0b00010_000000000_00000000; /** Set when task is queued; cleared when it runs */ private static final int FLAG_TASK_QUEUED = 0b00100_000000000_00000000; /** Set when the engine needs the result of a delegated task to continue */ private static final int FLAG_NEED_ENGINE_TASK = 0b01000_000000000_00000000; /** we need a flush to proceed with handshake */ private static final int FLAG_FLUSH_NEEDED = 0b10000_000000000_00000000; // read state flags /** shutdownReads() was called upstream */ private static final int READ_FLAG_SHUTDOWN = 0b00000_000000000_00000001; /** -1 was read */ private static final int READ_FLAG_EOF = 0b00000_000000000_00000010; /** resumeReads() called */ private static final int READ_FLAG_RESUMED = 0b00000_000000000_00000100; /** upstream resumeReads() called */ private static final int READ_FLAG_UP_RESUMED = 0b00000_000000000_00001000; /** wakeupReads() called */ private static final int READ_FLAG_WAKEUP = 0b00000_000000000_00010000; /** user read handler should be called if resumed */ private static final int READ_FLAG_READY = 0b00000_000000000_00100000; /** read needs wrap to proceed */ private static final int READ_FLAG_NEEDS_WRITE = 0b00000_000000000_01000000; // write state flags /** shutdownWrites() was called */ private static final int WRITE_FLAG_SHUTDOWN = 0b00000_000000001_00000000; /** send buffer cleared for final wrap after shutdownWrites() */ private static final int WRITE_FLAG_SHUTDOWN2 = 0b00000_000000010_00000000; /** wrapping was completed after shutdownWrites() was called */ private static final int WRITE_FLAG_SHUTDOWN3 = 0b00000_000000100_00000000; /** flush() returned true after shutdown */ private static final int WRITE_FLAG_FINISHED = 0b00000_000001000_00000000; /** resumeWrites() called */ private static final int WRITE_FLAG_RESUMED = 0b00000_000010000_00000000; /** upstream resumeWrites() called */ private static final int WRITE_FLAG_UP_RESUMED = 0b00000_000100000_00000000; /** wakeupWrites() called */ private static final int WRITE_FLAG_WAKEUP = 0b00000_001000000_00000000; /** user write handler should be called if resumed */ private static final int WRITE_FLAG_READY = 0b00000_010000000_00000000; /** write needs unwrap to proceed */ private static final int WRITE_FLAG_NEEDS_READ = 0b00000_100000000_00000000; public String getStatus() { final StringBuilder b = new StringBuilder(); b.append("General flags:"); final int state = this.state; if (allAreSet(state, FLAG_TLS)) b.append(" TLS"); if (allAreSet(state, FLAG_INLINE_TASKS)) b.append(" INLINE_TASKS"); if (allAreSet(state, FLAG_TASK_QUEUED)) b.append(" TASK_QUEUED"); if (allAreSet(state, FLAG_NEED_ENGINE_TASK)) b.append(" NEED_ENGINE_TASK"); if (allAreSet(state, FLAG_FLUSH_NEEDED)) b.append(" FLUSH_NEEDED"); b.append("\nRead flags:"); if (allAreSet(state, READ_FLAG_SHUTDOWN)) b.append(" SHUTDOWN"); if (allAreSet(state, READ_FLAG_EOF)) b.append(" EOF"); if (allAreSet(state, READ_FLAG_RESUMED)) b.append(" RESUMED"); if (allAreSet(state, READ_FLAG_UP_RESUMED)) b.append(" UP_RESUMED"); if (allAreSet(state, READ_FLAG_WAKEUP)) b.append(" WAKEUP"); if (allAreSet(state, READ_FLAG_READY)) b.append(" READY"); if (allAreSet(state, READ_FLAG_NEEDS_WRITE)) b.append(" NEEDS_WRITE"); b.append("\nWrite flags:"); if (allAreSet(state, WRITE_FLAG_SHUTDOWN)) b.append(" SHUTDOWN"); if (allAreSet(state, WRITE_FLAG_SHUTDOWN2)) b.append(" SHUTDOWN2"); if (allAreSet(state, WRITE_FLAG_SHUTDOWN3)) b.append(" SHUTDOWN3"); if (allAreSet(state, WRITE_FLAG_FINISHED)) b.append(" FINISHED"); if (allAreSet(state, WRITE_FLAG_RESUMED)) b.append(" RESUMED"); if (allAreSet(state, WRITE_FLAG_UP_RESUMED)) b.append(" UP_RESUMED"); if (allAreSet(state, WRITE_FLAG_WAKEUP)) b.append(" WAKEUP"); if (allAreSet(state, WRITE_FLAG_READY)) b.append(" READY"); if (allAreSet(state, WRITE_FLAG_NEEDS_READ)) b.append(" NEEDS_READ"); b.append('\n'); return b.toString(); } public String toString() { return String.format("JSSE Stream Conduit for %s, status:%n%s", connection, getStatus()); } //================================================================ // // Global API // //================================================================ public XnioWorker getWorker() { return connection.getIoThread().getWorker(); } public XnioIoThread getReadThread() { return connection.getIoThread(); } public XnioIoThread getWriteThread() { return connection.getIoThread(); } private final WriteReadyHandler writeReady = new WriteReadyHandler() { @Override public void forceTermination() { if (anyAreClear(state, WRITE_FLAG_FINISHED)) { state |= WRITE_FLAG_SHUTDOWN | WRITE_FLAG_SHUTDOWN2 | WRITE_FLAG_SHUTDOWN3 | WRITE_FLAG_FINISHED; } final WriteReadyHandler writeReadyHandler = JsseStreamConduit.this.writeReadyHandler; if (writeReadyHandler != null) try { writeReadyHandler.forceTermination(); } catch (Throwable ignored) { } } @Override public void terminated() { if (anyAreClear(state, WRITE_FLAG_FINISHED)) { state |= WRITE_FLAG_SHUTDOWN | WRITE_FLAG_SHUTDOWN2 | WRITE_FLAG_SHUTDOWN3 | WRITE_FLAG_FINISHED; } final WriteReadyHandler writeReadyHandler = JsseStreamConduit.this.writeReadyHandler; if (writeReadyHandler != null) try { writeReadyHandler.terminated(); } catch (Throwable ignored) { } } @Override public void writeReady() { JsseStreamConduit.this.writeReady(); } }; private final ReadReadyHandler readReady = new ReadReadyHandler() { @Override public void forceTermination() { if (anyAreClear(state, READ_FLAG_SHUTDOWN)) { state |= READ_FLAG_SHUTDOWN; } final ReadReadyHandler readReadyHandler = JsseStreamConduit.this.readReadyHandler; if (readReadyHandler != null) try { readReadyHandler.forceTermination(); } catch (Throwable ignored) { } } @Override public void terminated() { if (anyAreClear(state, READ_FLAG_SHUTDOWN)) { state |= READ_FLAG_SHUTDOWN; } final ReadReadyHandler readReadyHandler = JsseStreamConduit.this.readReadyHandler; if (readReadyHandler != null) try { readReadyHandler.terminated(); } catch (Throwable ignored) { } } @Override public void readReady() { JsseStreamConduit.this.readReady(); } }; // non-public void beginHandshake() throws IOException { final int state = this.state; if (anyAreSet(state, READ_FLAG_EOF | WRITE_FLAG_SHUTDOWN)) { throw new ClosedChannelException(); } if (allAreClear(state, FLAG_TLS)) { this.state = state | FLAG_TLS; } engine.beginHandshake(); } SSLSession getSslSession() { return allAreSet(state, FLAG_TLS) ? engine.getSession() : null; } SSLEngine getEngine() { return engine; } boolean isTls() { return allAreSet(state, FLAG_TLS); } boolean markTerminated() { readBuffer.free(); receiveBuffer.free(); sendBuffer.free(); if (anyAreClear(state, READ_FLAG_SHUTDOWN | WRITE_FLAG_FINISHED)) { state |= READ_FLAG_SHUTDOWN | WRITE_FLAG_SHUTDOWN | WRITE_FLAG_SHUTDOWN2 | WRITE_FLAG_SHUTDOWN3 | WRITE_FLAG_FINISHED; return true; } else { return false; } } //================================================================ // // Main run task // //================================================================ public void run() { assert currentThread() == getWriteThread(); int state = JsseStreamConduit.this.state; final boolean flagTaskQueued = allAreSet(state, FLAG_TASK_QUEUED); boolean modify = flagTaskQueued; boolean queueTask = false; state &= ~FLAG_TASK_QUEUED; try { // task(s) if (allAreSet(state, FLAG_NEED_ENGINE_TASK)) { throw new UnsupportedOperationException(); } // write side if (anyAreSet(state, WRITE_FLAG_WAKEUP) || allAreSet(state, WRITE_FLAG_RESUMED | WRITE_FLAG_READY)) { final WriteReadyHandler writeReadyHandler = JsseStreamConduit.this.writeReadyHandler; if (allAreSet(state, WRITE_FLAG_WAKEUP)) { state = state & ~WRITE_FLAG_WAKEUP | WRITE_FLAG_RESUMED; modify = true; } if (writeReadyHandler != null) { if (allAreSet(state, WRITE_FLAG_RESUMED)) { try { // save flags -------------------------------+ if (modify) { // | modify = false; // | JsseStreamConduit.this.state = state; // | } // | writeReadyHandler.writeReady(); // | } catch (Throwable ignored) { // | } finally { // | // restore flags <---------------------------+ // it is OK if this is stale state = JsseStreamConduit.this.state & ~FLAG_TASK_QUEUED; // make sure we flag to save state with no FLAG_TASK_QUEUED modify = true; } // Thread safety notice: //---> We must not modify flags unless read and/or write is still resumed; otherwise, the user might // be doing something in another thread and we could end up overwriting each others' changes. // level-triggering if (allAreSet(state, WRITE_FLAG_RESUMED)) { if (!allAreSet(state, WRITE_FLAG_READY) && allAreSet(state, WRITE_FLAG_NEEDS_READ) && allAreClear(state, READ_FLAG_UP_RESUMED)) { state |= READ_FLAG_UP_RESUMED; modify = true; sourceConduit.resumeReads(); } else if (allAreClear(state, WRITE_FLAG_UP_RESUMED)) { sinkConduit.resumeWrites(); } } } else { if (allAreClear(state, READ_FLAG_NEEDS_WRITE | READ_FLAG_RESUMED) && allAreSet(state, WRITE_FLAG_UP_RESUMED)) { state &= ~WRITE_FLAG_UP_RESUMED; modify = true; suspendWrites(); } } } else { // no handler, we should not be resumed state &= ~WRITE_FLAG_RESUMED; modify = true; if (allAreClear(state, READ_FLAG_NEEDS_WRITE | READ_FLAG_RESUMED) && allAreSet(state, WRITE_FLAG_UP_RESUMED)) { state &= ~WRITE_FLAG_UP_RESUMED; modify = true; suspendWrites(); } } } // read side if (anyAreSet(state, READ_FLAG_WAKEUP) || allAreSet(state, READ_FLAG_RESUMED | READ_FLAG_READY)) { final ReadReadyHandler readReadyHandler = JsseStreamConduit.this.readReadyHandler; if (allAreSet(state, READ_FLAG_WAKEUP)) { state = state & ~READ_FLAG_WAKEUP | READ_FLAG_RESUMED; modify = true; } if (readReadyHandler != null) { if (allAreSet(state, READ_FLAG_RESUMED)) { try { // save flags -------------------------------+ if (modify) { // | modify = false; // | JsseStreamConduit.this.state = state; // | } // | readReadyHandler.readReady(); // | } catch (Throwable ignored) { // | } finally { // | // restore flags <---------------------------+ // it is OK if this is stale state = JsseStreamConduit.this.state & ~FLAG_TASK_QUEUED; modify = true; } // Thread safety notice: //---> We must not modify flags unless read and/or write is still resumed; otherwise, the user might // be doing something in another thread and we could end up overwriting each others' changes. // level-triggering if (allAreSet(state, READ_FLAG_RESUMED)) { if (allAreSet(state, READ_FLAG_READY)) { if (!flagTaskQueued) { state |= FLAG_TASK_QUEUED; modify = queueTask = true; } } else if (allAreSet(state, READ_FLAG_NEEDS_WRITE) && allAreClear(state, WRITE_FLAG_UP_RESUMED)) { state |= WRITE_FLAG_UP_RESUMED; modify = true; sinkConduit.resumeWrites(); } else if (allAreClear(state, READ_FLAG_UP_RESUMED)) { sourceConduit.resumeReads(); } } } else { if (allAreClear(state, WRITE_FLAG_NEEDS_READ | WRITE_FLAG_RESUMED) && allAreSet(state, READ_FLAG_UP_RESUMED)) { state &= ~READ_FLAG_UP_RESUMED; modify = true; suspendReads(); } } } else { // no handler, we should not be resumed state &= ~READ_FLAG_RESUMED; modify = true; if (allAreClear(state, WRITE_FLAG_NEEDS_READ | WRITE_FLAG_RESUMED) && allAreSet(state, READ_FLAG_UP_RESUMED)) { state &= ~READ_FLAG_UP_RESUMED; suspendReads(); } } } } finally { if (modify) { JsseStreamConduit.this.state = state; // execute this on read thread only after updating the state if (queueTask) getReadThread().execute(this); } } } //================================================================ // // Ready handlers // //================================================================ public void setWriteReadyHandler(final WriteReadyHandler handler) { this.writeReadyHandler = handler; } public void setReadReadyHandler(final ReadReadyHandler handler) { this.readReadyHandler = handler; } /** * Called by the upstream conduit when writes are ready. */ public void writeReady() { int state = this.state; state |= WRITE_FLAG_READY; if (allAreSet(state, READ_FLAG_NEEDS_WRITE)) { state |= READ_FLAG_READY; } this.state = state; // avoid double-fire if (allAreClear(state, FLAG_TASK_QUEUED)) { run(); } state = this.state; if (sinkConduit.isWriteResumed() && allAreClear(state, WRITE_FLAG_RESUMED | READ_FLAG_NEEDS_WRITE)) { sinkConduit.suspendWrites(); } if (sourceConduit.isReadResumed() && allAreClear(state, READ_FLAG_RESUMED | WRITE_FLAG_NEEDS_READ)) { sourceConduit.suspendReads(); } } /** * Called by the upstream conduit when reads are ready. */ public void readReady() { int state = this.state; state |= READ_FLAG_READY; if (allAreSet(state, WRITE_FLAG_NEEDS_READ)) { state |= WRITE_FLAG_READY; } this.state = state; // avoid double-fire if (allAreClear(state, FLAG_TASK_QUEUED)) { run(); } state = this.state; if (sourceConduit.isReadResumed() && allAreClear(state, READ_FLAG_RESUMED | WRITE_FLAG_NEEDS_READ)) { sourceConduit.suspendReads(); } if (sinkConduit.isWriteResumed() && allAreClear(state, WRITE_FLAG_RESUMED | READ_FLAG_NEEDS_WRITE)) { sinkConduit.suspendWrites(); } } //================================================================ // // Resume state management // //================================================================ // writes public void suspendWrites() { int state = this.state; try { if (allAreSet(state, WRITE_FLAG_RESUMED)) { state &= ~WRITE_FLAG_RESUMED; if (allAreSet(state, WRITE_FLAG_UP_RESUMED) && allAreClear(state, READ_FLAG_NEEDS_WRITE)) { // upstream writes were resumed but now may be suspended state &= ~WRITE_FLAG_UP_RESUMED; sinkConduit.suspendWrites(); } if (allAreSet(state, READ_FLAG_UP_RESUMED) && allAreClear(state, READ_FLAG_RESUMED)) { // reads were likely resumed because of us; clear it state &= ~READ_FLAG_UP_RESUMED; sourceConduit.suspendReads(); } } } finally { this.state = state; } } public void resumeWrites() { int state = this.state; if (allAreClear(state, WRITE_FLAG_RESUMED)) { if (allAreSet(state, WRITE_FLAG_FINISHED)) { // just re-call the handler one time wakeupWrites(); return; } boolean queueTask = false; try { state |= WRITE_FLAG_RESUMED; if (allAreSet(state, WRITE_FLAG_READY)) { if (queueTask = allAreClear(state, FLAG_TASK_QUEUED)) { state |= FLAG_TASK_QUEUED; } } else if (allAreSet(state, WRITE_FLAG_NEEDS_READ) && allAreClear(state, READ_FLAG_UP_RESUMED)) { // need to resume reads to make this happen state |= READ_FLAG_UP_RESUMED; sourceConduit.resumeReads(); } else if (allAreClear(state, WRITE_FLAG_UP_RESUMED)) { // upstream writes were not resumed state |= WRITE_FLAG_UP_RESUMED; sinkConduit.resumeWrites(); } } finally { this.state = state; // execute this on read thread only after updating the state if (queueTask) getReadThread().execute(this); } } } public void wakeupWrites() { final int state = this.state; if (allAreClear(state, WRITE_FLAG_WAKEUP)) { if (allAreClear(state, FLAG_TASK_QUEUED)) { this.state = state | WRITE_FLAG_WAKEUP | FLAG_TASK_QUEUED; getReadThread().execute(this); } else { this.state = state | WRITE_FLAG_WAKEUP; } } } public void terminateWrites() throws IOException { int state = this.state; if (allAreClear(state, WRITE_FLAG_FINISHED)) { this.state = state | WRITE_FLAG_SHUTDOWN; if (allAreSet(state, FLAG_TLS)) try { if (engine.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING) { engine.closeOutbound(); } performIO(IO_GOAL_FLUSH, NO_BUFFERS, 0, 0, NO_BUFFERS, 0, 0); if (allAreSet(this.state, WRITE_FLAG_FINISHED)) { sinkConduit.terminateWrites(); } } catch (Throwable t) { this.state |= WRITE_FLAG_FINISHED; try { sinkConduit.truncateWrites(); } catch (Throwable t2) { t.addSuppressed(t2); } throw t; } else { sinkConduit.terminateWrites(); } } } public void truncateWrites() throws IOException { int state = this.state; if (allAreClear(state, WRITE_FLAG_SHUTDOWN)) { if (allAreSet(state, FLAG_TLS)) try { state |= WRITE_FLAG_SHUTDOWN | WRITE_FLAG_SHUTDOWN3 | WRITE_FLAG_FINISHED; try { engine.closeOutbound(); } catch (Throwable t) { try { sinkConduit.truncateWrites(); } catch (Throwable t2) { t.addSuppressed(t2); } throw t; } sinkConduit.truncateWrites(); } finally { this.state = state; } else { this.state = state | WRITE_FLAG_SHUTDOWN | WRITE_FLAG_SHUTDOWN3 | WRITE_FLAG_FINISHED; sinkConduit.truncateWrites(); } } } // queries public boolean isWriteResumed() { return anyAreSet(state, WRITE_FLAG_RESUMED | WRITE_FLAG_WAKEUP); } public boolean isWriteShutdown() { return allAreSet(state, WRITE_FLAG_SHUTDOWN); } // await public void awaitWritable() throws IOException { int state = this.state; while (allAreSet(state, FLAG_NEED_ENGINE_TASK)) { synchronized (this) { while (tasks != 0) { try { wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new InterruptedIOException(); } } state &= ~FLAG_NEED_ENGINE_TASK; this.state = state; } } if (allAreClear(state, WRITE_FLAG_READY)) { if (allAreSet(state, WRITE_FLAG_NEEDS_READ)) { sourceConduit.awaitReadable(); } else { sinkConduit.awaitWritable(); } } } public void awaitWritable(final long time, final TimeUnit timeUnit) throws IOException { int state = this.state; long nanos = timeUnit.toNanos(time); while (allAreSet(state, FLAG_NEED_ENGINE_TASK)) { synchronized (this) { long start = System.nanoTime(); while (tasks != 0) { try { if (nanos <= 0) { return; } wait(nanos / 1_000_000, (int) (nanos % 1_000_000)); nanos -= -start + (start = System.nanoTime()); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new InterruptedIOException(); } } state &= ~FLAG_NEED_ENGINE_TASK; this.state = state; } } if (allAreClear(state, WRITE_FLAG_READY)) { if (allAreSet(state, WRITE_FLAG_NEEDS_READ)) { sourceConduit.awaitReadable(nanos, TimeUnit.NANOSECONDS); } else { sinkConduit.awaitWritable(nanos, TimeUnit.NANOSECONDS); } } } // reads public void suspendReads() { int state = this.state; try { if (allAreSet(state, READ_FLAG_RESUMED)) { state &= ~READ_FLAG_RESUMED; if (allAreSet(state, READ_FLAG_UP_RESUMED) && allAreClear(state, WRITE_FLAG_NEEDS_READ)) { // upstream reads were resumed but now may be suspended state &= ~READ_FLAG_UP_RESUMED; sourceConduit.suspendReads(); } if (allAreSet(state, WRITE_FLAG_UP_RESUMED) && allAreClear(state, WRITE_FLAG_RESUMED)) { // writes were likely resumed because of us; clear it state &= ~WRITE_FLAG_UP_RESUMED; sinkConduit.suspendWrites(); } } } finally { this.state = state; } } public void resumeReads() { int state = this.state; boolean queueTask = false; if (allAreClear(state, READ_FLAG_RESUMED)) try { state |= READ_FLAG_RESUMED; // in the absence of a writer, we need to wake up the reader to make sure that handshake will take place // (the read listener could never be called if this is the side that is supposed to make the first wrap // for handshake; if that happens, this side of the connection would starve) if (allAreClear(state, WRITE_FLAG_RESUMED)) { state |= READ_FLAG_READY; } if (allAreSet(state, READ_FLAG_READY)) { if (queueTask = allAreClear(state, FLAG_TASK_QUEUED)) { state |= FLAG_TASK_QUEUED; } } else if (allAreSet(state, READ_FLAG_NEEDS_WRITE) && allAreClear(state, WRITE_FLAG_UP_RESUMED)) { // need to resume writes to make this happen state |= WRITE_FLAG_UP_RESUMED; sinkConduit.resumeWrites(); } else if (allAreClear(state, READ_FLAG_UP_RESUMED)) { // upstream reads were not resumed state |= READ_FLAG_UP_RESUMED; sourceConduit.resumeReads(); } } finally { this.state = state; // execute this on read thread only after updating the state if (queueTask) getReadThread().execute(this); } } public void wakeupReads() { final int state = this.state; if (allAreClear(state, READ_FLAG_WAKEUP)) { if (allAreClear(state, FLAG_TASK_QUEUED)) { this.state = state | READ_FLAG_WAKEUP | FLAG_TASK_QUEUED; getReadThread().execute(this); } else { this.state = state | READ_FLAG_WAKEUP; } } } public void terminateReads() throws IOException { int state = this.state; if (allAreClear(state, READ_FLAG_SHUTDOWN)) { if (allAreClear(state, FLAG_TLS)) { // never fired up TLS in the first place sourceConduit.terminateReads(); } else { // indicate that the user doesn't want any more data this.state = state | READ_FLAG_SHUTDOWN; if (allAreClear(state, READ_FLAG_EOF)) { performIO(IO_GOAL_FLUSH, NO_BUFFERS, 0, 0, NO_BUFFERS, 0, 0); if (allAreSet(state, WRITE_FLAG_NEEDS_READ)) { if (allAreClear(state, READ_FLAG_EOF)) { return; } } if (!engine.isInboundDone() && engine.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING) { engine.closeInbound(); } final long res = performIO(IO_GOAL_READ, NO_BUFFERS, 0, 0, NO_BUFFERS, 0, 0); if (res == -1) { this.state |= READ_FLAG_EOF; } } if (allAreClear(this.state, READ_FLAG_EOF) || this.receiveBuffer.getResource().hasRemaining()) { // potentially unread data :( final EOFException exception = msg.connectionClosedEarly(); try { sourceConduit.terminateReads(); } catch (IOException e) { exception.addSuppressed(e); } throw exception; } else { sourceConduit.terminateReads(); } } } } // queries public boolean isReadResumed() { return anyAreSet(state, READ_FLAG_RESUMED | READ_FLAG_WAKEUP); } public boolean isReadShutdown() { return allAreSet(state, READ_FLAG_SHUTDOWN); } // await public void awaitReadable() throws IOException { int state = this.state; while (allAreSet(state, FLAG_NEED_ENGINE_TASK)) { synchronized (this) { while (tasks != 0) { try { wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new InterruptedIOException(); } } state &= ~FLAG_NEED_ENGINE_TASK; this.state = state; } } if (allAreClear(state, READ_FLAG_READY)) { if (allAreSet(state, READ_FLAG_NEEDS_WRITE)) { sinkConduit.awaitWritable(); } else { sourceConduit.awaitReadable(); } } } public void awaitReadable(final long time, final TimeUnit timeUnit) throws IOException { int state = this.state; long nanos = timeUnit.toNanos(time); while (allAreSet(state, FLAG_NEED_ENGINE_TASK)) { synchronized (this) { long start = System.nanoTime(); while (tasks != 0) { try { if (nanos <= 0) { return; } wait(nanos / 1_000_000, (int) (nanos % 1_000_000)); nanos -= -start + (start = System.nanoTime()); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new InterruptedIOException(); } } state &= ~FLAG_NEED_ENGINE_TASK; this.state = state; } } if (allAreClear(state, READ_FLAG_READY)) { if (allAreSet(state, READ_FLAG_NEEDS_WRITE)) { sinkConduit.awaitWritable(nanos, TimeUnit.NANOSECONDS); } else { sourceConduit.awaitReadable(nanos, TimeUnit.NANOSECONDS); } } } //================================================================ // // I/O methods - read // //================================================================ private final ByteBuffer[] readBufferHolder = new ByteBuffer[1]; public int read(final ByteBuffer dst) throws IOException { final int state = this.state; if (anyAreSet(state, READ_FLAG_SHUTDOWN)) { return -1; } if (anyAreSet(state, READ_FLAG_EOF)) { // read data if (readBuffer.getResource().position() > 0) { final ByteBuffer readBufferResource = readBuffer.getResource(); readBufferResource.flip(); try { if (TRACE_SSL) msg.tracef("TLS copy unwrapped data from %s to %s", Buffers.debugString(readBufferResource), Buffers.debugString(dst)); return Buffers.copy(dst, readBufferResource); } finally { readBufferResource.compact(); } } return -1; } else if (allAreClear(state, FLAG_TLS)) { int res = sourceConduit.read(dst); if (res == 0) { if (allAreSet(state, READ_FLAG_READY)) { this.state = state & ~READ_FLAG_READY; } } else if (res == -1) { this.state = (state | READ_FLAG_EOF) & ~READ_FLAG_READY; } return res; } else { // regular TLS time final ByteBuffer[] readBufferHolder = this.readBufferHolder; readBufferHolder[0] = dst; try { return (int) performIO(IO_GOAL_READ, NO_BUFFERS, 0, 0, readBufferHolder, 0, 1); } finally { readBufferHolder[0] = null; } } } public long read(final ByteBuffer[] dsts, final int offs, final int len) throws IOException { final int state = this.state; if (anyAreSet(state, READ_FLAG_SHUTDOWN)) { return -1; } else if (anyAreSet(state, READ_FLAG_EOF)){ if (readBuffer.getResource().position() > 0) { final ByteBuffer readBufferResource = readBuffer.getResource(); readBufferResource.flip(); try { if (TRACE_SSL) msg.tracef("TLS copy unwrapped data from %s to %s", Buffers.debugString(readBufferResource), Buffers.debugString(dsts, offs, len)); return Buffers.copy(dsts, offs, len, readBufferResource); } finally { readBufferResource.compact(); } } return -1; } else if (allAreClear(state, FLAG_TLS)) { long res = sourceConduit.read(dsts, offs, len); if (res == 0) { if (allAreSet(state, READ_FLAG_READY)) { this.state = state & ~READ_FLAG_READY; } } else if (res == -1) { this.state = (state | READ_FLAG_EOF) & ~READ_FLAG_READY; } return res; } else { // regular TLS time return performIO(IO_GOAL_READ, NO_BUFFERS, 0, 0, dsts, offs, len); } } public long transferTo(final long position, final long count, final FileChannel target) throws IOException { if (allAreClear(state, FLAG_TLS)) { return sourceConduit.transferTo(position, count, target); } else { return target.transferFrom(new ConduitReadableByteChannel(this), position, count); } } public long transferTo(final long count, final ByteBuffer throughBuffer, final StreamSinkChannel target) throws IOException { if (allAreClear(state, FLAG_TLS)) { return sourceConduit.transferTo(count, throughBuffer, target); } else { // todo - transfer via application read buffer return Conduits.transfer(this, count, throughBuffer, target); } } //================================================================ // // I/O methods - write // //================================================================ private final ByteBuffer[] writeBufferHolder = new ByteBuffer[1]; public int write(final ByteBuffer src) throws IOException { if (allAreSet(state, WRITE_FLAG_SHUTDOWN)) { throw new ClosedChannelException(); } if (allAreClear(state, FLAG_TLS)) { return sinkConduit.write(src); } else { final ByteBuffer[] writeBufferHolder = this.writeBufferHolder; writeBufferHolder[0] = src; try { return (int) write(writeBufferHolder, 0, 1); } finally { writeBufferHolder[0] = null; } } } public int writeFinal(final ByteBuffer src) throws IOException { if (allAreSet(state, WRITE_FLAG_SHUTDOWN)) { throw new ClosedChannelException(); } if (allAreClear(state, FLAG_TLS)) { return sinkConduit.writeFinal(src); } else { final ByteBuffer[] writeBufferHolder = this.writeBufferHolder; writeBufferHolder[0] = src; try { return (int) writeFinal(writeBufferHolder, 0, 1); } finally { writeBufferHolder[0] = null; } } } public long write(final ByteBuffer[] srcs, final int offs, final int len) throws IOException { if (allAreSet(state, WRITE_FLAG_SHUTDOWN)) { throw new ClosedChannelException(); } if (allAreClear(state, FLAG_TLS)) { return sinkConduit.write(srcs, offs, len); } else { final long r1 = Buffers.remaining(srcs, offs, len); performIO(IO_GOAL_WRITE, srcs, offs, len, NO_BUFFERS, 0, 0); return (r1 - Buffers.remaining(srcs, offs, len)); } } public long writeFinal(final ByteBuffer[] srcs, final int offs, final int len) throws IOException { if (allAreSet(state, WRITE_FLAG_SHUTDOWN)) { throw new ClosedChannelException(); } if (allAreClear(state, FLAG_TLS)) { return sinkConduit.writeFinal(srcs, offs, len); } else { final long r1 = Buffers.remaining(srcs, offs, len); performIO(IO_GOAL_WRITE_FINAL, srcs, offs, len, NO_BUFFERS, 0, 0); return (r1 - Buffers.remaining(srcs, offs, len)); } } public boolean flush() throws IOException { int state = this.state; if (allAreSet(state, WRITE_FLAG_FINISHED)) { return true; } else if (allAreSet(state, WRITE_FLAG_SHUTDOWN3)) { // just waiting for upstream flush if (sinkConduit.flush()) { this.state = state | WRITE_FLAG_FINISHED; return true; } else { return false; } } else if (allAreClear(state, FLAG_TLS)) { final boolean flushed = sinkConduit.flush(); if (allAreSet(state, WRITE_FLAG_SHUTDOWN) && flushed) { this.state = state | WRITE_FLAG_SHUTDOWN2 | WRITE_FLAG_SHUTDOWN3 | WRITE_FLAG_FINISHED; } return flushed; } else if (allAreSet(state, WRITE_FLAG_SHUTDOWN)) { // waiting for final wrap, then upstream shutdown & flush return performIO(IO_GOAL_FLUSH, NO_BUFFERS, 0, 0, NO_BUFFERS, 0, 0) != 0L; } else { // regular flush return performIO(IO_GOAL_FLUSH, NO_BUFFERS, 0, 0, NO_BUFFERS, 0, 0) != 0L; } } public long transferFrom(final FileChannel src, final long position, final long count) throws IOException { if (allAreClear(state, FLAG_TLS)) { return sinkConduit.transferFrom(src, position, count); } else { return src.transferTo(position, count, new ConduitWritableByteChannel(this)); } } public long transferFrom(final StreamSourceChannel source, final long count, final ByteBuffer throughBuffer) throws IOException { if (allAreClear(state, FLAG_TLS)) { return sinkConduit.transferFrom(source, count, throughBuffer); } else { return Conduits.transfer(source, count, throughBuffer, this); } } //================================================================ // // Main SSLEngine I/O loop // //================================================================ private static final ByteBuffer[] NO_BUFFERS = new ByteBuffer[0]; private static final int IO_GOAL_READ = 0; private static final int IO_GOAL_WRITE = 1; private static final int IO_GOAL_FLUSH = 2; private static final int IO_GOAL_WRITE_FINAL = 3; private static long actualIOResult(final long xfer, final int goal, final boolean flushed, final boolean eof) { final long result = goal == IO_GOAL_FLUSH && flushed ? 1L : goal == IO_GOAL_READ && eof && xfer == 0L ? -1L : xfer; if (TRACE_SSL) msg.tracef("returned TLS result %d", result); return result; } private static String decodeGoal(int goal) { switch (goal) { case 0: return "READ"; case 1: return "WRITE"; case 2: return "FLUSH"; case 3: return "WRITE_FINAL"; default: return "UNKNOWN(" + goal + ")"; } } private long performIO(final int goal, final ByteBuffer[] srcs, final int srcOff, final int srcLen, final ByteBuffer[] dsts, final int dstOff, final int dstLen) throws IOException { if (TRACE_SSL) msg.tracef("performing TLS I/O operation, goal %s, src: %s, dst: %s", decodeGoal(goal), Buffers.debugString(srcs, srcOff, srcLen), Buffers.debugString(dsts, dstOff, dstLen)); // one of these arrays is empty assert srcs == NO_BUFFERS || dsts == NO_BUFFERS; int state = this.state; // this contradiction should never occur assert ! allAreSet(state, READ_FLAG_NEEDS_WRITE | WRITE_FLAG_NEEDS_READ); if (allAreSet(state, FLAG_NEED_ENGINE_TASK)) { // can't do anything until the task is done return 0L; } final SSLEngine engine = this.engine; final ByteBuffer sendBuffer = this.sendBuffer.getResource(); final ByteBuffer receiveBuffer = this.receiveBuffer.getResource(); final ByteBuffer readBuffer = this.readBuffer.getResource(); // unwrap into our read buffer if necessary to avoid underflow problems final ByteBuffer[] realDsts = Arrays.copyOfRange(dsts, dstOff, dstLen + 1); realDsts[dstLen] = readBuffer; long remaining = max(Buffers.remaining(srcs, srcOff, srcLen), Buffers.remaining(dsts, dstOff, dstLen)); boolean wrap = goal == IO_GOAL_READ ? anyAreSet(state, READ_FLAG_NEEDS_WRITE | FLAG_FLUSH_NEEDED) : allAreSet(state, FLAG_FLUSH_NEEDED) || allAreClear(state, WRITE_FLAG_NEEDS_READ); boolean unwrap = !wrap; boolean flushed = false; boolean eof = false; boolean readBlocked = false; boolean writeBlocked = false; boolean copiedUnwrappedBytes = false; boolean wakeupReads = false; SSLEngineResult result; SSLEngineResult.HandshakeStatus handshakeStatus; int rv = 0; // amount of data moved to/from the user buffers (remember only zero or one of srcs/dsts can be given) long xfer = 0L; if (TRACE_SSL) msg.trace("TLS perform IO"); try { for (;;) { if (TRACE_SSL) msg.trace("TLS begin IO operation"); if (goal == IO_GOAL_READ && remaining > 0 && readBuffer.position() > 0) { // read data readBuffer.flip(); try { if (TRACE_SSL) msg.tracef("TLS copy unwrapped data from %s to %s", Buffers.debugString(readBuffer), Buffers.debugString(dsts, dstOff, dstLen)); rv = Buffers.copy(dsts, dstOff, dstLen, readBuffer); } finally { readBuffer.compact(); } if (rv > 0) { copiedUnwrappedBytes = true; xfer += rv; if ((remaining -= rv) == 0L) { return actualIOResult(xfer, goal, flushed, eof); } } } assert ! (wrap && unwrap); if (wrap) { if (TRACE_SSL) msg.tracef("TLS wrap from %s to %s", Buffers.debugString(srcs, srcOff, srcLen), Buffers.debugString(sendBuffer)); result = engine.wrap(srcs, srcOff, srcLen, sendBuffer); WRAP_RESULT: switch (result.getStatus()) { case BUFFER_UNDERFLOW: { assert result.bytesConsumed() == 0; assert result.bytesProduced() == 0; // move directly to handshake result if (TRACE_SSL) msg.trace("TLS wrap operation UNDERFLOW"); break; } case BUFFER_OVERFLOW: { assert result.bytesConsumed() == 0; assert result.bytesProduced() == 0; if (TRACE_SSL) msg.trace("TLS wrap operation OVERFLOW"); if (sendBuffer.position() == 0) { // our buffer is empty, and definitely large enough, so just throw an exception throw msg.wrongBufferExpansion(); } else { // there's some data in there, so send it first sendBuffer.flip(); try { while (sendBuffer.hasRemaining()) { if (TRACE_SSL) msg.tracef("TLS wrap operation send %s", Buffers.debugString(sendBuffer)); final int res = sinkConduit.write(sendBuffer); if (res == 0) { writeBlocked = true; state &= ~WRITE_FLAG_READY; // not flushed assert goal != IO_GOAL_FLUSH || xfer == 0L; flushed = false; wrap = false; break WRAP_RESULT; } } } finally { sendBuffer.compact(); } if (goal == IO_GOAL_FLUSH || allAreSet(state, FLAG_FLUSH_NEEDED)) { if (flushed = sinkConduit.flush()) { state &= ~FLAG_FLUSH_NEEDED; } } if (goal == IO_GOAL_FLUSH && allAreSet(state, WRITE_FLAG_SHUTDOWN)) { state |= WRITE_FLAG_SHUTDOWN2; } } // move to handshake result break; } case CLOSED: { if (TRACE_SSL) msg.trace("TLS wrap operation CLOSED"); if (allAreClear(state, WRITE_FLAG_SHUTDOWN) && result.bytesProduced() == 0) { if (goal == IO_GOAL_FLUSH) { // this is okay, the client is flushing and we may have received a close from the other // end before we actually close this side wrap = false; if (goal == IO_GOAL_FLUSH || allAreSet(state, FLAG_FLUSH_NEEDED)) { if (flushed = sinkConduit.flush()) { state &= ~FLAG_FLUSH_NEEDED; } } break; } // attempted write after shutdown (should not be possible) state &= ~(WRITE_FLAG_NEEDS_READ | READ_FLAG_NEEDS_WRITE); state |= WRITE_FLAG_SHUTDOWN | WRITE_FLAG_SHUTDOWN2 | WRITE_FLAG_SHUTDOWN3 | WRITE_FLAG_FINISHED; final ClosedChannelException exception = new ClosedChannelException(); try { sinkConduit.truncateWrites(); } catch (IOException e) { exception.addSuppressed(e); } throw exception; } if (allAreSet(state, WRITE_FLAG_SHUTDOWN2)) { state |= WRITE_FLAG_SHUTDOWN3; } // else treat as OK // fall thru!!! } case OK: { if (TRACE_SSL) msg.tracef("TLS wrap operation OK consumed: %d produced: %d", result.bytesConsumed(), result.bytesProduced()); state &= ~(WRITE_FLAG_NEEDS_READ | READ_FLAG_NEEDS_WRITE); final int consumed = result.bytesConsumed(); if (goal == IO_GOAL_READ) { // sources should be empty assert consumed == 0; wrap = false; unwrap = true; } else { if (consumed > 0 || remaining == 0) { // if consumed > 0 then remaining must also be 0 assert remaining != 0 || consumed == 0; // we've returned some data, or else there's nothing to consume wrap = false; } xfer += consumed; remaining -= consumed; } // try to send the generated bytes sendBuffer.flip(); try { flushed = false; while (sendBuffer.hasRemaining()) { final int res = allAreSet(state, WRITE_FLAG_SHUTDOWN3) ? sinkConduit.writeFinal(sendBuffer) : sinkConduit.write(sendBuffer); if (res == 0) { // not flushed; probably can't wrap any more anyway writeBlocked = true; wrap = false; break; } } } finally { sendBuffer.compact(); } // make sure we *really* flushed if (sendBuffer.position() == 0) { if (goal == IO_GOAL_FLUSH || allAreSet(state, FLAG_FLUSH_NEEDED)) { if (flushed = sinkConduit.flush()) { state &= ~FLAG_FLUSH_NEEDED; } } if (allAreSet(state, WRITE_FLAG_SHUTDOWN)) { if (allAreClear(state, WRITE_FLAG_SHUTDOWN2)) { // send buffer should already be cleared, thanks to above write block assert sendBuffer.position() == 0; state |= WRITE_FLAG_SHUTDOWN2; if (result.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING) { // make sure we get to shutdown 3 right away if we are no longer handshaking state |= WRITE_FLAG_SHUTDOWN3; } } if (allAreSet(state, WRITE_FLAG_SHUTDOWN3)) { // the last wrap has occurred, and writes were shut down; we just need last flush if (goal == IO_GOAL_FLUSH || sinkConduit.flush()) { state |= WRITE_FLAG_FINISHED; } sinkConduit.terminateWrites(); } } } // move to handshake result break; } default: { throw msg.unexpectedWrapResult(result.getStatus()); } } } else if (unwrap) { if (TRACE_SSL) msg.tracef("TLS unwrap from %s to %s", Buffers.debugString(receiveBuffer), Buffers.debugString(realDsts, 0, dstLen + 1)); // use dstLen + 1 so that any leftovers are unwrapped into our read buffer to avoid underflow // * offset is 0 because realDsts is a copyOfRange of the original dsts with one extra buf at the end assert realDsts.length == 1 || realDsts[0] == dsts[dstOff]; assert realDsts[dstLen] == readBuffer; // user-visible counts final long preRem = Buffers.remaining(dsts, dstOff, dstLen); result = engine.unwrap(receiveBuffer, realDsts, 0, dstLen + 1); final long userProduced = preRem - Buffers.remaining(dsts, dstOff, dstLen); switch (result.getStatus()) { case BUFFER_OVERFLOW: { assert result.bytesConsumed() == 0; assert result.bytesProduced() == 0; assert userProduced == 0; if (TRACE_SSL) msg.trace("TLS unwrap operation OVERFLOW"); // not enough space in destination buffer; caller should consume the data they have first if (!copiedUnwrappedBytes) { // realDsts is too small for message to unwrap return actualIOResult(xfer, goal, flushed, eof); } unwrap = false; break; } case BUFFER_UNDERFLOW: { assert result.bytesConsumed() == 0; assert result.bytesProduced() == 0; assert userProduced == 0; if (TRACE_SSL) msg.trace("TLS unwrap operation UNDERFLOW"); // fill the rest of the buffer, then retry! receiveBuffer.compact(); try { int res; res = sourceConduit.read(receiveBuffer); if (TRACE_SSL) msg.tracef("TLS unwrap operation read %s", Buffers.debugString(receiveBuffer)); if (res == -1) { state &= ~READ_FLAG_READY; engine.closeInbound(); } else if (res == 0) { readBlocked = true; state &= ~READ_FLAG_READY; unwrap = false; } else if (receiveBuffer.hasRemaining()) { do { // try more reads just in case res = sourceConduit.read(receiveBuffer); } while (res > 0 && receiveBuffer.hasRemaining()); if (res == 0) { state &= ~READ_FLAG_READY; } } } finally { receiveBuffer.flip(); } // we should now be able to unwrap. break; } case CLOSED: { if (result.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP) { // treat as buffer underflow // fill the rest of the buffer, then retry! receiveBuffer.compact(); try { int res; res = sourceConduit.read(receiveBuffer); if (TRACE_SSL) msg.tracef("TLS unwrap operation read %s", Buffers.debugString(receiveBuffer)); if (res == -1) { state &= ~READ_FLAG_READY; engine.closeInbound(); return actualIOResult(xfer, goal, flushed, eof); } else if (res == 0) { readBlocked = true; state &= ~READ_FLAG_READY; unwrap = false; return actualIOResult(xfer, goal, flushed, eof); } else if (receiveBuffer.hasRemaining()) { do { // try more reads just in case res = sourceConduit.read(receiveBuffer); } while (res > 0 && receiveBuffer.hasRemaining()); if (res == 0) { state &= ~READ_FLAG_READY; } } } finally { receiveBuffer.flip(); } // we should now be able to unwrap. break; } if (TRACE_SSL) msg.trace("TLS unwrap operation CLOSED"); state &= ~(WRITE_FLAG_NEEDS_READ | READ_FLAG_NEEDS_WRITE); if (goal == IO_GOAL_READ) { xfer += userProduced; remaining -= userProduced; // if we are performing any action that not read, we don't want to disable read handler yet // (the handler needs to read -1 before that) state = (state & ~READ_FLAG_READY) | READ_FLAG_EOF; } else { wakeupReads = true; } // if unwrap processed any data, it should return bytes produced instead of -1 eof = true; unwrap = false; if (goal == IO_GOAL_FLUSH) { wrap = true; } break; } case OK: { if (TRACE_SSL) msg.tracef("TLS unwrap operation OK consumed: %d produced: %d", result.bytesConsumed(), result.bytesProduced()); if (allAreClear(state, READ_FLAG_READY)) { // make sure the caller keeps reading until the unwrapped data is consumed state |= READ_FLAG_READY; } state &= ~(WRITE_FLAG_NEEDS_READ | READ_FLAG_NEEDS_WRITE); if (goal == IO_GOAL_READ) { xfer += userProduced; remaining -= userProduced; } else { wrap = true; unwrap = false; if (result.bytesProduced() > 0) { wakeupReads = true; } } // handshake result break; } default: { throw msg.unexpectedUnwrapResult(result.getStatus()); } } } else { // done return actualIOResult(xfer, goal, flushed, eof); } // now handle handshake handshakeStatus = result.getHandshakeStatus(); HS: for (;;) { switch (handshakeStatus) { case FINISHED: { if (TRACE_SSL) msg.trace("TLS handshake FINISHED"); connection.invokeHandshakeListener(); // try original op again // fall thru! } case NOT_HANDSHAKING: { if (allAreSet(state, WRITE_FLAG_SHUTDOWN)) { engine.closeOutbound(); } // move on to next operation until I/O blocks break HS; } case NEED_TASK: { if (TRACE_SSL) msg.trace("TLS handshake NEED_TASK"); if (xfer != 0L) { // only queue a task if the user isn't going to retry an I/O op immediately after return actualIOResult(xfer, goal, flushed, eof); } if (allAreSet(state, FLAG_INLINE_TASKS)) { Runnable task; for (; ; ) { task = engine.getDelegatedTask(); if (task == null) { break; } try { task.run(); } catch (Throwable cause) { throw new SSLException("Delegated task threw an exception", cause); } } // and that's that; loop again handshakeStatus = engine.getHandshakeStatus(); // retry handshake evaluation break; } else { state |= FLAG_NEED_ENGINE_TASK; // await methods or the handler blob will take care of this final ArrayList<Runnable> tasks = new ArrayList<>(4); Runnable task; for (;;) { task = engine.getDelegatedTask(); if (task != null) { tasks.add(task); } else { break; } } final int size = tasks.size(); synchronized (JsseStreamConduit.this) { this.tasks = size; } // use indexes to avoid iterator creation (which does the same thing anyway) //noinspection ForLoopReplaceableByForEach for (int i = 0; i < size; i ++) { getWorker().execute(new TaskWrapper(tasks.get(i))); } return actualIOResult(xfer, goal, flushed, eof); } } case NEED_WRAP: { if (TRACE_SSL) msg.trace("TLS handshake NEED_WRAP"); state |= READ_FLAG_NEEDS_WRITE | FLAG_FLUSH_NEEDED; if (writeBlocked) { return actualIOResult(xfer, goal, flushed, eof); } wrap = true; unwrap = false; break HS; } case NEED_UNWRAP: { if (TRACE_SSL) msg.trace("TLS handshake NEED_UNWRAP"); if (wrap && ! flushed && ! sinkConduit.flush()) { // our wrap operation was probably actually a handshake msg, but we couldn't flush it // we need to flush it to proceed else the other side may never send us a response state |= FLAG_FLUSH_NEEDED; } state |= WRITE_FLAG_NEEDS_READ; if (readBlocked) { return actualIOResult(xfer, goal, flushed, eof); } wrap = false; unwrap = true; break HS; } default: { throw msg.unexpectedHandshakeStatus(result.getHandshakeStatus()); } } } } } finally { this.state = state; if (wakeupReads) { wakeupReads(); } } } class TaskWrapper implements Runnable { private final Runnable task; TaskWrapper(final Runnable task) { this.task = task; } public void run() { try { task.run(); } finally { synchronized (JsseStreamConduit.this) { if (tasks -- == 1) JsseStreamConduit.this.notifyAll(); } } } } }