/// <summary>Unwraps inbound SSL records.</summary> private void Unwrap(IChannelHandlerContext ctx, IByteBuffer packet, int offset, int length) { bool pending = false; IByteBuffer outputBuffer = null; try { #if NETCOREAPP || NETSTANDARD_2_0_GREATER ReadOnlyMemory <byte> inputIoBuffer = packet.GetReadableMemory(offset, length); _mediationStream.SetSource(inputIoBuffer, ctx.Allocator); #else ArraySegment <byte> inputIoBuffer = packet.GetIoBuffer(offset, length); _mediationStream.SetSource(inputIoBuffer.Array, inputIoBuffer.Offset, ctx.Allocator); #endif if (!EnsureAuthenticationCompleted(ctx)) { _mediationStream.ExpandSource(length); return; } _mediationStream.ExpandSource(length); var currentReadFuture = _pendingSslStreamReadFuture; if (currentReadFuture is object) { // restoring context from previous read Debug.Assert(_pendingSslStreamReadBuffer is object); outputBuffer = _pendingSslStreamReadBuffer; var outputBufferLength = outputBuffer.WritableBytes; _pendingSslStreamReadFuture = null; _pendingSslStreamReadBuffer = null; // there was a read pending already, so we make sure we completed that first if (currentReadFuture.IsCompleted) { if (currentReadFuture.IsFailure()) { // The decryption operation failed ExceptionDispatchInfo.Capture(currentReadFuture.Exception.InnerException).Throw(); } int read = currentReadFuture.Result; if (0u >= (uint)read) { // Stream closed NotifyClosePromise(null); return; } // Now output the result of previous read and decide whether to do an extra read on the same source or move forward outputBuffer.Advance(read); _firedChannelRead = true; ctx.FireChannelRead(outputBuffer); currentReadFuture = null; outputBuffer = null; if (0u >= (uint)_mediationStream.SourceReadableBytes) { // we just made a frame available for reading but there was already pending read so SslStream read it out to make further progress there if (read < outputBufferLength) { // SslStream returned non-full buffer and there's no more input to go through -> // typically it means SslStream is done reading current frame so we skip return; } // we've read out `read` bytes out of current packet to fulfil previously outstanding read outputBufferLength = length - read; if ((uint)(outputBufferLength - 1) > SharedConstants.TooBigOrNegative) // <= 0 { // after feeding to SslStream current frame it read out more bytes than current packet size outputBufferLength = c_fallbackReadBufferSize; } } outputBuffer = ctx.Allocator.Buffer(outputBufferLength); currentReadFuture = ReadFromSslStreamAsync(outputBuffer, outputBufferLength); } } else { // there was no pending read before so we estimate buffer of `length` bytes to be sufficient outputBuffer = ctx.Allocator.Buffer(length); currentReadFuture = ReadFromSslStreamAsync(outputBuffer, length); } // read out the rest of SslStream's output (if any) at risk of going async // using FallbackReadBufferSize - buffer size we're ok to have pinned with the SslStream until it's done reading while (true) { if (currentReadFuture is object) { if (!currentReadFuture.IsCompleted) { break; } if (currentReadFuture.IsFailure()) { // The decryption operation failed ExceptionDispatchInfo.Capture(currentReadFuture.Exception.InnerException).Throw(); } int read = currentReadFuture.Result; if (0u >= (uint)read) { // Stream closed NotifyClosePromise(null); return; } outputBuffer.Advance(read); _firedChannelRead = true; ctx.FireChannelRead(outputBuffer); currentReadFuture = null; outputBuffer = null; if (0u >= (uint)_mediationStream.SourceReadableBytes) { return; } } outputBuffer = ctx.Allocator.Buffer(c_fallbackReadBufferSize); currentReadFuture = ReadFromSslStreamAsync(outputBuffer, c_fallbackReadBufferSize); } pending = true; _pendingSslStreamReadBuffer = outputBuffer; _pendingSslStreamReadFuture = currentReadFuture; } finally { _mediationStream.ResetSource(ctx.Allocator); if (!pending && outputBuffer is object) { if (outputBuffer.IsReadable()) { _firedChannelRead = true; ctx.FireChannelRead(outputBuffer); } else { outputBuffer.SafeRelease(); } } } }