Merge branch 'jetty-9' of ssh://git.eclipse.org/gitroot/jetty/org.eclipse.jetty.project into jetty-9

This commit is contained in:
Joakim Erdfelt 2012-08-01 10:47:05 -07:00
commit 4b59767bd2
3 changed files with 258 additions and 170 deletions

View File

@ -20,6 +20,7 @@ import java.util.concurrent.Executor;
import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLEngineResult.HandshakeStatus; import javax.net.ssl.SSLEngineResult.HandshakeStatus;
import javax.net.ssl.SSLEngineResult.Status;
import javax.net.ssl.SSLException; import javax.net.ssl.SSLException;
import org.eclipse.jetty.io.AbstractAsyncConnection; import org.eclipse.jetty.io.AbstractAsyncConnection;
@ -39,31 +40,54 @@ import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
/** /**
* An AsyncConnection that acts as an interceptor between and EndPoint and another * An AsyncConnection that acts as an intercepter between an AsyncEndPoint providing SSL encrypted data
* Connection, that implements TLS encryption using an {@link SSLEngine}. * and another consumer of an AsyncEndPoint (typically an {@link AsyncConnection} like HttpConnection) that
* <p/> * wants unencrypted data.
* The connector uses an {@link EndPoint} (like {@link SelectChannelEndPoint}) as * <p>
* it's source/sink of encrypted data. It then provides {@link #getSslEndPoint()} to * The connector uses an {@link AsyncEndPoint} (typically {@link SelectChannelEndPoint}) as
* it's source/sink of encrypted data. It then provides an endpoint via {@link #getSslEndPoint()} to
* expose a source/sink of unencrypted data to another connection (eg HttpConnection). * expose a source/sink of unencrypted data to another connection (eg HttpConnection).
* <p>
* The design of this class is based on a clear separation between the passive methods, which do not block nor schedule any
* asynchronous callbacks, and active methods that do schedule asynchronous callbacks.
* <p>
* The passive methods are {@link DecryptedEndPoint#fill(ByteBuffer)} and {@link DecryptedEndPoint#flush(ByteBuffer...)}. They make best
* effort attempts to progress the connection using only calls to the encrypted {@link AsyncEndPoint#fill(ByteBuffer)} and {@link AsyncEndPoint#flush(ByteBuffer...)}
* methods. They will never block nor schedule any readInterest or write callbacks. If a fill/flush cannot progress either because
* of network congestion or waiting for an SSL handshake message, then the fill/flush will simply return with zero bytes filled/flushed.
* Specifically, if a flush cannot proceed because it needs to receive a handshake message, then the flush will attempt to fill bytes from the
* encrypted endpoint, but if insufficient bytes are read it will NOT call {@link AsyncEndPoint#fillInterested(Object, Callback)}.
* <p>
* It is only the active methods : {@link DecryptedEndPoint#fillInterested(Object, Callback)} and
* {@link DecryptedEndPoint#write(Object, Callback, ByteBuffer...)} that may schedule callbacks by calling the encrypted
* {@link AsyncEndPoint#fillInterested(Object, Callback)} and {@link AsyncEndPoint#write(Object, Callback, ByteBuffer...)}
* methods. For normal data handling, the decrypted fillInterest method will result in an encrypted fillInterest and a decrypted
* write will result in an encrypted write. However, due to SSL handshaking requirements, it is also possible for a decrypted fill
* to call the encrypted write and for the decrypted flush to call the encrypted fillInterested methods.
* <p>
* MOST IMPORTANTLY, the encrypted callbacks from the active methods (#onFillable() and WriteFlusher#completeWrite()) do no filling or flushing
* themselves. Instead they simple make the callbacks to the decrypted callbacks, so that the passive encyrpted fill/flush will
* be called again and make another best effort attempt to progress the connection.
*
*/ */
public class SslConnection extends AbstractAsyncConnection public class SslConnection extends AbstractAsyncConnection
{ {
private static final Logger LOG = Log.getLogger(SslConnection.class); private static final Logger LOG = Log.getLogger(SslConnection.class);
private final ByteBufferPool _bufferPool; private final ByteBufferPool _bufferPool;
private final SSLEngine _sslEngine; private final SSLEngine _sslEngine;
private final SslEndPoint _appEndPoint; private final DecryptedEndPoint _decryptedEndPoint;
private ByteBuffer _appIn; private ByteBuffer _decryptedInput;
private ByteBuffer _netIn; private ByteBuffer _encryptedInput;
private ByteBuffer _netOut; private ByteBuffer _encryptedOutput;
private final boolean _netDirect = false; private final boolean _encryptedDirectBuffers = false;
private final boolean _appDirect = false; private final boolean _decryptedDirectBuffers = false;
public SslConnection(ByteBufferPool byteBufferPool, Executor executor, AsyncEndPoint endPoint, SSLEngine sslEngine) public SslConnection(ByteBufferPool byteBufferPool, Executor executor, AsyncEndPoint endPoint, SSLEngine sslEngine)
{ {
super(endPoint, executor, true); super(endPoint, executor, true);
this._bufferPool = byteBufferPool; this._bufferPool = byteBufferPool;
this._sslEngine = sslEngine; this._sslEngine = sslEngine;
this._appEndPoint = new SslEndPoint(); this._decryptedEndPoint = new DecryptedEndPoint();
} }
public SSLEngine getSSLEngine() public SSLEngine getSSLEngine()
@ -73,7 +97,7 @@ public class SslConnection extends AbstractAsyncConnection
public AsyncEndPoint getSslEndPoint() public AsyncEndPoint getSslEndPoint()
{ {
return _appEndPoint; return _decryptedEndPoint;
} }
@Override @Override
@ -87,7 +111,7 @@ public class SslConnection extends AbstractAsyncConnection
_sslEngine.beginHandshake(); _sslEngine.beginHandshake();
if (_sslEngine.getUseClientMode()) if (_sslEngine.getUseClientMode())
_appEndPoint.write(null, new Callback.Empty<>(), BufferUtil.EMPTY_BUFFER); _decryptedEndPoint.write(null, new Callback.Empty<>(), BufferUtil.EMPTY_BUFFER);
} }
catch (SSLException x) catch (SSLException x)
{ {
@ -100,18 +124,27 @@ public class SslConnection extends AbstractAsyncConnection
@Override @Override
public void onFillable() public void onFillable()
{ {
// onFillable means that there are encrypted bytes ready to be filled.
// however we do not fill them here on this callback, but instead wakeup
// the decrypted readInterest and/or writeFlusher so that they will attempt
// to do the fill and/or flush again and these calls will do the actually
// filling.
LOG.debug("{} onReadable", this); LOG.debug("{} onReadable", this);
// wake up whoever is doing the fill or the flush so they can synchronized(_decryptedEndPoint)
// do all the filling, unwrapping ,wrapping and flushing
if (_appEndPoint._readInterest.isInterested())
_appEndPoint._readInterest.readable();
// If we are handshaking, then wake up any waiting write as well as it may have been blocked on the read
if (_appEndPoint._writeFlusher.isWriting() && _appEndPoint._flushUnwrap)
{ {
_appEndPoint._flushUnwrap = false; // wake up whoever is doing the fill or the flush so they can
_appEndPoint._writeFlusher.completeWrite(); // do all the filling, unwrapping ,wrapping and flushing
if (_decryptedEndPoint._readInterest.isInterested())
_decryptedEndPoint._readInterest.readable();
// If we are handshaking, then wake up any waiting write as well as it may have been blocked on the read
if ( _decryptedEndPoint._flushRequiresFillToProgress)
{
_decryptedEndPoint._flushRequiresFillToProgress = false;
_decryptedEndPoint._writeFlusher.completeWrite();
}
} }
} }
@ -119,17 +152,25 @@ public class SslConnection extends AbstractAsyncConnection
@Override @Override
public void onFillInterestedFailed(Throwable cause) public void onFillInterestedFailed(Throwable cause)
{ {
// this means that the fill interest in encrypted bytes has failed.
// However we do not handle that here on this callback, but instead wakeup
// the decrypted readInterest and/or writeFlusher so that they will attempt
// to do the fill and/or flush again and these calls will do the actually
// handle the cause.
super.onFillInterestedFailed(cause); super.onFillInterestedFailed(cause);
if (_appEndPoint._readInterest.isInterested()) synchronized(_decryptedEndPoint)
_appEndPoint._readInterest.failed(cause);
if (_appEndPoint._writeFlusher.isWriting() && _appEndPoint._flushUnwrap)
{ {
_appEndPoint._flushUnwrap = false; if (_decryptedEndPoint._readInterest.isInterested())
_appEndPoint._writeFlusher.failed(cause); _decryptedEndPoint._readInterest.failed(cause);
}
if (_decryptedEndPoint._flushRequiresFillToProgress)
{
_decryptedEndPoint._flushRequiresFillToProgress = false;
_decryptedEndPoint._writeFlusher.failed(cause);
}
}
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@ -139,18 +180,18 @@ public class SslConnection extends AbstractAsyncConnection
return String.format("SslConnection@%x{%s,%s%s}", return String.format("SslConnection@%x{%s,%s%s}",
hashCode(), hashCode(),
_sslEngine.getHandshakeStatus(), _sslEngine.getHandshakeStatus(),
_appEndPoint._readInterest.isInterested() ? "R" : "", _decryptedEndPoint._readInterest.isInterested() ? "R" : "",
_appEndPoint._writeFlusher.isWriting() ? "W" : ""); _decryptedEndPoint._writeFlusher.isWriting() ? "W" : "");
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
public class SslEndPoint extends AbstractEndPoint implements AsyncEndPoint public class DecryptedEndPoint extends AbstractEndPoint implements AsyncEndPoint
{ {
private AsyncConnection _connection; private AsyncConnection _connection;
private boolean _fillWrap; private boolean _fillRequiresFlushToProgress;
private boolean _flushUnwrap; private boolean _flushRequiresFillToProgress;
private boolean _netWriting; private boolean _cannotAcceptMoreAppDataToFlush;
private boolean _underflown; private boolean _needToFillMoreDataToProgress;
private boolean _ishut = false; private boolean _ishut = false;
@Override @Override
@ -169,16 +210,20 @@ public class SslConnection extends AbstractAsyncConnection
@Override @Override
public void completed(Void context) public void completed(Void context)
{ {
synchronized (SslEndPoint.this) // This means that a write of data has completed. Writes are done
// only if there is an active writeflusher or a read needed to write
// data. In either case the appropriate callback is passed on.
synchronized (DecryptedEndPoint.this)
{ {
LOG.debug("{} write.complete {}", SslConnection.this, _netWriting ? (_fillWrap ? "FW" : "F") : (_fillWrap ? "W" : "")); LOG.debug("{} write.complete {}", SslConnection.this, _cannotAcceptMoreAppDataToFlush ? (_fillRequiresFlushToProgress ? "FW" : "F") : (_fillRequiresFlushToProgress ? "W" : ""));
releaseNetOut(); releaseNetOut();
_netWriting = false; _cannotAcceptMoreAppDataToFlush = false;
if (_fillWrap)
if (_fillRequiresFlushToProgress)
{ {
_fillWrap = false; _fillRequiresFlushToProgress = false;
_readInterest.readable(); _readInterest.readable();
} }
@ -190,16 +235,21 @@ public class SslConnection extends AbstractAsyncConnection
@Override @Override
public void failed(Void context, Throwable x) public void failed(Void context, Throwable x)
{ {
synchronized (SslEndPoint.this) // This means that a write of data has failed. Writes are done
// only if there is an active writeflusher or a read needed to write
// data. In either case the appropriate callback is passed on.
synchronized (DecryptedEndPoint.this)
{ {
LOG.debug("{} write.failed", SslConnection.this, x); LOG.debug("{} write.failed", SslConnection.this, x);
if (_netOut != null) if (_encryptedOutput != null)
BufferUtil.clear(_netOut); BufferUtil.clear(_encryptedOutput);
releaseNetOut(); releaseNetOut();
_netWriting = false;
if (_fillWrap) _cannotAcceptMoreAppDataToFlush = false;
if (_fillRequiresFlushToProgress)
{ {
_fillWrap = false; _fillRequiresFlushToProgress = false;
_readInterest.failed(x); _readInterest.failed(x);
} }
@ -216,39 +266,54 @@ public class SslConnection extends AbstractAsyncConnection
@Override @Override
protected boolean needsFill() throws IOException protected boolean needsFill() throws IOException
{ {
synchronized (SslEndPoint.this) // This means that the decrypted data consumer has called the fillInterested
// method on the DecryptedEndPoint, so we have to work out if there is
// decrypted data to be filled or what callbacks to setup to be told when there
// might be more encrypted data available to attempt another call to fill
synchronized (DecryptedEndPoint.this)
{ {
// Do we already have some app data // Do we already have some app data, then app can fill now so return true
if (BufferUtil.hasContent(_appIn)) if (BufferUtil.hasContent(_decryptedInput))
return true; return true;
// If we are not underflown and have net data // If we have no encrypted data to decrypt OR we have some, but it is not enough
if (!_underflown && BufferUtil.hasContent(_netIn)) if (BufferUtil.isEmpty(_encryptedInput) || _needToFillMoreDataToProgress)
return true;
// So we are not read ready
// Are we actually write blocked?
if (_fillWrap)
{ {
// we must be blocked trying to write before we can read // We are not ready to read data
// If we have written the net data
if (BufferUtil.isEmpty(_netOut))
{
// pretend we are readable so the wrap is done by next readable callback
_fillWrap = false;
return true;
}
// otherwise write the net data // Are we actually write blocked?
_netWriting = true; if (_fillRequiresFlushToProgress)
getEndPoint().write(null, _writeCallback, _netOut); {
// we must be blocked trying to write before we can read
// Do we have data to write
if (BufferUtil.hasContent(_encryptedOutput))
{
// write it
_cannotAcceptMoreAppDataToFlush = true;
getEndPoint().write(null, _writeCallback, _encryptedOutput);
}
else
{
// we have already written the net data
// pretend we are readable so the wrap is done by next readable callback
_fillRequiresFlushToProgress = false;
return true;
}
}
else
// Normal readable callback
// Get called back on onfillable when then is more data to fill
SslConnection.this.fillInterested();
return false;
} }
else else
// Normal readable callback {
SslConnection.this.fillInterested(); // We are ready to read data
return true;
return false; }
} }
} }
}; };
@ -258,16 +323,19 @@ public class SslConnection extends AbstractAsyncConnection
@Override @Override
protected void onIncompleteFlushed() protected void onIncompleteFlushed()
{ {
synchronized (SslEndPoint.this) // This means that the decripted endpoint write method was called and not
// all data could be wrapped. So either we need to write some encrypted data,
// OR if we are handshaking we need to read some encrypted data OR
// if neither than we should just try the flush again.
synchronized (DecryptedEndPoint.this)
{ {
// If we have pending output data, // If we have pending output data,
if (BufferUtil.hasContent(_netOut)) if (BufferUtil.hasContent(_encryptedOutput))
{ {
// write it // write it
_netWriting = true; _cannotAcceptMoreAppDataToFlush = true;
getEndPoint().write(null, _writeCallback, _netOut); getEndPoint().write(null, _writeCallback, _encryptedOutput);
} }
// TODO test this with _flushUnwrap
else if (_sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP) else if (_sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP)
// we are actually read blocked in order to write // we are actually read blocked in order to write
SslConnection.this.fillInterested(); SslConnection.this.fillInterested();
@ -278,7 +346,7 @@ public class SslConnection extends AbstractAsyncConnection
} }
}; };
public SslEndPoint() public DecryptedEndPoint()
{ {
super(getEndPoint().getLocalAddress(), getEndPoint().getRemoteAddress()); super(getEndPoint().getLocalAddress(), getEndPoint().getRemoteAddress());
} }
@ -307,37 +375,37 @@ public class SslConnection extends AbstractAsyncConnection
try try
{ {
// Do we already have some decrypted data? // Do we already have some decrypted data?
if (BufferUtil.hasContent(_appIn)) if (BufferUtil.hasContent(_decryptedInput))
return BufferUtil.append(_appIn, buffer); return BufferUtil.append(_decryptedInput, buffer);
// We will need a network buffer // We will need a network buffer
if (_netIn == null) if (_encryptedInput == null)
_netIn = _bufferPool.acquire(_sslEngine.getSession().getPacketBufferSize(), _netDirect); _encryptedInput = _bufferPool.acquire(_sslEngine.getSession().getPacketBufferSize(), _encryptedDirectBuffers);
else else
BufferUtil.compact(_netIn); BufferUtil.compact(_encryptedInput);
// We also need an app buffer, but can use the passed buffer if it is big enough // We also need an app buffer, but can use the passed buffer if it is big enough
ByteBuffer app_in; ByteBuffer app_in;
if (BufferUtil.space(buffer) > _sslEngine.getSession().getApplicationBufferSize()) if (BufferUtil.space(buffer) > _sslEngine.getSession().getApplicationBufferSize())
app_in = buffer; app_in = buffer;
else if (_appIn == null) else if (_decryptedInput == null)
app_in = _appIn = _bufferPool.acquire(_sslEngine.getSession().getApplicationBufferSize(), _appDirect); app_in = _decryptedInput = _bufferPool.acquire(_sslEngine.getSession().getApplicationBufferSize(), _decryptedDirectBuffers);
else else
app_in = _appIn; app_in = _decryptedInput;
// loop filling and unwrapping until we have something // loop filling and unwrapping until we have something
while (true) while (true)
{ {
// Let's try reading some encrypted data... even if we have some already. // Let's try reading some encrypted data... even if we have some already.
int net_filled = getEndPoint().fill(_netIn); int net_filled = getEndPoint().fill(_encryptedInput);
LOG.debug("{} filled {} encrypted bytes", SslConnection.this, net_filled); LOG.debug("{} filled {} encrypted bytes", SslConnection.this, net_filled);
if (net_filled > 0) if (net_filled > 0)
_underflown = false; _needToFillMoreDataToProgress = false;
// Let's try the SSL thang even if we have no net data because in that // Let's try the SSL thang even if we have no net data because in that
// case we want to fall through to the handshake handling // case we want to fall through to the handshake handling
int pos = BufferUtil.flipToFill(app_in); int pos = BufferUtil.flipToFill(app_in);
SSLEngineResult unwrapResult = _sslEngine.unwrap(_netIn, app_in); SSLEngineResult unwrapResult = _sslEngine.unwrap(_encryptedInput, app_in);
LOG.debug("{} unwrap {}", SslConnection.this, unwrapResult); LOG.debug("{} unwrap {}", SslConnection.this, unwrapResult);
BufferUtil.flipToFlush(app_in, pos); BufferUtil.flipToFlush(app_in, pos);
@ -348,10 +416,11 @@ public class SslConnection extends AbstractAsyncConnection
throw new IllegalStateException(); throw new IllegalStateException();
case CLOSED: case CLOSED:
// Dang! we have to care about the handshake state // Dang! we have to care about the handshake state specially for close
switch (_sslEngine.getHandshakeStatus()) switch (_sslEngine.getHandshakeStatus())
{ {
case NOT_HANDSHAKING: case NOT_HANDSHAKING:
// We were not handshaking, so just tell the app we are closed
return -1; return -1;
case NEED_TASK: case NEED_TASK:
@ -360,40 +429,47 @@ public class SslConnection extends AbstractAsyncConnection
continue; continue;
case NEED_WRAP: case NEED_WRAP:
// we need to send some handshake data // we need to send some handshake data (probably to send a close handshake).
if (!_flushUnwrap) if (_flushRequiresFillToProgress)
return -1; // we were called from flush, so it can deal with sending the close handshake
// We need to call flush to cause the wrap to happen
_fillRequiresFlushToProgress = true;
try
{ {
_fillWrap = true; // flushing an empty buffer will invoke the wrap mechanisms
try flush(BufferUtil.EMPTY_BUFFER);
{ // If encrypted output is all written, we can proceed with close
flush(BufferUtil.EMPTY_BUFFER); if (BufferUtil.isEmpty(_encryptedOutput))
}
catch(IOException e)
{ {
_fillRequiresFlushToProgress = false;
return -1; return -1;
} }
if (BufferUtil.hasContent(_netOut))
return 0; // Otherwise return as if a normal fill and let a subsequent call
_fillWrap = false; // return -1 to the caller.
return unwrapResult.bytesProduced();
}
catch(IOException e)
{
LOG.debug(e);
// The flush failed, oh well nothing more to do than tell the app
// that the connection is closed.
return -1;
} }
return -1;
default:
throw new IllegalStateException();
} }
throw new IllegalStateException();
case BUFFER_UNDERFLOW:
_underflown = true;
//$FALL-THROUGH$ to deal with handshaking stuff
default: default:
// if we produced bytes, we don't care about the handshake state if (unwrapResult.getStatus()==Status.BUFFER_UNDERFLOW)
_needToFillMoreDataToProgress=true;
// if we produced bytes, we don't care about the handshake state for now and it can be dealt with on another call to fill or flush
if (unwrapResult.bytesProduced() > 0) if (unwrapResult.bytesProduced() > 0)
{ {
if (app_in == buffer) if (app_in == buffer)
return unwrapResult.bytesProduced(); return unwrapResult.bytesProduced();
return BufferUtil.append(_appIn, buffer); return BufferUtil.append(_decryptedInput, buffer);
} }
// Dang! we have to care about the handshake state // Dang! we have to care about the handshake state
@ -412,14 +488,17 @@ public class SslConnection extends AbstractAsyncConnection
case NEED_WRAP: case NEED_WRAP:
// we need to send some handshake data // we need to send some handshake data
if (_flushUnwrap) if (_flushRequiresFillToProgress)
return 0; return 0;
_fillWrap = true; _fillRequiresFlushToProgress = true;
flush(BufferUtil.EMPTY_BUFFER); flush(BufferUtil.EMPTY_BUFFER);
if (BufferUtil.hasContent(_netOut)) if (BufferUtil.isEmpty(_encryptedOutput))
return 0; {
_fillWrap = false; // the flush completed so continue
continue; _fillRequiresFlushToProgress = false;
continue;
}
return 0;
case NEED_UNWRAP: case NEED_UNWRAP:
// if we just filled some net data // if we just filled some net data
@ -450,15 +529,15 @@ public class SslConnection extends AbstractAsyncConnection
} }
finally finally
{ {
if (_netIn != null && !_netIn.hasRemaining()) if (_encryptedInput != null && !_encryptedInput.hasRemaining())
{ {
_bufferPool.release(_netIn); _bufferPool.release(_encryptedInput);
_netIn = null; _encryptedInput = null;
} }
if (_appIn != null && !_appIn.hasRemaining()) if (_decryptedInput != null && !_decryptedInput.hasRemaining())
{ {
_bufferPool.release(_appIn); _bufferPool.release(_decryptedInput);
_appIn = null; _decryptedInput = null;
} }
LOG.debug("{} fill exit", SslConnection.this); LOG.debug("{} fill exit", SslConnection.this);
} }
@ -477,77 +556,86 @@ public class SslConnection extends AbstractAsyncConnection
LOG.debug("{} flush enter {}", SslConnection.this, Arrays.toString(appOuts)); LOG.debug("{} flush enter {}", SslConnection.this, Arrays.toString(appOuts));
try try
{ {
if (_netWriting) if (_cannotAcceptMoreAppDataToFlush)
return 0; return 0;
// We will need a network buffer // We will need a network buffer
if (_netOut == null) if (_encryptedOutput == null)
_netOut = _bufferPool.acquire(_sslEngine.getSession().getPacketBufferSize() * 2, _netDirect); _encryptedOutput = _bufferPool.acquire(_sslEngine.getSession().getPacketBufferSize() * 2, _encryptedDirectBuffers);
int consumed=0;
while (true) while (true)
{ {
// do the funky SSL thang! // do the funky SSL thang!
BufferUtil.compact(_netOut); // We call sslEngine.wrap to try to take bytes from appOut buffers and encrypt them into the _netOut buffer
int pos = BufferUtil.flipToFill(_netOut); BufferUtil.compact(_encryptedOutput);
SSLEngineResult wrapResult = _sslEngine.wrap(appOuts, _netOut); int pos = BufferUtil.flipToFill(_encryptedOutput);
SSLEngineResult wrapResult = _sslEngine.wrap(appOuts, _encryptedOutput);
LOG.debug("{} wrap {}", SslConnection.this, wrapResult); LOG.debug("{} wrap {}", SslConnection.this, wrapResult);
BufferUtil.flipToFlush(_netOut, pos); BufferUtil.flipToFlush(_encryptedOutput, pos);
consumed+=wrapResult.bytesConsumed();
// and deal with the results // and deal with the results returned from the sslEngineWrap
switch (wrapResult.getStatus()) switch (wrapResult.getStatus())
{ {
case CLOSED: case CLOSED:
if (BufferUtil.hasContent(_netOut)) // The SSL engine has close, but there may be close handshake that needs to be written
if (BufferUtil.hasContent(_encryptedOutput))
{ {
_netWriting = true; _cannotAcceptMoreAppDataToFlush = true;
getEndPoint().flush(_netOut); getEndPoint().flush(_encryptedOutput);
if (BufferUtil.hasContent(_netOut)) // If we failed to flush the close handshake then we will just pretend that
return 0; // the write has progressed normally and let a subsequent call to flush (or WriteFlusher#onIncompleteFlushed)
// to finish writing the close handshake. The caller will find out about the close on a subsequent flush or fill.
if (BufferUtil.hasContent(_encryptedOutput))
return consumed;
} }
if (_fillWrap)
return 0; // If we we flushing because of a fill needing to wrap, return normally and it will handle the closed state.
if (_fillRequiresFlushToProgress)
return consumed;
// otherwise it is an exception to write to a closed endpoint
throw new EofException(); throw new EofException();
case BUFFER_UNDERFLOW: case BUFFER_UNDERFLOW:
throw new IllegalStateException(); throw new IllegalStateException();
case BUFFER_OVERFLOW:
if (LOG.isDebugEnabled())
LOG.debug("{} OVERFLOW {}", this, BufferUtil.toDetailString(_netOut));
//$FALL-THROUGH$
default: default:
if (LOG.isDebugEnabled())
LOG.debug("{} {} {}", this, wrapResult.getStatus(), BufferUtil.toDetailString(_encryptedOutput));
// if we have net bytes, let's try to flush them // if we have net bytes, let's try to flush them
if (BufferUtil.hasContent(_netOut)) if (BufferUtil.hasContent(_encryptedOutput))
{ getEndPoint().flush(_encryptedOutput);
getEndPoint().flush(_netOut);
return wrapResult.bytesConsumed();
}
// Dang! we have to deal with handshake state // But we also might have more to do for the handshaking state.
switch (_sslEngine.getHandshakeStatus()) switch (_sslEngine.getHandshakeStatus())
{ {
case NOT_HANDSHAKING: case NOT_HANDSHAKING:
// we just didn't write anything. Strange? // Return with the number of bytes consumed (which may be 0)
return 0; return consumed;
case NEED_TASK: case NEED_TASK:
// run the task // run the task and continue
_sslEngine.getDelegatedTask().run(); _sslEngine.getDelegatedTask().run();
continue; continue;
case NEED_WRAP: case NEED_WRAP:
// Hey we just wrapped! // Hey we just wrapped! Oh well who knows what the sslEngine is thinking, so continue and we will wrap again
continue; continue;
case NEED_UNWRAP: case NEED_UNWRAP:
// Were we were not called from fill and not reading anyway // Ah we need to fill some data so we can write.
if (!_fillWrap && !_readInterest.isInterested()) // So if we were not called from fill and the app is not reading anyway
if (!_fillRequiresFlushToProgress && !_readInterest.isInterested())
{ {
_flushUnwrap = true; // Tell the onFillable method that there might be a write to complete
// TODO move this to the writeFlusher?
_flushRequiresFillToProgress = true;
fill(BufferUtil.EMPTY_BUFFER); fill(BufferUtil.EMPTY_BUFFER);
} }
return 0; return consumed;
case FINISHED: case FINISHED:
throw new IllegalStateException(); throw new IllegalStateException();
@ -570,10 +658,10 @@ public class SslConnection extends AbstractAsyncConnection
private void releaseNetOut() private void releaseNetOut()
{ {
if (_netOut != null && !_netOut.hasRemaining()) if (_encryptedOutput != null && !_encryptedOutput.hasRemaining())
{ {
_bufferPool.release(_netOut); _bufferPool.release(_encryptedOutput);
_netOut = null; _encryptedOutput = null;
if (_sslEngine.isOutboundDone()) if (_sslEngine.isOutboundDone())
getEndPoint().shutdownOutput(); getEndPoint().shutdownOutput();
} }
@ -639,7 +727,7 @@ public class SslConnection extends AbstractAsyncConnection
@Override @Override
public String toString() public String toString()
{ {
return String.format("%s{%s%s%s}", super.toString(), _readInterest.isInterested() ? "R" : "", _writeFlusher.isWriting() ? "W" : "", _netWriting ? "w" : ""); return String.format("%s{%s%s%s}", super.toString(), _readInterest.isInterested() ? "R" : "", _writeFlusher.isWriting() ? "W" : "", _cannotAcceptMoreAppDataToFlush ? "w" : "");
} }
} }

View File

@ -88,7 +88,7 @@ public class SslSelectChannelConnector extends SelectChannelConnector implements
request.setScheme(HttpScheme.HTTPS.asString()); request.setScheme(HttpScheme.HTTPS.asString());
super.customize(request); super.customize(request);
SslConnection.SslEndPoint ssl_endp = (SslConnection.SslEndPoint)request.getHttpChannel().getEndPoint(); SslConnection.DecryptedEndPoint ssl_endp = (SslConnection.DecryptedEndPoint)request.getHttpChannel().getEndPoint();
SslConnection sslConnection = ssl_endp.getSslConnection(); SslConnection sslConnection = ssl_endp.getSslConnection();
SSLEngine sslEngine=sslConnection.getSSLEngine(); SSLEngine sslEngine=sslConnection.getSSLEngine();
SslCertificates.customize(sslEngine,request); SslCertificates.customize(sslEngine,request);

View File

@ -149,8 +149,8 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
// Get the server side endpoint // Get the server side endpoint
EndPoint endp = endpoint.exchange(null,10,TimeUnit.SECONDS); EndPoint endp = endpoint.exchange(null,10,TimeUnit.SECONDS);
if (endp instanceof SslConnection.SslEndPoint) if (endp instanceof SslConnection.DecryptedEndPoint)
endp=((SslConnection.SslEndPoint)endp).getAsyncConnection().getEndPoint(); endp=((SslConnection.DecryptedEndPoint)endp).getAsyncConnection().getEndPoint();
// read the response // read the response
String result=IO.toString(is); String result=IO.toString(is);
@ -222,8 +222,8 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
// Get the server side endpoint // Get the server side endpoint
EndPoint endp = endpoint.exchange(null,10,TimeUnit.SECONDS); EndPoint endp = endpoint.exchange(null,10,TimeUnit.SECONDS);
if (endp instanceof SslConnection.SslEndPoint) if (endp instanceof SslConnection.DecryptedEndPoint)
endp=((SslConnection.SslEndPoint)endp).getAsyncConnection().getEndPoint(); endp=((SslConnection.DecryptedEndPoint)endp).getAsyncConnection().getEndPoint();
// read the response // read the response
String result=IO.toString(is); String result=IO.toString(is);