// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Buffers; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.IO.Pipelines; using System.Linq; 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; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Testing; using Xunit; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { public class Http2ConnectionTests : IDisposable, IHttpHeadersHandler { private static readonly string _largeHeaderValue = new string('a', HPackDecoder.MaxStringOctets); private static readonly IEnumerable> _postRequestHeaders = new[] { new KeyValuePair(":method", "POST"), new KeyValuePair(":path", "/"), new KeyValuePair(":scheme", "http"), }; private static readonly IEnumerable> _expectContinueRequestHeaders = new[] { new KeyValuePair(":method", "POST"), new KeyValuePair(":path", "/"), new KeyValuePair(":authority", "127.0.0.1"), new KeyValuePair(":scheme", "https"), new KeyValuePair("expect", "100-continue"), }; private static readonly IEnumerable> _browserRequestHeaders = new[] { new KeyValuePair(":method", "GET"), new KeyValuePair(":path", "/"), new KeyValuePair(":scheme", "http"), new KeyValuePair("user-agent", "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:54.0) Gecko/20100101 Firefox/54.0"), new KeyValuePair("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"), new KeyValuePair("accept-language", "en-US,en;q=0.5"), new KeyValuePair("accept-encoding", "gzip, deflate, br"), new KeyValuePair("upgrade-insecure-requests", "1"), }; private static readonly IEnumerable> _requestTrailers = new[] { new KeyValuePair("trailer-one", "1"), new KeyValuePair("trailer-two", "2"), }; private static readonly IEnumerable> _oneContinuationRequestHeaders = new[] { new KeyValuePair(":method", "GET"), new KeyValuePair(":path", "/"), new KeyValuePair(":scheme", "http"), new KeyValuePair("a", _largeHeaderValue), new KeyValuePair("b", _largeHeaderValue), new KeyValuePair("c", _largeHeaderValue), new KeyValuePair("d", _largeHeaderValue) }; private static readonly IEnumerable> _twoContinuationsRequestHeaders = new[] { new KeyValuePair(":method", "GET"), new KeyValuePair(":path", "/"), new KeyValuePair(":scheme", "http"), new KeyValuePair("a", _largeHeaderValue), new KeyValuePair("b", _largeHeaderValue), new KeyValuePair("c", _largeHeaderValue), new KeyValuePair("d", _largeHeaderValue), new KeyValuePair("e", _largeHeaderValue), new KeyValuePair("f", _largeHeaderValue), new KeyValuePair("g", _largeHeaderValue), new KeyValuePair("h", _largeHeaderValue) }; private static readonly byte[] _helloBytes = Encoding.ASCII.GetBytes("hello"); private static readonly byte[] _worldBytes = Encoding.ASCII.GetBytes("world"); private static readonly byte[] _helloWorldBytes = Encoding.ASCII.GetBytes("hello, world"); private static readonly byte[] _noData = new byte[0]; private static readonly byte[] _maxData = Encoding.ASCII.GetBytes(new string('a', Http2Frame.MinAllowedMaxFrameSize)); private readonly MemoryPool _memoryPool = KestrelMemoryPool.Create(); private readonly DuplexPipe.DuplexPipePair _pair; private readonly TestApplicationErrorLogger _logger; private readonly Http2ConnectionContext _connectionContext; private readonly Http2Connection _connection; private readonly Http2PeerSettings _clientSettings = new Http2PeerSettings(); private readonly HPackEncoder _hpackEncoder = new HPackEncoder(); private readonly HPackDecoder _hpackDecoder; private readonly ConcurrentDictionary> _runningStreams = new ConcurrentDictionary>(); private readonly Dictionary _receivedHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly Dictionary _decodedHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly HashSet _abortedStreamIds = new HashSet(); private readonly object _abortedStreamIdsLock = new object(); private readonly RequestDelegate _noopApplication; private readonly RequestDelegate _readHeadersApplication; private readonly RequestDelegate _readTrailersApplication; private readonly RequestDelegate _bufferingApplication; private readonly RequestDelegate _echoApplication; private readonly RequestDelegate _echoWaitForAbortApplication; private readonly RequestDelegate _largeHeadersApplication; private readonly RequestDelegate _waitForAbortApplication; private readonly RequestDelegate _waitForAbortFlushingApplication; private Task _connectionTask; public Http2ConnectionTests() { var inlineSchedulingPipeOptions = new PipeOptions( pool: _memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false ); _pair = DuplexPipe.CreateConnectionPair(inlineSchedulingPipeOptions, inlineSchedulingPipeOptions); _noopApplication = context => Task.CompletedTask; _readHeadersApplication = context => { foreach (var header in context.Request.Headers) { _receivedHeaders[header.Key] = header.Value.ToString(); } return Task.CompletedTask; }; _readTrailersApplication = async context => { using (var ms = new MemoryStream()) { // Consuming the entire request body guarantees trailers will be available await context.Request.Body.CopyToAsync(ms); } foreach (var header in context.Request.Headers) { _receivedHeaders[header.Key] = header.Value.ToString(); } }; _bufferingApplication = async context => { var data = new List(); var buffer = new byte[1024]; var received = 0; while ((received = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length)) > 0) { data.AddRange(new ArraySegment(buffer, 0, received)); } await context.Response.Body.WriteAsync(data.ToArray(), 0, data.Count); }; _echoApplication = async context => { var buffer = new byte[Http2Frame.MinAllowedMaxFrameSize]; var received = 0; while ((received = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length)) > 0) { await context.Response.Body.WriteAsync(buffer, 0, received); } }; _echoWaitForAbortApplication = async context => { var buffer = new byte[Http2Frame.MinAllowedMaxFrameSize]; var received = 0; while ((received = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length)) > 0) { await context.Response.Body.WriteAsync(buffer, 0, received); } var sem = new SemaphoreSlim(0); context.RequestAborted.Register(() => { sem.Release(); }); await sem.WaitAsync().TimeoutAfter(TestConstants.DefaultTimeout); }; _largeHeadersApplication = context => { foreach (var name in new[] { "a", "b", "c", "d", "e", "f", "g", "h" }) { context.Response.Headers[name] = _largeHeaderValue; } return Task.CompletedTask; }; _waitForAbortApplication = async context => { var streamIdFeature = context.Features.Get(); var sem = new SemaphoreSlim(0); context.RequestAborted.Register(() => { lock (_abortedStreamIdsLock) { _abortedStreamIds.Add(streamIdFeature.StreamId); } sem.Release(); }); await sem.WaitAsync().TimeoutAfter(TestConstants.DefaultTimeout); _runningStreams[streamIdFeature.StreamId].TrySetResult(null); }; _waitForAbortFlushingApplication = async context => { var streamIdFeature = context.Features.Get(); var sem = new SemaphoreSlim(0); context.RequestAborted.Register(() => { lock (_abortedStreamIdsLock) { _abortedStreamIds.Add(streamIdFeature.StreamId); } sem.Release(); }); await sem.WaitAsync().TimeoutAfter(TestConstants.DefaultTimeout); await context.Response.Body.FlushAsync(); _runningStreams[streamIdFeature.StreamId].TrySetResult(null); }; _hpackDecoder = new HPackDecoder((int)_clientSettings.HeaderTableSize); _logger = new TestApplicationErrorLogger(); _connectionContext = new Http2ConnectionContext { ServiceContext = new TestServiceContext() { Log = new TestKestrelTrace(_logger) }, MemoryPool = _memoryPool, Application = _pair.Application, Transport = _pair.Transport }; _connection = new Http2Connection(_connectionContext); } public void Dispose() { _memoryPool.Dispose(); } void IHttpHeadersHandler.OnHeader(Span name, Span value) { _decodedHeaders[name.GetAsciiStringNonNullCharacters()] = value.GetAsciiStringNonNullCharacters(); } [Fact] public async Task DATA_Received_ReadByStream() { await InitializeConnectionAsync(_echoApplication); await StartStreamAsync(1, _browserRequestHeaders, endStream: false); await SendDataAsync(1, _helloWorldBytes, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, withLength: 37, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); var dataFrame = await ExpectAsync(Http2FrameType.DATA, withLength: 12, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, withLength: 0, withFlags: (byte)Http2DataFrameFlags.END_STREAM, withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); Assert.Equal(dataFrame.DataPayload, _helloWorldBytes); } [Fact] public async Task DATA_Received_MaxSize_ReadByStream() { await InitializeConnectionAsync(_echoApplication); await StartStreamAsync(1, _browserRequestHeaders, endStream: false); await SendDataAsync(1, _maxData, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, withLength: 37, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); var dataFrame = await ExpectAsync(Http2FrameType.DATA, withLength: _maxData.Length, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, withLength: 0, withFlags: (byte)Http2DataFrameFlags.END_STREAM, withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); Assert.Equal(dataFrame.DataPayload, _maxData); } [Fact] public async Task DATA_Received_Multiple_ReadByStream() { await InitializeConnectionAsync(_bufferingApplication); await StartStreamAsync(1, _browserRequestHeaders, endStream: false); for (var i = 0; i < _helloWorldBytes.Length; i++) { await SendDataAsync(1, new ArraySegment(_helloWorldBytes, i, 1), endStream: false); } await SendDataAsync(1, _noData, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, withLength: 37, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); var dataFrame = await ExpectAsync(Http2FrameType.DATA, withLength: 12, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, withLength: 0, withFlags: (byte)Http2DataFrameFlags.END_STREAM, withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); Assert.Equal(dataFrame.DataPayload, _helloWorldBytes); } [Fact] public async Task DATA_Received_Multiplexed_ReadByStreams() { await InitializeConnectionAsync(_echoApplication); await StartStreamAsync(1, _browserRequestHeaders, endStream: false); await StartStreamAsync(3, _browserRequestHeaders, endStream: false); await SendDataAsync(1, _helloBytes, endStream: false); await ExpectAsync(Http2FrameType.HEADERS, withLength: 37, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); var stream1DataFrame1 = await ExpectAsync(Http2FrameType.DATA, withLength: 5, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 1); await SendDataAsync(3, _helloBytes, endStream: false); await ExpectAsync(Http2FrameType.HEADERS, withLength: 37, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 3); var stream3DataFrame1 = await ExpectAsync(Http2FrameType.DATA, withLength: 5, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 3); await SendDataAsync(3, _worldBytes, endStream: false); var stream3DataFrame2 = await ExpectAsync(Http2FrameType.DATA, withLength: 5, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 3); await SendDataAsync(1, _worldBytes, endStream: false); var stream1DataFrame2 = await ExpectAsync(Http2FrameType.DATA, withLength: 5, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 1); await SendDataAsync(1, _noData, endStream: true); await ExpectAsync(Http2FrameType.DATA, withLength: 0, withFlags: (byte)Http2DataFrameFlags.END_STREAM, withStreamId: 1); await SendDataAsync(3, _noData, endStream: true); await ExpectAsync(Http2FrameType.DATA, withLength: 0, withFlags: (byte)Http2DataFrameFlags.END_STREAM, withStreamId: 3); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); Assert.Equal(stream1DataFrame1.DataPayload, _helloBytes); Assert.Equal(stream1DataFrame2.DataPayload, _worldBytes); Assert.Equal(stream3DataFrame1.DataPayload, _helloBytes); Assert.Equal(stream3DataFrame2.DataPayload, _worldBytes); } [Theory] [InlineData(0)] [InlineData(1)] [InlineData(255)] public async Task DATA_Received_WithPadding_ReadByStream(byte padLength) { await InitializeConnectionAsync(_echoApplication); await StartStreamAsync(1, _browserRequestHeaders, endStream: false); await SendDataWithPaddingAsync(1, _helloWorldBytes, padLength, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, withLength: 37, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); var dataFrame = await ExpectAsync(Http2FrameType.DATA, withLength: 12, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, withLength: 0, withFlags: (byte)Http2DataFrameFlags.END_STREAM, withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); Assert.Equal(dataFrame.DataPayload, _helloWorldBytes); } [Fact] public async Task DATA_Received_StreamIdZero_ConnectionError() { await InitializeConnectionAsync(_noopApplication); await SendDataAsync(0, _noData, endStream: false); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdZero(Http2FrameType.DATA)); } [Fact] public async Task DATA_Received_StreamIdEven_ConnectionError() { await InitializeConnectionAsync(_noopApplication); await SendDataAsync(2, _noData, endStream: false); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdEven(Http2FrameType.DATA, streamId: 2)); } [Fact] public async Task DATA_Received_PaddingEqualToFramePayloadLength_ConnectionError() { await InitializeConnectionAsync(_echoApplication); await StartStreamAsync(1, _browserRequestHeaders, endStream: false); await SendInvalidDataFrameAsync(1, frameLength: 5, padLength: 5); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: true, expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorPaddingTooLong(Http2FrameType.DATA)); } [Fact] public async Task DATA_Received_PaddingGreaterThanFramePayloadLength_ConnectionError() { await InitializeConnectionAsync(_echoApplication); await StartStreamAsync(1, _browserRequestHeaders, endStream: false); await SendInvalidDataFrameAsync(1, frameLength: 5, padLength: 6); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: true, expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorPaddingTooLong(Http2FrameType.DATA)); } [Fact] public async Task DATA_Received_FrameLengthZeroPaddingZero_ConnectionError() { await InitializeConnectionAsync(_echoApplication); await StartStreamAsync(1, _browserRequestHeaders, endStream: false); await SendInvalidDataFrameAsync(1, frameLength: 0, padLength: 0); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: true, expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorPaddingTooLong(Http2FrameType.DATA)); } [Fact] public async Task DATA_Received_InterleavedWithHeaders_ConnectionError() { await InitializeConnectionAsync(_noopApplication); await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders); await SendDataAsync(1, _helloWorldBytes, endStream: true); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.DATA, streamId: 1, headersStreamId: 1)); } [Fact] public async Task DATA_Received_StreamIdle_ConnectionError() { await InitializeConnectionAsync(_noopApplication); await SendDataAsync(1, _helloWorldBytes, endStream: false); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdle(Http2FrameType.DATA, streamId: 1)); } [Fact] public async Task DATA_Received_StreamHalfClosedRemote_ConnectionError() { // Use _waitForAbortApplication so we know the stream will still be active when we send the illegal DATA frame await InitializeConnectionAsync(_waitForAbortApplication); await StartStreamAsync(1, _postRequestHeaders, endStream: true); await SendDataAsync(1, _helloWorldBytes, endStream: false); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.DATA, streamId: 1)); } [Fact] public async Task DATA_Received_StreamClosed_ConnectionError() { await InitializeConnectionAsync(_noopApplication); await StartStreamAsync(1, _postRequestHeaders, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, withLength: 55, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, withLength: 0, withFlags: (byte)Http2DataFrameFlags.END_STREAM, withStreamId: 1); await SendDataAsync(1, _helloWorldBytes, endStream: false); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.DATA, streamId: 1)); } [Fact] public async Task DATA_Received_StreamClosedImplicitly_ConnectionError() { // http://httpwg.org/specs/rfc7540.html#rfc.section.5.1.1 // // The first use of a new stream identifier implicitly closes all streams in the "idle" state that // might have been initiated by that peer with a lower-valued stream identifier. For example, if a // client sends a HEADERS frame on stream 7 without ever sending a frame on stream 5, then stream 5 // transitions to the "closed" state when the first frame for stream 7 is sent or received. await InitializeConnectionAsync(_noopApplication); await StartStreamAsync(3, _browserRequestHeaders, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, withLength: 55, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 3); await ExpectAsync(Http2FrameType.DATA, withLength: 0, withFlags: (byte)Http2DataFrameFlags.END_STREAM, withStreamId: 3); await SendDataAsync(1, _helloWorldBytes, endStream: true); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 3, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.DATA, streamId: 1)); } [Fact] public async Task HEADERS_Received_Decoded() { await InitializeConnectionAsync(_readHeadersApplication); await StartStreamAsync(1, _browserRequestHeaders, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, withLength: 55, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, withLength: 0, withFlags: (byte)Http2DataFrameFlags.END_STREAM, withStreamId: 1); VerifyDecodedRequestHeaders(_browserRequestHeaders); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); } [Theory] [InlineData(0)] [InlineData(1)] [InlineData(255)] public async Task HEADERS_Received_WithPadding_Decoded(byte padLength) { await InitializeConnectionAsync(_readHeadersApplication); await SendHeadersWithPaddingAsync(1, _browserRequestHeaders, padLength, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, withLength: 55, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, withLength: 0, withFlags: (byte)Http2DataFrameFlags.END_STREAM, withStreamId: 1); VerifyDecodedRequestHeaders(_browserRequestHeaders); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); } [Fact] public async Task HEADERS_Received_WithPriority_Decoded() { await InitializeConnectionAsync(_readHeadersApplication); await SendHeadersWithPriorityAsync(1, _browserRequestHeaders, priority: 42, streamDependency: 0, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, withLength: 55, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, withLength: 0, withFlags: (byte)Http2DataFrameFlags.END_STREAM, withStreamId: 1); VerifyDecodedRequestHeaders(_browserRequestHeaders); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); } [Theory] [InlineData(0)] [InlineData(1)] [InlineData(255)] public async Task HEADERS_Received_WithPriorityAndPadding_Decoded(byte padLength) { await InitializeConnectionAsync(_readHeadersApplication); await SendHeadersWithPaddingAndPriorityAsync(1, _browserRequestHeaders, padLength, priority: 42, streamDependency: 0, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, withLength: 55, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, withLength: 0, withFlags: (byte)Http2DataFrameFlags.END_STREAM, withStreamId: 1); VerifyDecodedRequestHeaders(_browserRequestHeaders); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); } [Theory] [InlineData(true)] [InlineData(false)] public async Task HEADERS_Received_WithTrailers_Decoded(bool sendData) { await InitializeConnectionAsync(_readTrailersApplication); await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, _browserRequestHeaders); // Initialize another stream with a higher stream ID, and verify that after trailers are // decoded by the other stream, the highest opened stream ID is not reset to the lower ID // (the highest opened stream ID is sent by the server in the GOAWAY frame when shutting // down the connection). await SendHeadersAsync(3, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, _browserRequestHeaders); // The second stream should end first, since the first one is waiting for the request body. await ExpectAsync(Http2FrameType.HEADERS, withLength: 55, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 3); await ExpectAsync(Http2FrameType.DATA, withLength: 0, withFlags: (byte)Http2DataFrameFlags.END_STREAM, withStreamId: 3); if (sendData) { await SendDataAsync(1, _helloBytes, endStream: false); } await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, _requestTrailers); await ExpectAsync(Http2FrameType.HEADERS, withLength: 55, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, withLength: 0, withFlags: (byte)Http2DataFrameFlags.END_STREAM, withStreamId: 1); VerifyDecodedRequestHeaders(_browserRequestHeaders.Concat(_requestTrailers)); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); } [Fact] public async Task HEADERS_Received_ContainsExpect100Continue_100ContinueSent() { await InitializeConnectionAsync(_echoApplication); await StartStreamAsync(1, _expectContinueRequestHeaders, false); var frame = await ExpectAsync(Http2FrameType.HEADERS, withLength: 5, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); await SendDataAsync(1, _helloBytes, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, withLength: 37, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, withLength: 5, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, withLength: 0, withFlags: (byte)Http2DataFrameFlags.END_STREAM, withStreamId: 1); Assert.Equal(new byte[] { 0x08, 0x03, (byte)'1', (byte)'0', (byte)'0' }, frame.HeadersPayload.ToArray()); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); } [Fact] public async Task HEADERS_Received_StreamIdZero_ConnectionError() { await InitializeConnectionAsync(_noopApplication); await StartStreamAsync(0, _browserRequestHeaders, endStream: true); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdZero(Http2FrameType.HEADERS)); } [Fact] public async Task HEADERS_Received_StreamIdEven_ConnectionError() { await InitializeConnectionAsync(_noopApplication); await StartStreamAsync(2, _browserRequestHeaders, endStream: true); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdEven(Http2FrameType.HEADERS, streamId: 2)); } [Fact] public async Task HEADERS_Received_StreamClosed_ConnectionError() { await InitializeConnectionAsync(_noopApplication); await StartStreamAsync(1, _browserRequestHeaders, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, withLength: 55, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, withLength: 0, withFlags: (byte)Http2DataFrameFlags.END_STREAM, withStreamId: 1); // Try to re-use the stream ID (http://httpwg.org/specs/rfc7540.html#rfc.section.5.1.1) await StartStreamAsync(1, _browserRequestHeaders, endStream: true); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1)); } [Fact] public async Task HEADERS_Received_StreamHalfClosedRemote_ConnectionError() { // Use _waitForAbortApplication so we know the stream will still be active when we send the illegal DATA frame await InitializeConnectionAsync(_waitForAbortApplication); await StartStreamAsync(1, _browserRequestHeaders, endStream: true); await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.HEADERS, streamId: 1)); } [Fact] public async Task HEADERS_Received_StreamClosedImplicitly_ConnectionError() { await InitializeConnectionAsync(_noopApplication); await StartStreamAsync(3, _browserRequestHeaders, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, withLength: 55, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 3); await ExpectAsync(Http2FrameType.DATA, withLength: 0, withFlags: (byte)Http2DataFrameFlags.END_STREAM, withStreamId: 3); // Stream 1 was implicitly closed by opening stream 3 before (http://httpwg.org/specs/rfc7540.html#rfc.section.5.1.1) await StartStreamAsync(1, _browserRequestHeaders, endStream: true); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 3, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1)); } [Theory] [InlineData(0)] [InlineData(1)] [InlineData(255)] public async Task HEADERS_Received_PaddingEqualToFramePayloadLength_ConnectionError(byte padLength) { await InitializeConnectionAsync(_noopApplication); await SendInvalidHeadersFrameAsync(1, frameLength: padLength, padLength: padLength); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: true, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorPaddingTooLong(Http2FrameType.HEADERS)); } [Theory] [InlineData(0, 1)] [InlineData(1, 2)] [InlineData(254, 255)] public async Task HEADERS_Received_PaddingGreaterThanFramePayloadLength_ConnectionError(int frameLength, byte padLength) { await InitializeConnectionAsync(_noopApplication); await SendInvalidHeadersFrameAsync(1, frameLength, padLength); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: true, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorPaddingTooLong(Http2FrameType.HEADERS)); } [Fact] public async Task HEADERS_Received_InterleavedWithHeaders_ConnectionError() { await InitializeConnectionAsync(_noopApplication); await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders); await SendHeadersAsync(3, Http2HeadersFrameFlags.NONE, _browserRequestHeaders); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.HEADERS, streamId: 3, headersStreamId: 1)); } [Fact] public async Task HEADERS_Received_WithPriority_StreamDependencyOnSelf_ConnectionError() { await InitializeConnectionAsync(_readHeadersApplication); await SendHeadersWithPriorityAsync(1, _browserRequestHeaders, priority: 42, streamDependency: 1, endStream: true); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamSelfDependency(Http2FrameType.HEADERS, streamId: 1)); } [Fact] public async Task HEADERS_Received_IncompleteHeaderBlock_ConnectionError() { await InitializeConnectionAsync(_noopApplication); await SendIncompleteHeadersFrameAsync(streamId: 1); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.COMPRESSION_ERROR, expectedErrorMessage: CoreStrings.HPackErrorIncompleteHeaderBlock); } [Theory] [MemberData(nameof(IllegalTrailerData))] public async Task HEADERS_Received_WithTrailers_ContainsIllegalTrailer_ConnectionError(byte[] trailers, string expectedErrorMessage) { await InitializeConnectionAsync(_readTrailersApplication); await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, _browserRequestHeaders); await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, trailers); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: expectedErrorMessage); } [Theory] [InlineData(Http2HeadersFrameFlags.NONE)] [InlineData(Http2HeadersFrameFlags.END_HEADERS)] public async Task HEADERS_Received_WithTrailers_EndStreamNotSet_ConnectionError(Http2HeadersFrameFlags flags) { await InitializeConnectionAsync(_readTrailersApplication); await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, _browserRequestHeaders); await SendHeadersAsync(1, flags, _requestTrailers); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorHeadersWithTrailersNoEndStream); } [Theory] [MemberData(nameof(UpperCaseHeaderNameData))] public async Task HEADERS_Received_HeaderNameContainsUpperCaseCharacter_StreamError(byte[] headerBlock) { await InitializeConnectionAsync(_noopApplication); await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, headerBlock); 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( ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1)); } [Fact] public Task HEADERS_Received_HeaderBlockContainsUnknownPseudoHeaderField_StreamError() { var headers = new[] { new KeyValuePair(":method", "GET"), new KeyValuePair(":path", "/"), new KeyValuePair(":scheme", "http"), new KeyValuePair(":unknown", "0"), }; return HEADERS_Received_InvalidHeaderFields_StreamError(headers, expectedErrorMessage: CoreStrings.Http2ErrorUnknownPseudoHeaderField); } [Fact] public Task HEADERS_Received_HeaderBlockContainsResponsePseudoHeaderField_StreamError() { var headers = new[] { new KeyValuePair(":method", "GET"), new KeyValuePair(":path", "/"), new KeyValuePair(":scheme", "http"), new KeyValuePair(":status", "200"), }; 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, expectedErrorMessage: CoreStrings.Http2ErrorDuplicatePseudoHeaderField); } [Theory] [MemberData(nameof(MissingPseudoHeaderFieldData))] public Task HEADERS_Received_HeaderBlockDoesNotContainMandatoryPseudoHeaderField_StreamError(IEnumerable> headers) { return HEADERS_Received_InvalidHeaderFields_StreamError(headers, expectedErrorMessage: CoreStrings.Http2ErrorMissingMandatoryPseudoHeaderFields); } [Theory] [MemberData(nameof(ConnectMissingPseudoHeaderFieldData))] public async Task HEADERS_Received_HeaderBlockDoesNotContainMandatoryPseudoHeaderField_MethodIsCONNECT_NoError(IEnumerable> 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); } [Theory] [MemberData(nameof(PseudoHeaderFieldAfterRegularHeadersData))] public Task HEADERS_Received_HeaderBlockContainsPseudoHeaderFieldAfterRegularHeaders_StreamError(IEnumerable> headers) { return HEADERS_Received_InvalidHeaderFields_StreamError(headers, expectedErrorMessage: CoreStrings.Http2ErrorPseudoHeaderFieldAfterRegularHeaders); } private async Task HEADERS_Received_InvalidHeaderFields_StreamError(IEnumerable> headers, string expectedErrorMessage) { await InitializeConnectionAsync(_noopApplication); await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, headers); 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( ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1)); } [Fact] public Task HEADERS_Received_HeaderBlockContainsConnectionHeader_StreamError() { var headers = new[] { new KeyValuePair(":method", "GET"), new KeyValuePair(":path", "/"), new KeyValuePair(":scheme", "http"), new KeyValuePair("connection", "keep-alive") }; return HEADERS_Received_InvalidHeaderFields_StreamError(headers, CoreStrings.Http2ErrorConnectionSpecificHeaderField); } [Fact] public Task HEADERS_Received_HeaderBlockContainsTEHeader_ValueIsNotTrailers_StreamError() { var headers = new[] { new KeyValuePair(":method", "GET"), new KeyValuePair(":path", "/"), new KeyValuePair(":scheme", "http"), new KeyValuePair("te", "trailers, deflate") }; return HEADERS_Received_InvalidHeaderFields_StreamError(headers, CoreStrings.Http2ErrorConnectionSpecificHeaderField); } [Fact] 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") }; 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] public async Task PRIORITY_Received_StreamIdZero_ConnectionError() { await InitializeConnectionAsync(_noopApplication); await SendPriorityAsync(0); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdZero(Http2FrameType.PRIORITY)); } [Fact] public async Task PRIORITY_Received_StreamIdEven_ConnectionError() { await InitializeConnectionAsync(_noopApplication); await SendPriorityAsync(2); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdEven(Http2FrameType.PRIORITY, streamId: 2)); } [Theory] [InlineData(4)] [InlineData(6)] public async Task PRIORITY_Received_LengthNotFive_ConnectionError(int length) { await InitializeConnectionAsync(_noopApplication); await SendInvalidPriorityFrameAsync(1, length); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.PRIORITY, expectedLength: 5)); } [Fact] public async Task PRIORITY_Received_InterleavedWithHeaders_ConnectionError() { await InitializeConnectionAsync(_noopApplication); await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders); await SendPriorityAsync(1); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.PRIORITY, streamId: 1, headersStreamId: 1)); } [Fact] public async Task PRIORITY_Received_StreamDependencyOnSelf_ConnectionError() { await InitializeConnectionAsync(_readHeadersApplication); await SendPriorityAsync(1, streamDependency: 1); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamSelfDependency(Http2FrameType.PRIORITY, 1)); } [Fact] public async Task RST_STREAM_Received_AbortsStream() { await InitializeConnectionAsync(_waitForAbortApplication); await StartStreamAsync(1, _browserRequestHeaders, endStream: true); await SendRstStreamAsync(1); // No data is received from the stream since it was aborted before writing anything await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); await WaitForAllStreamsAsync(); Assert.Contains(1, _abortedStreamIds); } [Fact] public async Task RST_STREAM_Received_AbortsStream_FlushedDataIsSent() { await InitializeConnectionAsync(_waitForAbortFlushingApplication); await StartStreamAsync(1, _browserRequestHeaders, endStream: true); await SendRstStreamAsync(1); await ExpectAsync(Http2FrameType.HEADERS, withLength: 37, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); // No END_STREAM DATA frame is received since the stream was aborted await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); Assert.Contains(1, _abortedStreamIds); } [Fact] public async Task RST_STREAM_Received_StreamIdZero_ConnectionError() { await InitializeConnectionAsync(_noopApplication); await SendRstStreamAsync(0); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdZero(Http2FrameType.RST_STREAM)); } [Fact] public async Task RST_STREAM_Received_StreamIdEven_ConnectionError() { await InitializeConnectionAsync(_noopApplication); await SendRstStreamAsync(2); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdEven(Http2FrameType.RST_STREAM, streamId: 2)); } [Fact] public async Task RST_STREAM_Received_StreamIdle_ConnectionError() { await InitializeConnectionAsync(_noopApplication); await SendRstStreamAsync(1); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdle(Http2FrameType.RST_STREAM, streamId: 1)); } [Theory] [InlineData(3)] [InlineData(5)] public async Task RST_STREAM_Received_LengthNotFour_ConnectionError(int length) { await InitializeConnectionAsync(_noopApplication); // Start stream 1 so it's legal to send it RST_STREAM frames await StartStreamAsync(1, _browserRequestHeaders, endStream: true); await SendInvalidRstStreamFrameAsync(1, length); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: true, expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.RST_STREAM, expectedLength: 4)); } [Fact] public async Task RST_STREAM_Received_InterleavedWithHeaders_ConnectionError() { await InitializeConnectionAsync(_noopApplication); await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders); await SendRstStreamAsync(1); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.RST_STREAM, streamId: 1, headersStreamId: 1)); } [Fact] public async Task SETTINGS_Received_Sends_ACK() { await InitializeConnectionAsync(_noopApplication); await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false); } [Fact] public async Task SETTINGS_Received_StreamIdNotZero_ConnectionError() { await InitializeConnectionAsync(_noopApplication); await SendSettingsWithInvalidStreamIdAsync(1); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdNotZero(Http2FrameType.SETTINGS)); } [Theory] [InlineData(Http2SettingsParameter.SETTINGS_ENABLE_PUSH, 2, Http2ErrorCode.PROTOCOL_ERROR)] [InlineData(Http2SettingsParameter.SETTINGS_ENABLE_PUSH, uint.MaxValue, Http2ErrorCode.PROTOCOL_ERROR)] [InlineData(Http2SettingsParameter.SETTINGS_INITIAL_WINDOW_SIZE, (uint)int.MaxValue + 1, Http2ErrorCode.FLOW_CONTROL_ERROR)] [InlineData(Http2SettingsParameter.SETTINGS_INITIAL_WINDOW_SIZE, uint.MaxValue, Http2ErrorCode.FLOW_CONTROL_ERROR)] [InlineData(Http2SettingsParameter.SETTINGS_MAX_FRAME_SIZE, 0, Http2ErrorCode.PROTOCOL_ERROR)] [InlineData(Http2SettingsParameter.SETTINGS_MAX_FRAME_SIZE, 1, Http2ErrorCode.PROTOCOL_ERROR)] [InlineData(Http2SettingsParameter.SETTINGS_MAX_FRAME_SIZE, 16 * 1024 - 1, Http2ErrorCode.PROTOCOL_ERROR)] [InlineData(Http2SettingsParameter.SETTINGS_MAX_FRAME_SIZE, 16 * 1024 * 1024, Http2ErrorCode.PROTOCOL_ERROR)] [InlineData(Http2SettingsParameter.SETTINGS_MAX_FRAME_SIZE, uint.MaxValue, Http2ErrorCode.PROTOCOL_ERROR)] public async Task SETTINGS_Received_InvalidParameterValue_ConnectionError(Http2SettingsParameter parameter, uint value, Http2ErrorCode expectedErrorCode) { await InitializeConnectionAsync(_noopApplication); await SendSettingsWithInvalidParameterValueAsync(parameter, value); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 0, expectedErrorCode: expectedErrorCode, expectedErrorMessage: CoreStrings.FormatHttp2ErrorSettingsParameterOutOfRange(parameter)); } [Fact] public async Task SETTINGS_Received_InterleavedWithHeaders_ConnectionError() { await InitializeConnectionAsync(_noopApplication); await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders); await SendSettingsAsync(); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.SETTINGS, streamId: 0, headersStreamId: 1)); } [Theory] [InlineData(1)] [InlineData(16 * 1024 - 9)] // Min. max. frame size minus header length public async Task SETTINGS_Received_WithACK_LengthNotZero_ConnectionError(int length) { await InitializeConnectionAsync(_noopApplication); await SendSettingsAckWithInvalidLengthAsync(length); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorSettingsAckLengthNotZero); } [Theory] [InlineData(1)] [InlineData(5)] [InlineData(7)] [InlineData(34)] [InlineData(37)] public async Task SETTINGS_Received_LengthNotMultipleOfSix_ConnectionError(int length) { await InitializeConnectionAsync(_noopApplication); await SendSettingsWithInvalidLengthAsync(length); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorSettingsLengthNotMultipleOfSix); } [Fact] public async Task PUSH_PROMISE_Received_ConnectionError() { await InitializeConnectionAsync(_noopApplication); await SendPushPromiseFrameAsync(); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorPushPromiseReceived); } [Fact] public async Task PING_Received_SendsACK() { await InitializeConnectionAsync(_noopApplication); await SendPingAsync(Http2PingFrameFlags.NONE); await ExpectAsync(Http2FrameType.PING, withLength: 8, withFlags: (byte)Http2PingFrameFlags.ACK, withStreamId: 0); await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false); } [Fact] public async Task PING_Received_WithACK_DoesNotSendACK() { await InitializeConnectionAsync(_noopApplication); await SendPingAsync(Http2PingFrameFlags.ACK); await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false); } [Fact] public async Task PING_Received_InterleavedWithHeaders_ConnectionError() { await InitializeConnectionAsync(_noopApplication); await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders); await SendPingAsync(Http2PingFrameFlags.NONE); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.PING, streamId: 0, headersStreamId: 1)); } [Fact] public async Task PING_Received_StreamIdNotZero_ConnectionError() { await InitializeConnectionAsync(_noopApplication); await SendPingWithInvalidStreamIdAsync(streamId: 1); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdNotZero(Http2FrameType.PING)); } [Theory] [InlineData(0)] [InlineData(1)] [InlineData(7)] [InlineData(9)] public async Task PING_Received_LengthNotEight_ConnectionError(int length) { await InitializeConnectionAsync(_noopApplication); await SendPingWithInvalidLengthAsync(length); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.PING, expectedLength: 8)); } [Fact] public async Task GOAWAY_Received_ConnectionStops() { await InitializeConnectionAsync(_noopApplication); await SendGoAwayAsync(); await WaitForConnectionStopAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false); } [Fact] public async Task GOAWAY_Received_AbortsAllStreams() { await InitializeConnectionAsync(_waitForAbortApplication); // Start some streams await StartStreamAsync(1, _browserRequestHeaders, endStream: true); await StartStreamAsync(3, _browserRequestHeaders, endStream: true); await StartStreamAsync(5, _browserRequestHeaders, endStream: true); await SendGoAwayAsync(); await WaitForConnectionStopAsync(expectedLastStreamId: 5, ignoreNonGoAwayFrames: true); await WaitForAllStreamsAsync(); Assert.Contains(1, _abortedStreamIds); Assert.Contains(3, _abortedStreamIds); Assert.Contains(5, _abortedStreamIds); } [Fact] public async Task GOAWAY_Received_StreamIdNotZero_ConnectionError() { await InitializeConnectionAsync(_noopApplication); await SendInvalidGoAwayFrameAsync(); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdNotZero(Http2FrameType.GOAWAY)); } [Fact] public async Task GOAWAY_Received_InterleavedWithHeaders_ConnectionError() { await InitializeConnectionAsync(_noopApplication); await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders); await SendGoAwayAsync(); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.GOAWAY, streamId: 0, headersStreamId: 1)); } [Fact] public async Task WINDOW_UPDATE_Received_StreamIdEven_ConnectionError() { await InitializeConnectionAsync(_noopApplication); await SendWindowUpdateAsync(2, sizeIncrement: 42); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdEven(Http2FrameType.WINDOW_UPDATE, streamId: 2)); } [Fact] public async Task WINDOW_UPDATE_Received_InterleavedWithHeaders_ConnectionError() { await InitializeConnectionAsync(_noopApplication); await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders); await SendWindowUpdateAsync(1, sizeIncrement: 42); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.WINDOW_UPDATE, streamId: 1, headersStreamId: 1)); } [Theory] [InlineData(0, 3)] [InlineData(0, 5)] [InlineData(1, 3)] [InlineData(1, 5)] public async Task WINDOW_UPDATE_Received_LengthNotFour_ConnectionError(int streamId, int length) { await InitializeConnectionAsync(_noopApplication); await SendInvalidWindowUpdateAsync(streamId, sizeIncrement: 42, length: length); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.WINDOW_UPDATE, expectedLength: 4)); } [Fact] public async Task WINDOW_UPDATE_Received_OnConnection_SizeIncrementZero_ConnectionError() { await InitializeConnectionAsync(_noopApplication); await SendWindowUpdateAsync(0, sizeIncrement: 0); 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] public async Task WINDOW_UPDATE_Received_StreamIdle_ConnectionError() { await InitializeConnectionAsync(_waitForAbortApplication); await SendWindowUpdateAsync(1, sizeIncrement: 1); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdle(Http2FrameType.WINDOW_UPDATE, streamId: 1)); } [Fact] public async Task CONTINUATION_Received_Decoded() { await InitializeConnectionAsync(_readHeadersApplication); await StartStreamAsync(1, _twoContinuationsRequestHeaders, endStream: true); 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); VerifyDecodedRequestHeaders(_twoContinuationsRequestHeaders); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); } [Theory] [InlineData(true)] [InlineData(false)] public async Task CONTINUATION_Received_WithTrailers_Decoded(bool sendData) { await InitializeConnectionAsync(_readTrailersApplication); await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, _browserRequestHeaders); // Initialize another stream with a higher stream ID, and verify that after trailers are // decoded by the other stream, the highest opened stream ID is not reset to the lower ID // (the highest opened stream ID is sent by the server in the GOAWAY frame when shutting // down the connection). await SendHeadersAsync(3, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, _browserRequestHeaders); // The second stream should end first, since the first one is waiting for the request body. await ExpectAsync(Http2FrameType.HEADERS, withLength: 55, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 3); await ExpectAsync(Http2FrameType.DATA, withLength: 0, withFlags: (byte)Http2DataFrameFlags.END_STREAM, withStreamId: 3); if (sendData) { await SendDataAsync(1, _helloBytes, endStream: false); } // Trailers encoded as Literal Header Field without Indexing - New Name // trailer-1: 1 // trailer-2: 2 var trailers = new byte[] { 0x00, 0x09 } .Concat(Encoding.ASCII.GetBytes("trailer-1")) .Concat(new byte[] { 0x01, (byte)'1' }) .Concat(new byte[] { 0x00, 0x09 }) .Concat(Encoding.ASCII.GetBytes("trailer-2")) .Concat(new byte[] { 0x01, (byte)'2' }) .ToArray(); await SendHeadersAsync(1, Http2HeadersFrameFlags.END_STREAM, new byte[0]); await SendContinuationAsync(1, Http2ContinuationFrameFlags.END_HEADERS, trailers); await ExpectAsync(Http2FrameType.HEADERS, withLength: 55, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, withLength: 0, withFlags: (byte)Http2DataFrameFlags.END_STREAM, withStreamId: 1); VerifyDecodedRequestHeaders(_browserRequestHeaders.Concat(new[] { new KeyValuePair("trailer-1", "1"), new KeyValuePair("trailer-2", "2") })); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); } [Fact] public async Task CONTINUATION_Received_StreamIdMismatch_ConnectionError() { await InitializeConnectionAsync(_readHeadersApplication); await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _oneContinuationRequestHeaders); await SendContinuationAsync(3, Http2ContinuationFrameFlags.END_HEADERS); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.CONTINUATION, streamId: 3, headersStreamId: 1)); } [Fact] public async Task CONTINUATION_Received_IncompleteHeaderBlock_ConnectionError() { await InitializeConnectionAsync(_noopApplication); await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _postRequestHeaders); await SendIncompleteContinuationFrameAsync(streamId: 1); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.COMPRESSION_ERROR, expectedErrorMessage: CoreStrings.HPackErrorIncompleteHeaderBlock); } [Theory] [MemberData(nameof(IllegalTrailerData))] public async Task CONTINUATION_Received_WithTrailers_ContainsIllegalTrailer_ConnectionError(byte[] trailers, string expectedErrorMessage) { await InitializeConnectionAsync(_readTrailersApplication); await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, _browserRequestHeaders); await SendHeadersAsync(1, Http2HeadersFrameFlags.END_STREAM, new byte[0]); await SendContinuationAsync(1, Http2ContinuationFrameFlags.END_HEADERS, trailers); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: expectedErrorMessage); } [Theory] [MemberData(nameof(MissingPseudoHeaderFieldData))] public async Task CONTINUATION_Received_HeaderBlockDoesNotContainMandatoryPseudoHeaderField_StreamError(IEnumerable> headers) { await InitializeConnectionAsync(_noopApplication); Assert.True(await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, headers)); await SendEmptyContinuationFrameAsync(1, Http2ContinuationFrameFlags.END_HEADERS); 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( ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1)); } [Theory] [MemberData(nameof(ConnectMissingPseudoHeaderFieldData))] public async Task CONTINUATION_Received_HeaderBlockDoesNotContainMandatoryPseudoHeaderField_MethodIsCONNECT_NoError(IEnumerable> headers) { await InitializeConnectionAsync(_noopApplication); await SendHeadersAsync(1, Http2HeadersFrameFlags.END_STREAM, headers); await SendEmptyContinuationFrameAsync(1, Http2ContinuationFrameFlags.END_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] public async Task CONTINUATION_Sent_WhenHeadersLargerThanFrameLength() { await InitializeConnectionAsync(_largeHeadersApplication); await StartStreamAsync(1, _browserRequestHeaders, endStream: true); var headersFrame = await ExpectAsync(Http2FrameType.HEADERS, withLength: 12361, withFlags: (byte)Http2HeadersFrameFlags.NONE, withStreamId: 1); var continuationFrame1 = await ExpectAsync(Http2FrameType.CONTINUATION, withLength: 12306, withFlags: (byte)Http2ContinuationFrameFlags.NONE, withStreamId: 1); var continuationFrame2 = await ExpectAsync(Http2FrameType.CONTINUATION, withLength: 8204, withFlags: (byte)Http2ContinuationFrameFlags.END_HEADERS, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, withLength: 0, withFlags: (byte)Http2DataFrameFlags.END_STREAM, withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); _hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this); _hpackDecoder.Decode(continuationFrame1.HeadersPayload, endHeaders: false, handler: this); _hpackDecoder.Decode(continuationFrame2.HeadersPayload, endHeaders: true, handler: this); Assert.Equal(11, _decodedHeaders.Count); Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase); Assert.Equal("200", _decodedHeaders[":status"]); Assert.Equal("0", _decodedHeaders["content-length"]); Assert.Equal(_largeHeaderValue, _decodedHeaders["a"]); Assert.Equal(_largeHeaderValue, _decodedHeaders["b"]); Assert.Equal(_largeHeaderValue, _decodedHeaders["c"]); Assert.Equal(_largeHeaderValue, _decodedHeaders["d"]); Assert.Equal(_largeHeaderValue, _decodedHeaders["e"]); Assert.Equal(_largeHeaderValue, _decodedHeaders["f"]); Assert.Equal(_largeHeaderValue, _decodedHeaders["g"]); Assert.Equal(_largeHeaderValue, _decodedHeaders["h"]); } [Fact] public async Task UnknownFrameType_Received_Ignored() { await InitializeConnectionAsync(_noopApplication); await SendUnknownFrameTypeAsync(streamId: 1, frameType: 42); // Check that the connection is still alive await SendPingAsync(Http2PingFrameFlags.NONE); await ExpectAsync(Http2FrameType.PING, withLength: 8, withFlags: (byte)Http2PingFrameFlags.ACK, withStreamId: 0); await StopConnectionAsync(0, ignoreNonGoAwayFrames: false); } [Fact] public async Task UnknownFrameType_Received_InterleavedWithHeaders_ConnectionError() { await InitializeConnectionAsync(_noopApplication); await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders); await SendUnknownFrameTypeAsync(streamId: 1, frameType: 42); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(frameType: 42, streamId: 1, headersStreamId: 1)); } [Fact] public async Task ConnectionErrorAbortsAllStreams() { await InitializeConnectionAsync(_waitForAbortApplication); // Start some streams await StartStreamAsync(1, _browserRequestHeaders, endStream: true); await StartStreamAsync(3, _browserRequestHeaders, endStream: true); await StartStreamAsync(5, _browserRequestHeaders, endStream: true); // Cause a connection error by sending an invalid frame await SendDataAsync(0, _noData, endStream: false); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 5, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdZero(Http2FrameType.DATA)); await WaitForAllStreamsAsync(); Assert.Contains(1, _abortedStreamIds); Assert.Contains(3, _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) { _connectionTask = _connection.ProcessRequestsAsync(new DummyApplication(application)); await SendPreambleAsync().ConfigureAwait(false); await SendSettingsAsync(); await ExpectAsync(Http2FrameType.SETTINGS, withLength: 0, withFlags: 0, withStreamId: 0); await ExpectAsync(Http2FrameType.SETTINGS, withLength: 0, withFlags: (byte)Http2SettingsFrameFlags.ACK, withStreamId: 0); } private async Task StartStreamAsync(int streamId, IEnumerable> headers, bool endStream) { var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); _runningStreams[streamId] = tcs; var frame = new Http2Frame(); frame.PrepareHeaders(Http2HeadersFrameFlags.NONE, streamId); var done = _hpackEncoder.BeginEncode(headers, frame.HeadersPayload, out var length); frame.Length = length; if (done) { frame.HeadersFlags = Http2HeadersFrameFlags.END_HEADERS; } if (endStream) { frame.HeadersFlags |= Http2HeadersFrameFlags.END_STREAM; } await SendAsync(frame.Raw); while (!done) { frame.PrepareContinuation(Http2ContinuationFrameFlags.NONE, streamId); done = _hpackEncoder.Encode(frame.HeadersPayload, out length); frame.Length = length; if (done) { frame.ContinuationFlags = Http2ContinuationFrameFlags.END_HEADERS; } await SendAsync(frame.Raw); } } private async Task SendHeadersWithPaddingAsync(int streamId, IEnumerable> headers, byte padLength, bool endStream) { var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); _runningStreams[streamId] = tcs; var frame = new Http2Frame(); frame.PrepareHeaders(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.PADDED, streamId); frame.HeadersPadLength = padLength; _hpackEncoder.BeginEncode(headers, frame.HeadersPayload, out var length); frame.Length = 1 + length + padLength; frame.Payload.Slice(1 + length).Fill(0); if (endStream) { frame.HeadersFlags |= Http2HeadersFrameFlags.END_STREAM; } await SendAsync(frame.Raw); } private async Task SendHeadersWithPriorityAsync(int streamId, IEnumerable> headers, byte priority, int streamDependency, bool endStream) { var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); _runningStreams[streamId] = tcs; var frame = new Http2Frame(); frame.PrepareHeaders(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.PRIORITY, streamId); frame.HeadersPriority = priority; frame.HeadersStreamDependency = streamDependency; _hpackEncoder.BeginEncode(headers, frame.HeadersPayload, out var length); frame.Length = 5 + length; if (endStream) { frame.HeadersFlags |= Http2HeadersFrameFlags.END_STREAM; } await SendAsync(frame.Raw); } private async Task SendHeadersWithPaddingAndPriorityAsync(int streamId, IEnumerable> headers, byte padLength, byte priority, int streamDependency, bool endStream) { var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); _runningStreams[streamId] = tcs; var frame = new Http2Frame(); frame.PrepareHeaders(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.PADDED | Http2HeadersFrameFlags.PRIORITY, streamId); frame.HeadersPadLength = padLength; frame.HeadersPriority = priority; frame.HeadersStreamDependency = streamDependency; _hpackEncoder.BeginEncode(headers, frame.HeadersPayload, out var length); frame.Length = 6 + length + padLength; frame.Payload.Slice(6 + length).Fill(0); if (endStream) { frame.HeadersFlags |= Http2HeadersFrameFlags.END_STREAM; } await SendAsync(frame.Raw); } private Task SendStreamDataAsync(int streamId, Span data) { var tasks = new List(); var frame = new Http2Frame(); frame.PrepareData(streamId); while (data.Length > frame.Length) { data.Slice(0, frame.Length).CopyTo(frame.Payload); data = data.Slice(frame.Length); tasks.Add(SendAsync(frame.Raw)); } frame.Length = data.Length; frame.DataFlags = Http2DataFrameFlags.END_STREAM; data.CopyTo(frame.Payload); tasks.Add(SendAsync(frame.Raw)); return Task.WhenAll(tasks); } private Task WaitForAllStreamsAsync() { return Task.WhenAll(_runningStreams.Values.Select(tcs => tcs.Task)).TimeoutAfter(TestConstants.DefaultTimeout); } private Task SendAsync(ReadOnlySpan span) { var writableBuffer = _pair.Application.Output; writableBuffer.Write(span); return FlushAsync(writableBuffer); } private static async Task FlushAsync(PipeWriter writableBuffer) { await writableBuffer.FlushAsync(); } private Task SendPreambleAsync() => SendAsync(new ArraySegment(Http2Connection.ClientPreface)); private Task SendSettingsAsync() { var frame = new Http2Frame(); frame.PrepareSettings(Http2SettingsFrameFlags.NONE, _clientSettings); return SendAsync(frame.Raw); } private Task SendSettingsAckWithInvalidLengthAsync(int length) { var frame = new Http2Frame(); frame.PrepareSettings(Http2SettingsFrameFlags.ACK); frame.Length = length; return SendAsync(frame.Raw); } private Task SendSettingsWithInvalidStreamIdAsync(int streamId) { var frame = new Http2Frame(); frame.PrepareSettings(Http2SettingsFrameFlags.NONE, _clientSettings); frame.StreamId = streamId; return SendAsync(frame.Raw); } private Task SendSettingsWithInvalidLengthAsync(int length) { var frame = new Http2Frame(); frame.PrepareSettings(Http2SettingsFrameFlags.NONE, _clientSettings); frame.Length = length; return SendAsync(frame.Raw); } private Task SendSettingsWithInvalidParameterValueAsync(Http2SettingsParameter parameter, uint value) { var frame = new Http2Frame(); frame.PrepareSettings(Http2SettingsFrameFlags.NONE); frame.Length = 6; frame.Payload[0] = (byte)((ushort)parameter >> 8); frame.Payload[1] = (byte)(ushort)parameter; frame.Payload[2] = (byte)(value >> 24); frame.Payload[3] = (byte)(value >> 16); frame.Payload[4] = (byte)(value >> 8); frame.Payload[5] = (byte)value; return SendAsync(frame.Raw); } private Task SendPushPromiseFrameAsync() { var frame = new Http2Frame(); frame.Length = 0; frame.Type = Http2FrameType.PUSH_PROMISE; frame.StreamId = 1; return SendAsync(frame.Raw); } private async Task SendHeadersAsync(int streamId, Http2HeadersFrameFlags flags, IEnumerable> headers) { var frame = new Http2Frame(); frame.PrepareHeaders(flags, streamId); var done = _hpackEncoder.BeginEncode(headers, frame.Payload, out var length); frame.Length = length; await SendAsync(frame.Raw); return done; } private Task SendHeadersAsync(int streamId, Http2HeadersFrameFlags flags, byte[] headerBlock) { var frame = new Http2Frame(); frame.PrepareHeaders(flags, streamId); frame.Length = headerBlock.Length; headerBlock.CopyTo(frame.HeadersPayload); return SendAsync(frame.Raw); } private Task SendInvalidHeadersFrameAsync(int streamId, int frameLength, byte padLength) { Assert.True(padLength >= frameLength, $"{nameof(padLength)} must be greater than or equal to {nameof(frameLength)} to create an invalid frame."); var frame = new Http2Frame(); frame.PrepareHeaders(Http2HeadersFrameFlags.PADDED, streamId); frame.Payload[0] = padLength; // Set length last so .Payload can be written to frame.Length = frameLength; return SendAsync(frame.Raw); } private Task SendIncompleteHeadersFrameAsync(int streamId) { var frame = new Http2Frame(); frame.PrepareHeaders(Http2HeadersFrameFlags.END_HEADERS, streamId); frame.Length = 3; // Set up an incomplete Literal Header Field w/ Incremental Indexing frame, // with an incomplete new name frame.Payload[0] = 0; frame.Payload[1] = 2; frame.Payload[2] = (byte)'a'; return SendAsync(frame.Raw); } private async Task SendContinuationAsync(int streamId, Http2ContinuationFrameFlags flags) { var frame = new Http2Frame(); frame.PrepareContinuation(flags, streamId); var done = _hpackEncoder.Encode(frame.Payload, out var length); frame.Length = length; await SendAsync(frame.Raw); return done; } private async Task SendContinuationAsync(int streamId, Http2ContinuationFrameFlags flags, byte[] payload) { var frame = new Http2Frame(); frame.PrepareContinuation(flags, streamId); frame.Length = payload.Length; payload.CopyTo(frame.Payload); await SendAsync(frame.Raw); } private Task SendEmptyContinuationFrameAsync(int streamId, Http2ContinuationFrameFlags flags) { var frame = new Http2Frame(); frame.PrepareContinuation(flags, streamId); frame.Length = 0; return SendAsync(frame.Raw); } private Task SendIncompleteContinuationFrameAsync(int streamId) { var frame = new Http2Frame(); frame.PrepareContinuation(Http2ContinuationFrameFlags.END_HEADERS, streamId); frame.Length = 3; // Set up an incomplete Literal Header Field w/ Incremental Indexing frame, // with an incomplete new name frame.Payload[0] = 0; frame.Payload[1] = 2; frame.Payload[2] = (byte)'a'; return SendAsync(frame.Raw); } private Task SendDataAsync(int streamId, Span data, bool endStream) { var frame = new Http2Frame(); frame.PrepareData(streamId); frame.Length = data.Length; frame.DataFlags = endStream ? Http2DataFrameFlags.END_STREAM : Http2DataFrameFlags.NONE; data.CopyTo(frame.DataPayload); return SendAsync(frame.Raw); } private Task SendDataWithPaddingAsync(int streamId, Span data, byte padLength, bool endStream) { var frame = new Http2Frame(); frame.PrepareData(streamId, padLength); frame.Length = data.Length + 1 + padLength; data.CopyTo(frame.DataPayload); if (endStream) { frame.DataFlags |= Http2DataFrameFlags.END_STREAM; } return SendAsync(frame.Raw); } private Task SendInvalidDataFrameAsync(int streamId, int frameLength, byte padLength) { Assert.True(padLength >= frameLength, $"{nameof(padLength)} must be greater than or equal to {nameof(frameLength)} to create an invalid frame."); var frame = new Http2Frame(); frame.PrepareData(streamId); frame.DataFlags = Http2DataFrameFlags.PADDED; frame.Payload[0] = padLength; // Set length last so .Payload can be written to frame.Length = frameLength; return SendAsync(frame.Raw); } private Task SendPingAsync(Http2PingFrameFlags flags) { var pingFrame = new Http2Frame(); pingFrame.PreparePing(flags); return SendAsync(pingFrame.Raw); } private Task SendPingWithInvalidLengthAsync(int length) { var pingFrame = new Http2Frame(); pingFrame.PreparePing(Http2PingFrameFlags.NONE); pingFrame.Length = length; return SendAsync(pingFrame.Raw); } private Task SendPingWithInvalidStreamIdAsync(int streamId) { Assert.NotEqual(0, streamId); var pingFrame = new Http2Frame(); pingFrame.PreparePing(Http2PingFrameFlags.NONE); pingFrame.StreamId = streamId; return SendAsync(pingFrame.Raw); } private Task SendPriorityAsync(int streamId, int streamDependency = 0) { var priorityFrame = new Http2Frame(); priorityFrame.PreparePriority(streamId, streamDependency: streamDependency, exclusive: false, weight: 0); return SendAsync(priorityFrame.Raw); } private Task SendInvalidPriorityFrameAsync(int streamId, int length) { var priorityFrame = new Http2Frame(); priorityFrame.PreparePriority(streamId, streamDependency: 0, exclusive: false, weight: 0); priorityFrame.Length = length; return SendAsync(priorityFrame.Raw); } private Task SendRstStreamAsync(int streamId) { var rstStreamFrame = new Http2Frame(); rstStreamFrame.PrepareRstStream(streamId, Http2ErrorCode.CANCEL); return SendAsync(rstStreamFrame.Raw); } private Task SendInvalidRstStreamFrameAsync(int streamId, int length) { var frame = new Http2Frame(); frame.PrepareRstStream(streamId, Http2ErrorCode.CANCEL); frame.Length = length; return SendAsync(frame.Raw); } private Task SendGoAwayAsync() { var frame = new Http2Frame(); frame.PrepareGoAway(0, Http2ErrorCode.NO_ERROR); return SendAsync(frame.Raw); } private Task SendInvalidGoAwayFrameAsync() { var frame = new Http2Frame(); frame.PrepareGoAway(0, Http2ErrorCode.NO_ERROR); frame.StreamId = 1; return SendAsync(frame.Raw); } private Task SendWindowUpdateAsync(int streamId, int sizeIncrement) { var frame = new Http2Frame(); frame.PrepareWindowUpdate(streamId, sizeIncrement); return SendAsync(frame.Raw); } private Task SendInvalidWindowUpdateAsync(int streamId, int sizeIncrement, int length) { var frame = new Http2Frame(); frame.PrepareWindowUpdate(streamId, sizeIncrement); frame.Length = length; return SendAsync(frame.Raw); } private Task SendUnknownFrameTypeAsync(int streamId, int frameType) { var frame = new Http2Frame(); frame.StreamId = streamId; frame.Type = (Http2FrameType)frameType; frame.Length = 0; return SendAsync(frame.Raw); } private async Task ReceiveFrameAsync() { var frame = new Http2Frame(); while (true) { var result = await _pair.Application.Input.ReadAsync(); var buffer = result.Buffer; var consumed = buffer.Start; var examined = buffer.End; try { Assert.True(buffer.Length > 0); if (Http2FrameReader.ReadFrame(buffer, frame, out consumed, out examined)) { return frame; } } finally { _pair.Application.Input.AdvanceTo(consumed, examined); } } } private async Task ReceiveSettingsAck() { var frame = await ReceiveFrameAsync(); Assert.Equal(Http2FrameType.SETTINGS, frame.Type); Assert.Equal(Http2SettingsFrameFlags.ACK, frame.SettingsFlags); } private async Task ExpectAsync(Http2FrameType type, int withLength, byte withFlags, int withStreamId) { var frame = await ReceiveFrameAsync(); Assert.Equal(type, frame.Type); Assert.Equal(withLength, frame.Length); Assert.Equal(withFlags, frame.Flags); Assert.Equal(withStreamId, frame.StreamId); return frame; } private Task StopConnectionAsync(int expectedLastStreamId, bool ignoreNonGoAwayFrames) { _pair.Application.Output.Complete(); return WaitForConnectionStopAsync(expectedLastStreamId, ignoreNonGoAwayFrames); } private Task WaitForConnectionStopAsync(int expectedLastStreamId, bool ignoreNonGoAwayFrames) { return WaitForConnectionErrorAsync(ignoreNonGoAwayFrames, expectedLastStreamId, Http2ErrorCode.NO_ERROR, expectedErrorMessage: null); } private async Task WaitForConnectionErrorAsync(bool ignoreNonGoAwayFrames, int expectedLastStreamId, Http2ErrorCode expectedErrorCode, string expectedErrorMessage) where TException : Exception { var frame = await ReceiveFrameAsync(); if (ignoreNonGoAwayFrames) { while (frame.Type != Http2FrameType.GOAWAY) { frame = await ReceiveFrameAsync(); } } Assert.Equal(Http2FrameType.GOAWAY, frame.Type); Assert.Equal(8, frame.Length); Assert.Equal(0, frame.Flags); Assert.Equal(0, frame.StreamId); 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(bool ignoreNonRstStreamFrames, int expectedStreamId, Http2ErrorCode expectedErrorCode, string expectedErrorMessage) { var frame = await ReceiveFrameAsync(); if (ignoreNonRstStreamFrames) { while (frame.Type != Http2FrameType.RST_STREAM) { frame = await ReceiveFrameAsync(); } } Assert.Equal(Http2FrameType.RST_STREAM, frame.Type); Assert.Equal(4, frame.Length); 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) { foreach (var header in expectedHeaders) { Assert.True(_receivedHeaders.TryGetValue(header.Key, out var value), header.Key); Assert.Equal(header.Value, value, ignoreCase: true); } } public static TheoryData UpperCaseHeaderNameData { get { // We can't use HPackEncoder here because it will convert header names to lowercase var headerName = "abcdefghijklmnopqrstuvwxyz"; var headerBlockStart = new byte[] { 0x82, // Indexed Header Field - :method: GET 0x84, // Indexed Header Field - :path: / 0x86, // Indexed Header Field - :scheme: http 0x00, // Literal Header Field without Indexing - New Name (byte)headerName.Length, // Header name length }; var headerBlockEnd = new byte[] { 0x01, // Header value length 0x30 // "0" }; var data = new TheoryData(); for (var i = 0; i < headerName.Length; i++) { var bytes = Encoding.ASCII.GetBytes(headerName); bytes[i] &= 0xdf; var headerBlock = headerBlockStart.Concat(bytes).Concat(headerBlockEnd).ToArray(); data.Add(headerBlock); } return data; } } public static TheoryData>> DuplicatePseudoHeaderFieldData { get { var data = new TheoryData>>(); var requestHeaders = new[] { new KeyValuePair(":method", "GET"), new KeyValuePair(":path", "/"), new KeyValuePair(":authority", "127.0.0.1"), new KeyValuePair(":scheme", "http"), }; foreach (var headerField in requestHeaders) { var headers = requestHeaders.Concat(new[] { new KeyValuePair(headerField.Key, headerField.Value) }); data.Add(headers); } return data; } } public static TheoryData>> MissingPseudoHeaderFieldData { get { var data = new TheoryData>>(); var requestHeaders = new[] { new KeyValuePair(":method", "GET"), new KeyValuePair(":path", "/"), new KeyValuePair(":scheme", "http"), }; foreach (var headerField in requestHeaders) { var headers = requestHeaders.Except(new[] { headerField }); data.Add(headers); } return data; } } public static TheoryData>> ConnectMissingPseudoHeaderFieldData { get { var data = new TheoryData>>(); var methodHeader = new[] { new KeyValuePair(":method", "CONNECT") }; var requestHeaders = new[] { new KeyValuePair(":path", "/"), new KeyValuePair(":scheme", "http"), new KeyValuePair(":authority", "127.0.0.1"), }; foreach (var headerField in requestHeaders) { var headers = methodHeader.Concat(requestHeaders.Except(new[] { headerField })); data.Add(headers); } return data; } } public static TheoryData>> PseudoHeaderFieldAfterRegularHeadersData { get { var data = new TheoryData>>(); var requestHeaders = new[] { new KeyValuePair(":method", "GET"), new KeyValuePair(":path", "/"), new KeyValuePair(":authority", "127.0.0.1"), new KeyValuePair(":scheme", "http"), new KeyValuePair("content-length", "0") }; foreach (var headerField in requestHeaders.Where(h => h.Key.StartsWith(":"))) { var headers = requestHeaders.Except(new[] { headerField }).Concat(new[] { headerField }); data.Add(headers); } return data; } } public static TheoryData IllegalTrailerData { get { // We can't use HPackEncoder here because it will convert header names to lowercase var data = new TheoryData(); // Indexed Header Field - :method: GET data.Add(new byte[] { 0x82 }, CoreStrings.Http2ErrorTrailersContainPseudoHeaderField); // Indexed Header Field - :path: / data.Add(new byte[] { 0x84 }, CoreStrings.Http2ErrorTrailersContainPseudoHeaderField); // Indexed Header Field - :scheme: http data.Add(new byte[] { 0x86 }, CoreStrings.Http2ErrorTrailersContainPseudoHeaderField); // Literal Header Field without Indexing - Indexed Name - :authority: 127.0.0.1 data.Add(new byte[] { 0x01, 0x09 }.Concat(Encoding.ASCII.GetBytes("127.0.0.1")).ToArray(), CoreStrings.Http2ErrorTrailersContainPseudoHeaderField); // Literal Header Field without Indexing - New Name - contains-Uppercase: 0 data.Add(new byte[] { 0x00, 0x12 } .Concat(Encoding.ASCII.GetBytes("contains-Uppercase")) .Concat(new byte[] { 0x01, (byte)'0' }) .ToArray(), CoreStrings.Http2ErrorTrailerNameUppercase); return data; } } } }