HTTP/2: connection error when receiving frames disallowed by stream states.

This commit is contained in:
Cesar Blum Silveira 2017-10-02 10:24:57 -07:00 committed by GitHub
parent bb9840a552
commit 1b1137b880
8 changed files with 210 additions and 75 deletions

View File

@ -14,7 +14,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{ {
public class Http1OutputProducer : IHttpOutputProducer public class Http1OutputProducer : IHttpOutputProducer
{ {
private static readonly ArraySegment<byte> _emptyData = new ArraySegment<byte>(new byte[0]);
private static readonly ArraySegment<byte> _continueBytes = new ArraySegment<byte>(Encoding.ASCII.GetBytes("HTTP/1.1 100 Continue\r\n\r\n")); private static readonly ArraySegment<byte> _continueBytes = new ArraySegment<byte>(Encoding.ASCII.GetBytes("HTTP/1.1 100 Continue\r\n\r\n"));
private static readonly byte[] _bytesHttpVersion11 = Encoding.ASCII.GetBytes("HTTP/1.1 "); private static readonly byte[] _bytesHttpVersion11 = Encoding.ASCII.GetBytes("HTTP/1.1 ");
private static readonly byte[] _bytesEndHeaders = Encoding.ASCII.GetBytes("\r\n\r\n"); private static readonly byte[] _bytesEndHeaders = Encoding.ASCII.GetBytes("\r\n\r\n");
@ -71,7 +70,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
public Task FlushAsync(CancellationToken cancellationToken = default(CancellationToken)) public Task FlushAsync(CancellationToken cancellationToken = default(CancellationToken))
{ {
return WriteAsync(_emptyData, cancellationToken); return WriteAsync(Constants.EmptyData, cancellationToken);
} }
public void Write<T>(Action<WritableBuffer, T> callback, T state) public void Write<T>(Action<WritableBuffer, T> callback, T state)

View File

@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
private readonly Http2Frame _incomingFrame = new Http2Frame(); private readonly Http2Frame _incomingFrame = new Http2Frame();
private Http2Stream _currentHeadersStream; private Http2Stream _currentHeadersStream;
private int _lastStreamId; private int _highestOpenedStreamId;
private bool _stopping; private bool _stopping;
@ -156,7 +156,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
stream.Abort(error); stream.Abort(error);
} }
await _frameWriter.WriteGoAwayAsync(_lastStreamId, errorCode); await _frameWriter.WriteGoAwayAsync(_highestOpenedStreamId, errorCode);
} }
finally finally
{ {
@ -247,16 +247,37 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR); throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR);
} }
if (_streams.TryGetValue(_incomingFrame.StreamId, out var stream) && !stream.HasReceivedEndStream) ThrowIfIncomingFrameSentToIdleStream();
if (_streams.TryGetValue(_incomingFrame.StreamId, out var stream) && !stream.EndStreamReceived)
{ {
return stream.OnDataAsync(_incomingFrame.DataPayload, return stream.OnDataAsync(_incomingFrame.DataPayload,
endStream: (_incomingFrame.DataFlags & Http2DataFrameFlags.END_STREAM) == Http2DataFrameFlags.END_STREAM); endStream: (_incomingFrame.DataFlags & Http2DataFrameFlags.END_STREAM) == Http2DataFrameFlags.END_STREAM);
} }
return _frameWriter.WriteRstStreamAsync(_incomingFrame.StreamId, Http2ErrorCode.STREAM_CLOSED); // http://httpwg.org/specs/rfc7540.html#rfc.section.5.1
//
// ...an endpoint that receives any frames after receiving a frame with the
// END_STREAM flag set MUST treat that as a connection error (Section 5.4.1)
// of type STREAM_CLOSED, unless the frame is permitted as described below.
//
// (The allowed frame types for this situation are WINDOW_UPDATE, RST_STREAM and PRIORITY)
//
// If we couldn't find the stream, it was either alive previously but closed with
// END_STREAM or RST_STREAM, or it was implicitly closed when the client opened
// a new stream with a higher ID. Per the spec, we should send RST_STREAM if
// the stream was closed with RST_STREAM or implicitly, but the spec also says
// in http://httpwg.org/specs/rfc7540.html#rfc.section.5.4.1 that
//
// An endpoint can end a connection at any time. In particular, an endpoint MAY
// choose to treat a stream error as a connection error.
//
// We choose to do that here so we don't have to keep state to track implicitly closed
// streams vs. streams closed with END_STREAM or RST_STREAM.
throw new Http2ConnectionErrorException(Http2ErrorCode.STREAM_CLOSED);
} }
private Task ProcessHeadersFrameAsync<TContext>(IHttpApplication<TContext> application) private async Task ProcessHeadersFrameAsync<TContext>(IHttpApplication<TContext> application)
{ {
if (_currentHeadersStream != null) if (_currentHeadersStream != null)
{ {
@ -278,33 +299,66 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR); throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR);
} }
_currentHeadersStream = new Http2Stream<TContext>(application, new Http2StreamContext if (_streams.TryGetValue(_incomingFrame.StreamId, out var stream))
{ {
ConnectionId = ConnectionId, // http://httpwg.org/specs/rfc7540.html#rfc.section.5.1
StreamId = _incomingFrame.StreamId, //
ServiceContext = _context.ServiceContext, // ...an endpoint that receives any frames after receiving a frame with the
ConnectionFeatures = _context.ConnectionFeatures, // END_STREAM flag set MUST treat that as a connection error (Section 5.4.1)
PipeFactory = _context.PipeFactory, // of type STREAM_CLOSED, unless the frame is permitted as described below.
LocalEndPoint = _context.LocalEndPoint, //
RemoteEndPoint = _context.RemoteEndPoint, // (The allowed frame types for this situation are WINDOW_UPDATE, RST_STREAM and PRIORITY)
StreamLifetimeHandler = this, if (stream.EndStreamReceived)
FrameWriter = _frameWriter {
}); throw new Http2ConnectionErrorException(Http2ErrorCode.STREAM_CLOSED);
_currentHeadersStream.ExpectData = (_incomingFrame.HeadersFlags & Http2HeadersFrameFlags.END_STREAM) == 0; }
_currentHeadersStream.Reset(); // TODO: trailers
_streams[_incomingFrame.StreamId] = _currentHeadersStream;
_hpackDecoder.Decode(_incomingFrame.HeadersPayload, _currentHeadersStream.RequestHeaders);
if ((_incomingFrame.HeadersFlags & Http2HeadersFrameFlags.END_HEADERS) == Http2HeadersFrameFlags.END_HEADERS)
{
_lastStreamId = _incomingFrame.StreamId;
_ = _currentHeadersStream.ProcessRequestsAsync();
_currentHeadersStream = null;
} }
else if (_incomingFrame.StreamId <= _highestOpenedStreamId)
{
// http://httpwg.org/specs/rfc7540.html#rfc.section.5.1.1
//
// The first use of a new stream identifier implicitly closes all streams in the "idle"
// state that might have been initiated by that peer with a lower-valued stream identifier.
//
// If we couldn't find the stream, it was previously closed (either implicitly or with
// END_STREAM or RST_STREAM).
throw new Http2ConnectionErrorException(Http2ErrorCode.STREAM_CLOSED);
}
else
{
// Start a new stream
_currentHeadersStream = new Http2Stream<TContext>(application, new Http2StreamContext
{
ConnectionId = ConnectionId,
StreamId = _incomingFrame.StreamId,
ServiceContext = _context.ServiceContext,
ConnectionFeatures = _context.ConnectionFeatures,
PipeFactory = _context.PipeFactory,
LocalEndPoint = _context.LocalEndPoint,
RemoteEndPoint = _context.RemoteEndPoint,
StreamLifetimeHandler = this,
FrameWriter = _frameWriter
});
return Task.CompletedTask; if ((_incomingFrame.HeadersFlags & Http2HeadersFrameFlags.END_STREAM) == Http2HeadersFrameFlags.END_STREAM)
{
await _currentHeadersStream.OnDataAsync(Constants.EmptyData, endStream: true);
}
_currentHeadersStream.Reset();
_streams[_incomingFrame.StreamId] = _currentHeadersStream;
_hpackDecoder.Decode(_incomingFrame.HeadersPayload, _currentHeadersStream.RequestHeaders);
if ((_incomingFrame.HeadersFlags & Http2HeadersFrameFlags.END_HEADERS) == Http2HeadersFrameFlags.END_HEADERS)
{
_highestOpenedStreamId = _incomingFrame.StreamId;
_ = _currentHeadersStream.ProcessRequestsAsync();
_currentHeadersStream = null;
}
}
} }
private Task ProcessPriorityFrameAsync() private Task ProcessPriorityFrameAsync()
@ -349,14 +403,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
throw new Http2ConnectionErrorException(Http2ErrorCode.FRAME_SIZE_ERROR); throw new Http2ConnectionErrorException(Http2ErrorCode.FRAME_SIZE_ERROR);
} }
ThrowIfIncomingFrameSentToIdleStream();
if (_streams.TryGetValue(_incomingFrame.StreamId, out var stream)) if (_streams.TryGetValue(_incomingFrame.StreamId, out var stream))
{ {
stream.Abort(error: null); stream.Abort(error: null);
} }
else if (_incomingFrame.StreamId > _lastStreamId)
{
throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR);
}
return Task.CompletedTask; return Task.CompletedTask;
} }
@ -449,16 +501,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
throw new Http2ConnectionErrorException(Http2ErrorCode.FRAME_SIZE_ERROR); throw new Http2ConnectionErrorException(Http2ErrorCode.FRAME_SIZE_ERROR);
} }
if (_incomingFrame.StreamId == 0) ThrowIfIncomingFrameSentToIdleStream();
if (_incomingFrame.WindowUpdateSizeIncrement == 0)
{ {
if (_incomingFrame.WindowUpdateSizeIncrement == 0) if (_incomingFrame.StreamId == 0)
{ {
throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR); throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR);
} }
} else
else
{
if (_incomingFrame.WindowUpdateSizeIncrement == 0)
{ {
return _frameWriter.WriteRstStreamAsync(_incomingFrame.StreamId, Http2ErrorCode.PROTOCOL_ERROR); return _frameWriter.WriteRstStreamAsync(_incomingFrame.StreamId, Http2ErrorCode.PROTOCOL_ERROR);
} }
@ -478,7 +529,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
if ((_incomingFrame.ContinuationFlags & Http2ContinuationFrameFlags.END_HEADERS) == Http2ContinuationFrameFlags.END_HEADERS) if ((_incomingFrame.ContinuationFlags & Http2ContinuationFrameFlags.END_HEADERS) == Http2ContinuationFrameFlags.END_HEADERS)
{ {
_lastStreamId = _currentHeadersStream.StreamId; _highestOpenedStreamId = _currentHeadersStream.StreamId;
_ = _currentHeadersStream.ProcessRequestsAsync(); _ = _currentHeadersStream.ProcessRequestsAsync();
_currentHeadersStream = null; _currentHeadersStream = null;
} }
@ -496,6 +547,25 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
return Task.CompletedTask; return Task.CompletedTask;
} }
private void ThrowIfIncomingFrameSentToIdleStream()
{
// http://httpwg.org/specs/rfc7540.html#rfc.section.5.1
// 5.1. Stream states
// ...
// idle:
// ...
// Receiving any frame other than HEADERS or PRIORITY on a stream in this state MUST be
// treated as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
//
// If the stream ID in the incoming frame is higher than the highest opened stream ID so
// far, then the incoming frame's target stream is in the idle state, which is the implicit
// initial state for all streams.
if (_incomingFrame.StreamId > _highestOpenedStreamId)
{
throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR);
}
}
void IHttp2StreamLifetimeHandler.OnStreamCompleted(int streamId) void IHttp2StreamLifetimeHandler.OnStreamCompleted(int streamId)
{ {
_streams.TryRemove(streamId, out _); _streams.TryRemove(streamId, out _);

View File

@ -7,16 +7,13 @@ using System.IO.Pipelines;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack;
using Microsoft.Extensions.Logging; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{ {
public class Http2FrameWriter : IHttp2FrameWriter public class Http2FrameWriter : IHttp2FrameWriter
{ {
private static readonly ArraySegment<byte> _emptyData = new ArraySegment<byte>(new byte[0]);
private readonly Http2Frame _outgoingFrame = new Http2Frame(); private readonly Http2Frame _outgoingFrame = new Http2Frame();
private readonly object _writeLock = new object(); private readonly object _writeLock = new object();
private readonly HPackEncoder _hpackEncoder = new HPackEncoder(); private readonly HPackEncoder _hpackEncoder = new HPackEncoder();
@ -48,7 +45,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
public Task FlushAsync(CancellationToken cancellationToken) public Task FlushAsync(CancellationToken cancellationToken)
{ {
return WriteAsync(_emptyData); return WriteAsync(Constants.EmptyData);
} }
public Task Write100ContinueAsync(int streamId) public Task Write100ContinueAsync(int streamId)

View File

@ -29,9 +29,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
HttpRequestHeaders headers, HttpRequestHeaders headers,
Http2Stream context) Http2Stream context)
{ {
if (!context.ExpectData) if (context.EndStreamReceived)
{ {
return MessageBody.ZeroContentLengthClose; return ZeroContentLengthClose;
} }
return new ForHttp2(context); return new ForHttp2(context);

View File

@ -6,13 +6,12 @@ using System.IO.Pipelines;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{ {
public class Http2OutputProducer : IHttpOutputProducer public class Http2OutputProducer : IHttpOutputProducer
{ {
private static readonly ArraySegment<byte> _emptyData = new ArraySegment<byte>(new byte[0]);
private readonly int _streamId; private readonly int _streamId;
private readonly IHttp2FrameWriter _frameWriter; private readonly IHttp2FrameWriter _frameWriter;
@ -47,7 +46,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
public Task WriteStreamSuffixAsync(CancellationToken cancellationToken) public Task WriteStreamSuffixAsync(CancellationToken cancellationToken)
{ {
return _frameWriter.WriteDataAsync(_streamId, _emptyData, endStream: true, cancellationToken: cancellationToken); return _frameWriter.WriteDataAsync(_streamId, Constants.EmptyData, endStream: true, cancellationToken: cancellationToken);
} }
public void WriteResponseHeaders(int statusCode, string ReasonPhrase, HttpResponseHeaders responseHeaders) public void WriteResponseHeaders(int statusCode, string ReasonPhrase, HttpResponseHeaders responseHeaders)

View File

@ -24,12 +24,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
public int StreamId => _context.StreamId; public int StreamId => _context.StreamId;
public bool HasReceivedEndStream { get; private set; } public bool EndStreamReceived { get; private set; }
protected IHttp2StreamLifetimeHandler StreamLifetimeHandler => _context.StreamLifetimeHandler; protected IHttp2StreamLifetimeHandler StreamLifetimeHandler => _context.StreamLifetimeHandler;
public bool ExpectData { get; set; }
public override bool IsUpgradableRequest => false; public override bool IsUpgradableRequest => false;
protected override void OnReset() protected override void OnReset()
@ -91,7 +89,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
if (endStream) if (endStream)
{ {
HasReceivedEndStream = true; EndStreamReceived = true;
RequestBodyPipe.Writer.Complete(); RequestBodyPipe.Writer.Complete();
} }
} }

View File

@ -32,5 +32,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
public const string ServerName = "Kestrel"; public const string ServerName = "Kestrel";
public static readonly TimeSpan RequestBodyDrainTimeout = TimeSpan.FromSeconds(5); public static readonly TimeSpan RequestBodyDrainTimeout = TimeSpan.FromSeconds(5);
public static readonly ArraySegment<byte> EmptyData = new ArraySegment<byte>(new byte[0]);
} }
} }

View File

@ -467,33 +467,30 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
} }
[Fact] [Fact]
public async Task DATA_Received_StreamIdle_StreamError() public async Task DATA_Received_StreamIdle_ConnectionError()
{ {
await InitializeConnectionAsync(_noopApplication); await InitializeConnectionAsync(_noopApplication);
await SendDataAsync(1, _helloWorldBytes, endStream: false); await SendDataAsync(1, _helloWorldBytes, endStream: false);
await WaitForStreamErrorAsync(expectedStreamId: 1, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, ignoreNonRstStreamFrames: false); await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false);
await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false);
} }
[Fact] [Fact]
public async Task DATA_Received_StreamHalfClosedRemote_StreamError() public async Task DATA_Received_StreamHalfClosedRemote_ConnectionError()
{ {
await InitializeConnectionAsync(_echoWaitForAbortApplication); // Use _waitForAbortApplication so we know the stream will still be active when we send the illegal DATA frame
await InitializeConnectionAsync(_waitForAbortApplication);
await StartStreamAsync(1, _postRequestHeaders, endStream: false); await StartStreamAsync(1, _postRequestHeaders, endStream: true);
await SendDataAsync(1, _helloBytes, endStream: true);
await SendDataAsync(1, _worldBytes, endStream: true);
await WaitForStreamErrorAsync(expectedStreamId: 1, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, ignoreNonRstStreamFrames: true); await SendDataAsync(1, _helloWorldBytes, endStream: false);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: true); await WaitForConnectionErrorAsync(expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, ignoreNonGoAwayFrames: false);
} }
[Fact] [Fact]
public async Task DATA_Received_StreamClosed_StreamError() public async Task DATA_Received_StreamClosed_ConnectionError()
{ {
await InitializeConnectionAsync(_noopApplication); await InitializeConnectionAsync(_noopApplication);
@ -510,13 +507,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendDataAsync(1, _helloWorldBytes, endStream: false); await SendDataAsync(1, _helloWorldBytes, endStream: false);
await WaitForStreamErrorAsync(expectedStreamId: 1, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, ignoreNonRstStreamFrames: false); await WaitForConnectionErrorAsync(expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, ignoreNonGoAwayFrames: false);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
} }
[Fact] [Fact]
public async Task DATA_Received_StreamClosedImplicitly_StreamError() public async Task DATA_Received_StreamClosedImplicitly_ConnectionError()
{ {
// http://httpwg.org/specs/rfc7540.html#rfc.section.5.1.1 // http://httpwg.org/specs/rfc7540.html#rfc.section.5.1.1
// //
@ -540,9 +535,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendDataAsync(1, _helloWorldBytes, endStream: true); await SendDataAsync(1, _helloWorldBytes, endStream: true);
await WaitForStreamErrorAsync(expectedStreamId: 1, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, ignoreNonRstStreamFrames: false); await WaitForConnectionErrorAsync(expectedLastStreamId: 3, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, ignoreNonGoAwayFrames: false);
await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
} }
[Fact] [Fact]
@ -655,6 +648,63 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false);
} }
[Fact]
public async Task HEADERS_Received_StreamClosed_ConnectionError()
{
await InitializeConnectionAsync(_noopApplication);
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
withLength: 55,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 0,
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
withStreamId: 1);
// Try to re-use the stream ID (http://httpwg.org/specs/rfc7540.html#rfc.section.5.1.1)
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
await WaitForConnectionErrorAsync(expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, ignoreNonGoAwayFrames: false);
}
[Fact]
public async Task HEADERS_Received_StreamHalfClosedRemote_ConnectionError()
{
// Use _waitForAbortApplication so we know the stream will still be active when we send the illegal DATA frame
await InitializeConnectionAsync(_waitForAbortApplication);
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders);
await WaitForConnectionErrorAsync(expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, ignoreNonGoAwayFrames: false);
}
[Fact]
public async Task HEADERS_Received_StreamClosedImplicitly_ConnectionError()
{
await InitializeConnectionAsync(_noopApplication);
await StartStreamAsync(3, _browserRequestHeaders, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
withLength: 55,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 3);
await ExpectAsync(Http2FrameType.DATA,
withLength: 0,
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
withStreamId: 3);
// Stream 1 was implicitly closed by opening stream 3 before (http://httpwg.org/specs/rfc7540.html#rfc.section.5.1.1)
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
await WaitForConnectionErrorAsync(expectedLastStreamId: 3, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, ignoreNonGoAwayFrames: false);
}
[Theory] [Theory]
[InlineData(0)] [InlineData(0)]
[InlineData(1)] [InlineData(1)]
@ -811,6 +861,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false);
} }
[Fact]
public async Task RST_STREAM_Received_StreamIdle_ConnectionError()
{
await InitializeConnectionAsync(_noopApplication);
await SendRstStreamAsync(1);
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false);
}
[Theory] [Theory]
[InlineData(3)] [InlineData(3)]
[InlineData(5)] [InlineData(5)]
@ -1077,6 +1137,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false);
} }
[Fact]
public async Task WINDOW_UPDATE_Received_StreamIdle_ConnectionError()
{
await InitializeConnectionAsync(_waitForAbortApplication);
await SendWindowUpdateAsync(1, sizeIncrement: 1);
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false);
}
[Fact] [Fact]
public async Task WINDOW_UPDATE_Received_OnStream_SizeIncrementZero_StreamError() public async Task WINDOW_UPDATE_Received_OnStream_SizeIncrementZero_StreamError()
{ {