diff --git a/benchmarks/Kestrel.Performance/Mocks/MockTrace.cs b/benchmarks/Kestrel.Performance/Mocks/MockTrace.cs index 77290ee718..fef2b970f1 100644 --- a/benchmarks/Kestrel.Performance/Mocks/MockTrace.cs +++ b/benchmarks/Kestrel.Performance/Mocks/MockTrace.cs @@ -3,6 +3,8 @@ using System; 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.Extensions.Logging; @@ -40,5 +42,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance public void RequestBodyDone(string connectionId, string traceIdentifier) { } public void RequestBodyMininumDataRateNotSatisfied(string connectionId, string traceIdentifier, double rate) { } 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) { } } } diff --git a/src/Kestrel.Core/CoreStrings.resx b/src/Kestrel.Core/CoreStrings.resx index 7127c41905..fdbebb8aaf 100644 --- a/src/Kestrel.Core/CoreStrings.resx +++ b/src/Kestrel.Core/CoreStrings.resx @@ -1,17 +1,17 @@  - @@ -384,4 +384,82 @@ The header block was incomplete and could not be fully decoded. - \ No newline at end of file + + The client sent a {frameType} frame with even stream ID {streamId}. + + + The client sent a A PUSH_PROMISE frame. + + + The client sent a {frameType} frame to stream ID {streamId} before signaling of the header block for stream ID {headersStreamId}. + + + The client sent a {frameType} frame with stream ID 0. + + + The client sent a {frameType} frame with stream ID different than 0. + + + The client sent a {frameType} frame with padding longer than or with the same length as the sent data. + + + The client sent a {frameType} frame to closed stream ID {streamId}. + + + The client sent a {frameType} frame to stream ID {streamId} which is in the "half-closed (remote) state". + + + The client sent a {frameType} frame with dependency information that would cause stream ID {streamId} to depend on itself. + + + The client sent a {frameType} frame with length different than {expectedLength}. + + + The client sent a SETTINGS frame with a length that is not a multiple of 6. + + + The client sent a SETTINGS frame with ACK set and length different than 0. + + + The client sent a SETTINGS frame with a value for parameter {parameter} that is out of range. + + + The client sent a WINDOW_UPDATE frame with a window size increment of 0. + + + The client sent a CONTINUATION frame not preceded by a HEADERS frame. + + + The client sent a {frameType} frame to idle stream ID {streamId}. + + + The client sent trailers containing one or more pseudo-header fields. + + + The client sent a header with uppercase characters in its name. + + + The client sent a trailer with uppercase characters in its name. + + + The client sent a HEADERS frame containing trailers without setting the END_STREAM flag. + + + Request headers missing one or more mandatory pseudo-header fields. + + + Pseudo-header field found in request headers after regular header fields. + + + Request headers contain unknown pseudo-header field. + + + Request headers contain response-specific pseudo-header field. + + + Request headers contain duplicate pseudo-header field. + + + Request headers contain connection-specific header field. + + diff --git a/src/Kestrel.Core/Internal/Http2/Http2Connection.cs b/src/Kestrel.Core/Internal/Http2/Http2Connection.cs index 50e023196d..40d35ebee3 100644 --- a/src/Kestrel.Core/Internal/Http2/Http2Connection.cs +++ b/src/Kestrel.Core/Internal/Http2/Http2Connection.cs @@ -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; } catch (Http2ConnectionErrorException ex) { - // TODO: log + Log.Http2ConnectionError(ConnectionId, ex); error = ex; errorCode = ex.ErrorCode; } catch (HPackDecodingException ex) { - // TODO: log + Log.HPackDecodingError(ConnectionId, _currentHeadersStream.StreamId, ex); error = ex; errorCode = Http2ErrorCode.COMPRESSION_ERROR; } catch (Exception ex) { - // TODO: log error = ex; errorCode = Http2ErrorCode.INTERNAL_ERROR; + throw; } finally { @@ -242,7 +247,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 // a connection error (Section 5.4.1) of type PROTOCOL_ERROR. 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) @@ -258,7 +263,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 case Http2FrameType.SETTINGS: return ProcessSettingsFrameAsync(); case Http2FrameType.PUSH_PROMISE: - throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR); + throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorPushPromiseReceived, Http2ErrorCode.PROTOCOL_ERROR); case Http2FrameType.PING: return ProcessPingFrameAsync(); case Http2FrameType.GOAWAY: @@ -276,35 +281,39 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { 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) { - throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR); + throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR); } if (_incomingFrame.DataHasPadding && _incomingFrame.DataPadLength >= _incomingFrame.Length) { - throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR); + throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorPaddingTooLong(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR); } 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, 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 // 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 @@ -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 // 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(IHttpApplication application) { 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) { - throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR); + throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR); } 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) { - 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)) @@ -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) if (stream.EndStreamReceived) { - throw new Http2ConnectionErrorException(Http2ErrorCode.STREAM_CLOSED); + throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(_incomingFrame.Type, stream.StreamId), Http2ErrorCode.STREAM_CLOSED); } // 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 // END_STREAM or RST_STREAM). - throw new Http2ConnectionErrorException(Http2ErrorCode.STREAM_CLOSED); + throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamClosed(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.STREAM_CLOSED); } else { @@ -400,22 +409,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { 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) { - throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR); + throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR); } 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) { - throw new Http2ConnectionErrorException(Http2ErrorCode.FRAME_SIZE_ERROR); + throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(_incomingFrame.Type, 5), Http2ErrorCode.FRAME_SIZE_ERROR); } return Task.CompletedTask; @@ -425,17 +434,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { 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) { - throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR); + throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR); } if (_incomingFrame.Length != 4) { - throw new Http2ConnectionErrorException(Http2ErrorCode.FRAME_SIZE_ERROR); + throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(_incomingFrame.Type, 4), Http2ErrorCode.FRAME_SIZE_ERROR); } ThrowIfIncomingFrameSentToIdleStream(); @@ -452,22 +461,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { 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) { - 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) { - throw new Http2ConnectionErrorException(Http2ErrorCode.FRAME_SIZE_ERROR); + throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorSettingsAckLengthNotZero, Http2ErrorCode.FRAME_SIZE_ERROR); } if (_incomingFrame.Length % 6 != 0) { - throw new Http2ConnectionErrorException(Http2ErrorCode.FRAME_SIZE_ERROR); + throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorSettingsLengthNotMultipleOfSix, Http2ErrorCode.FRAME_SIZE_ERROR); } try @@ -477,7 +486,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 } 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.PROTOCOL_ERROR); } @@ -487,21 +496,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { 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) { - throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR); + throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdNotZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR); } 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) { + // TODO: verify that payload is equal to the outgoing PING frame return Task.CompletedTask; } @@ -512,12 +522,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { 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) { - throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR); + throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdNotZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR); } Stop(); @@ -528,26 +538,34 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { 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) { - throw new Http2ConnectionErrorException(Http2ErrorCode.FRAME_SIZE_ERROR); + throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(_incomingFrame.Type, 4), Http2ErrorCode.FRAME_SIZE_ERROR); } ThrowIfIncomingFrameSentToIdleStream(); if (_incomingFrame.WindowUpdateSizeIncrement == 0) { - if (_incomingFrame.StreamId == 0) - { - throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR); - } - else - { - return _frameWriter.WriteRstStreamAsync(_incomingFrame.StreamId, Http2ErrorCode.PROTOCOL_ERROR); - } + // http://httpwg.org/specs/rfc7540.html#rfc.section.6.9 + // A receiver MUST treat the receipt of a WINDOW_UPDATE + // frame with an flow-control window increment of 0 as a + // stream error (Section 5.4.2) of type PROTOCOL_ERROR; + // errors on the connection flow-control window MUST be + // treated as a connection error (Section 5.4.1). + // + // 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; @@ -555,9 +573,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 private Task ProcessContinuationFrameAsync(IHttpApplication 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; @@ -569,7 +592,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { 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; @@ -589,6 +612,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 } catch (Http2StreamErrorException ex) { + Log.Http2StreamError(ConnectionId, ex); ResetRequestHeaderParsingState(); 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 // 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). - throw new Http2StreamErrorException(_currentHeadersStream.StreamId, Http2ErrorCode.PROTOCOL_ERROR); + throw new Http2StreamErrorException(_currentHeadersStream.StreamId, CoreStrings.Http2ErrorMissingMandatoryPseudoHeaderFields, Http2ErrorCode.PROTOCOL_ERROR); } _streams[_incomingFrame.StreamId] = _currentHeadersStream; @@ -638,7 +662,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 // initial state for all streams. 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. // 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). - throw new Http2StreamErrorException(_currentHeadersStream.StreamId, Http2ErrorCode.PROTOCOL_ERROR); + throw new Http2StreamErrorException(_currentHeadersStream.StreamId, CoreStrings.Http2ErrorPseudoHeaderFieldAfterRegularHeaders, Http2ErrorCode.PROTOCOL_ERROR); } _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 // 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) { // Pseudo-header fields defined for requests MUST NOT appear in responses; pseudo-header fields // 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) { // 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 - throw new Http2StreamErrorException(_currentHeadersStream.StreamId, Http2ErrorCode.PROTOCOL_ERROR); + throw new Http2StreamErrorException(_currentHeadersStream.StreamId, CoreStrings.Http2ErrorDuplicatePseudoHeaderField, Http2ErrorCode.PROTOCOL_ERROR); } if (headerField == PseudoHeaderFields.Method) @@ -706,7 +730,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 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 @@ -715,7 +739,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { 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); } } } diff --git a/src/Kestrel.Core/Internal/Http2/Http2ConnectionErrorException.cs b/src/Kestrel.Core/Internal/Http2/Http2ConnectionErrorException.cs index b1b9136774..dd1314b1a5 100644 --- a/src/Kestrel.Core/Internal/Http2/Http2ConnectionErrorException.cs +++ b/src/Kestrel.Core/Internal/Http2/Http2ConnectionErrorException.cs @@ -7,8 +7,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { public class Http2ConnectionErrorException : Exception { - public Http2ConnectionErrorException(Http2ErrorCode errorCode) - : base($"HTTP/2 connection error: {errorCode}") + public Http2ConnectionErrorException(string message, Http2ErrorCode errorCode) + : base($"HTTP/2 connection error ({errorCode}): {message}") { ErrorCode = errorCode; } diff --git a/src/Kestrel.Core/Internal/Http2/Http2StreamErrorException.cs b/src/Kestrel.Core/Internal/Http2/Http2StreamErrorException.cs index d2fcdc6bb0..2f63df1412 100644 --- a/src/Kestrel.Core/Internal/Http2/Http2StreamErrorException.cs +++ b/src/Kestrel.Core/Internal/Http2/Http2StreamErrorException.cs @@ -7,8 +7,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { public class Http2StreamErrorException : Exception { - public Http2StreamErrorException(int streamId, Http2ErrorCode errorCode) - : base($"HTTP/2 stream ID {streamId} error: {errorCode}") + public Http2StreamErrorException(int streamId, string message, Http2ErrorCode errorCode) + : base($"HTTP/2 stream ID {streamId} error ({errorCode}): {message}") { StreamId = streamId; ErrorCode = errorCode; diff --git a/src/Kestrel.Core/Internal/Infrastructure/IKestrelTrace.cs b/src/Kestrel.Core/Internal/Infrastructure/IKestrelTrace.cs index 456e132274..46aed5b8ad 100644 --- a/src/Kestrel.Core/Internal/Infrastructure/IKestrelTrace.cs +++ b/src/Kestrel.Core/Internal/Infrastructure/IKestrelTrace.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack; using Microsoft.Extensions.Logging; 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 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); } -} \ No newline at end of file +} diff --git a/src/Kestrel.Core/Internal/Infrastructure/KestrelTrace.cs b/src/Kestrel.Core/Internal/Infrastructure/KestrelTrace.cs index ab6eb51293..1c31f4394c 100644 --- a/src/Kestrel.Core/Internal/Infrastructure/KestrelTrace.cs +++ b/src/Kestrel.Core/Internal/Infrastructure/KestrelTrace.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 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.Extensions.Logging; @@ -66,6 +68,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal private static readonly Action _responseMinimumDataRateNotSatisfied = LoggerMessage.Define(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 _http2ConnectionError = + LoggerMessage.Define(LogLevel.Information, 29, @"Connection id ""{ConnectionId}"": HTTP/2 connection error."); + + private static readonly Action _http2StreamError = + LoggerMessage.Define(LogLevel.Information, 30, @"Connection id ""{ConnectionId}"": HTTP/2 stream error."); + + private static readonly Action _hpackDecodingError = + LoggerMessage.Define(LogLevel.Information, 31, @"Connection id ""{ConnectionId}"": HPACK decoding error while decoding headers for stream ID {StreamId}."); + protected readonly ILogger _logger; public KestrelTrace(ILogger logger) @@ -168,6 +179,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal _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(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) => _logger.Log(logLevel, eventId, state, exception, formatter); diff --git a/src/Kestrel.Core/Properties/CoreStrings.Designer.cs b/src/Kestrel.Core/Properties/CoreStrings.Designer.cs index 7084b94b0c..346b18a969 100644 --- a/src/Kestrel.Core/Properties/CoreStrings.Designer.cs +++ b/src/Kestrel.Core/Properties/CoreStrings.Designer.cs @@ -1256,6 +1256,370 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core internal static string FormatHPackErrorIncompleteHeaderBlock() => GetString("HPackErrorIncompleteHeaderBlock"); + /// + /// The client sent a {frameType} frame with even stream ID {streamId}. + /// + internal static string Http2ErrorStreamIdEven + { + get => GetString("Http2ErrorStreamIdEven"); + } + + /// + /// The client sent a {frameType} frame with even stream ID {streamId}. + /// + internal static string FormatHttp2ErrorStreamIdEven(object frameType, object streamId) + => string.Format(CultureInfo.CurrentCulture, GetString("Http2ErrorStreamIdEven", "frameType", "streamId"), frameType, streamId); + + /// + /// The client sent a A PUSH_PROMISE frame. + /// + internal static string Http2ErrorPushPromiseReceived + { + get => GetString("Http2ErrorPushPromiseReceived"); + } + + /// + /// The client sent a A PUSH_PROMISE frame. + /// + internal static string FormatHttp2ErrorPushPromiseReceived() + => GetString("Http2ErrorPushPromiseReceived"); + + /// + /// The client sent a {frameType} frame to stream ID {streamId} before signaling of the header block for stream ID {headersStreamId}. + /// + internal static string Http2ErrorHeadersInterleaved + { + get => GetString("Http2ErrorHeadersInterleaved"); + } + + /// + /// The client sent a {frameType} frame to stream ID {streamId} before signaling of the header block for stream ID {headersStreamId}. + /// + internal static string FormatHttp2ErrorHeadersInterleaved(object frameType, object streamId, object headersStreamId) + => string.Format(CultureInfo.CurrentCulture, GetString("Http2ErrorHeadersInterleaved", "frameType", "streamId", "headersStreamId"), frameType, streamId, headersStreamId); + + /// + /// The client sent a {frameType} frame with stream ID 0. + /// + internal static string Http2ErrorStreamIdZero + { + get => GetString("Http2ErrorStreamIdZero"); + } + + /// + /// The client sent a {frameType} frame with stream ID 0. + /// + internal static string FormatHttp2ErrorStreamIdZero(object frameType) + => string.Format(CultureInfo.CurrentCulture, GetString("Http2ErrorStreamIdZero", "frameType"), frameType); + + /// + /// The client sent a {frameType} frame with stream ID different than 0. + /// + internal static string Http2ErrorStreamIdNotZero + { + get => GetString("Http2ErrorStreamIdNotZero"); + } + + /// + /// The client sent a {frameType} frame with stream ID different than 0. + /// + internal static string FormatHttp2ErrorStreamIdNotZero(object frameType) + => string.Format(CultureInfo.CurrentCulture, GetString("Http2ErrorStreamIdNotZero", "frameType"), frameType); + + /// + /// The client sent a {frameType} frame with padding longer than or with the same length as the sent data. + /// + internal static string Http2ErrorPaddingTooLong + { + get => GetString("Http2ErrorPaddingTooLong"); + } + + /// + /// The client sent a {frameType} frame with padding longer than or with the same length as the sent data. + /// + internal static string FormatHttp2ErrorPaddingTooLong(object frameType) + => string.Format(CultureInfo.CurrentCulture, GetString("Http2ErrorPaddingTooLong", "frameType"), frameType); + + /// + /// The client sent a {frameType} frame to closed stream ID {streamId}. + /// + internal static string Http2ErrorStreamClosed + { + get => GetString("Http2ErrorStreamClosed"); + } + + /// + /// The client sent a {frameType} frame to closed stream ID {streamId}. + /// + internal static string FormatHttp2ErrorStreamClosed(object frameType, object streamId) + => string.Format(CultureInfo.CurrentCulture, GetString("Http2ErrorStreamClosed", "frameType", "streamId"), frameType, streamId); + + /// + /// The client sent a {frameType} frame to stream ID {streamId} which is in the "half-closed (remote) state". + /// + internal static string Http2ErrorStreamHalfClosedRemote + { + get => GetString("Http2ErrorStreamHalfClosedRemote"); + } + + /// + /// The client sent a {frameType} frame to stream ID {streamId} which is in the "half-closed (remote) state". + /// + internal static string FormatHttp2ErrorStreamHalfClosedRemote(object frameType, object streamId) + => string.Format(CultureInfo.CurrentCulture, GetString("Http2ErrorStreamHalfClosedRemote", "frameType", "streamId"), frameType, streamId); + + /// + /// The client sent a {frameType} frame with dependency information that would cause stream ID {streamId} to depend on itself. + /// + internal static string Http2ErrorStreamSelfDependency + { + get => GetString("Http2ErrorStreamSelfDependency"); + } + + /// + /// The client sent a {frameType} frame with dependency information that would cause stream ID {streamId} to depend on itself. + /// + internal static string FormatHttp2ErrorStreamSelfDependency(object frameType, object streamId) + => string.Format(CultureInfo.CurrentCulture, GetString("Http2ErrorStreamSelfDependency", "frameType", "streamId"), frameType, streamId); + + /// + /// The client sent a {frameType} frame with length different than {expectedLength}. + /// + internal static string Http2ErrorUnexpectedFrameLength + { + get => GetString("Http2ErrorUnexpectedFrameLength"); + } + + /// + /// The client sent a {frameType} frame with length different than {expectedLength}. + /// + internal static string FormatHttp2ErrorUnexpectedFrameLength(object frameType, object expectedLength) + => string.Format(CultureInfo.CurrentCulture, GetString("Http2ErrorUnexpectedFrameLength", "frameType", "expectedLength"), frameType, expectedLength); + + /// + /// The client sent a SETTINGS frame with a length that is not a multiple of 6. + /// + internal static string Http2ErrorSettingsLengthNotMultipleOfSix + { + get => GetString("Http2ErrorSettingsLengthNotMultipleOfSix"); + } + + /// + /// The client sent a SETTINGS frame with a length that is not a multiple of 6. + /// + internal static string FormatHttp2ErrorSettingsLengthNotMultipleOfSix() + => GetString("Http2ErrorSettingsLengthNotMultipleOfSix"); + + /// + /// The client sent a SETTINGS frame with ACK set and length different than 0. + /// + internal static string Http2ErrorSettingsAckLengthNotZero + { + get => GetString("Http2ErrorSettingsAckLengthNotZero"); + } + + /// + /// The client sent a SETTINGS frame with ACK set and length different than 0. + /// + internal static string FormatHttp2ErrorSettingsAckLengthNotZero() + => GetString("Http2ErrorSettingsAckLengthNotZero"); + + /// + /// The client sent a SETTINGS frame with a value for parameter {parameter} that is out of range. + /// + internal static string Http2ErrorSettingsParameterOutOfRange + { + get => GetString("Http2ErrorSettingsParameterOutOfRange"); + } + + /// + /// The client sent a SETTINGS frame with a value for parameter {parameter} that is out of range. + /// + internal static string FormatHttp2ErrorSettingsParameterOutOfRange(object parameter) + => string.Format(CultureInfo.CurrentCulture, GetString("Http2ErrorSettingsParameterOutOfRange", "parameter"), parameter); + + /// + /// The client sent a WINDOW_UPDATE frame with a window size increment of 0. + /// + internal static string Http2ErrorWindowUpdateIncrementZero + { + get => GetString("Http2ErrorWindowUpdateIncrementZero"); + } + + /// + /// The client sent a WINDOW_UPDATE frame with a window size increment of 0. + /// + internal static string FormatHttp2ErrorWindowUpdateIncrementZero() + => GetString("Http2ErrorWindowUpdateIncrementZero"); + + /// + /// The client sent a CONTINUATION frame not preceded by a HEADERS frame. + /// + internal static string Http2ErrorContinuationWithNoHeaders + { + get => GetString("Http2ErrorContinuationWithNoHeaders"); + } + + /// + /// The client sent a CONTINUATION frame not preceded by a HEADERS frame. + /// + internal static string FormatHttp2ErrorContinuationWithNoHeaders() + => GetString("Http2ErrorContinuationWithNoHeaders"); + + /// + /// The client sent a {frameType} frame to idle stream ID {streamId}. + /// + internal static string Http2ErrorStreamIdle + { + get => GetString("Http2ErrorStreamIdle"); + } + + /// + /// The client sent a {frameType} frame to idle stream ID {streamId}. + /// + internal static string FormatHttp2ErrorStreamIdle(object frameType, object streamId) + => string.Format(CultureInfo.CurrentCulture, GetString("Http2ErrorStreamIdle", "frameType", "streamId"), frameType, streamId); + + /// + /// The client sent trailers containing one or more pseudo-header fields. + /// + internal static string Http2ErrorTrailersContainPseudoHeaderField + { + get => GetString("Http2ErrorTrailersContainPseudoHeaderField"); + } + + /// + /// The client sent trailers containing one or more pseudo-header fields. + /// + internal static string FormatHttp2ErrorTrailersContainPseudoHeaderField() + => GetString("Http2ErrorTrailersContainPseudoHeaderField"); + + /// + /// The client sent a header with uppercase characters in its name. + /// + internal static string Http2ErrorHeaderNameUppercase + { + get => GetString("Http2ErrorHeaderNameUppercase"); + } + + /// + /// The client sent a header with uppercase characters in its name. + /// + internal static string FormatHttp2ErrorHeaderNameUppercase() + => GetString("Http2ErrorHeaderNameUppercase"); + + /// + /// The client sent a trailer with uppercase characters in its name. + /// + internal static string Http2ErrorTrailerNameUppercase + { + get => GetString("Http2ErrorTrailerNameUppercase"); + } + + /// + /// The client sent a trailer with uppercase characters in its name. + /// + internal static string FormatHttp2ErrorTrailerNameUppercase() + => GetString("Http2ErrorTrailerNameUppercase"); + + /// + /// The client sent a HEADERS frame containing trailers without setting the END_STREAM flag. + /// + internal static string Http2ErrorHeadersWithTrailersNoEndStream + { + get => GetString("Http2ErrorHeadersWithTrailersNoEndStream"); + } + + /// + /// The client sent a HEADERS frame containing trailers without setting the END_STREAM flag. + /// + internal static string FormatHttp2ErrorHeadersWithTrailersNoEndStream() + => GetString("Http2ErrorHeadersWithTrailersNoEndStream"); + + /// + /// Request headers missing one or more mandatory pseudo-header fields. + /// + internal static string Http2ErrorMissingMandatoryPseudoHeaderFields + { + get => GetString("Http2ErrorMissingMandatoryPseudoHeaderFields"); + } + + /// + /// Request headers missing one or more mandatory pseudo-header fields. + /// + internal static string FormatHttp2ErrorMissingMandatoryPseudoHeaderFields() + => GetString("Http2ErrorMissingMandatoryPseudoHeaderFields"); + + /// + /// Pseudo-header field found in request headers after regular header fields. + /// + internal static string Http2ErrorPseudoHeaderFieldAfterRegularHeaders + { + get => GetString("Http2ErrorPseudoHeaderFieldAfterRegularHeaders"); + } + + /// + /// Pseudo-header field found in request headers after regular header fields. + /// + internal static string FormatHttp2ErrorPseudoHeaderFieldAfterRegularHeaders() + => GetString("Http2ErrorPseudoHeaderFieldAfterRegularHeaders"); + + /// + /// Request headers contain unknown pseudo-header field. + /// + internal static string Http2ErrorUnknownPseudoHeaderField + { + get => GetString("Http2ErrorUnknownPseudoHeaderField"); + } + + /// + /// Request headers contain unknown pseudo-header field. + /// + internal static string FormatHttp2ErrorUnknownPseudoHeaderField() + => GetString("Http2ErrorUnknownPseudoHeaderField"); + + /// + /// Request headers contain response-specific pseudo-header field. + /// + internal static string Http2ErrorResponsePseudoHeaderField + { + get => GetString("Http2ErrorResponsePseudoHeaderField"); + } + + /// + /// Request headers contain response-specific pseudo-header field. + /// + internal static string FormatHttp2ErrorResponsePseudoHeaderField() + => GetString("Http2ErrorResponsePseudoHeaderField"); + + /// + /// Request headers contain duplicate pseudo-header field. + /// + internal static string Http2ErrorDuplicatePseudoHeaderField + { + get => GetString("Http2ErrorDuplicatePseudoHeaderField"); + } + + /// + /// Request headers contain duplicate pseudo-header field. + /// + internal static string FormatHttp2ErrorDuplicatePseudoHeaderField() + => GetString("Http2ErrorDuplicatePseudoHeaderField"); + + /// + /// Request headers contain connection-specific header field. + /// + internal static string Http2ErrorConnectionSpecificHeaderField + { + get => GetString("Http2ErrorConnectionSpecificHeaderField"); + } + + /// + /// Request headers contain connection-specific header field. + /// + internal static string FormatHttp2ErrorConnectionSpecificHeaderField() + => GetString("Http2ErrorConnectionSpecificHeaderField"); + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/test/Kestrel.Core.Tests/Http2ConnectionTests.cs b/test/Kestrel.Core.Tests/Http2ConnectionTests.cs index 8e7a80aeb4..40fdf47094 100644 --- a/test/Kestrel.Core.Tests/Http2ConnectionTests.cs +++ b/test/Kestrel.Core.Tests/Http2ConnectionTests.cs @@ -10,6 +10,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Protocols; using Microsoft.AspNetCore.Server.Kestrel.Core.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; 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 (IPipeConnection Transport, IPipeConnection Application) _pair; + private readonly TestApplicationErrorLogger _logger; private readonly Http2ConnectionContext _connectionContext; private readonly Http2Connection _connection; private readonly Http2PeerSettings _clientSettings = new Http2PeerSettings(); @@ -215,9 +217,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests _hpackDecoder = new HPackDecoder((int)_clientSettings.HeaderTableSize); + _logger = new TestApplicationErrorLogger(); + _connectionContext = new Http2ConnectionContext { - ServiceContext = new TestServiceContext(), + ServiceContext = new TestServiceContext() + { + Log = new TestKestrelTrace(_logger) + }, PipeFactory = _pipeFactory, Application = _pair.Application, Transport = _pair.Transport @@ -421,7 +428,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(0, _noData, endStream: false); - await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 0, + expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdZero(Http2FrameType.DATA)); } [Fact] @@ -431,7 +442,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(2, _noData, endStream: false); - await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 0, + expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdEven(Http2FrameType.DATA, streamId: 2)); } [Fact] @@ -442,7 +457,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, _browserRequestHeaders, endStream: false); await SendInvalidDataFrameAsync(1, frameLength: 5, padLength: 5); - await WaitForConnectionErrorAsync(expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: true); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: true, + expectedLastStreamId: 1, + expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorPaddingTooLong(Http2FrameType.DATA)); } [Fact] @@ -453,7 +472,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, _browserRequestHeaders, endStream: false); await SendInvalidDataFrameAsync(1, frameLength: 5, padLength: 6); - await WaitForConnectionErrorAsync(expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: true); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: true, + expectedLastStreamId: 1, + expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorPaddingTooLong(Http2FrameType.DATA)); } [Fact] @@ -464,7 +487,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(1, _browserRequestHeaders, endStream: false); await SendInvalidDataFrameAsync(1, frameLength: 0, padLength: 0); - await WaitForConnectionErrorAsync(expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: true); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: true, + expectedLastStreamId: 1, + expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorPaddingTooLong(Http2FrameType.DATA)); } [Fact] @@ -475,7 +502,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders); await SendDataAsync(1, _helloWorldBytes, endStream: true); - await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 0, + expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.DATA, streamId: 1, headersStreamId: 1)); } [Fact] @@ -485,7 +516,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(1, _helloWorldBytes, endStream: false); - await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 0, + expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdle(Http2FrameType.DATA, streamId: 1)); } [Fact] @@ -498,7 +533,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(1, _helloWorldBytes, endStream: false); - await WaitForConnectionErrorAsync(expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 1, + expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.DATA, streamId: 1)); } [Fact] @@ -519,7 +558,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(1, _helloWorldBytes, endStream: false); - await WaitForConnectionErrorAsync(expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 1, + expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.DATA, streamId: 1)); } [Fact] @@ -547,7 +590,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendDataAsync(1, _helloWorldBytes, endStream: true); - await WaitForConnectionErrorAsync(expectedLastStreamId: 3, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 3, + expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.DATA, streamId: 1)); } [Fact] @@ -647,7 +694,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(0, _browserRequestHeaders, endStream: true); - await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 0, + expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdZero(Http2FrameType.HEADERS)); } [Fact] @@ -657,7 +708,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await StartStreamAsync(2, _browserRequestHeaders, endStream: true); - await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 0, + expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdEven(Http2FrameType.HEADERS, streamId: 2)); } [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) await StartStreamAsync(1, _browserRequestHeaders, endStream: true); - await WaitForConnectionErrorAsync(expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 1, + expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1)); } [Fact] @@ -692,7 +751,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders); - await WaitForConnectionErrorAsync(expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 1, + expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.HEADERS, streamId: 1)); } [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) await StartStreamAsync(1, _browserRequestHeaders, endStream: true); - await WaitForConnectionErrorAsync(expectedLastStreamId: 3, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 3, + expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1)); } [Theory] @@ -727,7 +794,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendInvalidHeadersFrameAsync(1, frameLength: padLength, padLength: padLength); - await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: true, + expectedLastStreamId: 0, + expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorPaddingTooLong(Http2FrameType.HEADERS)); } [Theory] @@ -740,7 +811,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendInvalidHeadersFrameAsync(1, frameLength, padLength); - await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: true, + expectedLastStreamId: 0, + expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorPaddingTooLong(Http2FrameType.HEADERS)); } [Fact] @@ -751,7 +826,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders); await SendHeadersAsync(3, Http2HeadersFrameFlags.NONE, _browserRequestHeaders); - await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 0, + expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.HEADERS, streamId: 3, headersStreamId: 1)); } [Fact] @@ -761,7 +840,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendHeadersWithPriorityAsync(1, _browserRequestHeaders, priority: 42, streamDependency: 1, endStream: true); - await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 0, + expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamSelfDependency(Http2FrameType.HEADERS, streamId: 1)); } [Fact] @@ -771,7 +854,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendIncompleteHeadersFrameAsync(streamId: 1); - await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.COMPRESSION_ERROR, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 0, + expectedErrorCode: Http2ErrorCode.COMPRESSION_ERROR, + expectedErrorMessage: CoreStrings.HPackErrorIncompleteHeaderBlock); } [Theory] @@ -781,11 +868,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await InitializeConnectionAsync(_noopApplication); 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 await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, _browserRequestHeaders); - await WaitForConnectionErrorAsync(expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 1, + expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1)); } [Fact] @@ -799,7 +894,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests new KeyValuePair(":unknown", "0"), }; - return HEADERS_Received_InvalidHeaderFields_StreamError(headers); + return HEADERS_Received_InvalidHeaderFields_StreamError(headers, expectedErrorMessage: CoreStrings.Http2ErrorUnknownPseudoHeaderField); } [Fact] @@ -813,21 +908,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests new KeyValuePair(":status", "200"), }; - return HEADERS_Received_InvalidHeaderFields_StreamError(headers); + return HEADERS_Received_InvalidHeaderFields_StreamError(headers, expectedErrorMessage: CoreStrings.Http2ErrorResponsePseudoHeaderField); } [Theory] [MemberData(nameof(DuplicatePseudoHeaderFieldData))] public Task HEADERS_Received_HeaderBlockContainsDuplicatePseudoHeaderField_StreamError(IEnumerable> headers) { - return HEADERS_Received_InvalidHeaderFields_StreamError(headers); + return HEADERS_Received_InvalidHeaderFields_StreamError(headers, expectedErrorMessage: CoreStrings.Http2ErrorDuplicatePseudoHeaderField); } [Theory] [MemberData(nameof(MissingPseudoHeaderFieldData))] public Task HEADERS_Received_HeaderBlockDoesNotContainMandatoryPseudoHeaderField_StreamError(IEnumerable> headers) { - return HEADERS_Received_InvalidHeaderFields_StreamError(headers); + return HEADERS_Received_InvalidHeaderFields_StreamError(headers, expectedErrorMessage: CoreStrings.Http2ErrorMissingMandatoryPseudoHeaderFields); } [Theory] @@ -854,23 +949,31 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [MemberData(nameof(PseudoHeaderFieldAfterRegularHeadersData))] public Task HEADERS_Received_HeaderBlockContainsPseudoHeaderFieldAfterRegularHeaders_StreamError(IEnumerable> headers) { - return HEADERS_Received_InvalidHeaderFields_StreamError(headers); + return HEADERS_Received_InvalidHeaderFields_StreamError(headers, expectedErrorMessage: CoreStrings.Http2ErrorPseudoHeaderFieldAfterRegularHeaders); } - private async Task HEADERS_Received_InvalidHeaderFields_StreamError(IEnumerable> headers) + private async Task HEADERS_Received_InvalidHeaderFields_StreamError(IEnumerable> headers, string expectedErrorMessage) { await InitializeConnectionAsync(_noopApplication); 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 await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, _browserRequestHeaders); - await WaitForConnectionErrorAsync(expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 1, + expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1)); } [Fact] - public Task HEADERS_Received_HeaderBlockContainsConnectionSpecificHeader_StreamError() + public Task HEADERS_Received_HeaderBlockContainsConnectionHeader_StreamError() { var headers = new[] { @@ -880,7 +983,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests new KeyValuePair("connection", "keep-alive") }; - return HEADERS_Received_InvalidHeaderFields_StreamError(headers); + return HEADERS_Received_InvalidHeaderFields_StreamError(headers, CoreStrings.Http2ErrorConnectionSpecificHeaderField); } [Fact] @@ -894,21 +997,34 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests new KeyValuePair("te", "trailers, deflate") }; - return HEADERS_Received_InvalidHeaderFields_StreamError(headers); + return HEADERS_Received_InvalidHeaderFields_StreamError(headers, CoreStrings.Http2ErrorConnectionSpecificHeaderField); } [Fact] - public Task HEADERS_Received_HeaderBlockContainsTEHeader_ValueIsTrailers_NoError() + public async Task HEADERS_Received_HeaderBlockContainsTEHeader_ValueIsTrailers_NoError() { var headers = new[] { new KeyValuePair(":method", "GET"), new KeyValuePair(":path", "/"), new KeyValuePair(":scheme", "http"), - new KeyValuePair("te", "trailers, deflate") + new KeyValuePair("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] @@ -918,7 +1034,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendPriorityAsync(0); - await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 0, + expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdZero(Http2FrameType.PRIORITY)); } [Fact] @@ -928,7 +1048,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendPriorityAsync(2); - await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 0, + expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdEven(Http2FrameType.PRIORITY, streamId: 2)); } [Theory] @@ -940,7 +1064,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendInvalidPriorityFrameAsync(1, length); - await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 0, + expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.PRIORITY, expectedLength: 5)); } [Fact] @@ -951,7 +1079,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders); await SendPriorityAsync(1); - await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 0, + expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.PRIORITY, streamId: 1, headersStreamId: 1)); } [Fact] @@ -961,7 +1093,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendPriorityAsync(1, streamDependency: 1); - await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 0, + expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamSelfDependency(Http2FrameType.PRIORITY, 1)); } [Fact] @@ -1007,7 +1143,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendRstStreamAsync(0); - await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 0, + expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdZero(Http2FrameType.RST_STREAM)); } [Fact] @@ -1017,7 +1157,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendRstStreamAsync(2); - await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 0, + expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdEven(Http2FrameType.RST_STREAM, streamId: 2)); } [Fact] @@ -1027,7 +1171,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendRstStreamAsync(1); - await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 0, + expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdle(Http2FrameType.RST_STREAM, streamId: 1)); } [Theory] @@ -1042,7 +1190,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendInvalidRstStreamFrameAsync(1, length); - await WaitForConnectionErrorAsync(expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, ignoreNonGoAwayFrames: true); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: true, + expectedLastStreamId: 1, + expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.RST_STREAM, expectedLength: 4)); } [Fact] @@ -1053,7 +1205,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders); await SendRstStreamAsync(1); - await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 0, + expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.RST_STREAM, streamId: 1, headersStreamId: 1)); } [Fact] @@ -1065,13 +1221,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests } [Fact] - public async Task SETTINGS_Received_StreamIdNonZero_ConnectionError() + public async Task SETTINGS_Received_StreamIdNotZero_ConnectionError() { await InitializeConnectionAsync(_noopApplication); await SendSettingsWithInvalidStreamIdAsync(1); - await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 0, + expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdNotZero(Http2FrameType.SETTINGS)); } [Theory] @@ -1090,7 +1250,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendSettingsWithInvalidParameterValueAsync(parameter, value); - await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: expectedErrorCode, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 0, + expectedErrorCode: expectedErrorCode, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorSettingsParameterOutOfRange(parameter)); } [Fact] @@ -1101,7 +1265,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders); await SendSettingsAsync(); - await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 0, + expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.SETTINGS, streamId: 0, headersStreamId: 1)); } [Theory] @@ -1113,7 +1281,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendSettingsAckWithInvalidLengthAsync(length); - await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 0, + expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, + expectedErrorMessage: CoreStrings.Http2ErrorSettingsAckLengthNotZero); } [Theory] @@ -1128,7 +1300,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendSettingsWithInvalidLengthAsync(length); - await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 0, + expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, + expectedErrorMessage: CoreStrings.Http2ErrorSettingsLengthNotMultipleOfSix); } [Fact] @@ -1138,7 +1314,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendPushPromiseFrameAsync(); - await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 0, + expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, + expectedErrorMessage: CoreStrings.Http2ErrorPushPromiseReceived); } [Fact] @@ -1173,7 +1353,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders); await SendPingAsync(Http2PingFrameFlags.NONE); - await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 0, + expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.PING, streamId: 0, headersStreamId: 1)); } [Fact] @@ -1183,7 +1367,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendPingWithInvalidStreamIdAsync(streamId: 1); - await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 0, + expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdNotZero(Http2FrameType.PING)); } [Theory] @@ -1197,7 +1385,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendPingWithInvalidLengthAsync(length); - await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 0, + expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.PING, expectedLength: 8)); } [Fact] @@ -1231,13 +1423,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests } [Fact] - public async Task GOAWAY_Received_StreamIdNonZero_ConnectionError() + public async Task GOAWAY_Received_StreamIdNotZero_ConnectionError() { await InitializeConnectionAsync(_noopApplication); await SendInvalidGoAwayFrameAsync(); - await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 0, + expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdNotZero(Http2FrameType.GOAWAY)); } [Fact] @@ -1248,7 +1444,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders); await SendGoAwayAsync(); - await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 0, + expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.GOAWAY, streamId: 0, headersStreamId: 1)); } [Fact] @@ -1258,7 +1458,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendWindowUpdateAsync(2, sizeIncrement: 42); - await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 0, + expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdEven(Http2FrameType.WINDOW_UPDATE, streamId: 2)); } [Fact] @@ -1269,7 +1473,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders); await SendWindowUpdateAsync(1, sizeIncrement: 42); - await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 0, + expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.WINDOW_UPDATE, streamId: 1, headersStreamId: 1)); } [Theory] @@ -1283,7 +1491,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendInvalidWindowUpdateAsync(streamId, sizeIncrement: 42, length: length); - await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 0, + expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.WINDOW_UPDATE, expectedLength: 4)); } [Fact] @@ -1293,7 +1505,26 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendWindowUpdateAsync(0, sizeIncrement: 0); - await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + 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( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 1, + expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, + expectedErrorMessage: CoreStrings.Http2ErrorWindowUpdateIncrementZero); } [Fact] @@ -1303,20 +1534,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendWindowUpdateAsync(1, sizeIncrement: 1); - await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); - } - - [Fact] - public async Task WINDOW_UPDATE_Received_OnStream_SizeIncrementZero_StreamError() - { - 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); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 0, + expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdle(Http2FrameType.WINDOW_UPDATE, streamId: 1)); } [Fact] @@ -1348,7 +1570,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _oneContinuationRequestHeaders); await SendContinuationAsync(3, Http2ContinuationFrameFlags.END_HEADERS); - await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 0, + expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.CONTINUATION, streamId: 3, headersStreamId: 1)); } [Fact] @@ -1359,7 +1585,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _postRequestHeaders); await SendIncompleteContinuationFrameAsync(streamId: 1); - await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.COMPRESSION_ERROR, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 0, + expectedErrorCode: Http2ErrorCode.COMPRESSION_ERROR, + expectedErrorMessage: CoreStrings.HPackErrorIncompleteHeaderBlock); } [Theory] @@ -1371,11 +1601,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests Assert.True(await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, 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 await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, headers); - await WaitForConnectionErrorAsync(expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 1, + expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1)); } [Theory] @@ -1448,7 +1686,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { await InitializeConnectionAsync(_noopApplication); - await SendUnknownFrameTypeAsync(streamId: 1); + await SendUnknownFrameTypeAsync(streamId: 1, frameType: 42); // Check that the connection is still alive await SendPingAsync(Http2PingFrameFlags.NONE); @@ -1466,13 +1704,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await InitializeConnectionAsync(_noopApplication); 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( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 0, + expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(frameType: 42, streamId: 1, headersStreamId: 1)); } [Fact] - public async Task ConnectionError_AbortsAllStreams() + public async Task ConnectionErrorAbortsAllStreams() { await InitializeConnectionAsync(_waitForAbortApplication); @@ -1484,7 +1726,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests // Cause a connection error by sending an invalid frame await SendDataAsync(0, _noData, endStream: false); - await WaitForConnectionErrorAsync(expectedLastStreamId: 5, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); + await WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: false, + expectedLastStreamId: 5, + expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, + expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdZero(Http2FrameType.DATA)); await WaitForAllStreamsAsync(); Assert.Contains(1, _abortedStreamIds); @@ -1492,6 +1738,32 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests 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) { _connectionTask = _connection.ProcessAsync(new DummyApplication(application)); @@ -1931,11 +2203,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests return SendAsync(frame.Raw); } - private Task SendUnknownFrameTypeAsync(int streamId) + private Task SendUnknownFrameTypeAsync(int streamId, int frameType) { var frame = new Http2Frame(); frame.StreamId = streamId; - frame.Type = (Http2FrameType)42; + frame.Type = (Http2FrameType)frameType; frame.Length = 0; return SendAsync(frame.Raw); } @@ -1996,10 +2268,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests private Task WaitForConnectionStopAsync(int expectedLastStreamId, bool ignoreNonGoAwayFrames) { - return WaitForConnectionErrorAsync(expectedLastStreamId, Http2ErrorCode.NO_ERROR, ignoreNonGoAwayFrames); + return WaitForConnectionErrorAsync(ignoreNonGoAwayFrames, expectedLastStreamId, Http2ErrorCode.NO_ERROR, expectedErrorMessage: null); } - private async Task WaitForConnectionErrorAsync(int expectedLastStreamId, Http2ErrorCode expectedErrorCode, bool ignoreNonGoAwayFrames) + private async Task WaitForConnectionErrorAsync(bool ignoreNonGoAwayFrames, int expectedLastStreamId, Http2ErrorCode expectedErrorCode, string expectedErrorMessage) + where TException : Exception { var frame = await ReceiveFrameAsync(); @@ -2018,11 +2291,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests Assert.Equal(expectedLastStreamId, frame.GoAwayLastStreamId); 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; _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(); @@ -2039,6 +2318,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests Assert.Equal(0, frame.Flags); Assert.Equal(expectedStreamId, frame.StreamId); 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> expectedHeaders)