HTTP/2: add exception error messages and log them.

This commit is contained in:
Cesar Blum Silveira 2017-10-12 11:23:57 -07:00 committed by GitHub
parent fdb4184dbf
commit deed6c9780
9 changed files with 979 additions and 189 deletions

View File

@ -3,6 +3,8 @@
using System; using System;
using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -40,5 +42,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
public void RequestBodyDone(string connectionId, string traceIdentifier) { } public void RequestBodyDone(string connectionId, string traceIdentifier) { }
public void RequestBodyMininumDataRateNotSatisfied(string connectionId, string traceIdentifier, double rate) { } public void RequestBodyMininumDataRateNotSatisfied(string connectionId, string traceIdentifier, double rate) { }
public void ResponseMininumDataRateNotSatisfied(string connectionId, string traceIdentifier) { } public void ResponseMininumDataRateNotSatisfied(string connectionId, string traceIdentifier) { }
public void Http2ConnectionError(string connectionId, Http2ConnectionErrorException ex) { }
public void Http2StreamError(string connectionId, Http2StreamErrorException ex) { }
public void HPackDecodingError(string connectionId, int streamId, HPackDecodingException ex) { }
} }
} }

View File

@ -384,4 +384,82 @@
<data name="HPackErrorIncompleteHeaderBlock" xml:space="preserve"> <data name="HPackErrorIncompleteHeaderBlock" xml:space="preserve">
<value>The header block was incomplete and could not be fully decoded.</value> <value>The header block was incomplete and could not be fully decoded.</value>
</data> </data>
<data name="Http2ErrorStreamIdEven" xml:space="preserve">
<value>The client sent a {frameType} frame with even stream ID {streamId}.</value>
</data>
<data name="Http2ErrorPushPromiseReceived" xml:space="preserve">
<value>The client sent a A PUSH_PROMISE frame.</value>
</data>
<data name="Http2ErrorHeadersInterleaved" xml:space="preserve">
<value>The client sent a {frameType} frame to stream ID {streamId} before signaling of the header block for stream ID {headersStreamId}.</value>
</data>
<data name="Http2ErrorStreamIdZero" xml:space="preserve">
<value>The client sent a {frameType} frame with stream ID 0.</value>
</data>
<data name="Http2ErrorStreamIdNotZero" xml:space="preserve">
<value>The client sent a {frameType} frame with stream ID different than 0.</value>
</data>
<data name="Http2ErrorPaddingTooLong" xml:space="preserve">
<value>The client sent a {frameType} frame with padding longer than or with the same length as the sent data.</value>
</data>
<data name="Http2ErrorStreamClosed" xml:space="preserve">
<value>The client sent a {frameType} frame to closed stream ID {streamId}.</value>
</data>
<data name="Http2ErrorStreamHalfClosedRemote" xml:space="preserve">
<value>The client sent a {frameType} frame to stream ID {streamId} which is in the "half-closed (remote) state".</value>
</data>
<data name="Http2ErrorStreamSelfDependency" xml:space="preserve">
<value>The client sent a {frameType} frame with dependency information that would cause stream ID {streamId} to depend on itself.</value>
</data>
<data name="Http2ErrorUnexpectedFrameLength" xml:space="preserve">
<value>The client sent a {frameType} frame with length different than {expectedLength}.</value>
</data>
<data name="Http2ErrorSettingsLengthNotMultipleOfSix" xml:space="preserve">
<value>The client sent a SETTINGS frame with a length that is not a multiple of 6.</value>
</data>
<data name="Http2ErrorSettingsAckLengthNotZero" xml:space="preserve">
<value>The client sent a SETTINGS frame with ACK set and length different than 0.</value>
</data>
<data name="Http2ErrorSettingsParameterOutOfRange" xml:space="preserve">
<value>The client sent a SETTINGS frame with a value for parameter {parameter} that is out of range.</value>
</data>
<data name="Http2ErrorWindowUpdateIncrementZero" xml:space="preserve">
<value>The client sent a WINDOW_UPDATE frame with a window size increment of 0.</value>
</data>
<data name="Http2ErrorContinuationWithNoHeaders" xml:space="preserve">
<value>The client sent a CONTINUATION frame not preceded by a HEADERS frame.</value>
</data>
<data name="Http2ErrorStreamIdle" xml:space="preserve">
<value>The client sent a {frameType} frame to idle stream ID {streamId}.</value>
</data>
<data name="Http2ErrorTrailersContainPseudoHeaderField" xml:space="preserve">
<value>The client sent trailers containing one or more pseudo-header fields.</value>
</data>
<data name="Http2ErrorHeaderNameUppercase" xml:space="preserve">
<value>The client sent a header with uppercase characters in its name.</value>
</data>
<data name="Http2ErrorTrailerNameUppercase" xml:space="preserve">
<value>The client sent a trailer with uppercase characters in its name.</value>
</data>
<data name="Http2ErrorHeadersWithTrailersNoEndStream" xml:space="preserve">
<value>The client sent a HEADERS frame containing trailers without setting the END_STREAM flag.</value>
</data>
<data name="Http2ErrorMissingMandatoryPseudoHeaderFields" xml:space="preserve">
<value>Request headers missing one or more mandatory pseudo-header fields.</value>
</data>
<data name="Http2ErrorPseudoHeaderFieldAfterRegularHeaders" xml:space="preserve">
<value>Pseudo-header field found in request headers after regular header fields.</value>
</data>
<data name="Http2ErrorUnknownPseudoHeaderField" xml:space="preserve">
<value>Request headers contain unknown pseudo-header field.</value>
</data>
<data name="Http2ErrorResponsePseudoHeaderField" xml:space="preserve">
<value>Request headers contain response-specific pseudo-header field.</value>
</data>
<data name="Http2ErrorDuplicatePseudoHeaderField" xml:space="preserve">
<value>Request headers contain duplicate pseudo-header field.</value>
</data>
<data name="Http2ErrorConnectionSpecificHeaderField" xml:space="preserve">
<value>Request headers contain connection-specific header field.</value>
</data>
</root> </root>

View File

@ -166,28 +166,33 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
} }
} }
} }
catch (ConnectionAbortedException ex) catch (ConnectionResetException ex)
{ {
// TODO: log // Don't log ECONNRESET errors when there are no active streams on the connection. Browsers like IE will reset connections regularly.
if (_streams.Count > 0)
{
Log.RequestProcessingError(ConnectionId, ex);
}
error = ex; error = ex;
} }
catch (Http2ConnectionErrorException ex) catch (Http2ConnectionErrorException ex)
{ {
// TODO: log Log.Http2ConnectionError(ConnectionId, ex);
error = ex; error = ex;
errorCode = ex.ErrorCode; errorCode = ex.ErrorCode;
} }
catch (HPackDecodingException ex) catch (HPackDecodingException ex)
{ {
// TODO: log Log.HPackDecodingError(ConnectionId, _currentHeadersStream.StreamId, ex);
error = ex; error = ex;
errorCode = Http2ErrorCode.COMPRESSION_ERROR; errorCode = Http2ErrorCode.COMPRESSION_ERROR;
} }
catch (Exception ex) catch (Exception ex)
{ {
// TODO: log
error = ex; error = ex;
errorCode = Http2ErrorCode.INTERNAL_ERROR; errorCode = Http2ErrorCode.INTERNAL_ERROR;
throw;
} }
finally finally
{ {
@ -242,7 +247,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
// a connection error (Section 5.4.1) of type PROTOCOL_ERROR. // a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
if (_incomingFrame.StreamId != 0 && (_incomingFrame.StreamId & 1) == 0) if (_incomingFrame.StreamId != 0 && (_incomingFrame.StreamId & 1) == 0)
{ {
throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR); throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdEven(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.PROTOCOL_ERROR);
} }
switch (_incomingFrame.Type) switch (_incomingFrame.Type)
@ -258,7 +263,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
case Http2FrameType.SETTINGS: case Http2FrameType.SETTINGS:
return ProcessSettingsFrameAsync(); return ProcessSettingsFrameAsync();
case Http2FrameType.PUSH_PROMISE: case Http2FrameType.PUSH_PROMISE:
throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR); throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorPushPromiseReceived, Http2ErrorCode.PROTOCOL_ERROR);
case Http2FrameType.PING: case Http2FrameType.PING:
return ProcessPingFrameAsync(); return ProcessPingFrameAsync();
case Http2FrameType.GOAWAY: case Http2FrameType.GOAWAY:
@ -276,35 +281,39 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{ {
if (_currentHeadersStream != null) if (_currentHeadersStream != null)
{ {
throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR); throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorHeadersInterleaved(_incomingFrame.Type, _incomingFrame.StreamId, _currentHeadersStream.StreamId), Http2ErrorCode.PROTOCOL_ERROR);
} }
if (_incomingFrame.StreamId == 0) if (_incomingFrame.StreamId == 0)
{ {
throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR); throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR);
} }
if (_incomingFrame.DataHasPadding && _incomingFrame.DataPadLength >= _incomingFrame.Length) if (_incomingFrame.DataHasPadding && _incomingFrame.DataPadLength >= _incomingFrame.Length)
{ {
throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR); throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorPaddingTooLong(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR);
} }
ThrowIfIncomingFrameSentToIdleStream(); ThrowIfIncomingFrameSentToIdleStream();
if (_streams.TryGetValue(_incomingFrame.StreamId, out var stream) && !stream.EndStreamReceived) if (_streams.TryGetValue(_incomingFrame.StreamId, out var stream))
{ {
if (stream.EndStreamReceived)
{
// 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)
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(_incomingFrame.Type, stream.StreamId), Http2ErrorCode.STREAM_CLOSED);
}
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);
} }
// 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 // 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 // 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 // a new stream with a higher ID. Per the spec, we should send RST_STREAM if
@ -316,29 +325,29 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
// //
// We choose to do that here so we don't have to keep state to track implicitly closed // 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. // streams vs. streams closed with END_STREAM or RST_STREAM.
throw new Http2ConnectionErrorException(Http2ErrorCode.STREAM_CLOSED); throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamClosed(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.STREAM_CLOSED);
} }
private async Task ProcessHeadersFrameAsync<TContext>(IHttpApplication<TContext> application) private async Task ProcessHeadersFrameAsync<TContext>(IHttpApplication<TContext> application)
{ {
if (_currentHeadersStream != null) if (_currentHeadersStream != null)
{ {
throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR); throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorHeadersInterleaved(_incomingFrame.Type, _incomingFrame.StreamId, _currentHeadersStream.StreamId), Http2ErrorCode.PROTOCOL_ERROR);
} }
if (_incomingFrame.StreamId == 0) if (_incomingFrame.StreamId == 0)
{ {
throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR); throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR);
} }
if (_incomingFrame.HeadersHasPadding && _incomingFrame.HeadersPadLength >= _incomingFrame.Length) if (_incomingFrame.HeadersHasPadding && _incomingFrame.HeadersPadLength >= _incomingFrame.Length)
{ {
throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR); throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorPaddingTooLong(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR);
} }
if (_incomingFrame.HeadersHasPriority && _incomingFrame.HeadersStreamDependency == _incomingFrame.StreamId) if (_incomingFrame.HeadersHasPriority && _incomingFrame.HeadersStreamDependency == _incomingFrame.StreamId)
{ {
throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR); throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamSelfDependency(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.PROTOCOL_ERROR);
} }
if (_streams.TryGetValue(_incomingFrame.StreamId, out var stream)) if (_streams.TryGetValue(_incomingFrame.StreamId, out var stream))
@ -352,7 +361,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
// (The allowed frame types for this situation are WINDOW_UPDATE, RST_STREAM and PRIORITY) // (The allowed frame types for this situation are WINDOW_UPDATE, RST_STREAM and PRIORITY)
if (stream.EndStreamReceived) if (stream.EndStreamReceived)
{ {
throw new Http2ConnectionErrorException(Http2ErrorCode.STREAM_CLOSED); throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(_incomingFrame.Type, stream.StreamId), Http2ErrorCode.STREAM_CLOSED);
} }
// TODO: trailers // TODO: trailers
@ -366,7 +375,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
// //
// If we couldn't find the stream, it was previously closed (either implicitly or with // If we couldn't find the stream, it was previously closed (either implicitly or with
// END_STREAM or RST_STREAM). // END_STREAM or RST_STREAM).
throw new Http2ConnectionErrorException(Http2ErrorCode.STREAM_CLOSED); throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamClosed(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.STREAM_CLOSED);
} }
else else
{ {
@ -400,22 +409,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{ {
if (_currentHeadersStream != null) if (_currentHeadersStream != null)
{ {
throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR); throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorHeadersInterleaved(_incomingFrame.Type, _incomingFrame.StreamId, _currentHeadersStream.StreamId), Http2ErrorCode.PROTOCOL_ERROR);
} }
if (_incomingFrame.StreamId == 0) if (_incomingFrame.StreamId == 0)
{ {
throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR); throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR);
} }
if (_incomingFrame.PriorityStreamDependency == _incomingFrame.StreamId) if (_incomingFrame.PriorityStreamDependency == _incomingFrame.StreamId)
{ {
throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR); throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamSelfDependency(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.PROTOCOL_ERROR);
} }
if (_incomingFrame.Length != 5) if (_incomingFrame.Length != 5)
{ {
throw new Http2ConnectionErrorException(Http2ErrorCode.FRAME_SIZE_ERROR); throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(_incomingFrame.Type, 5), Http2ErrorCode.FRAME_SIZE_ERROR);
} }
return Task.CompletedTask; return Task.CompletedTask;
@ -425,17 +434,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{ {
if (_currentHeadersStream != null) if (_currentHeadersStream != null)
{ {
throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR); throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorHeadersInterleaved(_incomingFrame.Type, _incomingFrame.StreamId, _currentHeadersStream.StreamId), Http2ErrorCode.PROTOCOL_ERROR);
} }
if (_incomingFrame.StreamId == 0) if (_incomingFrame.StreamId == 0)
{ {
throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR); throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR);
} }
if (_incomingFrame.Length != 4) if (_incomingFrame.Length != 4)
{ {
throw new Http2ConnectionErrorException(Http2ErrorCode.FRAME_SIZE_ERROR); throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(_incomingFrame.Type, 4), Http2ErrorCode.FRAME_SIZE_ERROR);
} }
ThrowIfIncomingFrameSentToIdleStream(); ThrowIfIncomingFrameSentToIdleStream();
@ -452,22 +461,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{ {
if (_currentHeadersStream != null) if (_currentHeadersStream != null)
{ {
throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR); throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorHeadersInterleaved(_incomingFrame.Type, _incomingFrame.StreamId, _currentHeadersStream.StreamId), Http2ErrorCode.PROTOCOL_ERROR);
} }
if (_incomingFrame.StreamId != 0) if (_incomingFrame.StreamId != 0)
{ {
throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR); throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdNotZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR);
} }
if ((_incomingFrame.SettingsFlags & Http2SettingsFrameFlags.ACK) == Http2SettingsFrameFlags.ACK && _incomingFrame.Length != 0) if ((_incomingFrame.SettingsFlags & Http2SettingsFrameFlags.ACK) == Http2SettingsFrameFlags.ACK && _incomingFrame.Length != 0)
{ {
throw new Http2ConnectionErrorException(Http2ErrorCode.FRAME_SIZE_ERROR); throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorSettingsAckLengthNotZero, Http2ErrorCode.FRAME_SIZE_ERROR);
} }
if (_incomingFrame.Length % 6 != 0) if (_incomingFrame.Length % 6 != 0)
{ {
throw new Http2ConnectionErrorException(Http2ErrorCode.FRAME_SIZE_ERROR); throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorSettingsLengthNotMultipleOfSix, Http2ErrorCode.FRAME_SIZE_ERROR);
} }
try try
@ -477,7 +486,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
} }
catch (Http2SettingsParameterOutOfRangeException ex) catch (Http2SettingsParameterOutOfRangeException ex)
{ {
throw new Http2ConnectionErrorException(ex.Parameter == Http2SettingsParameter.SETTINGS_INITIAL_WINDOW_SIZE throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorSettingsParameterOutOfRange(ex.Parameter), ex.Parameter == Http2SettingsParameter.SETTINGS_INITIAL_WINDOW_SIZE
? Http2ErrorCode.FLOW_CONTROL_ERROR ? Http2ErrorCode.FLOW_CONTROL_ERROR
: Http2ErrorCode.PROTOCOL_ERROR); : Http2ErrorCode.PROTOCOL_ERROR);
} }
@ -487,21 +496,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{ {
if (_currentHeadersStream != null) if (_currentHeadersStream != null)
{ {
throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR); throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorHeadersInterleaved(_incomingFrame.Type, _incomingFrame.StreamId, _currentHeadersStream.StreamId), Http2ErrorCode.PROTOCOL_ERROR);
} }
if (_incomingFrame.StreamId != 0) if (_incomingFrame.StreamId != 0)
{ {
throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR); throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdNotZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR);
} }
if (_incomingFrame.Length != 8) if (_incomingFrame.Length != 8)
{ {
throw new Http2ConnectionErrorException(Http2ErrorCode.FRAME_SIZE_ERROR); throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(_incomingFrame.Type, 8), Http2ErrorCode.FRAME_SIZE_ERROR);
} }
if ((_incomingFrame.PingFlags & Http2PingFrameFlags.ACK) == Http2PingFrameFlags.ACK) if ((_incomingFrame.PingFlags & Http2PingFrameFlags.ACK) == Http2PingFrameFlags.ACK)
{ {
// TODO: verify that payload is equal to the outgoing PING frame
return Task.CompletedTask; return Task.CompletedTask;
} }
@ -512,12 +522,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{ {
if (_currentHeadersStream != null) if (_currentHeadersStream != null)
{ {
throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR); throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorHeadersInterleaved(_incomingFrame.Type, _incomingFrame.StreamId, _currentHeadersStream.StreamId), Http2ErrorCode.PROTOCOL_ERROR);
} }
if (_incomingFrame.StreamId != 0) if (_incomingFrame.StreamId != 0)
{ {
throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR); throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdNotZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR);
} }
Stop(); Stop();
@ -528,26 +538,34 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{ {
if (_currentHeadersStream != null) if (_currentHeadersStream != null)
{ {
throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR); throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorHeadersInterleaved(_incomingFrame.Type, _incomingFrame.StreamId, _currentHeadersStream.StreamId), Http2ErrorCode.PROTOCOL_ERROR);
} }
if (_incomingFrame.Length != 4) if (_incomingFrame.Length != 4)
{ {
throw new Http2ConnectionErrorException(Http2ErrorCode.FRAME_SIZE_ERROR); throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(_incomingFrame.Type, 4), Http2ErrorCode.FRAME_SIZE_ERROR);
} }
ThrowIfIncomingFrameSentToIdleStream(); ThrowIfIncomingFrameSentToIdleStream();
if (_incomingFrame.WindowUpdateSizeIncrement == 0) if (_incomingFrame.WindowUpdateSizeIncrement == 0)
{ {
if (_incomingFrame.StreamId == 0) // http://httpwg.org/specs/rfc7540.html#rfc.section.6.9
{ // A receiver MUST treat the receipt of a WINDOW_UPDATE
throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR); // frame with an flow-control window increment of 0 as a
} // stream error (Section 5.4.2) of type PROTOCOL_ERROR;
else // errors on the connection flow-control window MUST be
{ // treated as a connection error (Section 5.4.1).
return _frameWriter.WriteRstStreamAsync(_incomingFrame.StreamId, Http2ErrorCode.PROTOCOL_ERROR); //
} // http://httpwg.org/specs/rfc7540.html#rfc.section.5.4.1
// An endpoint can end a connection at any time. In
// particular, an endpoint MAY choose to treat a stream
// error as a connection error.
//
// Since server initiated stream resets are not yet properly
// implemented and tested, we treat all zero length window
// increments as connection errors for now.
throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorWindowUpdateIncrementZero, Http2ErrorCode.PROTOCOL_ERROR);
} }
return Task.CompletedTask; return Task.CompletedTask;
@ -555,9 +573,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
private Task ProcessContinuationFrameAsync<TContext>(IHttpApplication<TContext> application) private Task ProcessContinuationFrameAsync<TContext>(IHttpApplication<TContext> application)
{ {
if (_currentHeadersStream == null || _incomingFrame.StreamId != _currentHeadersStream.StreamId) if (_currentHeadersStream == null)
{ {
throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR); throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorContinuationWithNoHeaders, Http2ErrorCode.PROTOCOL_ERROR);
}
if (_incomingFrame.StreamId != _currentHeadersStream.StreamId)
{
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorHeadersInterleaved(_incomingFrame.Type, _incomingFrame.StreamId, _currentHeadersStream.StreamId), Http2ErrorCode.PROTOCOL_ERROR);
} }
var endHeaders = (_incomingFrame.ContinuationFlags & Http2ContinuationFrameFlags.END_HEADERS) == Http2ContinuationFrameFlags.END_HEADERS; var endHeaders = (_incomingFrame.ContinuationFlags & Http2ContinuationFrameFlags.END_HEADERS) == Http2ContinuationFrameFlags.END_HEADERS;
@ -569,7 +592,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{ {
if (_currentHeadersStream != null) if (_currentHeadersStream != null)
{ {
throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR); throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorHeadersInterleaved(_incomingFrame.Type, _incomingFrame.StreamId, _currentHeadersStream.StreamId), Http2ErrorCode.PROTOCOL_ERROR);
} }
return Task.CompletedTask; return Task.CompletedTask;
@ -589,6 +612,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
} }
catch (Http2StreamErrorException ex) catch (Http2StreamErrorException ex)
{ {
Log.Http2StreamError(ConnectionId, ex);
ResetRequestHeaderParsingState(); ResetRequestHeaderParsingState();
return _frameWriter.WriteRstStreamAsync(ex.StreamId, ex.ErrorCode); return _frameWriter.WriteRstStreamAsync(ex.StreamId, ex.ErrorCode);
} }
@ -603,7 +627,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
// All HTTP/2 requests MUST include exactly one valid value for the :method, :scheme, and :path pseudo-header // All HTTP/2 requests MUST include exactly one valid value for the :method, :scheme, and :path pseudo-header
// fields, unless it is a CONNECT request (Section 8.3). An HTTP request that omits mandatory pseudo-header // fields, unless it is a CONNECT request (Section 8.3). An HTTP request that omits mandatory pseudo-header
// fields is malformed (Section 8.1.2.6). // fields is malformed (Section 8.1.2.6).
throw new Http2StreamErrorException(_currentHeadersStream.StreamId, Http2ErrorCode.PROTOCOL_ERROR); throw new Http2StreamErrorException(_currentHeadersStream.StreamId, CoreStrings.Http2ErrorMissingMandatoryPseudoHeaderFields, Http2ErrorCode.PROTOCOL_ERROR);
} }
_streams[_incomingFrame.StreamId] = _currentHeadersStream; _streams[_incomingFrame.StreamId] = _currentHeadersStream;
@ -638,7 +662,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
// initial state for all streams. // initial state for all streams.
if (_incomingFrame.StreamId > _highestOpenedStreamId) if (_incomingFrame.StreamId > _highestOpenedStreamId)
{ {
throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR); throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdle(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.PROTOCOL_ERROR);
} }
} }
@ -666,7 +690,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
// All pseudo-header fields MUST appear in the header block before regular header fields. // All pseudo-header fields MUST appear in the header block before regular header fields.
// Any request or response that contains a pseudo-header field that appears in a header // Any request or response that contains a pseudo-header field that appears in a header
// block after a regular header field MUST be treated as malformed (Section 8.1.2.6). // block after a regular header field MUST be treated as malformed (Section 8.1.2.6).
throw new Http2StreamErrorException(_currentHeadersStream.StreamId, Http2ErrorCode.PROTOCOL_ERROR); throw new Http2StreamErrorException(_currentHeadersStream.StreamId, CoreStrings.Http2ErrorPseudoHeaderFieldAfterRegularHeaders, Http2ErrorCode.PROTOCOL_ERROR);
} }
_requestHeaderParsingState = RequestHeaderParsingState.PseudoHeaderFields; _requestHeaderParsingState = RequestHeaderParsingState.PseudoHeaderFields;
@ -675,21 +699,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{ {
// Endpoints MUST treat a request or response that contains undefined or invalid pseudo-header // Endpoints MUST treat a request or response that contains undefined or invalid pseudo-header
// fields as malformed (Section 8.1.2.6). // fields as malformed (Section 8.1.2.6).
throw new Http2StreamErrorException(_currentHeadersStream.StreamId, Http2ErrorCode.PROTOCOL_ERROR); throw new Http2StreamErrorException(_currentHeadersStream.StreamId, CoreStrings.Http2ErrorUnknownPseudoHeaderField, Http2ErrorCode.PROTOCOL_ERROR);
} }
if (headerField == PseudoHeaderFields.Status) if (headerField == PseudoHeaderFields.Status)
{ {
// Pseudo-header fields defined for requests MUST NOT appear in responses; pseudo-header fields // Pseudo-header fields defined for requests MUST NOT appear in responses; pseudo-header fields
// defined for responses MUST NOT appear in requests. // defined for responses MUST NOT appear in requests.
throw new Http2StreamErrorException(_currentHeadersStream.StreamId, Http2ErrorCode.PROTOCOL_ERROR); throw new Http2StreamErrorException(_currentHeadersStream.StreamId, CoreStrings.Http2ErrorResponsePseudoHeaderField, Http2ErrorCode.PROTOCOL_ERROR);
} }
if ((_parsedPseudoHeaderFields & headerField) == headerField) if ((_parsedPseudoHeaderFields & headerField) == headerField)
{ {
// http://httpwg.org/specs/rfc7540.html#rfc.section.8.1.2.3 // http://httpwg.org/specs/rfc7540.html#rfc.section.8.1.2.3
// All HTTP/2 requests MUST include exactly one valid value for the :method, :scheme, and :path pseudo-header fields // All HTTP/2 requests MUST include exactly one valid value for the :method, :scheme, and :path pseudo-header fields
throw new Http2StreamErrorException(_currentHeadersStream.StreamId, Http2ErrorCode.PROTOCOL_ERROR); throw new Http2StreamErrorException(_currentHeadersStream.StreamId, CoreStrings.Http2ErrorDuplicatePseudoHeaderField, Http2ErrorCode.PROTOCOL_ERROR);
} }
if (headerField == PseudoHeaderFields.Method) if (headerField == PseudoHeaderFields.Method)
@ -706,7 +730,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
if (IsConnectionSpecificHeaderField(name, value)) if (IsConnectionSpecificHeaderField(name, value))
{ {
throw new Http2StreamErrorException(_currentHeadersStream.StreamId, Http2ErrorCode.PROTOCOL_ERROR); throw new Http2StreamErrorException(_currentHeadersStream.StreamId, CoreStrings.Http2ErrorConnectionSpecificHeaderField, Http2ErrorCode.PROTOCOL_ERROR);
} }
// http://httpwg.org/specs/rfc7540.html#rfc.section.8.1.2 // http://httpwg.org/specs/rfc7540.html#rfc.section.8.1.2
@ -715,7 +739,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{ {
if (name[i] >= 65 && name[i] <= 90) if (name[i] >= 65 && name[i] <= 90)
{ {
throw new Http2StreamErrorException(_currentHeadersStream.StreamId, Http2ErrorCode.PROTOCOL_ERROR); throw new Http2StreamErrorException(_currentHeadersStream.StreamId, CoreStrings.Http2ErrorHeaderNameUppercase, Http2ErrorCode.PROTOCOL_ERROR);
} }
} }
} }

View File

@ -7,8 +7,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{ {
public class Http2ConnectionErrorException : Exception public class Http2ConnectionErrorException : Exception
{ {
public Http2ConnectionErrorException(Http2ErrorCode errorCode) public Http2ConnectionErrorException(string message, Http2ErrorCode errorCode)
: base($"HTTP/2 connection error: {errorCode}") : base($"HTTP/2 connection error ({errorCode}): {message}")
{ {
ErrorCode = errorCode; ErrorCode = errorCode;
} }

View File

@ -7,8 +7,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{ {
public class Http2StreamErrorException : Exception public class Http2StreamErrorException : Exception
{ {
public Http2StreamErrorException(int streamId, Http2ErrorCode errorCode) public Http2StreamErrorException(int streamId, string message, Http2ErrorCode errorCode)
: base($"HTTP/2 stream ID {streamId} error: {errorCode}") : base($"HTTP/2 stream ID {streamId} error ({errorCode}): {message}")
{ {
StreamId = streamId; StreamId = streamId;
ErrorCode = errorCode; ErrorCode = errorCode;

View File

@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
@ -45,5 +47,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
void RequestBodyMininumDataRateNotSatisfied(string connectionId, string traceIdentifier, double rate); void RequestBodyMininumDataRateNotSatisfied(string connectionId, string traceIdentifier, double rate);
void ResponseMininumDataRateNotSatisfied(string connectionId, string traceIdentifier); void ResponseMininumDataRateNotSatisfied(string connectionId, string traceIdentifier);
void Http2ConnectionError(string connectionId, Http2ConnectionErrorException ex);
void Http2StreamError(string connectionId, Http2StreamErrorException ex);
void HPackDecodingError(string connectionId, int streamId, HPackDecodingException ex);
} }
} }

View File

@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -66,6 +68,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
private static readonly Action<ILogger, string, string, Exception> _responseMinimumDataRateNotSatisfied = private static readonly Action<ILogger, string, string, Exception> _responseMinimumDataRateNotSatisfied =
LoggerMessage.Define<string, string>(LogLevel.Information, 28, @"Connection id ""{ConnectionId}"", Request id ""{TraceIdentifier}"": the connection was closed becuase the response was not read by the client at the specified minimum data rate."); LoggerMessage.Define<string, string>(LogLevel.Information, 28, @"Connection id ""{ConnectionId}"", Request id ""{TraceIdentifier}"": the connection was closed becuase the response was not read by the client at the specified minimum data rate.");
private static readonly Action<ILogger, string, Exception> _http2ConnectionError =
LoggerMessage.Define<string>(LogLevel.Information, 29, @"Connection id ""{ConnectionId}"": HTTP/2 connection error.");
private static readonly Action<ILogger, string, Exception> _http2StreamError =
LoggerMessage.Define<string>(LogLevel.Information, 30, @"Connection id ""{ConnectionId}"": HTTP/2 stream error.");
private static readonly Action<ILogger, string, int, Exception> _hpackDecodingError =
LoggerMessage.Define<string, int>(LogLevel.Information, 31, @"Connection id ""{ConnectionId}"": HPACK decoding error while decoding headers for stream ID {StreamId}.");
protected readonly ILogger _logger; protected readonly ILogger _logger;
public KestrelTrace(ILogger logger) public KestrelTrace(ILogger logger)
@ -168,6 +179,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
_responseMinimumDataRateNotSatisfied(_logger, connectionId, traceIdentifier, null); _responseMinimumDataRateNotSatisfied(_logger, connectionId, traceIdentifier, null);
} }
public void Http2ConnectionError(string connectionId, Http2ConnectionErrorException ex)
{
_http2ConnectionError(_logger, connectionId, ex);
}
public void Http2StreamError(string connectionId, Http2StreamErrorException ex)
{
_http2StreamError(_logger, connectionId, ex);
}
public void HPackDecodingError(string connectionId, int streamId, HPackDecodingException ex)
{
_hpackDecodingError(_logger, connectionId, streamId, ex);
}
public virtual void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) public virtual void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
=> _logger.Log(logLevel, eventId, state, exception, formatter); => _logger.Log(logLevel, eventId, state, exception, formatter);

View File

@ -1256,6 +1256,370 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
internal static string FormatHPackErrorIncompleteHeaderBlock() internal static string FormatHPackErrorIncompleteHeaderBlock()
=> GetString("HPackErrorIncompleteHeaderBlock"); => GetString("HPackErrorIncompleteHeaderBlock");
/// <summary>
/// The client sent a {frameType} frame with even stream ID {streamId}.
/// </summary>
internal static string Http2ErrorStreamIdEven
{
get => GetString("Http2ErrorStreamIdEven");
}
/// <summary>
/// The client sent a {frameType} frame with even stream ID {streamId}.
/// </summary>
internal static string FormatHttp2ErrorStreamIdEven(object frameType, object streamId)
=> string.Format(CultureInfo.CurrentCulture, GetString("Http2ErrorStreamIdEven", "frameType", "streamId"), frameType, streamId);
/// <summary>
/// The client sent a A PUSH_PROMISE frame.
/// </summary>
internal static string Http2ErrorPushPromiseReceived
{
get => GetString("Http2ErrorPushPromiseReceived");
}
/// <summary>
/// The client sent a A PUSH_PROMISE frame.
/// </summary>
internal static string FormatHttp2ErrorPushPromiseReceived()
=> GetString("Http2ErrorPushPromiseReceived");
/// <summary>
/// The client sent a {frameType} frame to stream ID {streamId} before signaling of the header block for stream ID {headersStreamId}.
/// </summary>
internal static string Http2ErrorHeadersInterleaved
{
get => GetString("Http2ErrorHeadersInterleaved");
}
/// <summary>
/// The client sent a {frameType} frame to stream ID {streamId} before signaling of the header block for stream ID {headersStreamId}.
/// </summary>
internal static string FormatHttp2ErrorHeadersInterleaved(object frameType, object streamId, object headersStreamId)
=> string.Format(CultureInfo.CurrentCulture, GetString("Http2ErrorHeadersInterleaved", "frameType", "streamId", "headersStreamId"), frameType, streamId, headersStreamId);
/// <summary>
/// The client sent a {frameType} frame with stream ID 0.
/// </summary>
internal static string Http2ErrorStreamIdZero
{
get => GetString("Http2ErrorStreamIdZero");
}
/// <summary>
/// The client sent a {frameType} frame with stream ID 0.
/// </summary>
internal static string FormatHttp2ErrorStreamIdZero(object frameType)
=> string.Format(CultureInfo.CurrentCulture, GetString("Http2ErrorStreamIdZero", "frameType"), frameType);
/// <summary>
/// The client sent a {frameType} frame with stream ID different than 0.
/// </summary>
internal static string Http2ErrorStreamIdNotZero
{
get => GetString("Http2ErrorStreamIdNotZero");
}
/// <summary>
/// The client sent a {frameType} frame with stream ID different than 0.
/// </summary>
internal static string FormatHttp2ErrorStreamIdNotZero(object frameType)
=> string.Format(CultureInfo.CurrentCulture, GetString("Http2ErrorStreamIdNotZero", "frameType"), frameType);
/// <summary>
/// The client sent a {frameType} frame with padding longer than or with the same length as the sent data.
/// </summary>
internal static string Http2ErrorPaddingTooLong
{
get => GetString("Http2ErrorPaddingTooLong");
}
/// <summary>
/// The client sent a {frameType} frame with padding longer than or with the same length as the sent data.
/// </summary>
internal static string FormatHttp2ErrorPaddingTooLong(object frameType)
=> string.Format(CultureInfo.CurrentCulture, GetString("Http2ErrorPaddingTooLong", "frameType"), frameType);
/// <summary>
/// The client sent a {frameType} frame to closed stream ID {streamId}.
/// </summary>
internal static string Http2ErrorStreamClosed
{
get => GetString("Http2ErrorStreamClosed");
}
/// <summary>
/// The client sent a {frameType} frame to closed stream ID {streamId}.
/// </summary>
internal static string FormatHttp2ErrorStreamClosed(object frameType, object streamId)
=> string.Format(CultureInfo.CurrentCulture, GetString("Http2ErrorStreamClosed", "frameType", "streamId"), frameType, streamId);
/// <summary>
/// The client sent a {frameType} frame to stream ID {streamId} which is in the "half-closed (remote) state".
/// </summary>
internal static string Http2ErrorStreamHalfClosedRemote
{
get => GetString("Http2ErrorStreamHalfClosedRemote");
}
/// <summary>
/// The client sent a {frameType} frame to stream ID {streamId} which is in the "half-closed (remote) state".
/// </summary>
internal static string FormatHttp2ErrorStreamHalfClosedRemote(object frameType, object streamId)
=> string.Format(CultureInfo.CurrentCulture, GetString("Http2ErrorStreamHalfClosedRemote", "frameType", "streamId"), frameType, streamId);
/// <summary>
/// The client sent a {frameType} frame with dependency information that would cause stream ID {streamId} to depend on itself.
/// </summary>
internal static string Http2ErrorStreamSelfDependency
{
get => GetString("Http2ErrorStreamSelfDependency");
}
/// <summary>
/// The client sent a {frameType} frame with dependency information that would cause stream ID {streamId} to depend on itself.
/// </summary>
internal static string FormatHttp2ErrorStreamSelfDependency(object frameType, object streamId)
=> string.Format(CultureInfo.CurrentCulture, GetString("Http2ErrorStreamSelfDependency", "frameType", "streamId"), frameType, streamId);
/// <summary>
/// The client sent a {frameType} frame with length different than {expectedLength}.
/// </summary>
internal static string Http2ErrorUnexpectedFrameLength
{
get => GetString("Http2ErrorUnexpectedFrameLength");
}
/// <summary>
/// The client sent a {frameType} frame with length different than {expectedLength}.
/// </summary>
internal static string FormatHttp2ErrorUnexpectedFrameLength(object frameType, object expectedLength)
=> string.Format(CultureInfo.CurrentCulture, GetString("Http2ErrorUnexpectedFrameLength", "frameType", "expectedLength"), frameType, expectedLength);
/// <summary>
/// The client sent a SETTINGS frame with a length that is not a multiple of 6.
/// </summary>
internal static string Http2ErrorSettingsLengthNotMultipleOfSix
{
get => GetString("Http2ErrorSettingsLengthNotMultipleOfSix");
}
/// <summary>
/// The client sent a SETTINGS frame with a length that is not a multiple of 6.
/// </summary>
internal static string FormatHttp2ErrorSettingsLengthNotMultipleOfSix()
=> GetString("Http2ErrorSettingsLengthNotMultipleOfSix");
/// <summary>
/// The client sent a SETTINGS frame with ACK set and length different than 0.
/// </summary>
internal static string Http2ErrorSettingsAckLengthNotZero
{
get => GetString("Http2ErrorSettingsAckLengthNotZero");
}
/// <summary>
/// The client sent a SETTINGS frame with ACK set and length different than 0.
/// </summary>
internal static string FormatHttp2ErrorSettingsAckLengthNotZero()
=> GetString("Http2ErrorSettingsAckLengthNotZero");
/// <summary>
/// The client sent a SETTINGS frame with a value for parameter {parameter} that is out of range.
/// </summary>
internal static string Http2ErrorSettingsParameterOutOfRange
{
get => GetString("Http2ErrorSettingsParameterOutOfRange");
}
/// <summary>
/// The client sent a SETTINGS frame with a value for parameter {parameter} that is out of range.
/// </summary>
internal static string FormatHttp2ErrorSettingsParameterOutOfRange(object parameter)
=> string.Format(CultureInfo.CurrentCulture, GetString("Http2ErrorSettingsParameterOutOfRange", "parameter"), parameter);
/// <summary>
/// The client sent a WINDOW_UPDATE frame with a window size increment of 0.
/// </summary>
internal static string Http2ErrorWindowUpdateIncrementZero
{
get => GetString("Http2ErrorWindowUpdateIncrementZero");
}
/// <summary>
/// The client sent a WINDOW_UPDATE frame with a window size increment of 0.
/// </summary>
internal static string FormatHttp2ErrorWindowUpdateIncrementZero()
=> GetString("Http2ErrorWindowUpdateIncrementZero");
/// <summary>
/// The client sent a CONTINUATION frame not preceded by a HEADERS frame.
/// </summary>
internal static string Http2ErrorContinuationWithNoHeaders
{
get => GetString("Http2ErrorContinuationWithNoHeaders");
}
/// <summary>
/// The client sent a CONTINUATION frame not preceded by a HEADERS frame.
/// </summary>
internal static string FormatHttp2ErrorContinuationWithNoHeaders()
=> GetString("Http2ErrorContinuationWithNoHeaders");
/// <summary>
/// The client sent a {frameType} frame to idle stream ID {streamId}.
/// </summary>
internal static string Http2ErrorStreamIdle
{
get => GetString("Http2ErrorStreamIdle");
}
/// <summary>
/// The client sent a {frameType} frame to idle stream ID {streamId}.
/// </summary>
internal static string FormatHttp2ErrorStreamIdle(object frameType, object streamId)
=> string.Format(CultureInfo.CurrentCulture, GetString("Http2ErrorStreamIdle", "frameType", "streamId"), frameType, streamId);
/// <summary>
/// The client sent trailers containing one or more pseudo-header fields.
/// </summary>
internal static string Http2ErrorTrailersContainPseudoHeaderField
{
get => GetString("Http2ErrorTrailersContainPseudoHeaderField");
}
/// <summary>
/// The client sent trailers containing one or more pseudo-header fields.
/// </summary>
internal static string FormatHttp2ErrorTrailersContainPseudoHeaderField()
=> GetString("Http2ErrorTrailersContainPseudoHeaderField");
/// <summary>
/// The client sent a header with uppercase characters in its name.
/// </summary>
internal static string Http2ErrorHeaderNameUppercase
{
get => GetString("Http2ErrorHeaderNameUppercase");
}
/// <summary>
/// The client sent a header with uppercase characters in its name.
/// </summary>
internal static string FormatHttp2ErrorHeaderNameUppercase()
=> GetString("Http2ErrorHeaderNameUppercase");
/// <summary>
/// The client sent a trailer with uppercase characters in its name.
/// </summary>
internal static string Http2ErrorTrailerNameUppercase
{
get => GetString("Http2ErrorTrailerNameUppercase");
}
/// <summary>
/// The client sent a trailer with uppercase characters in its name.
/// </summary>
internal static string FormatHttp2ErrorTrailerNameUppercase()
=> GetString("Http2ErrorTrailerNameUppercase");
/// <summary>
/// The client sent a HEADERS frame containing trailers without setting the END_STREAM flag.
/// </summary>
internal static string Http2ErrorHeadersWithTrailersNoEndStream
{
get => GetString("Http2ErrorHeadersWithTrailersNoEndStream");
}
/// <summary>
/// The client sent a HEADERS frame containing trailers without setting the END_STREAM flag.
/// </summary>
internal static string FormatHttp2ErrorHeadersWithTrailersNoEndStream()
=> GetString("Http2ErrorHeadersWithTrailersNoEndStream");
/// <summary>
/// Request headers missing one or more mandatory pseudo-header fields.
/// </summary>
internal static string Http2ErrorMissingMandatoryPseudoHeaderFields
{
get => GetString("Http2ErrorMissingMandatoryPseudoHeaderFields");
}
/// <summary>
/// Request headers missing one or more mandatory pseudo-header fields.
/// </summary>
internal static string FormatHttp2ErrorMissingMandatoryPseudoHeaderFields()
=> GetString("Http2ErrorMissingMandatoryPseudoHeaderFields");
/// <summary>
/// Pseudo-header field found in request headers after regular header fields.
/// </summary>
internal static string Http2ErrorPseudoHeaderFieldAfterRegularHeaders
{
get => GetString("Http2ErrorPseudoHeaderFieldAfterRegularHeaders");
}
/// <summary>
/// Pseudo-header field found in request headers after regular header fields.
/// </summary>
internal static string FormatHttp2ErrorPseudoHeaderFieldAfterRegularHeaders()
=> GetString("Http2ErrorPseudoHeaderFieldAfterRegularHeaders");
/// <summary>
/// Request headers contain unknown pseudo-header field.
/// </summary>
internal static string Http2ErrorUnknownPseudoHeaderField
{
get => GetString("Http2ErrorUnknownPseudoHeaderField");
}
/// <summary>
/// Request headers contain unknown pseudo-header field.
/// </summary>
internal static string FormatHttp2ErrorUnknownPseudoHeaderField()
=> GetString("Http2ErrorUnknownPseudoHeaderField");
/// <summary>
/// Request headers contain response-specific pseudo-header field.
/// </summary>
internal static string Http2ErrorResponsePseudoHeaderField
{
get => GetString("Http2ErrorResponsePseudoHeaderField");
}
/// <summary>
/// Request headers contain response-specific pseudo-header field.
/// </summary>
internal static string FormatHttp2ErrorResponsePseudoHeaderField()
=> GetString("Http2ErrorResponsePseudoHeaderField");
/// <summary>
/// Request headers contain duplicate pseudo-header field.
/// </summary>
internal static string Http2ErrorDuplicatePseudoHeaderField
{
get => GetString("Http2ErrorDuplicatePseudoHeaderField");
}
/// <summary>
/// Request headers contain duplicate pseudo-header field.
/// </summary>
internal static string FormatHttp2ErrorDuplicatePseudoHeaderField()
=> GetString("Http2ErrorDuplicatePseudoHeaderField");
/// <summary>
/// Request headers contain connection-specific header field.
/// </summary>
internal static string Http2ErrorConnectionSpecificHeaderField
{
get => GetString("Http2ErrorConnectionSpecificHeaderField");
}
/// <summary>
/// Request headers contain connection-specific header field.
/// </summary>
internal static string FormatHttp2ErrorConnectionSpecificHeaderField()
=> GetString("Http2ErrorConnectionSpecificHeaderField");
private static string GetString(string name, params string[] formatterNames) private static string GetString(string name, params string[] formatterNames)
{ {
var value = _resourceManager.GetString(name); var value = _resourceManager.GetString(name);

View File

@ -10,6 +10,7 @@ using System.Text;
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.Protocols;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
@ -77,6 +78,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
private readonly PipeFactory _pipeFactory = new PipeFactory(); private readonly PipeFactory _pipeFactory = new PipeFactory();
private readonly (IPipeConnection Transport, IPipeConnection Application) _pair; private readonly (IPipeConnection Transport, IPipeConnection Application) _pair;
private readonly TestApplicationErrorLogger _logger;
private readonly Http2ConnectionContext _connectionContext; private readonly Http2ConnectionContext _connectionContext;
private readonly Http2Connection _connection; private readonly Http2Connection _connection;
private readonly Http2PeerSettings _clientSettings = new Http2PeerSettings(); private readonly Http2PeerSettings _clientSettings = new Http2PeerSettings();
@ -215,9 +217,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
_hpackDecoder = new HPackDecoder((int)_clientSettings.HeaderTableSize); _hpackDecoder = new HPackDecoder((int)_clientSettings.HeaderTableSize);
_logger = new TestApplicationErrorLogger();
_connectionContext = new Http2ConnectionContext _connectionContext = new Http2ConnectionContext
{ {
ServiceContext = new TestServiceContext(), ServiceContext = new TestServiceContext()
{
Log = new TestKestrelTrace(_logger)
},
PipeFactory = _pipeFactory, PipeFactory = _pipeFactory,
Application = _pair.Application, Application = _pair.Application,
Transport = _pair.Transport Transport = _pair.Transport
@ -421,7 +428,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendDataAsync(0, _noData, endStream: false); await SendDataAsync(0, _noData, endStream: false);
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 0,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdZero(Http2FrameType.DATA));
} }
[Fact] [Fact]
@ -431,7 +442,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendDataAsync(2, _noData, endStream: false); await SendDataAsync(2, _noData, endStream: false);
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 0,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdEven(Http2FrameType.DATA, streamId: 2));
} }
[Fact] [Fact]
@ -442,7 +457,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StartStreamAsync(1, _browserRequestHeaders, endStream: false); await StartStreamAsync(1, _browserRequestHeaders, endStream: false);
await SendInvalidDataFrameAsync(1, frameLength: 5, padLength: 5); await SendInvalidDataFrameAsync(1, frameLength: 5, padLength: 5);
await WaitForConnectionErrorAsync(expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: true); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: true,
expectedLastStreamId: 1,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorPaddingTooLong(Http2FrameType.DATA));
} }
[Fact] [Fact]
@ -453,7 +472,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StartStreamAsync(1, _browserRequestHeaders, endStream: false); await StartStreamAsync(1, _browserRequestHeaders, endStream: false);
await SendInvalidDataFrameAsync(1, frameLength: 5, padLength: 6); await SendInvalidDataFrameAsync(1, frameLength: 5, padLength: 6);
await WaitForConnectionErrorAsync(expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: true); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: true,
expectedLastStreamId: 1,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorPaddingTooLong(Http2FrameType.DATA));
} }
[Fact] [Fact]
@ -464,7 +487,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StartStreamAsync(1, _browserRequestHeaders, endStream: false); await StartStreamAsync(1, _browserRequestHeaders, endStream: false);
await SendInvalidDataFrameAsync(1, frameLength: 0, padLength: 0); await SendInvalidDataFrameAsync(1, frameLength: 0, padLength: 0);
await WaitForConnectionErrorAsync(expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: true); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: true,
expectedLastStreamId: 1,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorPaddingTooLong(Http2FrameType.DATA));
} }
[Fact] [Fact]
@ -475,7 +502,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders); await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders);
await SendDataAsync(1, _helloWorldBytes, endStream: true); await SendDataAsync(1, _helloWorldBytes, endStream: true);
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 0,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.DATA, streamId: 1, headersStreamId: 1));
} }
[Fact] [Fact]
@ -485,7 +516,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendDataAsync(1, _helloWorldBytes, endStream: false); await SendDataAsync(1, _helloWorldBytes, endStream: false);
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 0,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdle(Http2FrameType.DATA, streamId: 1));
} }
[Fact] [Fact]
@ -498,7 +533,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendDataAsync(1, _helloWorldBytes, endStream: false); await SendDataAsync(1, _helloWorldBytes, endStream: false);
await WaitForConnectionErrorAsync(expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 1,
expectedErrorCode: Http2ErrorCode.STREAM_CLOSED,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.DATA, streamId: 1));
} }
[Fact] [Fact]
@ -519,7 +558,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendDataAsync(1, _helloWorldBytes, endStream: false); await SendDataAsync(1, _helloWorldBytes, endStream: false);
await WaitForConnectionErrorAsync(expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 1,
expectedErrorCode: Http2ErrorCode.STREAM_CLOSED,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.DATA, streamId: 1));
} }
[Fact] [Fact]
@ -547,7 +590,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendDataAsync(1, _helloWorldBytes, endStream: true); await SendDataAsync(1, _helloWorldBytes, endStream: true);
await WaitForConnectionErrorAsync(expectedLastStreamId: 3, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 3,
expectedErrorCode: Http2ErrorCode.STREAM_CLOSED,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.DATA, streamId: 1));
} }
[Fact] [Fact]
@ -647,7 +694,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StartStreamAsync(0, _browserRequestHeaders, endStream: true); await StartStreamAsync(0, _browserRequestHeaders, endStream: true);
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 0,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdZero(Http2FrameType.HEADERS));
} }
[Fact] [Fact]
@ -657,7 +708,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await StartStreamAsync(2, _browserRequestHeaders, endStream: true); await StartStreamAsync(2, _browserRequestHeaders, endStream: true);
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 0,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdEven(Http2FrameType.HEADERS, streamId: 2));
} }
[Fact] [Fact]
@ -679,7 +734,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
// Try to re-use the stream ID (http://httpwg.org/specs/rfc7540.html#rfc.section.5.1.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 StartStreamAsync(1, _browserRequestHeaders, endStream: true);
await WaitForConnectionErrorAsync(expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 1,
expectedErrorCode: Http2ErrorCode.STREAM_CLOSED,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1));
} }
[Fact] [Fact]
@ -692,7 +751,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders); await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders);
await WaitForConnectionErrorAsync(expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 1,
expectedErrorCode: Http2ErrorCode.STREAM_CLOSED,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.HEADERS, streamId: 1));
} }
[Fact] [Fact]
@ -714,7 +777,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
// Stream 1 was implicitly closed by opening stream 3 before (http://httpwg.org/specs/rfc7540.html#rfc.section.5.1.1) // 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 StartStreamAsync(1, _browserRequestHeaders, endStream: true);
await WaitForConnectionErrorAsync(expectedLastStreamId: 3, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 3,
expectedErrorCode: Http2ErrorCode.STREAM_CLOSED,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1));
} }
[Theory] [Theory]
@ -727,7 +794,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendInvalidHeadersFrameAsync(1, frameLength: padLength, padLength: padLength); await SendInvalidHeadersFrameAsync(1, frameLength: padLength, padLength: padLength);
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: true,
expectedLastStreamId: 0,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorPaddingTooLong(Http2FrameType.HEADERS));
} }
[Theory] [Theory]
@ -740,7 +811,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendInvalidHeadersFrameAsync(1, frameLength, padLength); await SendInvalidHeadersFrameAsync(1, frameLength, padLength);
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: true,
expectedLastStreamId: 0,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorPaddingTooLong(Http2FrameType.HEADERS));
} }
[Fact] [Fact]
@ -751,7 +826,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders); await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders);
await SendHeadersAsync(3, Http2HeadersFrameFlags.NONE, _browserRequestHeaders); await SendHeadersAsync(3, Http2HeadersFrameFlags.NONE, _browserRequestHeaders);
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 0,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.HEADERS, streamId: 3, headersStreamId: 1));
} }
[Fact] [Fact]
@ -761,7 +840,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendHeadersWithPriorityAsync(1, _browserRequestHeaders, priority: 42, streamDependency: 1, endStream: true); await SendHeadersWithPriorityAsync(1, _browserRequestHeaders, priority: 42, streamDependency: 1, endStream: true);
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 0,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamSelfDependency(Http2FrameType.HEADERS, streamId: 1));
} }
[Fact] [Fact]
@ -771,7 +854,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendIncompleteHeadersFrameAsync(streamId: 1); await SendIncompleteHeadersFrameAsync(streamId: 1);
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.COMPRESSION_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<HPackDecodingException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 0,
expectedErrorCode: Http2ErrorCode.COMPRESSION_ERROR,
expectedErrorMessage: CoreStrings.HPackErrorIncompleteHeaderBlock);
} }
[Theory] [Theory]
@ -781,11 +868,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await InitializeConnectionAsync(_noopApplication); await InitializeConnectionAsync(_noopApplication);
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, headerBlock); await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, headerBlock);
await WaitForStreamErrorAsync(1, Http2ErrorCode.PROTOCOL_ERROR, ignoreNonRstStreamFrames: false); await WaitForStreamErrorAsync(
ignoreNonRstStreamFrames: false,
expectedStreamId: 1,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.Http2ErrorHeaderNameUppercase);
// Verify that the stream ID can't be re-used // Verify that the stream ID can't be re-used
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, _browserRequestHeaders); await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, _browserRequestHeaders);
await WaitForConnectionErrorAsync(expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 1,
expectedErrorCode: Http2ErrorCode.STREAM_CLOSED,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1));
} }
[Fact] [Fact]
@ -799,7 +894,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
new KeyValuePair<string, string>(":unknown", "0"), new KeyValuePair<string, string>(":unknown", "0"),
}; };
return HEADERS_Received_InvalidHeaderFields_StreamError(headers); return HEADERS_Received_InvalidHeaderFields_StreamError(headers, expectedErrorMessage: CoreStrings.Http2ErrorUnknownPseudoHeaderField);
} }
[Fact] [Fact]
@ -813,21 +908,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
new KeyValuePair<string, string>(":status", "200"), new KeyValuePair<string, string>(":status", "200"),
}; };
return HEADERS_Received_InvalidHeaderFields_StreamError(headers); return HEADERS_Received_InvalidHeaderFields_StreamError(headers, expectedErrorMessage: CoreStrings.Http2ErrorResponsePseudoHeaderField);
} }
[Theory] [Theory]
[MemberData(nameof(DuplicatePseudoHeaderFieldData))] [MemberData(nameof(DuplicatePseudoHeaderFieldData))]
public Task HEADERS_Received_HeaderBlockContainsDuplicatePseudoHeaderField_StreamError(IEnumerable<KeyValuePair<string, string>> headers) public Task HEADERS_Received_HeaderBlockContainsDuplicatePseudoHeaderField_StreamError(IEnumerable<KeyValuePair<string, string>> headers)
{ {
return HEADERS_Received_InvalidHeaderFields_StreamError(headers); return HEADERS_Received_InvalidHeaderFields_StreamError(headers, expectedErrorMessage: CoreStrings.Http2ErrorDuplicatePseudoHeaderField);
} }
[Theory] [Theory]
[MemberData(nameof(MissingPseudoHeaderFieldData))] [MemberData(nameof(MissingPseudoHeaderFieldData))]
public Task HEADERS_Received_HeaderBlockDoesNotContainMandatoryPseudoHeaderField_StreamError(IEnumerable<KeyValuePair<string, string>> headers) public Task HEADERS_Received_HeaderBlockDoesNotContainMandatoryPseudoHeaderField_StreamError(IEnumerable<KeyValuePair<string, string>> headers)
{ {
return HEADERS_Received_InvalidHeaderFields_StreamError(headers); return HEADERS_Received_InvalidHeaderFields_StreamError(headers, expectedErrorMessage: CoreStrings.Http2ErrorMissingMandatoryPseudoHeaderFields);
} }
[Theory] [Theory]
@ -854,23 +949,31 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[MemberData(nameof(PseudoHeaderFieldAfterRegularHeadersData))] [MemberData(nameof(PseudoHeaderFieldAfterRegularHeadersData))]
public Task HEADERS_Received_HeaderBlockContainsPseudoHeaderFieldAfterRegularHeaders_StreamError(IEnumerable<KeyValuePair<string, string>> headers) public Task HEADERS_Received_HeaderBlockContainsPseudoHeaderFieldAfterRegularHeaders_StreamError(IEnumerable<KeyValuePair<string, string>> headers)
{ {
return HEADERS_Received_InvalidHeaderFields_StreamError(headers); return HEADERS_Received_InvalidHeaderFields_StreamError(headers, expectedErrorMessage: CoreStrings.Http2ErrorPseudoHeaderFieldAfterRegularHeaders);
} }
private async Task HEADERS_Received_InvalidHeaderFields_StreamError(IEnumerable<KeyValuePair<string, string>> headers) private async Task HEADERS_Received_InvalidHeaderFields_StreamError(IEnumerable<KeyValuePair<string, string>> headers, string expectedErrorMessage)
{ {
await InitializeConnectionAsync(_noopApplication); await InitializeConnectionAsync(_noopApplication);
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, headers); await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, headers);
await WaitForStreamErrorAsync(1, Http2ErrorCode.PROTOCOL_ERROR, ignoreNonRstStreamFrames: false); await WaitForStreamErrorAsync(
ignoreNonRstStreamFrames: false,
expectedStreamId: 1,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: expectedErrorMessage);
// Verify that the stream ID can't be re-used // Verify that the stream ID can't be re-used
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, _browserRequestHeaders); await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, _browserRequestHeaders);
await WaitForConnectionErrorAsync(expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 1,
expectedErrorCode: Http2ErrorCode.STREAM_CLOSED,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1));
} }
[Fact] [Fact]
public Task HEADERS_Received_HeaderBlockContainsConnectionSpecificHeader_StreamError() public Task HEADERS_Received_HeaderBlockContainsConnectionHeader_StreamError()
{ {
var headers = new[] var headers = new[]
{ {
@ -880,7 +983,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
new KeyValuePair<string, string>("connection", "keep-alive") new KeyValuePair<string, string>("connection", "keep-alive")
}; };
return HEADERS_Received_InvalidHeaderFields_StreamError(headers); return HEADERS_Received_InvalidHeaderFields_StreamError(headers, CoreStrings.Http2ErrorConnectionSpecificHeaderField);
} }
[Fact] [Fact]
@ -894,21 +997,34 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
new KeyValuePair<string, string>("te", "trailers, deflate") new KeyValuePair<string, string>("te", "trailers, deflate")
}; };
return HEADERS_Received_InvalidHeaderFields_StreamError(headers); return HEADERS_Received_InvalidHeaderFields_StreamError(headers, CoreStrings.Http2ErrorConnectionSpecificHeaderField);
} }
[Fact] [Fact]
public Task HEADERS_Received_HeaderBlockContainsTEHeader_ValueIsTrailers_NoError() public async Task HEADERS_Received_HeaderBlockContainsTEHeader_ValueIsTrailers_NoError()
{ {
var headers = new[] var headers = new[]
{ {
new KeyValuePair<string, string>(":method", "GET"), new KeyValuePair<string, string>(":method", "GET"),
new KeyValuePair<string, string>(":path", "/"), new KeyValuePair<string, string>(":path", "/"),
new KeyValuePair<string, string>(":scheme", "http"), new KeyValuePair<string, string>(":scheme", "http"),
new KeyValuePair<string, string>("te", "trailers, deflate") new KeyValuePair<string, string>("te", "trailers")
}; };
return HEADERS_Received_InvalidHeaderFields_StreamError(headers); await InitializeConnectionAsync(_noopApplication);
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, headers);
await ExpectAsync(Http2FrameType.HEADERS,
withLength: 55,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 0,
withFlags: (byte)Http2HeadersFrameFlags.END_STREAM,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
} }
[Fact] [Fact]
@ -918,7 +1034,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendPriorityAsync(0); await SendPriorityAsync(0);
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 0,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdZero(Http2FrameType.PRIORITY));
} }
[Fact] [Fact]
@ -928,7 +1048,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendPriorityAsync(2); await SendPriorityAsync(2);
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 0,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdEven(Http2FrameType.PRIORITY, streamId: 2));
} }
[Theory] [Theory]
@ -940,7 +1064,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendInvalidPriorityFrameAsync(1, length); await SendInvalidPriorityFrameAsync(1, length);
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 0,
expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.PRIORITY, expectedLength: 5));
} }
[Fact] [Fact]
@ -951,7 +1079,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders); await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders);
await SendPriorityAsync(1); await SendPriorityAsync(1);
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 0,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.PRIORITY, streamId: 1, headersStreamId: 1));
} }
[Fact] [Fact]
@ -961,7 +1093,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendPriorityAsync(1, streamDependency: 1); await SendPriorityAsync(1, streamDependency: 1);
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 0,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamSelfDependency(Http2FrameType.PRIORITY, 1));
} }
[Fact] [Fact]
@ -1007,7 +1143,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendRstStreamAsync(0); await SendRstStreamAsync(0);
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 0,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdZero(Http2FrameType.RST_STREAM));
} }
[Fact] [Fact]
@ -1017,7 +1157,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendRstStreamAsync(2); await SendRstStreamAsync(2);
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 0,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdEven(Http2FrameType.RST_STREAM, streamId: 2));
} }
[Fact] [Fact]
@ -1027,7 +1171,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendRstStreamAsync(1); await SendRstStreamAsync(1);
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 0,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdle(Http2FrameType.RST_STREAM, streamId: 1));
} }
[Theory] [Theory]
@ -1042,7 +1190,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendInvalidRstStreamFrameAsync(1, length); await SendInvalidRstStreamFrameAsync(1, length);
await WaitForConnectionErrorAsync(expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, ignoreNonGoAwayFrames: true); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: true,
expectedLastStreamId: 1,
expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.RST_STREAM, expectedLength: 4));
} }
[Fact] [Fact]
@ -1053,7 +1205,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders); await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders);
await SendRstStreamAsync(1); await SendRstStreamAsync(1);
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 0,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.RST_STREAM, streamId: 1, headersStreamId: 1));
} }
[Fact] [Fact]
@ -1065,13 +1221,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
} }
[Fact] [Fact]
public async Task SETTINGS_Received_StreamIdNonZero_ConnectionError() public async Task SETTINGS_Received_StreamIdNotZero_ConnectionError()
{ {
await InitializeConnectionAsync(_noopApplication); await InitializeConnectionAsync(_noopApplication);
await SendSettingsWithInvalidStreamIdAsync(1); await SendSettingsWithInvalidStreamIdAsync(1);
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 0,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdNotZero(Http2FrameType.SETTINGS));
} }
[Theory] [Theory]
@ -1090,7 +1250,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendSettingsWithInvalidParameterValueAsync(parameter, value); await SendSettingsWithInvalidParameterValueAsync(parameter, value);
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: expectedErrorCode, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 0,
expectedErrorCode: expectedErrorCode,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorSettingsParameterOutOfRange(parameter));
} }
[Fact] [Fact]
@ -1101,7 +1265,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders); await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders);
await SendSettingsAsync(); await SendSettingsAsync();
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 0,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.SETTINGS, streamId: 0, headersStreamId: 1));
} }
[Theory] [Theory]
@ -1113,7 +1281,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendSettingsAckWithInvalidLengthAsync(length); await SendSettingsAckWithInvalidLengthAsync(length);
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 0,
expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR,
expectedErrorMessage: CoreStrings.Http2ErrorSettingsAckLengthNotZero);
} }
[Theory] [Theory]
@ -1128,7 +1300,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendSettingsWithInvalidLengthAsync(length); await SendSettingsWithInvalidLengthAsync(length);
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 0,
expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR,
expectedErrorMessage: CoreStrings.Http2ErrorSettingsLengthNotMultipleOfSix);
} }
[Fact] [Fact]
@ -1138,7 +1314,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendPushPromiseFrameAsync(); await SendPushPromiseFrameAsync();
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 0,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.Http2ErrorPushPromiseReceived);
} }
[Fact] [Fact]
@ -1173,7 +1353,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders); await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders);
await SendPingAsync(Http2PingFrameFlags.NONE); await SendPingAsync(Http2PingFrameFlags.NONE);
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 0,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.PING, streamId: 0, headersStreamId: 1));
} }
[Fact] [Fact]
@ -1183,7 +1367,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendPingWithInvalidStreamIdAsync(streamId: 1); await SendPingWithInvalidStreamIdAsync(streamId: 1);
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 0,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdNotZero(Http2FrameType.PING));
} }
[Theory] [Theory]
@ -1197,7 +1385,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendPingWithInvalidLengthAsync(length); await SendPingWithInvalidLengthAsync(length);
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 0,
expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.PING, expectedLength: 8));
} }
[Fact] [Fact]
@ -1231,13 +1423,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
} }
[Fact] [Fact]
public async Task GOAWAY_Received_StreamIdNonZero_ConnectionError() public async Task GOAWAY_Received_StreamIdNotZero_ConnectionError()
{ {
await InitializeConnectionAsync(_noopApplication); await InitializeConnectionAsync(_noopApplication);
await SendInvalidGoAwayFrameAsync(); await SendInvalidGoAwayFrameAsync();
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 0,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdNotZero(Http2FrameType.GOAWAY));
} }
[Fact] [Fact]
@ -1248,7 +1444,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders); await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders);
await SendGoAwayAsync(); await SendGoAwayAsync();
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 0,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.GOAWAY, streamId: 0, headersStreamId: 1));
} }
[Fact] [Fact]
@ -1258,7 +1458,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendWindowUpdateAsync(2, sizeIncrement: 42); await SendWindowUpdateAsync(2, sizeIncrement: 42);
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 0,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdEven(Http2FrameType.WINDOW_UPDATE, streamId: 2));
} }
[Fact] [Fact]
@ -1269,7 +1473,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders); await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders);
await SendWindowUpdateAsync(1, sizeIncrement: 42); await SendWindowUpdateAsync(1, sizeIncrement: 42);
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 0,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.WINDOW_UPDATE, streamId: 1, headersStreamId: 1));
} }
[Theory] [Theory]
@ -1283,7 +1491,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendInvalidWindowUpdateAsync(streamId, sizeIncrement: 42, length: length); await SendInvalidWindowUpdateAsync(streamId, sizeIncrement: 42, length: length);
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 0,
expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.WINDOW_UPDATE, expectedLength: 4));
} }
[Fact] [Fact]
@ -1293,7 +1505,26 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendWindowUpdateAsync(0, sizeIncrement: 0); await SendWindowUpdateAsync(0, sizeIncrement: 0);
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 0,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.Http2ErrorWindowUpdateIncrementZero);
}
[Fact]
public async Task WINDOW_UPDATE_Received_OnStream_SizeIncrementZero_ConnectionError()
{
await InitializeConnectionAsync(_waitForAbortApplication);
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
await SendWindowUpdateAsync(1, sizeIncrement: 0);
await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 1,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.Http2ErrorWindowUpdateIncrementZero);
} }
[Fact] [Fact]
@ -1303,20 +1534,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendWindowUpdateAsync(1, sizeIncrement: 1); await SendWindowUpdateAsync(1, sizeIncrement: 1);
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
} ignoreNonGoAwayFrames: false,
expectedLastStreamId: 0,
[Fact] expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
public async Task WINDOW_UPDATE_Received_OnStream_SizeIncrementZero_StreamError() expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdle(Http2FrameType.WINDOW_UPDATE, streamId: 1));
{
await InitializeConnectionAsync(_waitForAbortApplication);
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
await SendWindowUpdateAsync(1, sizeIncrement: 0);
await WaitForStreamErrorAsync(expectedStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonRstStreamFrames: true);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: true);
} }
[Fact] [Fact]
@ -1348,7 +1570,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _oneContinuationRequestHeaders); await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _oneContinuationRequestHeaders);
await SendContinuationAsync(3, Http2ContinuationFrameFlags.END_HEADERS); await SendContinuationAsync(3, Http2ContinuationFrameFlags.END_HEADERS);
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 0,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.CONTINUATION, streamId: 3, headersStreamId: 1));
} }
[Fact] [Fact]
@ -1359,7 +1585,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _postRequestHeaders); await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _postRequestHeaders);
await SendIncompleteContinuationFrameAsync(streamId: 1); await SendIncompleteContinuationFrameAsync(streamId: 1);
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.COMPRESSION_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<HPackDecodingException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 0,
expectedErrorCode: Http2ErrorCode.COMPRESSION_ERROR,
expectedErrorMessage: CoreStrings.HPackErrorIncompleteHeaderBlock);
} }
[Theory] [Theory]
@ -1371,11 +1601,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
Assert.True(await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, headers)); Assert.True(await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, headers));
await SendEmptyContinuationFrameAsync(1, Http2ContinuationFrameFlags.END_HEADERS); await SendEmptyContinuationFrameAsync(1, Http2ContinuationFrameFlags.END_HEADERS);
await WaitForStreamErrorAsync(1, Http2ErrorCode.PROTOCOL_ERROR, ignoreNonRstStreamFrames: false); await WaitForStreamErrorAsync(
ignoreNonRstStreamFrames: false,
expectedStreamId: 1,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.Http2ErrorMissingMandatoryPseudoHeaderFields);
// Verify that the stream ID can't be re-used // Verify that the stream ID can't be re-used
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, headers); await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, headers);
await WaitForConnectionErrorAsync(expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 1,
expectedErrorCode: Http2ErrorCode.STREAM_CLOSED,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1));
} }
[Theory] [Theory]
@ -1448,7 +1686,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{ {
await InitializeConnectionAsync(_noopApplication); await InitializeConnectionAsync(_noopApplication);
await SendUnknownFrameTypeAsync(streamId: 1); await SendUnknownFrameTypeAsync(streamId: 1, frameType: 42);
// Check that the connection is still alive // Check that the connection is still alive
await SendPingAsync(Http2PingFrameFlags.NONE); await SendPingAsync(Http2PingFrameFlags.NONE);
@ -1466,13 +1704,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await InitializeConnectionAsync(_noopApplication); await InitializeConnectionAsync(_noopApplication);
await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders); await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders);
await SendUnknownFrameTypeAsync(streamId: 1); await SendUnknownFrameTypeAsync(streamId: 1, frameType: 42);
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 0,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(frameType: 42, streamId: 1, headersStreamId: 1));
} }
[Fact] [Fact]
public async Task ConnectionError_AbortsAllStreams() public async Task ConnectionErrorAbortsAllStreams()
{ {
await InitializeConnectionAsync(_waitForAbortApplication); await InitializeConnectionAsync(_waitForAbortApplication);
@ -1484,7 +1726,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
// Cause a connection error by sending an invalid frame // Cause a connection error by sending an invalid frame
await SendDataAsync(0, _noData, endStream: false); await SendDataAsync(0, _noData, endStream: false);
await WaitForConnectionErrorAsync(expectedLastStreamId: 5, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: false,
expectedLastStreamId: 5,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdZero(Http2FrameType.DATA));
await WaitForAllStreamsAsync(); await WaitForAllStreamsAsync();
Assert.Contains(1, _abortedStreamIds); Assert.Contains(1, _abortedStreamIds);
@ -1492,6 +1738,32 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
Assert.Contains(5, _abortedStreamIds); Assert.Contains(5, _abortedStreamIds);
} }
[Fact]
public async Task ConnectionResetLoggedWithActiveStreams()
{
await InitializeConnectionAsync(_waitForAbortApplication);
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, _browserRequestHeaders);
_pair.Application.Output.Complete(new ConnectionResetException(string.Empty));
var result = await _pair.Application.Input.ReadAsync();
Assert.True(result.IsCompleted);
Assert.Single(_logger.Messages, m => m.Exception is ConnectionResetException);
}
[Fact]
public async Task ConnectionResetNotLoggedWithNoActiveStreams()
{
await InitializeConnectionAsync(_waitForAbortApplication);
_pair.Application.Output.Complete(new ConnectionResetException(string.Empty));
var result = await _pair.Application.Input.ReadAsync();
Assert.True(result.IsCompleted);
Assert.DoesNotContain(_logger.Messages, m => m.Exception is ConnectionResetException);
}
private async Task InitializeConnectionAsync(RequestDelegate application) private async Task InitializeConnectionAsync(RequestDelegate application)
{ {
_connectionTask = _connection.ProcessAsync(new DummyApplication(application)); _connectionTask = _connection.ProcessAsync(new DummyApplication(application));
@ -1931,11 +2203,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
return SendAsync(frame.Raw); return SendAsync(frame.Raw);
} }
private Task SendUnknownFrameTypeAsync(int streamId) private Task SendUnknownFrameTypeAsync(int streamId, int frameType)
{ {
var frame = new Http2Frame(); var frame = new Http2Frame();
frame.StreamId = streamId; frame.StreamId = streamId;
frame.Type = (Http2FrameType)42; frame.Type = (Http2FrameType)frameType;
frame.Length = 0; frame.Length = 0;
return SendAsync(frame.Raw); return SendAsync(frame.Raw);
} }
@ -1996,10 +2268,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
private Task WaitForConnectionStopAsync(int expectedLastStreamId, bool ignoreNonGoAwayFrames) private Task WaitForConnectionStopAsync(int expectedLastStreamId, bool ignoreNonGoAwayFrames)
{ {
return WaitForConnectionErrorAsync(expectedLastStreamId, Http2ErrorCode.NO_ERROR, ignoreNonGoAwayFrames); return WaitForConnectionErrorAsync<Exception>(ignoreNonGoAwayFrames, expectedLastStreamId, Http2ErrorCode.NO_ERROR, expectedErrorMessage: null);
} }
private async Task WaitForConnectionErrorAsync(int expectedLastStreamId, Http2ErrorCode expectedErrorCode, bool ignoreNonGoAwayFrames) private async Task WaitForConnectionErrorAsync<TException>(bool ignoreNonGoAwayFrames, int expectedLastStreamId, Http2ErrorCode expectedErrorCode, string expectedErrorMessage)
where TException : Exception
{ {
var frame = await ReceiveFrameAsync(); var frame = await ReceiveFrameAsync();
@ -2018,11 +2291,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
Assert.Equal(expectedLastStreamId, frame.GoAwayLastStreamId); Assert.Equal(expectedLastStreamId, frame.GoAwayLastStreamId);
Assert.Equal(expectedErrorCode, frame.GoAwayErrorCode); Assert.Equal(expectedErrorCode, frame.GoAwayErrorCode);
if (expectedErrorMessage != null)
{
var message = Assert.Single(_logger.Messages, m => m.Exception is TException);
Assert.Contains(expectedErrorMessage, message.Exception.Message);
}
await _connectionTask; await _connectionTask;
_pair.Application.Output.Complete(); _pair.Application.Output.Complete();
} }
private async Task WaitForStreamErrorAsync(int expectedStreamId, Http2ErrorCode expectedErrorCode, bool ignoreNonRstStreamFrames) private async Task WaitForStreamErrorAsync(bool ignoreNonRstStreamFrames, int expectedStreamId, Http2ErrorCode expectedErrorCode, string expectedErrorMessage)
{ {
var frame = await ReceiveFrameAsync(); var frame = await ReceiveFrameAsync();
@ -2039,6 +2318,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
Assert.Equal(0, frame.Flags); Assert.Equal(0, frame.Flags);
Assert.Equal(expectedStreamId, frame.StreamId); Assert.Equal(expectedStreamId, frame.StreamId);
Assert.Equal(expectedErrorCode, frame.RstStreamErrorCode); Assert.Equal(expectedErrorCode, frame.RstStreamErrorCode);
if (expectedErrorMessage != null)
{
var message = Assert.Single(_logger.Messages, m => m.Exception is Http2StreamErrorException);
Assert.Contains(expectedErrorMessage, message.Exception.Message);
}
} }
private void VerifyDecodedRequestHeaders(IEnumerable<KeyValuePair<string, string>> expectedHeaders) private void VerifyDecodedRequestHeaders(IEnumerable<KeyValuePair<string, string>> expectedHeaders)