下面列出了javax.net.ssl.SSLEngineResult#bytesConsumed ( ) 实例代码,或者点击链接到github查看源代码,也可以在右侧发表评论。
private void checkResult(SSLEngineResult result, boolean wrap)
throws SSLException {
handshakeStatus = result.getHandshakeStatus();
resultStatus = result.getStatus();
if (resultStatus != Status.OK &&
(wrap || resultStatus != Status.BUFFER_UNDERFLOW)) {
throw new SSLException(
sm.getString("asyncChannelWrapperSecure.check.notOk", resultStatus));
}
if (wrap && result.bytesConsumed() != 0) {
throw new SSLException(sm.getString("asyncChannelWrapperSecure.check.wrap"));
}
if (!wrap && result.bytesProduced() != 0) {
throw new SSLException(sm.getString("asyncChannelWrapperSecure.check.unwrap"));
}
}
protected void wrap() {
try {
if (!netOutBuffer.hasRemaining()) {
netOutBuffer.clear();
SSLEngineResult result = sslEngine.wrap(src, netOutBuffer);
written = result.bytesConsumed();
netOutBuffer.flip();
if (result.getStatus() == Status.OK) {
if (result.getHandshakeStatus() == HandshakeStatus.NEED_TASK)
tasks();
} else {
t = new IOException(sm.getString("channel.nio.ssl.wrapFail", result.getStatus()));
}
}
integer = sc.write(netOutBuffer);
} catch (SSLException e) {
t = e;
}
}
private WrapResult wrapLoop(ByteBufferSet source) throws SSLException {
while (true) {
SSLEngineResult result = callEngineWrap(source);
switch (result.getStatus()) {
case OK:
case CLOSED:
return new WrapResult(result.bytesConsumed(), result.getHandshakeStatus());
case BUFFER_OVERFLOW:
Util.assertTrue(result.bytesConsumed() == 0);
outEncrypted.enlarge();
break;
case BUFFER_UNDERFLOW:
throw new IllegalStateException();
default:
throw new RuntimeException("reached default clause in switch");
}
}
}
private void checkResult(SSLEngineResult result, boolean wrap)
throws SSLException {
handshakeStatus = result.getHandshakeStatus();
resultStatus = result.getStatus();
if (resultStatus != Status.OK &&
(wrap || resultStatus != Status.BUFFER_UNDERFLOW)) {
throw new SSLException("TODO");
}
if (wrap && result.bytesConsumed() != 0) {
throw new SSLException("TODO");
}
if (!wrap && result.bytesProduced() != 0) {
throw new SSLException("TODO");
}
}
/**
* Try to flush out any existing outbound data, then try to wrap
* anything new contained in the src buffer.
* <p>
* Return the number of bytes actually consumed from the buffer,
* but the data may actually be still sitting in the output buffer,
* waiting to be flushed.
*/
private int doWrite(ByteBuffer src) throws IOException {
int retValue = 0;
if (outNetBB.hasRemaining() && !tryFlush(outNetBB)) {
return retValue;
}
/*
* The data buffer is empty, we can reuse the entire buffer.
*/
outNetBB.clear();
SSLEngineResult result = sslEngine.wrap(src, outNetBB);
retValue = result.bytesConsumed();
outNetBB.flip();
if (result.getStatus() == Status.OK) {
if (result.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
doTasks();
}
} else {
throw new IOException("sslEngine error during data write: " +
result.getStatus());
}
/*
* Try to flush the data, regardless of whether or not
* it's been selected. Odds of a write buffer being full
* is less than a read buffer being empty.
*/
tryFlush(src);
if (outNetBB.hasRemaining()) {
tryFlush(outNetBB);
}
return retValue;
}
@Override
public void run() {
long written = 0;
try {
for (int i = offset; i < offset + length; i++) {
ByteBuffer src = srcs[i];
while (src.hasRemaining()) {
socketWriteBuffer.clear();
// Encrypt the data
SSLEngineResult r = sslEngine.wrap(src, socketWriteBuffer);
written += r.bytesConsumed();
Status s = r.getStatus();
if (s == Status.OK || s == Status.BUFFER_OVERFLOW) {
// Need to write out the bytes and may need to read from
// the source again to empty it
} else {
// Status.BUFFER_UNDERFLOW - only happens on unwrap
// Status.CLOSED - unexpected
throw new IllegalStateException(sm.getString(
"asyncChannelWrapperSecure.statusWrap"));
}
// Check for tasks
if (r.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
Runnable runnable = sslEngine.getDelegatedTask();
while (runnable != null) {
runnable.run();
runnable = sslEngine.getDelegatedTask();
}
}
socketWriteBuffer.flip();
// Do the write
int toWrite = r.bytesProduced();
while (toWrite > 0) {
Future<Integer> f =
socketChannel.write(socketWriteBuffer);
Integer socketWrite = f.get();
toWrite -= socketWrite.intValue();
}
}
}
if (writing.compareAndSet(true, false)) {
future.complete(Long.valueOf(written));
} else {
future.fail(new IllegalStateException(sm.getString(
"asyncChannelWrapperSecure.wrongStateWrite")));
}
} catch (Exception e) {
writing.set(false);
future.fail(e);
}
}
/**
* This method will not call
* {@link #setHandshakeFailure(ChannelHandlerContext, Throwable, boolean, boolean, boolean)} or
* {@link #setHandshakeFailure(ChannelHandlerContext, Throwable)}.
* @return {@code true} if this method ends on {@link SSLEngineResult.HandshakeStatus#NOT_HANDSHAKING}.
*/
private boolean wrapNonAppData(ChannelHandlerContext ctx, boolean inUnwrap) throws SSLException {
ByteBuf out = null;
ByteBufAllocator alloc = ctx.alloc();
try {
// Only continue to loop if the handler was not removed in the meantime.
// See https://github.com/netty/netty/issues/5860
while (!ctx.isRemoved()) {
if (out == null) {
// As this is called for the handshake we have no real idea how big the buffer needs to be.
// That said 2048 should give us enough room to include everything like ALPN / NPN data.
// If this is not enough we will increase the buffer in wrap(...).
out = allocateOutNetBuf(ctx, 2048, 1);
}
SSLEngineResult result = wrap(alloc, engine, Unpooled.EMPTY_BUFFER, out);
if (result.bytesProduced() > 0) {
ctx.write(out);
if (inUnwrap) {
needsFlush = true;
}
out = null;
}
switch (result.getHandshakeStatus()) {
case FINISHED:
setHandshakeSuccess();
return false;
case NEED_TASK:
runDelegatedTasks();
break;
case NEED_UNWRAP:
if (inUnwrap) {
// If we asked for a wrap, the engine requested an unwrap, and we are in unwrap there is
// no use in trying to call wrap again because we have already attempted (or will after we
// return) to feed more data to the engine.
return false;
}
unwrapNonAppData(ctx);
break;
case NEED_WRAP:
break;
case NOT_HANDSHAKING:
setHandshakeSuccessIfStillHandshaking();
// Workaround for TLS False Start problem reported at:
// https://github.com/netty/netty/issues/1108#issuecomment-14266970
if (!inUnwrap) {
unwrapNonAppData(ctx);
}
return true;
default:
throw new IllegalStateException("Unknown handshake status: " + result.getHandshakeStatus());
}
if (result.bytesProduced() == 0) {
break;
}
// It should not consume empty buffers when it is not handshaking
// Fix for Android, where it was encrypting empty buffers even when not handshaking
if (result.bytesConsumed() == 0 && result.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING) {
break;
}
}
} finally {
if (out != null) {
out.release();
}
}
return false;
}
@Test
public void testBufferUnderFlowAvoidedIfJDKCompatabilityModeOff() throws Exception {
SelfSignedCertificate cert = new SelfSignedCertificate();
clientSslCtx = SslContextBuilder
.forClient()
.trustManager(cert.cert())
.sslProvider(sslClientProvider())
.build();
SSLEngine client = clientSslCtx.newHandler(UnpooledByteBufAllocator.DEFAULT).engine();
serverSslCtx = SslContextBuilder
.forServer(cert.certificate(), cert.privateKey())
.sslProvider(sslServerProvider())
.build();
SSLEngine server = serverSslCtx.newHandler(UnpooledByteBufAllocator.DEFAULT).engine();
try {
ByteBuffer plainClient = allocateBuffer(1024);
plainClient.limit(plainClient.capacity());
ByteBuffer encClientToServer = allocateBuffer(client.getSession().getPacketBufferSize());
ByteBuffer plainServer = allocateBuffer(server.getSession().getApplicationBufferSize());
handshake(client, server);
SSLEngineResult result = client.wrap(plainClient, encClientToServer);
assertEquals(SSLEngineResult.Status.OK, result.getStatus());
assertEquals(result.bytesConsumed(), plainClient.capacity());
// Flip so we can read it.
encClientToServer.flip();
int remaining = encClientToServer.remaining();
// We limit the buffer so we have less then the header to read, this should result in an BUFFER_UNDERFLOW.
encClientToServer.limit(SslUtils.SSL_RECORD_HEADER_LENGTH - 1);
result = server.unwrap(encClientToServer, plainServer);
assertEquals(SSLEngineResult.Status.OK, result.getStatus());
assertEquals(SslUtils.SSL_RECORD_HEADER_LENGTH - 1, result.bytesConsumed());
assertEquals(0, result.bytesProduced());
remaining -= result.bytesConsumed();
// We limit the buffer so we can read the header but not the rest, this should result in an
// BUFFER_UNDERFLOW.
encClientToServer.limit(SslUtils.SSL_RECORD_HEADER_LENGTH);
result = server.unwrap(encClientToServer, plainServer);
assertEquals(SSLEngineResult.Status.OK, result.getStatus());
assertEquals(1, result.bytesConsumed());
assertEquals(0, result.bytesProduced());
remaining -= result.bytesConsumed();
// We limit the buffer so we can read the header and partly the rest, this should result in an
// BUFFER_UNDERFLOW.
encClientToServer.limit(
SslUtils.SSL_RECORD_HEADER_LENGTH + remaining - 1 - SslUtils.SSL_RECORD_HEADER_LENGTH);
result = server.unwrap(encClientToServer, plainServer);
assertEquals(SSLEngineResult.Status.OK, result.getStatus());
assertEquals(encClientToServer.limit() - SslUtils.SSL_RECORD_HEADER_LENGTH, result.bytesConsumed());
assertEquals(0, result.bytesProduced());
remaining -= result.bytesConsumed();
// Reset limit so we can read the full record.
encClientToServer.limit(remaining);
assertEquals(0, encClientToServer.remaining());
result = server.unwrap(encClientToServer, plainServer);
assertEquals(SSLEngineResult.Status.BUFFER_UNDERFLOW, result.getStatus());
assertEquals(0, result.bytesConsumed());
assertEquals(0, result.bytesProduced());
encClientToServer.position(0);
result = server.unwrap(encClientToServer, plainServer);
assertEquals(SSLEngineResult.Status.OK, result.getStatus());
assertEquals(remaining, result.bytesConsumed());
assertEquals(0, result.bytesProduced());
} finally {
cert.delete();
cleanupClientSslEngine(client);
cleanupServerSslEngine(server);
}
}
@Override
public OutputResult output(ByteBuffer outputData, boolean isFinalDataOfElement, boolean destinationAddressChanged,
boolean moreDataAvailable) throws SSLException {
if (outputData != null) {
pendingOutputData.add(outputData);
pendingOutputBytes += outputData.remaining();
if (moreDataAvailable && pendingOutputBytes < MAX_PENDING_OUTPUT_BYTES) {
return OutputResult.NO_OUTPUT;
}
}
ByteBuffer[] outputDataArray = pendingOutputData.toArray(new ByteBuffer[pendingOutputData.size()]);
myNetData.clear();
while (true) {
SSLEngineResult result;
try {
result = engine.wrap(outputDataArray, myNetData);
} catch (SSLException e) {
handleSslException(e);
throw e;
}
debugLogSslEngineResult("wrap", result);
SSLEngineResult.Status engineResultStatus = result.getStatus();
pendingOutputBytes -= result.bytesConsumed();
if (engineResultStatus == SSLEngineResult.Status.OK) {
wrapInBytes += result.bytesConsumed();
wrapOutBytes += result.bytesProduced();
SSLEngineResult.HandshakeStatus handshakeStatus = handleHandshakeStatus(result);
switch (handshakeStatus) {
case NEED_UNWRAP:
// NEED_UNWRAP means that we need to receive something in order to continue the handshake. The
// standard channelSelectedCallback logic will take care of this, as there is eventually always
// a interest to read from the socket.
break;
case NEED_WRAP:
// Same as need task: Cycle the reactor.
case NEED_TASK:
// Note that we also set pendingOutputFilterData in the OutputResult in the NEED_TASK case, as
// we also want to retry the wrap() operation above in this case.
return new OutputResult(true, myNetData);
default:
break;
}
}
switch (engineResultStatus) {
case OK:
// No need to outputData.compact() here, since we do not reuse the buffer.
// Clean up the pending output data.
pruneBufferList(pendingOutputData);
return new OutputResult(!pendingOutputData.isEmpty(), myNetData);
case CLOSED:
pendingOutputData.clear();
return OutputResult.NO_OUTPUT;
case BUFFER_OVERFLOW:
LOGGER.warning("SSLEngine status BUFFER_OVERFLOW, this is hopefully uncommon");
int outputDataRemaining = outputData != null ? outputData.remaining() : 0;
int newCapacity = (int) (1.3 * outputDataRemaining);
// If newCapacity would not increase myNetData, then double it.
if (newCapacity <= myNetData.capacity()) {
newCapacity = 2 * myNetData.capacity();
}
ByteBuffer newMyNetData = ByteBuffer.allocateDirect(newCapacity);
myNetData.flip();
newMyNetData.put(myNetData);
myNetData = newMyNetData;
continue;
case BUFFER_UNDERFLOW:
throw new IllegalStateException(
"Buffer underflow as result of SSLEngine.wrap() should never happen");
}
}
}
SSLEngineResult unwrap(ByteBuffer encryptedData) throws SSLException
{
ByteBuffer allEncryptedData = _buffers.prependCached(encryptedData);
_buffers.prepareForUnwrap(allEncryptedData);
SSLEngineResult result = doUnwrap();
debug("unwrap: doUnwrap result: " + result);
allEncryptedData.position(result.bytesConsumed());
ByteBuffer unprocessedEncryptedData = BufferUtils.slice(allEncryptedData);
emitPlainData(result);
switch (result.getStatus()) {
case BUFFER_UNDERFLOW:
_buffers.cache(unprocessedEncryptedData);
break;
case BUFFER_OVERFLOW:
_buffers.grow(BufferType.IN_PLAIN);
if (unprocessedEncryptedData == null) {
throw new RuntimeException("Worker.unwrap had "
+ "buffer_overflow but all data was consumed!!");
} else {
unwrap(unprocessedEncryptedData);
}
break;
case OK:
if (unprocessedEncryptedData == null) {
_buffers.clearCache();
} else {
_buffers.cache(unprocessedEncryptedData);
}
break;
case CLOSED:
break;
}
if (_buffers.isCacheEmpty() == false
&& result.getStatus() == SSLEngineResult.Status.OK
&& result.bytesConsumed() > 0) {
debug("Still data in cahce");
result = unwrap(ByteBuffer.allocate(0));
}
return result;
}
public synchronized int write(ByteBuffer src) throws IOException
{
if (socketChannel.socket().isOutputShutdown())
{
throw new ClosedChannelException();
}
else if (initialized != 0)
{
handshake(SelectionKey.OP_WRITE);
return 0;
}
else if (shutdown)
{
shutdown();
return 0;
}
// Check how much to write.
int t = src.remaining();
int n = 0;
// Write as much as we can.
SSLEngineResult result;
Status status;
do
{
if (!prepare(outputBuffer, minBufferSize))
{
// Overflow!
break;
}
inputBuffer[0].flip();
try
{
result = sslEngine.wrap(src, outputBuffer[0]);
}
finally
{
outputBuffer[0].flip();
}
n += result.bytesConsumed();
status = result.getStatus();
if (status == Status.OK)
{
if (result.getHandshakeStatus() == HandshakeStatus.NEED_TASK)
{
runTasks();
}
}
else
{
if (status == Status.CLOSED)
{
shutdown();
}
throw new IOException("Write error '" + result.getStatus() + '\'');
}
} while (n < t);
// Try to flush what we got.
flush();
return n;
}
SSLEngineResult unwrap(ByteBuffer encryptedData) throws SSLException
{
ByteBuffer allEncryptedData = _buffers.prependCached(encryptedData);
_buffers.prepareForUnwrap(allEncryptedData);
SSLEngineResult result = doUnwrap();
debug("unwrap: doUnwrap result: " + result);
allEncryptedData.position(result.bytesConsumed());
ByteBuffer unprocessedEncryptedData = BufferUtils.slice(allEncryptedData);
emitPlainData(result);
switch (result.getStatus()) {
case BUFFER_UNDERFLOW:
_buffers.cache(unprocessedEncryptedData);
break;
case BUFFER_OVERFLOW:
_buffers.grow(BufferType.IN_PLAIN);
if (unprocessedEncryptedData == null) {
throw new RuntimeException("Worker.unwrap had "
+ "buffer_overflow but all data was consumed!!");
} else {
unwrap(unprocessedEncryptedData);
}
break;
case OK:
if (unprocessedEncryptedData == null) {
_buffers.clearCache();
} else {
_buffers.cache(unprocessedEncryptedData);
}
break;
case CLOSED:
break;
}
if (_buffers.isCacheEmpty() == false
&& result.getStatus() == SSLEngineResult.Status.OK
&& result.bytesConsumed() > 0) {
debug("Still data in cahce");
result = unwrap(ByteBuffer.allocate(0));
}
return result;
}
private synchronized boolean unwrap(final Buffer buffer) throws IOException
{
if (!_inbound.hasContent())
return false;
ByteBuffer bbuf=extractByteBuffer(buffer);
final SSLEngineResult result;
synchronized(bbuf)
{
ByteBuffer in_buffer=_inbound.getByteBuffer();
synchronized(in_buffer)
{
try
{
bbuf.position(buffer.putIndex());
bbuf.limit(buffer.capacity());
in_buffer.position(_inbound.getIndex());
in_buffer.limit(_inbound.putIndex());
result=_engine.unwrap(in_buffer,bbuf);
if (_logger.isDebugEnabled())
_logger.debug("{} unwrap {} {} consumed={} produced={}",
_session,
result.getStatus(),
result.getHandshakeStatus(),
result.bytesConsumed(),
result.bytesProduced());
_inbound.skip(result.bytesConsumed());
_inbound.compact();
buffer.setPutIndex(buffer.putIndex()+result.bytesProduced());
}
catch(SSLException e)
{
_logger.debug(String.valueOf(_endp), e);
_endp.close();
throw e;
}
finally
{
in_buffer.position(0);
in_buffer.limit(in_buffer.capacity());
bbuf.position(0);
bbuf.limit(bbuf.capacity());
}
}
}
switch(result.getStatus())
{
case BUFFER_UNDERFLOW:
if (_endp.isInputShutdown())
_inbound.clear();
break;
case BUFFER_OVERFLOW:
if (_logger.isDebugEnabled()) _logger.debug("{} unwrap {} {}->{}",_session,result.getStatus(),_inbound.toDetailString(),buffer.toDetailString());
break;
case OK:
if (result.getHandshakeStatus()==HandshakeStatus.FINISHED)
_handshook=true;
break;
case CLOSED:
_logger.debug("unwrap CLOSE {} {}",this,result);
if (result.getHandshakeStatus()==HandshakeStatus.FINISHED)
_endp.close();
break;
default:
_logger.debug("{} wrap default {}",_session,result);
throw new IOException(result.toString());
}
//if (LOG.isDebugEnabled() && result.bytesProduced()>0)
// LOG.debug("{} unwrapped '{}'",_session,buffer);
return result.bytesConsumed()>0 || result.bytesProduced()>0;
}
/**
* Wraps the user data and attempts to send it to the remote client. If data has already been buffered then
* this is attempted to be sent first.
*
* If the supplied buffers are null then a wrap operation is still attempted, which will happen during the
* handshaking process.
* @param userBuffers The buffers
* @param off The offset
* @param len The length
* @return The amount of data consumed
* @throws IOException
*/
private long doWrap(ByteBuffer[] userBuffers, int off, int len) throws IOException {
if(anyAreSet(state, FLAG_CLOSED)) {
throw new ClosedChannelException();
}
if(outstandingTasks > 0) {
return 0;
}
if(anyAreSet(state, FLAG_WRITE_REQUIRES_READ)) {
doUnwrap(null, 0, 0);
if(allAreClear(state, FLAG_READ_REQUIRES_WRITE)) { //unless a wrap is immediatly required we just return
return 0;
}
}
if(wrappedData != null) {
int res = sink.write(wrappedData.getBuffer());
if(res == 0 || wrappedData.getBuffer().hasRemaining()) {
return 0;
}
wrappedData.getBuffer().clear();
} else {
wrappedData = bufferPool.allocate();
}
try {
SSLEngineResult result = null;
while (result == null || (result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP && result.getStatus() != SSLEngineResult.Status.BUFFER_OVERFLOW)) {
if (userBuffers == null) {
result = engine.wrap(EMPTY_BUFFER, wrappedData.getBuffer());
} else {
result = engine.wrap(userBuffers, off, len, wrappedData.getBuffer());
}
}
wrappedData.getBuffer().flip();
if (result.getStatus() == SSLEngineResult.Status.BUFFER_UNDERFLOW) {
throw new IOException("underflow"); //todo: can this happen?
} else if (result.getStatus() == SSLEngineResult.Status.BUFFER_OVERFLOW) {
if (!wrappedData.getBuffer().hasRemaining()) { //if an earlier wrap suceeded we ignore this
throw new IOException("overflow"); //todo: handle properly
}
}
//attempt to write it out, if we fail we just return
//we ignore the handshake status, as wrap will get called again
if (wrappedData.getBuffer().hasRemaining()) {
sink.write(wrappedData.getBuffer());
}
//if it was not a complete write we just return
if (wrappedData.getBuffer().hasRemaining()) {
return result.bytesConsumed();
}
if (!handleHandshakeResult(result)) {
return 0;
}
if (result.getStatus() == SSLEngineResult.Status.CLOSED && userBuffers != null) {
notifyWriteClosed();
throw new ClosedChannelException();
}
return result.bytesConsumed();
} catch (RuntimeException|IOException|Error e) {
try {
close();
} catch (Throwable ex) {
UndertowLogger.REQUEST_LOGGER.debug("Exception closing SSLConduit after exception in doWrap()", ex);
}
throw e;
} finally {
//this can be cleared if the channel is fully closed
if(wrappedData != null) {
if (!wrappedData.getBuffer().hasRemaining()) {
wrappedData.close();
wrappedData = null;
}
}
}
}
@Override
public void run() {
long written = 0;
try {
for (int i = offset; i < offset + length; i++) {
ByteBuffer src = srcs[i];
while (src.hasRemaining()) {
socketWriteBuffer.clear();
// Encrypt the data
SSLEngineResult r = sslEngine.wrap(src, socketWriteBuffer);
written += r.bytesConsumed();
Status s = r.getStatus();
if (s == Status.OK || s == Status.BUFFER_OVERFLOW) {
// Need to write out the bytes and may need to read from
// the source again to empty it
} else {
// Status.BUFFER_UNDERFLOW - only happens on unwrap
// Status.CLOSED - unexpected
throw new IllegalStateException(sm.getString(
"asyncChannelWrapperSecure.statusWrap"));
}
// Check for tasks
if (r.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
Runnable runnable = sslEngine.getDelegatedTask();
while (runnable != null) {
runnable.run();
runnable = sslEngine.getDelegatedTask();
}
}
socketWriteBuffer.flip();
// Do the write
int toWrite = r.bytesProduced();
while (toWrite > 0) {
Future<Integer> f =
socketChannel.write(socketWriteBuffer);
Integer socketWrite = f.get();
toWrite -= socketWrite.intValue();
}
}
}
if (writing.compareAndSet(true, false)) {
future.complete(Long.valueOf(written));
} else {
future.fail(new IllegalStateException(sm.getString(
"asyncChannelWrapperSecure.wrongStateWrite")));
}
} catch (Exception e) {
future.fail(e);
}
}
/**
* 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 decrypted and written to netWriteBuffer. No guarantee that data in the temporary
* buffer will be completely written to the underlying channel right away. So the caller has to make sure to check
* the remaining bytes in the netWriteBuffer apart from checking the remaining bytes in src bytebuffer. This method
* is called from write() in the same class
* @throws IOException If some other I/O error occurs
*/
@Override
public int write(ByteBuffer src) throws IOException {
if (closing) {
throw new IllegalStateException("Channel is in closing state");
} else if (!handshakeComplete) {
return 0;
} else if (!flush(netWriteBuffer)) {
return 0;
}
int written = 0;
while (src.remaining() != 0) {
netWriteBuffer.clear();
long startTimeNs = SystemTime.getInstance().nanoseconds();
SSLEngineResult wrapResult = sslEngine.wrap(src, netWriteBuffer);
long encryptionTimeNs = SystemTime.getInstance().nanoseconds() - startTimeNs;
logger.trace("SSL encryption time: {} ns for {} bytes", encryptionTimeNs, wrapResult.bytesConsumed());
if (wrapResult.bytesConsumed() > 0) {
metrics.sslEncryptionTimeInUsPerKB.mark(encryptionTimeNs / wrapResult.bytesConsumed());
}
netWriteBuffer.flip();
//handle ssl renegotiation
if (wrapResult.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING && wrapResult.getStatus() == Status.OK) {
handshake();
metrics.sslRenegotiationCount.inc();
break;
}
if (wrapResult.getStatus() == SSLEngineResult.Status.OK) {
written += wrapResult.bytesConsumed();
if (!flush(netWriteBuffer)) {
// break if socketChannel can't accept all data in netWriteBuffer
break;
}
// otherwise, we are safe to clear the buffer for next iteration.
} else if (wrapResult.getStatus() == Status.BUFFER_OVERFLOW) {
handleWrapOverflow();
} else if (wrapResult.getStatus() == Status.BUFFER_UNDERFLOW) {
throw new IllegalStateException("SSL BUFFER_UNDERFLOW during write");
} else if (wrapResult.getStatus() == Status.CLOSED) {
throw new EOFException();
}
}
return written;
}
/**
* Wrap the underlying transport's output, passing it to the output buffer.
*
* {@link #_outputBuffer} is assumed to be writeable on entry and is guaranteed to
* be still writeable on exit.
*/
private void wrapOutput() throws SSLException
{
while (true) {
int pending = _underlyingOutput.pending();
if (pending < 0) {
_head_closed = true;
}
ByteBuffer clearOutputBuffer = _underlyingOutput.head();
SSLEngineResult result = _sslEngine.wrap(clearOutputBuffer, _outputBuffer);
logEngineClientModeAndResult(result, "output");
int written = result.bytesConsumed();
_underlyingOutput.pop(written);
pending = _underlyingOutput.pending();
Status status = result.getStatus();
switch (status) {
case CLOSED:
_head_closed = true;
break;
case OK:
break;
case BUFFER_OVERFLOW:
ByteBuffer old = _outputBuffer;
_outputBuffer = newWriteableBuffer(_outputBuffer.capacity()*2);
_head = _outputBuffer.asReadOnlyBuffer();
old.flip();
_outputBuffer.put(old);
continue;
case BUFFER_UNDERFLOW:
throw new IllegalStateException("app buffer underflow");
}
HandshakeStatus hstatus = result.getHandshakeStatus();
switch (hstatus) {
case NEED_UNWRAP:
// wait for input data
if (_inputBuffer.position() == 0 && _tail_closed) {
_head_closed = true;
}
break;
case NEED_WRAP:
// keep looping
continue;
case NEED_TASK:
runDelegatedTasks(result);
continue;
case FINISHED:
updateCipherAndProtocolName(result);
// intentionally fall through
case NOT_HANDSHAKING:
if (pending > 0 && status == Status.OK) {
continue;
} else {
break;
}
}
break;
}
}
/**
* Unwraps inbound SSL records.
*/
private void unwrap(
ChannelHandlerContext ctx, ByteBuf packet, int offset, int length) throws SSLException {
boolean wrapLater = false;
boolean notifyClosure = false;
ByteBuf decodeOut = allocate(ctx, length);
try {
for (;;) {
final SSLEngineResult result = unwrap(engine, packet, offset, length, decodeOut);
final Status status = result.getStatus();
final HandshakeStatus handshakeStatus = result.getHandshakeStatus();
final int produced = result.bytesProduced();
final int consumed = result.bytesConsumed();
// Update indexes for the next iteration
offset += consumed;
length -= consumed;
if (status == Status.CLOSED) {
// notify about the CLOSED state of the SSLEngine. See #137
notifyClosure = true;
}
switch (handshakeStatus) {
case NEED_UNWRAP:
break;
case NEED_WRAP:
wrapNonAppData(ctx, true);
break;
case NEED_TASK:
runDelegatedTasks();
break;
case FINISHED:
setHandshakeSuccess();
wrapLater = true;
continue;
case NOT_HANDSHAKING:
if (setHandshakeSuccessIfStillHandshaking()) {
wrapLater = true;
continue;
}
if (flushedBeforeHandshake) {
// We need to call wrap(...) in case there was a flush done before the handshake completed.
//
// See https://github.com/netty/netty/pull/2437
flushedBeforeHandshake = false;
wrapLater = true;
}
break;
default:
throw new IllegalStateException("unknown handshake status: " + handshakeStatus);
}
if (status == Status.BUFFER_UNDERFLOW || consumed == 0 && produced == 0) {
break;
}
}
if (wrapLater) {
wrap(ctx, true);
}
if (notifyClosure) {
sslCloseFuture.trySuccess(ctx.channel());
}
} catch (SSLException e) {
setHandshakeFailure(ctx, e);
throw e;
} finally {
if (decodeOut.isReadable()) {
ctx.fireChannelRead(decodeOut);
} else {
decodeOut.release();
}
}
}
private synchronized boolean wrap(final Buffer buffer) throws IOException
{
ByteBuffer bbuf=extractByteBuffer(buffer);
final SSLEngineResult result;
synchronized(bbuf)
{
_outbound.compact();
ByteBuffer out_buffer=_outbound.getByteBuffer();
synchronized(out_buffer)
{
try
{
bbuf.position(buffer.getIndex());
bbuf.limit(buffer.putIndex());
out_buffer.position(_outbound.putIndex());
out_buffer.limit(out_buffer.capacity());
result=_engine.wrap(bbuf,out_buffer);
if (_logger.isDebugEnabled())
_logger.debug("{} wrap {} {} consumed={} produced={}",
_session,
result.getStatus(),
result.getHandshakeStatus(),
result.bytesConsumed(),
result.bytesProduced());
buffer.skip(result.bytesConsumed());
_outbound.setPutIndex(_outbound.putIndex()+result.bytesProduced());
}
catch(SSLException e)
{
_logger.debug(String.valueOf(_endp), e);
_endp.close();
throw e;
}
finally
{
out_buffer.position(0);
out_buffer.limit(out_buffer.capacity());
bbuf.position(0);
bbuf.limit(bbuf.capacity());
}
}
}
switch(result.getStatus())
{
case BUFFER_UNDERFLOW:
throw new IllegalStateException();
case BUFFER_OVERFLOW:
break;
case OK:
if (result.getHandshakeStatus()==HandshakeStatus.FINISHED)
_handshook=true;
break;
case CLOSED:
_logger.debug("wrap CLOSE {} {}",this,result);
if (result.getHandshakeStatus()==HandshakeStatus.FINISHED)
_endp.close();
break;
default:
_logger.debug("{} wrap default {}",_session,result);
throw new IOException(result.toString());
}
return result.bytesConsumed()>0 || result.bytesProduced()>0;
}
private synchronized boolean unwrap(final Buffer buffer) throws IOException
{
if (!_inbound.hasContent())
return false;
ByteBuffer bbuf=extractByteBuffer(buffer);
final SSLEngineResult result;
synchronized(bbuf)
{
ByteBuffer in_buffer=_inbound.getByteBuffer();
synchronized(in_buffer)
{
try
{
bbuf.position(buffer.putIndex());
bbuf.limit(buffer.capacity());
in_buffer.position(_inbound.getIndex());
in_buffer.limit(_inbound.putIndex());
result=_engine.unwrap(in_buffer,bbuf);
if (_logger.isDebugEnabled())
_logger.debug("{} unwrap {} {} consumed={} produced={}",
_session,
result.getStatus(),
result.getHandshakeStatus(),
result.bytesConsumed(),
result.bytesProduced());
_inbound.skip(result.bytesConsumed());
_inbound.compact();
buffer.setPutIndex(buffer.putIndex()+result.bytesProduced());
}
catch(SSLException e)
{
_logger.debug(String.valueOf(_endp), e);
_endp.close();
throw e;
}
finally
{
in_buffer.position(0);
in_buffer.limit(in_buffer.capacity());
bbuf.position(0);
bbuf.limit(bbuf.capacity());
}
}
}
switch(result.getStatus())
{
case BUFFER_UNDERFLOW:
if (_endp.isInputShutdown())
_inbound.clear();
break;
case BUFFER_OVERFLOW:
if (_logger.isDebugEnabled()) _logger.debug("{} unwrap {} {}->{}",_session,result.getStatus(),_inbound.toDetailString(),buffer.toDetailString());
break;
case OK:
if (result.getHandshakeStatus()==HandshakeStatus.FINISHED)
_handshook=true;
break;
case CLOSED:
_logger.debug("unwrap CLOSE {} {}",this,result);
if (result.getHandshakeStatus()==HandshakeStatus.FINISHED)
_endp.close();
break;
default:
_logger.debug("{} wrap default {}",_session,result);
throw new IOException(result.toString());
}
//if (LOG.isDebugEnabled() && result.bytesProduced()>0)
// LOG.debug("{} unwrapped '{}'",_session,buffer);
return result.bytesConsumed()>0 || result.bytesProduced()>0;
}