= timeout)) {
throw new SocketTimeoutException(sm.getString("channel.nio.ssl.timeoutDuringHandshake"));
}
isReadable = key.isReadable();
isWriteable = key.isWritable();
}
}
}
} catch (IOException x) {
closeSilently();
throw x;
} catch (Exception cx) {
closeSilently();
IOException x = new IOException(cx);
throw x;
} finally {
if (key != null) {
try {
key.cancel();
} catch (Exception ignore) {
}
}
if (selector != null) {
try {
selector.close();
} catch (Exception ignore) {
}
}
}
}
/**
* Executes all the tasks needed on the same thread.
* @return the status
*/
protected SSLEngineResult.HandshakeStatus tasks() {
Runnable r = null;
while ((r = sslEngine.getDelegatedTask()) != null) {
r.run();
}
return sslEngine.getHandshakeStatus();
}
/**
* Performs the WRAP function
* @param doWrite boolean
* @return the result
* @throws IOException An IO error occurred
*/
protected SSLEngineResult handshakeWrap(boolean doWrite) throws IOException {
//this should never be called with a network buffer that contains data
//so we can clear it here.
netOutBuffer.clear();
//perform the wrap
getBufHandler().configureWriteBufferForRead();
SSLEngineResult result = sslEngine.wrap(getBufHandler().getWriteBuffer(), netOutBuffer);
//prepare the results to be written
netOutBuffer.flip();
//set the status
handshakeStatus = result.getHandshakeStatus();
//optimization, if we do have a writable channel, write it now
if (doWrite) {
flush(netOutBuffer);
}
return result;
}
/**
* Perform handshake unwrap
* @param doread boolean
* @return the result
* @throws IOException An IO error occurred
*/
protected SSLEngineResult handshakeUnwrap(boolean doread) throws IOException {
if (netInBuffer.position() == netInBuffer.limit()) {
//clear the buffer if we have emptied it out on data
netInBuffer.clear();
}
if (doread) {
//if we have data to read, read it
int read = sc.read(netInBuffer);
if (read == -1) {
throw new IOException(sm.getString("channel.nio.ssl.eofDuringHandshake"));
}
}
SSLEngineResult result;
boolean cont = false;
//loop while we can perform pure SSLEngine data
do {
//prepare the buffer with the incoming data
netInBuffer.flip();
//call unwrap
getBufHandler().configureReadBufferForWrite();
result = sslEngine.unwrap(netInBuffer, getBufHandler().getReadBuffer());
//compact the buffer, this is an optional method, wonder what would happen if we didn't
netInBuffer.compact();
//read in the status
handshakeStatus = result.getHandshakeStatus();
if ( result.getStatus() == SSLEngineResult.Status.OK &&
result.getHandshakeStatus() == HandshakeStatus.NEED_TASK ) {
//execute tasks if we need to
handshakeStatus = tasks();
}
//perform another unwrap?
cont = result.getStatus() == SSLEngineResult.Status.OK &&
handshakeStatus == HandshakeStatus.NEED_UNWRAP;
}while ( cont );
return result;
}
/**
* Sends an SSL close message, will not physically close the connection here.
*
To close the connection, you could do something like
*
* close();
* while (isOpen() && !myTimeoutFunction()) Thread.sleep(25);
* if ( isOpen() ) close(true); //forces a close if you timed out
*
* @throws IOException if an I/O error occurs
* @throws IOException if there is data on the outgoing network buffer and
* we are unable to flush it
*/
@Override
public void close() throws IOException {
if (closing) {
return;
}
closing = true;
sslEngine.closeOutbound();
if (!flush(netOutBuffer)) {
throw new IOException(sm.getString("channel.nio.ssl.remainingDataDuringClose"));
}
//prep the buffer for the close message
netOutBuffer.clear();
//perform the close, since we called sslEngine.closeOutbound
SSLEngineResult handshake = sslEngine.wrap(getEmptyBuf(), netOutBuffer);
//we should be in a close state
if (handshake.getStatus() != SSLEngineResult.Status.CLOSED) {
throw new IOException(sm.getString("channel.nio.ssl.invalidCloseState"));
}
//prepare the buffer for writing
netOutBuffer.flip();
//if there is data to be written
flush(netOutBuffer);
//is the channel closed?
closed = (!netOutBuffer.hasRemaining() && (handshake.getHandshakeStatus() != HandshakeStatus.NEED_WRAP));
}
@Override
public void close(boolean force) throws IOException {
try {
close();
} finally {
if (force || closed) {
closed = true;
sc.socket().close();
sc.close();
}
}
}
private void closeSilently() {
try {
close(true);
} catch (IOException ioe) {
// This is expected - swallowing the exception is the reason this
// method exists. Log at debug in case someone is interested.
log.debug(sm.getString("channel.nio.ssl.closeSilentError"), ioe);
}
}
/**
* Reads a sequence of bytes from this channel into the given buffer.
*
* @param dst The buffer into which bytes are to be transferred
* @return The number of bytes read, possibly zero, or -1 if
* the channel has reached end-of-stream
* @throws IOException If some other I/O error occurs
* @throws IllegalArgumentException if the destination buffer is different
* than getBufHandler().getReadBuffer()
*/
@Override
public int read(ByteBuffer dst) throws IOException {
//are we in the middle of closing or closed?
if (closing || closed) {
return -1;
}
//did we finish our handshake?
if (!handshakeComplete) {
throw new IllegalStateException(sm.getString("channel.nio.ssl.incompleteHandshake"));
}
//read from the network
int netread = sc.read(netInBuffer);
//did we reach EOF? if so send EOF up one layer.
if (netread == -1) {
return -1;
}
//the data read
int read = 0;
//the SSL engine result
SSLEngineResult unwrap;
do {
//prepare the buffer
netInBuffer.flip();
//unwrap the data
unwrap = sslEngine.unwrap(netInBuffer, dst);
//compact the buffer
netInBuffer.compact();
if (unwrap.getStatus() == Status.OK || unwrap.getStatus() == Status.BUFFER_UNDERFLOW) {
//we did receive some data, add it to our total
read += unwrap.bytesProduced();
//perform any tasks if needed
if (unwrap.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
tasks();
}
//if we need more network data, then bail out for now.
if (unwrap.getStatus() == Status.BUFFER_UNDERFLOW) {
break;
}
} else if (unwrap.getStatus() == Status.BUFFER_OVERFLOW) {
if (read > 0) {
// Buffer overflow can happen if we have read data. Return
// so the destination buffer can be emptied before another
// read is attempted
break;
} else {
// The SSL session has increased the required buffer size
// since the buffer was created.
if (dst == getBufHandler().getReadBuffer()) {
// This is the normal case for this code
getBufHandler().expand(sslEngine.getSession().getApplicationBufferSize());
dst = getBufHandler().getReadBuffer();
} else if (dst == getAppReadBufHandler().getByteBuffer()) {
getAppReadBufHandler()
.expand(sslEngine.getSession().getApplicationBufferSize());
dst = getAppReadBufHandler().getByteBuffer();
} else {
// Can't expand the buffer as there is no way to signal
// to the caller that the buffer has been replaced.
throw new IOException(
sm.getString("channel.nio.ssl.unwrapFailResize", unwrap.getStatus()));
}
}
} else {
// Something else went wrong
throw new IOException(sm.getString("channel.nio.ssl.unwrapFail", unwrap.getStatus()));
}
} while (netInBuffer.position() != 0); //continue to unwrapping as long as the input buffer has stuff
return read;
}
@Override
public long read(ByteBuffer[] dsts, int offset, int length)
throws IOException {
//are we in the middle of closing or closed?
if (closing || closed) {
return -1;
}
//did we finish our handshake?
if (!handshakeComplete) {
throw new IllegalStateException(sm.getString("channel.nio.ssl.incompleteHandshake"));
}
//read from the network
int netread = sc.read(netInBuffer);
//did we reach EOF? if so send EOF up one layer.
if (netread == -1) {
return -1;
}
//the data read
int read = 0;
//the SSL engine result
SSLEngineResult unwrap;
boolean processOverflow = false;
do {
boolean useOverflow = false;
if (processOverflow) {
useOverflow = true;
}
processOverflow = false;
//prepare the buffer
netInBuffer.flip();
//unwrap the data
unwrap = sslEngine.unwrap(netInBuffer, dsts, offset, length);
//compact the buffer
netInBuffer.compact();
if (unwrap.getStatus() == Status.OK || unwrap.getStatus() == Status.BUFFER_UNDERFLOW) {
//we did receive some data, add it to our total
read += unwrap.bytesProduced();
if (useOverflow) {
// Remove the data read into the overflow buffer
read -= getBufHandler().getReadBuffer().position();
}
//perform any tasks if needed
if (unwrap.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
tasks();
}
//if we need more network data, then bail out for now.
if (unwrap.getStatus() == Status.BUFFER_UNDERFLOW) {
break;
}
} else if (unwrap.getStatus() == Status.BUFFER_OVERFLOW) {
if (read > 0) {
// Buffer overflow can happen if we have read data. Return
// so the destination buffer can be emptied before another
// read is attempted
break;
} else {
ByteBuffer readBuffer = getBufHandler().getReadBuffer();
boolean found = false;
boolean resized = true;
for (int i = 0; i < length; i++) {
// The SSL session has increased the required buffer size
// since the buffer was created.
if (dsts[offset + i] == getBufHandler().getReadBuffer()) {
getBufHandler().expand(sslEngine.getSession().getApplicationBufferSize());
if (dsts[offset + i] == getBufHandler().getReadBuffer()) {
resized = false;
}
dsts[offset + i] = getBufHandler().getReadBuffer();
found = true;
} else if (getAppReadBufHandler() != null && dsts[offset + i] == getAppReadBufHandler().getByteBuffer()) {
getAppReadBufHandler().expand(sslEngine.getSession().getApplicationBufferSize());
if (dsts[offset + i] == getAppReadBufHandler().getByteBuffer()) {
resized = false;
}
dsts[offset + i] = getAppReadBufHandler().getByteBuffer();
found = true;
}
}
if (found) {
if (!resized) {
throw new IOException(sm.getString("channel.nio.ssl.unwrapFail", unwrap.getStatus()));
}
} else {
// Add the main read buffer in the destinations and try again
ByteBuffer[] dsts2 = new ByteBuffer[dsts.length + 1];
int dstOffset = 0;
for (int i = 0; i < dsts.length + 1; i++) {
if (i == offset + length) {
dsts2[i] = readBuffer;
dstOffset = -1;
} else {
dsts2[i] = dsts[i + dstOffset];
}
}
dsts = dsts2;
length++;
getBufHandler().configureReadBufferForWrite();
processOverflow = true;
}
}
} else {
// Something else went wrong
throw new IOException(sm.getString("channel.nio.ssl.unwrapFail", unwrap.getStatus()));
}
} while (netInBuffer.position() != 0 || processOverflow); //continue to unwrapping as long as the input buffer has stuff
return read;
}
/**
* Writes a sequence of bytes to this channel from the given buffer.
*
* @param src The buffer from which bytes are to be retrieved
* @return The number of bytes written, possibly zero
* @throws IOException If some other I/O error occurs
*/
@Override
public int write(ByteBuffer src) throws IOException {
checkInterruptStatus();
if (src == this.netOutBuffer) {
//we can get here through a recursive call
//by using the NioBlockingSelector
int written = sc.write(src);
return written;
} else {
// Are we closing or closed?
if (closing || closed) {
throw new IOException(sm.getString("channel.nio.ssl.closing"));
}
if (!flush(netOutBuffer)) {
// We haven't emptied out the buffer yet
return 0;
}
// The data buffer is empty, we can reuse the entire buffer.
netOutBuffer.clear();
SSLEngineResult result = sslEngine.wrap(src, netOutBuffer);
// The number of bytes written
int written = result.bytesConsumed();
netOutBuffer.flip();
if (result.getStatus() == Status.OK) {
if (result.getHandshakeStatus() == HandshakeStatus.NEED_TASK) tasks();
} else {
throw new IOException(sm.getString("channel.nio.ssl.wrapFail", result.getStatus()));
}
// Force a flush
flush(netOutBuffer);
return written;
}
}
@Override
public long write(ByteBuffer[] srcs, int offset, int length)
throws IOException {
checkInterruptStatus();
// Are we closing or closed?
if (closing || closed) {
throw new IOException(sm.getString("channel.nio.ssl.closing"));
}
if (!flush(netOutBuffer)) {
// We haven't emptied out the buffer yet
return 0;
}
// The data buffer is empty, we can reuse the entire buffer.
netOutBuffer.clear();
SSLEngineResult result = sslEngine.wrap(srcs, offset, length, netOutBuffer);
// The number of bytes written
int written = result.bytesConsumed();
netOutBuffer.flip();
if (result.getStatus() == Status.OK) {
if (result.getHandshakeStatus() == HandshakeStatus.NEED_TASK) tasks();
} else {
throw new IOException(sm.getString("channel.nio.ssl.wrapFail", result.getStatus()));
}
// Force a flush
flush(netOutBuffer);
return written;
}
@Override
public int getOutboundRemaining() {
return netOutBuffer.remaining();
}
@Override
public boolean flushOutbound() throws IOException {
int remaining = netOutBuffer.remaining();
flush(netOutBuffer);
int remaining2= netOutBuffer.remaining();
return remaining2 < remaining;
}
@Override
public boolean isHandshakeComplete() {
return handshakeComplete;
}
@Override
public boolean isClosing() {
return closing;
}
public SSLEngine getSslEngine() {
return sslEngine;
}
public ByteBuffer getEmptyBuf() {
return emptyBuf;
}
}