org.xnio.ssl.JsseStreamConduit Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of xnio-api Show documentation
Show all versions of xnio-api Show documentation
The API JAR of the XNIO project
/*
* 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 receiveBuffer;
/** The buffer from which outbound SSL data is sent. */
private final Pooled sendBuffer;
/** The buffer into which inbound clear data is written. */
private final Pooled 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 socketBufferPool, final Pool applicationBufferPool) {
Pooled receiveBuffer;
Pooled sendBuffer;
Pooled 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 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();
}
}
}
}
}