Merge branch 'jetty-9' of ssh://git.eclipse.org/gitroot/jetty/org.eclipse.jetty.project into jetty-9
This commit is contained in:
commit
4b59767bd2
|
@ -20,6 +20,7 @@ import java.util.concurrent.Executor;
|
|||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.SSLEngineResult;
|
||||
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
|
||||
import javax.net.ssl.SSLEngineResult.Status;
|
||||
import javax.net.ssl.SSLException;
|
||||
|
||||
import org.eclipse.jetty.io.AbstractAsyncConnection;
|
||||
|
@ -39,31 +40,54 @@ import org.eclipse.jetty.util.log.Log;
|
|||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
/**
|
||||
* An AsyncConnection that acts as an interceptor between and EndPoint and another
|
||||
* Connection, that implements TLS encryption using an {@link SSLEngine}.
|
||||
* <p/>
|
||||
* The connector uses an {@link EndPoint} (like {@link SelectChannelEndPoint}) as
|
||||
* it's source/sink of encrypted data. It then provides {@link #getSslEndPoint()} to
|
||||
* An AsyncConnection that acts as an intercepter between an AsyncEndPoint providing SSL encrypted data
|
||||
* and another consumer of an AsyncEndPoint (typically an {@link AsyncConnection} like HttpConnection) that
|
||||
* wants unencrypted data.
|
||||
* <p>
|
||||
* 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).
|
||||
* <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
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(SslConnection.class);
|
||||
private final ByteBufferPool _bufferPool;
|
||||
private final SSLEngine _sslEngine;
|
||||
private final SslEndPoint _appEndPoint;
|
||||
private ByteBuffer _appIn;
|
||||
private ByteBuffer _netIn;
|
||||
private ByteBuffer _netOut;
|
||||
private final boolean _netDirect = false;
|
||||
private final boolean _appDirect = false;
|
||||
private final DecryptedEndPoint _decryptedEndPoint;
|
||||
private ByteBuffer _decryptedInput;
|
||||
private ByteBuffer _encryptedInput;
|
||||
private ByteBuffer _encryptedOutput;
|
||||
private final boolean _encryptedDirectBuffers = false;
|
||||
private final boolean _decryptedDirectBuffers = false;
|
||||
|
||||
public SslConnection(ByteBufferPool byteBufferPool, Executor executor, AsyncEndPoint endPoint, SSLEngine sslEngine)
|
||||
{
|
||||
super(endPoint, executor, true);
|
||||
this._bufferPool = byteBufferPool;
|
||||
this._sslEngine = sslEngine;
|
||||
this._appEndPoint = new SslEndPoint();
|
||||
this._decryptedEndPoint = new DecryptedEndPoint();
|
||||
}
|
||||
|
||||
public SSLEngine getSSLEngine()
|
||||
|
@ -73,7 +97,7 @@ public class SslConnection extends AbstractAsyncConnection
|
|||
|
||||
public AsyncEndPoint getSslEndPoint()
|
||||
{
|
||||
return _appEndPoint;
|
||||
return _decryptedEndPoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -87,7 +111,7 @@ public class SslConnection extends AbstractAsyncConnection
|
|||
_sslEngine.beginHandshake();
|
||||
|
||||
if (_sslEngine.getUseClientMode())
|
||||
_appEndPoint.write(null, new Callback.Empty<>(), BufferUtil.EMPTY_BUFFER);
|
||||
_decryptedEndPoint.write(null, new Callback.Empty<>(), BufferUtil.EMPTY_BUFFER);
|
||||
}
|
||||
catch (SSLException x)
|
||||
{
|
||||
|
@ -100,18 +124,27 @@ public class SslConnection extends AbstractAsyncConnection
|
|||
@Override
|
||||
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);
|
||||
|
||||
// wake up whoever is doing the fill or the flush so they can
|
||||
// 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)
|
||||
synchronized(_decryptedEndPoint)
|
||||
{
|
||||
_appEndPoint._flushUnwrap = false;
|
||||
_appEndPoint._writeFlusher.completeWrite();
|
||||
// wake up whoever is doing the fill or the flush so they can
|
||||
// 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
|
||||
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);
|
||||
|
||||
if (_appEndPoint._readInterest.isInterested())
|
||||
_appEndPoint._readInterest.failed(cause);
|
||||
|
||||
if (_appEndPoint._writeFlusher.isWriting() && _appEndPoint._flushUnwrap)
|
||||
synchronized(_decryptedEndPoint)
|
||||
{
|
||||
_appEndPoint._flushUnwrap = false;
|
||||
_appEndPoint._writeFlusher.failed(cause);
|
||||
}
|
||||
if (_decryptedEndPoint._readInterest.isInterested())
|
||||
_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}",
|
||||
hashCode(),
|
||||
_sslEngine.getHandshakeStatus(),
|
||||
_appEndPoint._readInterest.isInterested() ? "R" : "",
|
||||
_appEndPoint._writeFlusher.isWriting() ? "W" : "");
|
||||
_decryptedEndPoint._readInterest.isInterested() ? "R" : "",
|
||||
_decryptedEndPoint._writeFlusher.isWriting() ? "W" : "");
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public class SslEndPoint extends AbstractEndPoint implements AsyncEndPoint
|
||||
public class DecryptedEndPoint extends AbstractEndPoint implements AsyncEndPoint
|
||||
{
|
||||
private AsyncConnection _connection;
|
||||
private boolean _fillWrap;
|
||||
private boolean _flushUnwrap;
|
||||
private boolean _netWriting;
|
||||
private boolean _underflown;
|
||||
private boolean _fillRequiresFlushToProgress;
|
||||
private boolean _flushRequiresFillToProgress;
|
||||
private boolean _cannotAcceptMoreAppDataToFlush;
|
||||
private boolean _needToFillMoreDataToProgress;
|
||||
private boolean _ishut = false;
|
||||
|
||||
@Override
|
||||
|
@ -169,16 +210,20 @@ public class SslConnection extends AbstractAsyncConnection
|
|||
@Override
|
||||
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();
|
||||
|
||||
_netWriting = false;
|
||||
if (_fillWrap)
|
||||
_cannotAcceptMoreAppDataToFlush = false;
|
||||
|
||||
if (_fillRequiresFlushToProgress)
|
||||
{
|
||||
_fillWrap = false;
|
||||
_fillRequiresFlushToProgress = false;
|
||||
_readInterest.readable();
|
||||
}
|
||||
|
||||
|
@ -190,16 +235,21 @@ public class SslConnection extends AbstractAsyncConnection
|
|||
@Override
|
||||
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);
|
||||
if (_netOut != null)
|
||||
BufferUtil.clear(_netOut);
|
||||
if (_encryptedOutput != null)
|
||||
BufferUtil.clear(_encryptedOutput);
|
||||
releaseNetOut();
|
||||
_netWriting = false;
|
||||
if (_fillWrap)
|
||||
|
||||
_cannotAcceptMoreAppDataToFlush = false;
|
||||
|
||||
if (_fillRequiresFlushToProgress)
|
||||
{
|
||||
_fillWrap = false;
|
||||
_fillRequiresFlushToProgress = false;
|
||||
_readInterest.failed(x);
|
||||
}
|
||||
|
||||
|
@ -216,39 +266,54 @@ public class SslConnection extends AbstractAsyncConnection
|
|||
@Override
|
||||
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
|
||||
if (BufferUtil.hasContent(_appIn))
|
||||
// Do we already have some app data, then app can fill now so return true
|
||||
if (BufferUtil.hasContent(_decryptedInput))
|
||||
return true;
|
||||
|
||||
// If we are not underflown and have net data
|
||||
if (!_underflown && BufferUtil.hasContent(_netIn))
|
||||
return true;
|
||||
|
||||
// So we are not read ready
|
||||
|
||||
// Are we actually write blocked?
|
||||
if (_fillWrap)
|
||||
// If we have no encrypted data to decrypt OR we have some, but it is not enough
|
||||
if (BufferUtil.isEmpty(_encryptedInput) || _needToFillMoreDataToProgress)
|
||||
{
|
||||
// we must be blocked trying to write before we can read
|
||||
// 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;
|
||||
}
|
||||
// We are not ready to read data
|
||||
|
||||
// otherwise write the net data
|
||||
_netWriting = true;
|
||||
getEndPoint().write(null, _writeCallback, _netOut);
|
||||
// Are we actually write blocked?
|
||||
if (_fillRequiresFlushToProgress)
|
||||
{
|
||||
// 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
|
||||
// Normal readable callback
|
||||
SslConnection.this.fillInterested();
|
||||
|
||||
return false;
|
||||
{
|
||||
// We are ready to read data
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -258,16 +323,19 @@ public class SslConnection extends AbstractAsyncConnection
|
|||
@Override
|
||||
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 (BufferUtil.hasContent(_netOut))
|
||||
if (BufferUtil.hasContent(_encryptedOutput))
|
||||
{
|
||||
// write it
|
||||
_netWriting = true;
|
||||
getEndPoint().write(null, _writeCallback, _netOut);
|
||||
_cannotAcceptMoreAppDataToFlush = true;
|
||||
getEndPoint().write(null, _writeCallback, _encryptedOutput);
|
||||
}
|
||||
// TODO test this with _flushUnwrap
|
||||
else if (_sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP)
|
||||
// we are actually read blocked in order to write
|
||||
SslConnection.this.fillInterested();
|
||||
|
@ -278,7 +346,7 @@ public class SslConnection extends AbstractAsyncConnection
|
|||
}
|
||||
};
|
||||
|
||||
public SslEndPoint()
|
||||
public DecryptedEndPoint()
|
||||
{
|
||||
super(getEndPoint().getLocalAddress(), getEndPoint().getRemoteAddress());
|
||||
}
|
||||
|
@ -307,37 +375,37 @@ public class SslConnection extends AbstractAsyncConnection
|
|||
try
|
||||
{
|
||||
// Do we already have some decrypted data?
|
||||
if (BufferUtil.hasContent(_appIn))
|
||||
return BufferUtil.append(_appIn, buffer);
|
||||
if (BufferUtil.hasContent(_decryptedInput))
|
||||
return BufferUtil.append(_decryptedInput, buffer);
|
||||
|
||||
// We will need a network buffer
|
||||
if (_netIn == null)
|
||||
_netIn = _bufferPool.acquire(_sslEngine.getSession().getPacketBufferSize(), _netDirect);
|
||||
if (_encryptedInput == null)
|
||||
_encryptedInput = _bufferPool.acquire(_sslEngine.getSession().getPacketBufferSize(), _encryptedDirectBuffers);
|
||||
else
|
||||
BufferUtil.compact(_netIn);
|
||||
BufferUtil.compact(_encryptedInput);
|
||||
|
||||
// We also need an app buffer, but can use the passed buffer if it is big enough
|
||||
ByteBuffer app_in;
|
||||
if (BufferUtil.space(buffer) > _sslEngine.getSession().getApplicationBufferSize())
|
||||
app_in = buffer;
|
||||
else if (_appIn == null)
|
||||
app_in = _appIn = _bufferPool.acquire(_sslEngine.getSession().getApplicationBufferSize(), _appDirect);
|
||||
else if (_decryptedInput == null)
|
||||
app_in = _decryptedInput = _bufferPool.acquire(_sslEngine.getSession().getApplicationBufferSize(), _decryptedDirectBuffers);
|
||||
else
|
||||
app_in = _appIn;
|
||||
app_in = _decryptedInput;
|
||||
|
||||
// loop filling and unwrapping until we have something
|
||||
while (true)
|
||||
{
|
||||
// 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);
|
||||
if (net_filled > 0)
|
||||
_underflown = false;
|
||||
_needToFillMoreDataToProgress = false;
|
||||
|
||||
// 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
|
||||
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);
|
||||
BufferUtil.flipToFlush(app_in, pos);
|
||||
|
||||
|
@ -348,10 +416,11 @@ public class SslConnection extends AbstractAsyncConnection
|
|||
throw new IllegalStateException();
|
||||
|
||||
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())
|
||||
{
|
||||
case NOT_HANDSHAKING:
|
||||
// We were not handshaking, so just tell the app we are closed
|
||||
return -1;
|
||||
|
||||
case NEED_TASK:
|
||||
|
@ -360,40 +429,47 @@ public class SslConnection extends AbstractAsyncConnection
|
|||
continue;
|
||||
|
||||
case NEED_WRAP:
|
||||
// we need to send some handshake data
|
||||
if (!_flushUnwrap)
|
||||
// we need to send some handshake data (probably to send a close handshake).
|
||||
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;
|
||||
try
|
||||
{
|
||||
flush(BufferUtil.EMPTY_BUFFER);
|
||||
}
|
||||
catch(IOException e)
|
||||
// flushing an empty buffer will invoke the wrap mechanisms
|
||||
flush(BufferUtil.EMPTY_BUFFER);
|
||||
// If encrypted output is all written, we can proceed with close
|
||||
if (BufferUtil.isEmpty(_encryptedOutput))
|
||||
{
|
||||
_fillRequiresFlushToProgress = false;
|
||||
return -1;
|
||||
}
|
||||
if (BufferUtil.hasContent(_netOut))
|
||||
return 0;
|
||||
_fillWrap = false;
|
||||
|
||||
// Otherwise return as if a normal fill and let a subsequent call
|
||||
// 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();
|
||||
}
|
||||
|
||||
case BUFFER_UNDERFLOW:
|
||||
_underflown = true;
|
||||
|
||||
//$FALL-THROUGH$ to deal with handshaking stuff
|
||||
|
||||
throw new IllegalStateException();
|
||||
|
||||
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 (app_in == buffer)
|
||||
return unwrapResult.bytesProduced();
|
||||
return BufferUtil.append(_appIn, buffer);
|
||||
return BufferUtil.append(_decryptedInput, buffer);
|
||||
}
|
||||
|
||||
// Dang! we have to care about the handshake state
|
||||
|
@ -412,14 +488,17 @@ public class SslConnection extends AbstractAsyncConnection
|
|||
|
||||
case NEED_WRAP:
|
||||
// we need to send some handshake data
|
||||
if (_flushUnwrap)
|
||||
if (_flushRequiresFillToProgress)
|
||||
return 0;
|
||||
_fillWrap = true;
|
||||
_fillRequiresFlushToProgress = true;
|
||||
flush(BufferUtil.EMPTY_BUFFER);
|
||||
if (BufferUtil.hasContent(_netOut))
|
||||
return 0;
|
||||
_fillWrap = false;
|
||||
continue;
|
||||
if (BufferUtil.isEmpty(_encryptedOutput))
|
||||
{
|
||||
// the flush completed so continue
|
||||
_fillRequiresFlushToProgress = false;
|
||||
continue;
|
||||
}
|
||||
return 0;
|
||||
|
||||
case NEED_UNWRAP:
|
||||
// if we just filled some net data
|
||||
|
@ -450,15 +529,15 @@ public class SslConnection extends AbstractAsyncConnection
|
|||
}
|
||||
finally
|
||||
{
|
||||
if (_netIn != null && !_netIn.hasRemaining())
|
||||
if (_encryptedInput != null && !_encryptedInput.hasRemaining())
|
||||
{
|
||||
_bufferPool.release(_netIn);
|
||||
_netIn = null;
|
||||
_bufferPool.release(_encryptedInput);
|
||||
_encryptedInput = null;
|
||||
}
|
||||
if (_appIn != null && !_appIn.hasRemaining())
|
||||
if (_decryptedInput != null && !_decryptedInput.hasRemaining())
|
||||
{
|
||||
_bufferPool.release(_appIn);
|
||||
_appIn = null;
|
||||
_bufferPool.release(_decryptedInput);
|
||||
_decryptedInput = null;
|
||||
}
|
||||
LOG.debug("{} fill exit", SslConnection.this);
|
||||
}
|
||||
|
@ -477,77 +556,86 @@ public class SslConnection extends AbstractAsyncConnection
|
|||
LOG.debug("{} flush enter {}", SslConnection.this, Arrays.toString(appOuts));
|
||||
try
|
||||
{
|
||||
if (_netWriting)
|
||||
if (_cannotAcceptMoreAppDataToFlush)
|
||||
return 0;
|
||||
|
||||
// We will need a network buffer
|
||||
if (_netOut == null)
|
||||
_netOut = _bufferPool.acquire(_sslEngine.getSession().getPacketBufferSize() * 2, _netDirect);
|
||||
if (_encryptedOutput == null)
|
||||
_encryptedOutput = _bufferPool.acquire(_sslEngine.getSession().getPacketBufferSize() * 2, _encryptedDirectBuffers);
|
||||
|
||||
int consumed=0;
|
||||
while (true)
|
||||
{
|
||||
// do the funky SSL thang!
|
||||
BufferUtil.compact(_netOut);
|
||||
int pos = BufferUtil.flipToFill(_netOut);
|
||||
SSLEngineResult wrapResult = _sslEngine.wrap(appOuts, _netOut);
|
||||
// We call sslEngine.wrap to try to take bytes from appOut buffers and encrypt them into the _netOut buffer
|
||||
BufferUtil.compact(_encryptedOutput);
|
||||
int pos = BufferUtil.flipToFill(_encryptedOutput);
|
||||
SSLEngineResult wrapResult = _sslEngine.wrap(appOuts, _encryptedOutput);
|
||||
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())
|
||||
{
|
||||
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;
|
||||
getEndPoint().flush(_netOut);
|
||||
if (BufferUtil.hasContent(_netOut))
|
||||
return 0;
|
||||
_cannotAcceptMoreAppDataToFlush = true;
|
||||
getEndPoint().flush(_encryptedOutput);
|
||||
// If we failed to flush the close handshake then we will just pretend that
|
||||
// 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();
|
||||
|
||||
case BUFFER_UNDERFLOW:
|
||||
throw new IllegalStateException();
|
||||
|
||||
case BUFFER_OVERFLOW:
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} OVERFLOW {}", this, BufferUtil.toDetailString(_netOut));
|
||||
|
||||
//$FALL-THROUGH$
|
||||
default:
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} {} {}", this, wrapResult.getStatus(), BufferUtil.toDetailString(_encryptedOutput));
|
||||
|
||||
// if we have net bytes, let's try to flush them
|
||||
if (BufferUtil.hasContent(_netOut))
|
||||
{
|
||||
getEndPoint().flush(_netOut);
|
||||
return wrapResult.bytesConsumed();
|
||||
}
|
||||
if (BufferUtil.hasContent(_encryptedOutput))
|
||||
getEndPoint().flush(_encryptedOutput);
|
||||
|
||||
// Dang! we have to deal with handshake state
|
||||
// But we also might have more to do for the handshaking state.
|
||||
switch (_sslEngine.getHandshakeStatus())
|
||||
{
|
||||
case NOT_HANDSHAKING:
|
||||
// we just didn't write anything. Strange?
|
||||
return 0;
|
||||
// Return with the number of bytes consumed (which may be 0)
|
||||
return consumed;
|
||||
|
||||
case NEED_TASK:
|
||||
// run the task
|
||||
// run the task and continue
|
||||
_sslEngine.getDelegatedTask().run();
|
||||
continue;
|
||||
|
||||
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;
|
||||
|
||||
case NEED_UNWRAP:
|
||||
// Were we were not called from fill and not reading anyway
|
||||
if (!_fillWrap && !_readInterest.isInterested())
|
||||
// Ah we need to fill some data so we can write.
|
||||
// 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);
|
||||
}
|
||||
return 0;
|
||||
return consumed;
|
||||
|
||||
case FINISHED:
|
||||
throw new IllegalStateException();
|
||||
|
@ -570,10 +658,10 @@ public class SslConnection extends AbstractAsyncConnection
|
|||
|
||||
private void releaseNetOut()
|
||||
{
|
||||
if (_netOut != null && !_netOut.hasRemaining())
|
||||
if (_encryptedOutput != null && !_encryptedOutput.hasRemaining())
|
||||
{
|
||||
_bufferPool.release(_netOut);
|
||||
_netOut = null;
|
||||
_bufferPool.release(_encryptedOutput);
|
||||
_encryptedOutput = null;
|
||||
if (_sslEngine.isOutboundDone())
|
||||
getEndPoint().shutdownOutput();
|
||||
}
|
||||
|
@ -639,7 +727,7 @@ public class SslConnection extends AbstractAsyncConnection
|
|||
@Override
|
||||
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" : "");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -88,7 +88,7 @@ public class SslSelectChannelConnector extends SelectChannelConnector implements
|
|||
request.setScheme(HttpScheme.HTTPS.asString());
|
||||
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();
|
||||
SSLEngine sslEngine=sslConnection.getSSLEngine();
|
||||
SslCertificates.customize(sslEngine,request);
|
||||
|
|
|
@ -149,8 +149,8 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
|
|||
|
||||
// Get the server side endpoint
|
||||
EndPoint endp = endpoint.exchange(null,10,TimeUnit.SECONDS);
|
||||
if (endp instanceof SslConnection.SslEndPoint)
|
||||
endp=((SslConnection.SslEndPoint)endp).getAsyncConnection().getEndPoint();
|
||||
if (endp instanceof SslConnection.DecryptedEndPoint)
|
||||
endp=((SslConnection.DecryptedEndPoint)endp).getAsyncConnection().getEndPoint();
|
||||
|
||||
// read the response
|
||||
String result=IO.toString(is);
|
||||
|
@ -222,8 +222,8 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
|
|||
|
||||
// Get the server side endpoint
|
||||
EndPoint endp = endpoint.exchange(null,10,TimeUnit.SECONDS);
|
||||
if (endp instanceof SslConnection.SslEndPoint)
|
||||
endp=((SslConnection.SslEndPoint)endp).getAsyncConnection().getEndPoint();
|
||||
if (endp instanceof SslConnection.DecryptedEndPoint)
|
||||
endp=((SslConnection.DecryptedEndPoint)endp).getAsyncConnection().getEndPoint();
|
||||
|
||||
// read the response
|
||||
String result=IO.toString(is);
|
||||
|
|
Loading…
Reference in New Issue