org.jboss.xnio.channels.WrappingSslTcpChannel 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 2009, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.xnio.channels;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.Executor;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.Set;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.FileChannel;
import java.net.InetSocketAddress;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import org.jboss.xnio.ChannelListener;
import org.jboss.xnio.Option;
import org.jboss.xnio.IoUtils;
import org.jboss.xnio.Sequence;
import org.jboss.xnio.Buffers;
import org.jboss.xnio.Options;
import org.jboss.xnio.log.Logger;
import static org.jboss.xnio.Buffers.flip;
final class WrappingSslTcpChannel implements SslTcpChannel {
private static final Logger log = Logger.getLogger("org.jboss.xnio.ssl");
private final TcpChannel tcpChannel;
private final SSLEngine sslEngine;
private final Executor executor;
private volatile ChannelListener super SslTcpChannel> readListener = null;
private volatile ChannelListener super SslTcpChannel> writeListener = null;
private volatile ChannelListener super SslTcpChannel> closeListener = null;
private static final AtomicReferenceFieldUpdater readListenerUpdater = AtomicReferenceFieldUpdater.newUpdater(WrappingSslTcpChannel.class, ChannelListener.class, "readListener");
private static final AtomicReferenceFieldUpdater writeListenerUpdater = AtomicReferenceFieldUpdater.newUpdater(WrappingSslTcpChannel.class, ChannelListener.class, "writeListener");
private static final AtomicReferenceFieldUpdater closeListenerUpdater = AtomicReferenceFieldUpdater.newUpdater(WrappingSslTcpChannel.class, ChannelListener.class, "closeListener");
private final ChannelListener.Setter readSetter = IoUtils.getSetter(this, readListenerUpdater);
private final ChannelListener.Setter writeSetter = IoUtils.getSetter(this, writeListenerUpdater);
private final ChannelListener.Setter closeSetter = IoUtils.getSetter(this, closeListenerUpdater);
private final ChannelListener tcpCloseListener = new ChannelListener() {
public void handleEvent(final TcpChannel channel) {
IoUtils.safeClose(WrappingSslTcpChannel.this);
IoUtils.invokeChannelListener(WrappingSslTcpChannel.this, closeListener);
}
};
private final Runnable readTriggeredTask = new Runnable() {
public void run() {
runReadListener();
}
};
private final ChannelListener tcpReadListener = new ReadListener();
private final ChannelListener tcpWriteListener = new WriteListener();
private void runReadListener() {
IoUtils.invokeChannelListener(this, readListener);
}
private void runWriteListener() {
IoUtils.invokeChannelListener(this, writeListener);
}
private final Lock mainLock = new ReentrantLock();
/**
* Condition: threads waiting in awaitReadable(); signalAll whenever data is added to the read buffer, or whenever
* the TCP channel becomes readable.
*/
private final Condition readAwaiters = mainLock.newCondition();
/**
* Condition: threads waiting in awaitWritable(); signalAll whenever {@code needsUnwrap} is cleared
*/
private final Condition writeAwaiters = mainLock.newCondition();
private boolean userReads;
private boolean userWrites;
// readers need a wrap to proceed
private boolean needsWrap;
// writers need an unwrap to proceed
private boolean needsUnwrap;
// signal new data available
private boolean newReadData;
/**
* The application data read buffer. Filled if a read required more space than the user buffer had available. Reads
* pull data from this buffer first, and additional data from unwrap() if needed. This buffer should remain
* compacted for writing when the lock isn't held.
*/
private ByteBuffer readBuffer = Buffers.EMPTY_BYTE_BUFFER;
/**
* The socket receive buffer. Staging area for unwrap operations. This buffer should remain either empty or flipped
* for reading when the lock is not held.
*/
private ByteBuffer receiveBuffer = Buffers.EMPTY_BYTE_BUFFER;
/**
* The socket send buffer. Target area for wrap operations. Wrap operations have no source buffer, as there
* is generally no minimum size for outbound data (thankfully). This buffer should remain either empty or unflipped
* for appending when the lock is not held.
*/
private ByteBuffer sendBuffer = Buffers.EMPTY_BYTE_BUFFER;
WrappingSslTcpChannel(final TcpChannel tcpChannel, final SSLEngine sslEngine, final Executor executor) {
this.tcpChannel = tcpChannel;
this.sslEngine = sslEngine;
this.executor = executor;
tcpChannel.getReadSetter().set(tcpReadListener);
tcpChannel.getWriteSetter().set(tcpWriteListener);
tcpChannel.getCloseSetter().set(tcpCloseListener);
}
public InetSocketAddress getPeerAddress() {
return tcpChannel.getPeerAddress();
}
public InetSocketAddress getLocalAddress() {
return tcpChannel.getLocalAddress();
}
public void startHandshake() throws IOException {
sslEngine.beginHandshake();
}
public SSLSession getSslSession() {
return sslEngine.getSession();
}
public long transferTo(final long position, final long count, final FileChannel target) throws IOException {
return target.transferFrom(this, position, count);
}
public ChannelListener.Setter getReadSetter() {
return readSetter;
}
public long transferFrom(final FileChannel src, final long position, final long count) throws IOException {
return src.transferTo(position, count, this);
}
public ChannelListener.Setter getWriteSetter() {
return writeSetter;
}
public ChannelListener.Setter getCloseSetter() {
return closeSetter;
}
public boolean flush() throws IOException {
final Lock mainLock = this.mainLock;
mainLock.lock();
try {
return doFlush();
} finally {
mainLock.unlock();
}
}
/**
* Actually do the flush. Call with the (write) lock held.
*
* @return {@code true} if the buffers were flushed completely, or {@code false} if some data remains in the buffer
* @throws IOException if an I/O error occurs
*/
private boolean doFlush() throws IOException {
final TcpChannel tcpChannel = this.tcpChannel;
WRAP: for (;;) {
final ByteBuffer sendBuffer = this.sendBuffer;
sendBuffer.flip();
try {
while (sendBuffer.hasRemaining()) {
log.trace("Flushing send buffer %s", sendBuffer);
if (tcpChannel.write(sendBuffer) == 0) {
log.trace("Send (in flush) would block, return false");
return false;
}
}
if (! tcpChannel.flush()) {
log.trace("Flushing TCP channel would block, return false");
return false;
}
} finally {
sendBuffer.compact();
}
// now wrap until everything is flushed
final SSLEngine sslEngine = this.sslEngine;
log.trace("Wrapping empty buffer into send buffer %s", sendBuffer);
final SSLEngineResult wrapResult = sslEngine.wrap(Buffers.EMPTY_BYTE_BUFFER, sendBuffer);
log.trace("Wrap result is %s", wrapResult);
final int produced = wrapResult.bytesProduced();
switch (wrapResult.getStatus()) {
case CLOSED: {
return true;
}
case BUFFER_UNDERFLOW:
case OK: {
if (produced > 0) {
log.trace("Data produced, flush needed");
continue;
}
// make sure some handshake step is not needed to proceed
switch (wrapResult.getHandshakeStatus()) {
case NOT_HANDSHAKING:
case FINISHED: {
log.trace("Fully flushed, return true");
// fully flushed!
return true;
}
case NEED_TASK: {
final Runnable task = sslEngine.getDelegatedTask();
log.trace("Running delegated task %s", task);
task.run();
log.trace("Finished delegated task %s", task);
continue;
}
case NEED_UNWRAP: {
log.trace("Unwrap needed to proceed with flush");
// Ya gotta get input to get output...
UNWRAP: for (;;) {
final ByteBuffer receiveBuffer = this.receiveBuffer;
final ByteBuffer readBuffer = this.readBuffer;
log.trace("Unwrapping from receive buffer %s to read buffer %s", receiveBuffer, readBuffer);
final SSLEngineResult unwrapResult = sslEngine.unwrap(receiveBuffer, readBuffer);
readAwaiters.signalAll();
switch (unwrapResult.getStatus()) {
case BUFFER_UNDERFLOW: {
newReadData = false;
// not enough data. First, see if there is room left in the receive buf - if not, grow it.
if (receiveBuffer.position() == 0 && receiveBuffer.limit() == receiveBuffer.capacity()) {
log.trace("Receive buffer is too small, growing from %s", receiveBuffer);
// receive buffer is full but it's still not big enough, so grow it
final int pktBufSize = sslEngine.getSession().getPacketBufferSize();
if (receiveBuffer.capacity() >= pktBufSize) {
// it's already the required size...
throw new IOException("Unexpected/inexplicable buffer underflow from the SSL engine");
}
log.trace("Grew receive buffer to %s", this.receiveBuffer = Buffers.flip(ByteBuffer.allocate(pktBufSize).put(receiveBuffer)));
continue UNWRAP;
}
// not enough data in receive buffer, fill it up
receiveBuffer.compact();
try {
log.trace("Reading data into receive buffer %s", receiveBuffer);
final int res = tcpChannel.read(receiveBuffer);
if (res == -1) {
log.trace("End of input stream reached");
// bad news, end of stream...
sslEngine.closeInbound();
// but maybe that counts as unwrapping something :)
continue WRAP;
} else if (res == 0) {
log.trace("Read would block, set needsUnwrap = true");
needsUnwrap = true;
return false;
} else {
newReadData = true;
// retry the unwrap!
continue UNWRAP;
}
} finally {
receiveBuffer.flip();
}
}
case CLOSED: {
log.trace("Engine is closed, everything must be flushed; return true");
// I guess everything is flushed?
return true;
}
case OK: {
log.trace("Unwrap complete, proceeding with wrap");
// great, now we shold be able to proceed with wrap
continue WRAP;
}
default: {
throw new IOException("Unexpected unwrap result status " + unwrapResult.getStatus());
}
}
// not reached
}
// not reached
}
default: {
throw new IOException("Unexpected wrap result handshake status " + wrapResult.getStatus());
}
}
}
default: {
throw new IOException("Unexpected wrap result status " + wrapResult.getStatus());
}
}
}
}
public boolean isOpen() {
return tcpChannel.isOpen();
}
public void close() throws IOException {
final Lock mainLock = this.mainLock;
mainLock.lock();
try {
sslEngine.closeOutbound();
IOException e1 = null;
IOException e2 = null;
try {
sslEngine.closeInbound();
} catch (IOException e) {
e1 = e;
}
try {
tcpChannel.close();
} catch (IOException e) {
e2 = e;
}
if (e1 != null && e2 != null) {
final IOException t = new IOException("Multiple failures on close! The second exception is: " + e2.toString());
t.initCause(e1);
throw t;
}
if (e1 != null) {
throw e1;
}
if (e2 != null) {
throw e2;
}
} finally {
mainLock.unlock();
}
}
private static final Set
© 2015 - 2025 Weber Informatics LLC | Privacy Policy