// 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.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Server.Kestrel.Core.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; using Xunit; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { public class Http2ConnectionTests : Http2TestBase { private static readonly IEnumerable> _postRequestHeaders = new[] { new KeyValuePair(HeaderNames.Method, "POST"), new KeyValuePair(HeaderNames.Path, "/"), new KeyValuePair(HeaderNames.Scheme, "http"), new KeyValuePair(HeaderNames.Authority, "localhost:80"), }; private static readonly IEnumerable> _expectContinueRequestHeaders = new[] { new KeyValuePair(HeaderNames.Method, "POST"), new KeyValuePair(HeaderNames.Path, "/"), new KeyValuePair(HeaderNames.Authority, "127.0.0.1"), new KeyValuePair(HeaderNames.Scheme, "http"), new KeyValuePair("expect", "100-continue"), }; private static readonly IEnumerable> _requestTrailers = new[] { new KeyValuePair("trailer-one", "1"), new KeyValuePair("trailer-two", "2"), }; private static readonly IEnumerable> _oneContinuationRequestHeaders = new[] { new KeyValuePair(HeaderNames.Method, "GET"), new KeyValuePair(HeaderNames.Path, "/"), new KeyValuePair(HeaderNames.Scheme, "http"), new KeyValuePair(HeaderNames.Authority, "localhost:80"), new KeyValuePair("a", _4kHeaderValue), new KeyValuePair("b", _4kHeaderValue), new KeyValuePair("c", _4kHeaderValue), new KeyValuePair("d", _4kHeaderValue) }; private static readonly IEnumerable> _twoContinuationsRequestHeaders = new[] { new KeyValuePair(HeaderNames.Method, "GET"), new KeyValuePair(HeaderNames.Path, "/"), new KeyValuePair(HeaderNames.Scheme, "http"), new KeyValuePair(HeaderNames.Authority, "localhost:80"), new KeyValuePair("a", _4kHeaderValue), new KeyValuePair("b", _4kHeaderValue), new KeyValuePair("c", _4kHeaderValue), new KeyValuePair("d", _4kHeaderValue), new KeyValuePair("e", _4kHeaderValue), new KeyValuePair("f", _4kHeaderValue), new KeyValuePair("g", _4kHeaderValue), }; 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', Http2PeerSettings.MinAllowedMaxFrameSize)); [Fact] public async Task Frame_Received_OverMaxSize_FrameError() { await InitializeConnectionAsync(_echoApplication); await StartStreamAsync(1, _browserRequestHeaders, endStream: false); uint length = Http2PeerSettings.MinAllowedMaxFrameSize + 1; await SendDataAsync(1, new byte[length].AsSpan(), endStream: true); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: true, expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorFrameOverLimit(length, Http2PeerSettings.MinAllowedMaxFrameSize)); } [Fact] public async Task ServerSettings_ChangesRequestMaxFrameSize() { var length = Http2PeerSettings.MinAllowedMaxFrameSize + 10; _connectionContext.ServiceContext.ServerOptions.Limits.Http2.MaxFrameSize = length; _connection = new Http2Connection(_connectionContext); await InitializeConnectionAsync(_echoApplication, expectedSettingsCount: 3); await StartStreamAsync(1, _browserRequestHeaders, endStream: false); await SendDataAsync(1, new byte[length].AsSpan(), endStream: true); await ExpectAsync(Http2FrameType.HEADERS, withLength: 37, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); // The client's settings is still defaulted to Http2PeerSettings.MinAllowedMaxFrameSize so the echo response will come back in two separate frames await ExpectAsync(Http2FrameType.DATA, withLength: Http2PeerSettings.MinAllowedMaxFrameSize, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, withLength: length - Http2PeerSettings.MinAllowedMaxFrameSize, 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); } [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.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.DataPayload)); } [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.True(_maxData.AsSpan().SequenceEqual(dataFrame.DataPayload)); } [Fact] public async Task DATA_Received_GreaterThanDefaultInitialWindowSize_ReadByStream() { // _maxData should be 1/4th of the default initial window size + 1. Assert.Equal(Http2PeerSettings.DefaultInitialWindowSize + 1, (uint)_maxData.Length * 4); // Double the client stream windows to 128KiB so no stream WINDOW_UPDATEs need to be sent. _clientSettings.InitialWindowSize = Http2PeerSettings.DefaultInitialWindowSize * 2; await InitializeConnectionAsync(_echoApplication); // Double the client connection window to 128KiB. await SendWindowUpdateAsync(0, (int)Http2PeerSettings.DefaultInitialWindowSize); await StartStreamAsync(1, _browserRequestHeaders, endStream: false); await SendDataAsync(1, _maxData, endStream: false); await ExpectAsync(Http2FrameType.HEADERS, withLength: 37, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); var dataFrame1 = await ExpectAsync(Http2FrameType.DATA, withLength: _maxData.Length, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 1); // Writing over half the initial window size induces both a connection-level and stream-level window update. await SendDataAsync(1, _maxData, endStream: false); var streamWindowUpdateFrame1 = await ExpectAsync(Http2FrameType.WINDOW_UPDATE, withLength: 4, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 1); var connectionWindowUpdateFrame1 = await ExpectAsync(Http2FrameType.WINDOW_UPDATE, withLength: 4, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 0); var dataFrame2 = await ExpectAsync(Http2FrameType.DATA, withLength: _maxData.Length, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 1); await SendDataAsync(1, _maxData, endStream: false); var dataFrame3 = await ExpectAsync(Http2FrameType.DATA, withLength: _maxData.Length, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 1); await SendDataAsync(1, _maxData, endStream: true); var connectionWindowUpdateFrame2 = await ExpectAsync(Http2FrameType.WINDOW_UPDATE, withLength: 4, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 0); var dataFrame4 = 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.True(_maxData.AsSpan().SequenceEqual(dataFrame1.DataPayload)); Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame2.DataPayload)); Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame3.DataPayload)); Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame4.DataPayload)); Assert.Equal(_maxData.Length * 2, streamWindowUpdateFrame1.WindowUpdateSizeIncrement); Assert.Equal(_maxData.Length * 2, connectionWindowUpdateFrame1.WindowUpdateSizeIncrement); Assert.Equal(_maxData.Length * 2, connectionWindowUpdateFrame2.WindowUpdateSizeIncrement); } [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.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.DataPayload)); } [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.True(_helloBytes.AsSpan().SequenceEqual(stream1DataFrame1.DataPayload)); Assert.True(_worldBytes.AsSpan().SequenceEqual(stream1DataFrame2.DataPayload)); Assert.True(_helloBytes.AsSpan().SequenceEqual(stream3DataFrame1.DataPayload)); Assert.True(_worldBytes.AsSpan().SequenceEqual(stream3DataFrame2.DataPayload)); } [Fact] public async Task DATA_Received_Multiplexed_GreaterThanDefaultInitialWindowSize_ReadByStream() { // _maxData should be 1/4th of the default initial window size + 1. Assert.Equal(Http2PeerSettings.DefaultInitialWindowSize + 1, (uint)_maxData.Length * 4); // Double the client stream windows to 128KiB so no stream WINDOW_UPDATEs need to be sent. _clientSettings.InitialWindowSize = Http2PeerSettings.DefaultInitialWindowSize * 2; await InitializeConnectionAsync(_echoApplication); // Double the client connection window to 128KiB. await SendWindowUpdateAsync(0, (int)Http2PeerSettings.DefaultInitialWindowSize); await StartStreamAsync(1, _browserRequestHeaders, endStream: false); await SendDataAsync(1, _maxData, endStream: false); await ExpectAsync(Http2FrameType.HEADERS, withLength: 37, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); var dataFrame1 = await ExpectAsync(Http2FrameType.DATA, withLength: _maxData.Length, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 1); // Writing over half the initial window size induces both a connection-level and stream-level window update. await SendDataAsync(1, _maxData, endStream: false); var streamWindowUpdateFrame = await ExpectAsync(Http2FrameType.WINDOW_UPDATE, withLength: 4, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 1); var connectionWindowUpdateFrame1 = await ExpectAsync(Http2FrameType.WINDOW_UPDATE, withLength: 4, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 0); var dataFrame2 = await ExpectAsync(Http2FrameType.DATA, withLength: _maxData.Length, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 1); await SendDataAsync(1, _maxData, endStream: false); var dataFrame3 = await ExpectAsync(Http2FrameType.DATA, withLength: _maxData.Length, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 1); // Uploading data to a new stream induces a second connection-level but not stream-level window update. await StartStreamAsync(3, _browserRequestHeaders, endStream: false); await SendDataAsync(3, _maxData, endStream: true); var connectionWindowUpdateFrame2 = await ExpectAsync(Http2FrameType.WINDOW_UPDATE, withLength: 4, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 0); await ExpectAsync(Http2FrameType.HEADERS, withLength: 37, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 3); var dataFrame4 = await ExpectAsync(Http2FrameType.DATA, withLength: _maxData.Length, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 3); await ExpectAsync(Http2FrameType.DATA, withLength: 0, withFlags: (byte)Http2DataFrameFlags.END_STREAM, withStreamId: 3); await SendDataAsync(1, _maxData, endStream: true); var dataFrame5 = 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: 3, ignoreNonGoAwayFrames: false); Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame1.DataPayload)); Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame2.DataPayload)); Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame3.DataPayload)); Assert.Equal(_maxData.Length * 2, streamWindowUpdateFrame.WindowUpdateSizeIncrement); Assert.Equal(_maxData.Length * 2, connectionWindowUpdateFrame1.WindowUpdateSizeIncrement); Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame4.DataPayload)); Assert.Equal(_maxData.Length * 2, connectionWindowUpdateFrame2.WindowUpdateSizeIncrement); Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame5.DataPayload)); } [Fact] public async Task DATA_Received_Multiplexed_AppMustNotBlockOtherFrames() { var stream1Read = new ManualResetEvent(false); var stream1ReadFinished = new ManualResetEvent(false); var stream3Read = new ManualResetEvent(false); var stream3ReadFinished = new ManualResetEvent(false); await InitializeConnectionAsync(async context => { var data = new byte[10]; var read = await context.Request.Body.ReadAsync(new byte[10], 0, 10); if (context.Features.Get().StreamId == 1) { stream1Read.Set(); Assert.True(stream1ReadFinished.WaitOne(TimeSpan.FromSeconds(10))); } else { stream3Read.Set(); Assert.True(stream3ReadFinished.WaitOne(TimeSpan.FromSeconds(10))); } await context.Response.Body.WriteAsync(data, 0, read); }); await StartStreamAsync(1, _browserRequestHeaders, endStream: false); await StartStreamAsync(3, _browserRequestHeaders, endStream: false); await SendDataAsync(1, _helloBytes, endStream: false); Assert.True(stream1Read.WaitOne(TimeSpan.FromSeconds(10))); await SendDataAsync(3, _helloBytes, endStream: false); Assert.True(stream3Read.WaitOne(TimeSpan.FromSeconds(10))); stream3ReadFinished.Set(); await ExpectAsync(Http2FrameType.HEADERS, withLength: 37, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 3); await ExpectAsync(Http2FrameType.DATA, withLength: 5, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 3); await ExpectAsync(Http2FrameType.DATA, withLength: 0, withFlags: (byte)Http2DataFrameFlags.END_STREAM, withStreamId: 3); stream1ReadFinished.Set(); 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); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); } [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.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.DataPayload)); } [Theory] [InlineData(0)] [InlineData(1)] [InlineData(255)] public async Task DATA_Received_WithPadding_CountsTowardsInputFlowControl(byte padLength) { // _maxData should be 1/4th of the default initial window size + 1. Assert.Equal(Http2PeerSettings.DefaultInitialWindowSize + 1, (uint)_maxData.Length * 4); var maxDataMinusPadding = _maxData.AsMemory(0, _maxData.Length - padLength - 1); await InitializeConnectionAsync(_echoApplication); await StartStreamAsync(1, _browserRequestHeaders, endStream: false); await SendDataWithPaddingAsync(1, maxDataMinusPadding.Span, padLength, endStream: false); await ExpectAsync(Http2FrameType.HEADERS, withLength: 37, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); var dataFrame1 = await ExpectAsync(Http2FrameType.DATA, withLength: maxDataMinusPadding.Length, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 1); // Writing over half the initial window size induces both a connection-level and stream-level window update. await SendDataAsync(1, _maxData, endStream: true); var connectionWindowUpdateFrame = await ExpectAsync(Http2FrameType.WINDOW_UPDATE, withLength: 4, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 0); var dataFrame2 = 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.True(maxDataMinusPadding.Span.SequenceEqual(dataFrame1.DataPayload)); Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame2.DataPayload)); Assert.Equal(_maxData.Length * 2, connectionWindowUpdateFrame.WindowUpdateSizeIncrement); } [Fact] public async Task DATA_Received_ButNotConsumedByApp_CountsTowardsInputFlowControl() { // _maxData should be 1/4th of the default initial window size + 1. Assert.Equal(Http2PeerSettings.DefaultInitialWindowSize + 1, (uint)_maxData.Length * 4); await InitializeConnectionAsync(_noopApplication); await StartStreamAsync(1, _browserRequestHeaders, endStream: false); await SendDataAsync(1, _maxData, endStream: false); 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); // Writing over half the initial window size induces both a connection-level window update. await SendDataAsync(1, _maxData, endStream: true); var connectionWindowUpdateFrame = await ExpectAsync(Http2FrameType.WINDOW_UPDATE, withLength: 4, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 0); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); Assert.Equal(_maxData.Length * 2, connectionWindowUpdateFrame.WindowUpdateSizeIncrement); } [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.Http2FrameMissingFields); } [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: 1, 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: null); // There's a race where either of these messages could be logged, depending on if the stream cleanup has finished yet. var closedMessage = CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.DATA, streamId: 1); var halfClosedMessage = CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.DATA, streamId: 1); var message = Assert.Single(TestApplicationErrorLogger.Messages, m => m.Exception is Http2ConnectionErrorException); Assert.True(message.Exception.Message.IndexOf(closedMessage) >= 0 || message.Exception.Message.IndexOf(halfClosedMessage) >= 0); } [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 DATA_Received_NoStreamWindowSpace_ConnectionError() { // _maxData should be 1/4th of the default initial window size + 1. Assert.Equal(Http2PeerSettings.DefaultInitialWindowSize + 1, (uint)_maxData.Length * 4); await InitializeConnectionAsync(_waitForAbortApplication); await StartStreamAsync(1, _browserRequestHeaders, endStream: false); await SendDataAsync(1, _maxData, endStream: false); await SendDataAsync(1, _maxData, endStream: false); await SendDataAsync(1, _maxData, endStream: false); await SendDataAsync(1, _maxData, endStream: false); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.FLOW_CONTROL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorFlowControlWindowExceeded); } [Fact] public async Task DATA_Received_NoConnectionWindowSpace_ConnectionError() { // _maxData should be 1/4th of the default initial window size + 1. Assert.Equal(Http2PeerSettings.DefaultInitialWindowSize + 1, (uint)_maxData.Length * 4); await InitializeConnectionAsync(_waitForAbortApplication); await StartStreamAsync(1, _browserRequestHeaders, endStream: false); await SendDataAsync(1, _maxData, endStream: false); await SendDataAsync(1, _maxData, endStream: false); await StartStreamAsync(3, _browserRequestHeaders, endStream: false); await SendDataAsync(3, _maxData, endStream: false); await SendDataAsync(3, _maxData, endStream: false); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 3, expectedErrorCode: Http2ErrorCode.FLOW_CONTROL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorFlowControlWindowExceeded); } [Fact] public async Task DATA_Sent_DespiteConnectionOutputFlowControl_IfEmptyAndEndsStream() { // Zero-length data frames are allowed to be sent even if there is no space available in the flow control window. // https://httpwg.org/specs/rfc7540.html#rfc.section.6.9.1 var expectedFullFrameCountBeforeBackpressure = Http2PeerSettings.DefaultInitialWindowSize / _maxData.Length; var remainingBytesBeforeBackpressure = (int)Http2PeerSettings.DefaultInitialWindowSize % _maxData.Length; var remainingBytesAfterBackpressure = _maxData.Length - remainingBytesBeforeBackpressure; // Double the stream window to be 128KiB so it doesn't interfere with the rest of the test. _clientSettings.InitialWindowSize = Http2PeerSettings.DefaultInitialWindowSize * 2; await InitializeConnectionAsync(async context => { var streamId = context.Features.Get().StreamId; try { if (streamId == 1) { for (var i = 0; i < expectedFullFrameCountBeforeBackpressure + 1; i++) { await context.Response.Body.WriteAsync(_maxData, 0, _maxData.Length); } } _runningStreams[streamId].SetResult(null); } catch (Exception ex) { _runningStreams[streamId].SetException(ex); throw; } }); // Start one stream that consumes the entire connection output window. await StartStreamAsync(1, _browserRequestHeaders, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, withLength: 37, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); for (var i = 0; i < expectedFullFrameCountBeforeBackpressure; i++) { await ExpectAsync(Http2FrameType.DATA, withLength: _maxData.Length, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 1); } await ExpectAsync(Http2FrameType.DATA, withLength: remainingBytesBeforeBackpressure, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 1); // Start one more stream that receives an empty response despite connection backpressure. 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); // Relieve connection backpressure to receive the rest of the first streams body. await SendWindowUpdateAsync(0, remainingBytesAfterBackpressure); await ExpectAsync(Http2FrameType.DATA, withLength: remainingBytesAfterBackpressure, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, withLength: 0, withFlags: (byte)Http2DataFrameFlags.END_STREAM, withStreamId: 1); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); await WaitForAllStreamsAsync(); } [Fact] public async Task DATA_Sent_DespiteStreamOutputFlowControl_IfEmptyAndEndsStream() { // Zero-length data frames are allowed to be sent even if there is no space available in the flow control window. // https://httpwg.org/specs/rfc7540.html#rfc.section.6.9.1 // This only affects the stream windows. The connection-level window is always initialized at 64KiB. _clientSettings.InitialWindowSize = 0; 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); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); } [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_Discarded(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); // Make sure the trailers are missing. https://github.com/aspnet/KestrelHttpServer/issues/2630 foreach (var header in _requestTrailers) { Assert.False(_receivedHeaders.ContainsKey(header.Key)); } 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_AppCannotBlockOtherFrames() { var firstRequestReceived = new ManualResetEvent(false); var finishFirstRequest = new ManualResetEvent(false); var secondRequestReceived = new ManualResetEvent(false); var finishSecondRequest = new ManualResetEvent(false); await InitializeConnectionAsync(context => { if (!firstRequestReceived.WaitOne(0)) { firstRequestReceived.Set(); Assert.True(finishFirstRequest.WaitOne(TimeSpan.FromSeconds(10))); } else { secondRequestReceived.Set(); Assert.True(finishSecondRequest.WaitOne(TimeSpan.FromSeconds(10))); } return Task.CompletedTask; }); await StartStreamAsync(1, _browserRequestHeaders, endStream: true); Assert.True(firstRequestReceived.WaitOne(TimeSpan.FromSeconds(10))); await StartStreamAsync(3, _browserRequestHeaders, endStream: true); Assert.True(secondRequestReceived.WaitOne(TimeSpan.FromSeconds(10))); finishSecondRequest.Set(); 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); finishFirstRequest.Set(); 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 StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); } [Fact] public async Task HEADERS_OverMaxStreamLimit_Refused() { _connection.ServerSettings.MaxConcurrentStreams = 1; var requestBlocker = new TaskCompletionSource(); await InitializeConnectionAsync(context => requestBlocker.Task); await StartStreamAsync(1, _browserRequestHeaders, endStream: true); await StartStreamAsync(3, _browserRequestHeaders, endStream: true); await WaitForStreamErrorAsync(3, Http2ErrorCode.REFUSED_STREAM, CoreStrings.Http2ErrorMaxStreams); requestBlocker.SetResult(0); 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 StopConnectionAsync(expectedLastStreamId: 3, 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: null); // There's a race where either of these messages could be logged, depending on if the stream cleanup has finished yet. var closedMessage = CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1); var halfClosedMessage = CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.HEADERS, streamId: 1); var message = Assert.Single(TestApplicationErrorLogger.Messages, m => m.Exception is Http2ConnectionErrorException); Assert.True(message.Exception.Message.IndexOf(closedMessage) >= 0 || message.Exception.Message.IndexOf(halfClosedMessage) >= 0); } [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(1)] [InlineData(255)] public async Task HEADERS_Received_PaddingEqualToFramePayloadLength_ConnectionError(byte padLength) { await InitializeConnectionAsync(_noopApplication); // The payload length includes the pad length field await SendInvalidHeadersFrameAsync(1, payloadLength: padLength, padLength: padLength); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: true, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorPaddingTooLong(Http2FrameType.HEADERS)); } [Fact] public async Task HEADERS_Received_PaddingFieldMissing_ConnectionError() { await InitializeConnectionAsync(_noopApplication); await SendInvalidHeadersFrameAsync(1, payloadLength: 0, padLength: 1); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: true, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.Http2FrameMissingFields); } [Theory] [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: 1, 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: 1, 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_ConnectionError(byte[] headerBlock) { await InitializeConnectionAsync(_noopApplication); await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, headerBlock); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorHeaderNameUppercase); } [Fact] public Task HEADERS_Received_HeaderBlockContainsUnknownPseudoHeaderField_ConnectionError() { var headers = new[] { new KeyValuePair(HeaderNames.Method, "GET"), new KeyValuePair(HeaderNames.Path, "/"), new KeyValuePair(HeaderNames.Scheme, "http"), new KeyValuePair(":unknown", "0"), }; return HEADERS_Received_InvalidHeaderFields_ConnectionError(headers, expectedErrorMessage: CoreStrings.Http2ErrorUnknownPseudoHeaderField); } [Fact] public Task HEADERS_Received_HeaderBlockContainsResponsePseudoHeaderField_ConnectionError() { var headers = new[] { new KeyValuePair(HeaderNames.Method, "GET"), new KeyValuePair(HeaderNames.Path, "/"), new KeyValuePair(HeaderNames.Scheme, "http"), new KeyValuePair(HeaderNames.Status, "200"), }; return HEADERS_Received_InvalidHeaderFields_ConnectionError(headers, expectedErrorMessage: CoreStrings.Http2ErrorResponsePseudoHeaderField); } [Theory] [MemberData(nameof(DuplicatePseudoHeaderFieldData))] public Task HEADERS_Received_HeaderBlockContainsDuplicatePseudoHeaderField_ConnectionError(IEnumerable> headers) { return HEADERS_Received_InvalidHeaderFields_ConnectionError(headers, expectedErrorMessage: CoreStrings.Http2ErrorDuplicatePseudoHeaderField); } [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_ConnectionError(IEnumerable> headers) { return HEADERS_Received_InvalidHeaderFields_ConnectionError(headers, expectedErrorMessage: CoreStrings.Http2ErrorPseudoHeaderFieldAfterRegularHeaders); } private async Task HEADERS_Received_InvalidHeaderFields_ConnectionError(IEnumerable> headers, string expectedErrorMessage) { await InitializeConnectionAsync(_noopApplication); await StartStreamAsync(1, headers, endStream: true); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: expectedErrorMessage); } [Theory] [MemberData(nameof(MissingPseudoHeaderFieldData))] public async Task HEADERS_Received_HeaderBlockDoesNotContainMandatoryPseudoHeaderField_StreamError(IEnumerable> headers) { await InitializeConnectionAsync(_noopApplication); await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, headers); await WaitForStreamErrorAsync( 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, _browserRequestHeaders); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1)); } [Fact] public Task HEADERS_Received_HeaderBlockOverLimit_ConnectionError() { // > 32kb var headers = new[] { new KeyValuePair(HeaderNames.Method, "GET"), new KeyValuePair(HeaderNames.Path, "/"), new KeyValuePair(HeaderNames.Scheme, "http"), new KeyValuePair("a", _4kHeaderValue), new KeyValuePair("b", _4kHeaderValue), new KeyValuePair("c", _4kHeaderValue), new KeyValuePair("d", _4kHeaderValue), new KeyValuePair("e", _4kHeaderValue), new KeyValuePair("f", _4kHeaderValue), new KeyValuePair("g", _4kHeaderValue), new KeyValuePair("h", _4kHeaderValue), }; return HEADERS_Received_InvalidHeaderFields_ConnectionError(headers, CoreStrings.BadRequest_HeadersExceedMaxTotalSize); } [Fact] public Task HEADERS_Received_TooManyHeaders_ConnectionError() { // > MaxRequestHeaderCount (100) var headers = new List>(); headers.AddRange(new [] { new KeyValuePair(HeaderNames.Method, "GET"), new KeyValuePair(HeaderNames.Path, "/"), new KeyValuePair(HeaderNames.Scheme, "http"), }); for (var i = 0; i < 100; i++) { headers.Add(new KeyValuePair(i.ToString(), i.ToString())); } return HEADERS_Received_InvalidHeaderFields_ConnectionError(headers, CoreStrings.BadRequest_TooManyHeaders); } [Fact] public Task HEADERS_Received_InvalidCharacters_ConnectionError() { var headers = new[] { new KeyValuePair(HeaderNames.Method, "GET"), new KeyValuePair(HeaderNames.Path, "/"), new KeyValuePair(HeaderNames.Scheme, "http"), new KeyValuePair("Custom", "val\0ue"), }; return HEADERS_Received_InvalidHeaderFields_ConnectionError(headers, CoreStrings.BadRequest_MalformedRequestInvalidHeaders); } [Fact] public Task HEADERS_Received_HeaderBlockContainsConnectionHeader_ConnectionError() { var headers = new[] { new KeyValuePair(HeaderNames.Method, "GET"), new KeyValuePair(HeaderNames.Path, "/"), new KeyValuePair(HeaderNames.Scheme, "http"), new KeyValuePair("connection", "keep-alive") }; return HEADERS_Received_InvalidHeaderFields_ConnectionError(headers, CoreStrings.Http2ErrorConnectionSpecificHeaderField); } [Fact] public Task HEADERS_Received_HeaderBlockContainsTEHeader_ValueIsNotTrailers_ConnectionError() { var headers = new[] { new KeyValuePair(HeaderNames.Method, "GET"), new KeyValuePair(HeaderNames.Path, "/"), new KeyValuePair(HeaderNames.Scheme, "http"), new KeyValuePair("te", "trailers, deflate") }; return HEADERS_Received_InvalidHeaderFields_ConnectionError(headers, CoreStrings.Http2ErrorConnectionSpecificHeaderField); } [Fact] public async Task HEADERS_Received_HeaderBlockContainsTEHeader_ValueIsTrailers_NoError() { var headers = new[] { new KeyValuePair(HeaderNames.Method, "GET"), new KeyValuePair(HeaderNames.Path, "/"), new KeyValuePair(HeaderNames.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: 1, 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_ContinuesAppsAwaitingConnectionOutputFlowControl() { var writeTasks = new Task[4]; var expectedFullFrameCountBeforeBackpressure = Http2PeerSettings.DefaultInitialWindowSize / _maxData.Length; var remainingBytesBeforeBackpressure = (int)Http2PeerSettings.DefaultInitialWindowSize % _maxData.Length; // Double the stream window to be 128KiB so it doesn't interfere with the rest of the test. _clientSettings.InitialWindowSize = Http2PeerSettings.DefaultInitialWindowSize * 2; await InitializeConnectionAsync(async context => { var streamId = context.Features.Get().StreamId; var abortedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var writeTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); context.RequestAborted.Register(() => { lock (_abortedStreamIdsLock) { _abortedStreamIds.Add(streamId); abortedTcs.SetResult(null); } }); try { writeTasks[streamId] = writeTcs.Task; // Flush headers even if the body can't yet be written because of flow control. await context.Response.Body.FlushAsync(); for (var i = 0; i < expectedFullFrameCountBeforeBackpressure; i++) { await context.Response.Body.WriteAsync(_maxData, 0, _maxData.Length); } await context.Response.Body.WriteAsync(_maxData, 0, remainingBytesBeforeBackpressure + 1); writeTcs.SetResult(null); await abortedTcs.Task; _runningStreams[streamId].SetResult(null); } catch (Exception ex) { _runningStreams[streamId].SetException(ex); throw; } }); // Start one stream that consumes the entire connection output window. await StartStreamAsync(1, _browserRequestHeaders, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, withLength: 37, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); for (var i = 0; i < expectedFullFrameCountBeforeBackpressure; i++) { await ExpectAsync(Http2FrameType.DATA, withLength: _maxData.Length, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 1); } await ExpectAsync(Http2FrameType.DATA, withLength: remainingBytesBeforeBackpressure, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 1); // Ensure connection-level backpressure was hit. Assert.False(writeTasks[1].IsCompleted); // Start another stream that immediately experiences backpressure. await StartStreamAsync(3, _browserRequestHeaders, endStream: true); // The headers, but not the data for stream 3, can be sent prior to any window updates. await ExpectAsync(Http2FrameType.HEADERS, withLength: 37, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 3); await SendRstStreamAsync(1); // Any paused writes for stream 1 should complete after an RST_STREAM // even without any preceeding window updates. await _runningStreams[1].Task.DefaultTimeout(); // A connection-level window update allows the non-reset stream to continue. await SendWindowUpdateAsync(0, (int)Http2PeerSettings.DefaultInitialWindowSize); for (var i = 0; i < expectedFullFrameCountBeforeBackpressure; i++) { await ExpectAsync(Http2FrameType.DATA, withLength: _maxData.Length, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 3); } await ExpectAsync(Http2FrameType.DATA, withLength: remainingBytesBeforeBackpressure, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 3); Assert.False(writeTasks[3].IsCompleted); await SendRstStreamAsync(3); await _runningStreams[3].Task.DefaultTimeout(); await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); await WaitForAllStreamsAsync(); Assert.Contains(1, _abortedStreamIds); Assert.Contains(3, _abortedStreamIds); } [Fact] public async Task RST_STREAM_Received_ContinuesAppsAwaitingStreamOutputFlowControl() { var writeTasks = new Task[6]; var initialWindowSize = _helloWorldBytes.Length / 2; // This only affects the stream windows. The connection-level window is always initialized at 64KiB. _clientSettings.InitialWindowSize = (uint)initialWindowSize; await InitializeConnectionAsync(async context => { var streamId = context.Features.Get().StreamId; var abortedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var writeTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); context.RequestAborted.Register(() => { lock (_abortedStreamIdsLock) { _abortedStreamIds.Add(streamId); abortedTcs.SetResult(null); } }); try { writeTasks[streamId] = writeTcs.Task; await context.Response.Body.WriteAsync(_helloWorldBytes, 0, _helloWorldBytes.Length); writeTcs.SetResult(null); await abortedTcs.Task; _runningStreams[streamId].SetResult(null); } catch (Exception ex) { _runningStreams[streamId].SetException(ex); throw; } }); async Task VerifyStreamBackpressure(int streamId) { await StartStreamAsync(streamId, _browserRequestHeaders, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, withLength: 37, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: streamId); var dataFrame = await ExpectAsync(Http2FrameType.DATA, withLength: initialWindowSize, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: streamId); Assert.True(_helloWorldBytes.AsSpan(0, initialWindowSize).SequenceEqual(dataFrame.DataPayload)); Assert.False(writeTasks[streamId].IsCompleted); } await VerifyStreamBackpressure(1); await VerifyStreamBackpressure(3); await VerifyStreamBackpressure(5); await SendRstStreamAsync(1); await writeTasks[1].DefaultTimeout(); Assert.False(writeTasks[3].IsCompleted); Assert.False(writeTasks[5].IsCompleted); await SendRstStreamAsync(3); await writeTasks[3].DefaultTimeout(); Assert.False(writeTasks[5].IsCompleted); await SendRstStreamAsync(5); await writeTasks[5].DefaultTimeout(); await StopConnectionAsync(expectedLastStreamId: 5, ignoreNonGoAwayFrames: false); await WaitForAllStreamsAsync(); Assert.Contains(1, _abortedStreamIds); Assert.Contains(3, _abortedStreamIds); Assert.Contains(5, _abortedStreamIds); } [Fact] public async Task RST_STREAM_Received_ReturnsSpaceToConnectionInputFlowControlWindow() { // _maxData should be 1/4th of the default initial window size + 1. Assert.Equal(Http2PeerSettings.DefaultInitialWindowSize + 1, (uint)_maxData.Length * 4); await InitializeConnectionAsync(_waitForAbortApplication); await StartStreamAsync(1, _browserRequestHeaders, endStream: false); await SendDataAsync(1, _maxData, endStream: false); await SendDataAsync(1, _maxData, endStream: false); await SendDataAsync(1, _maxData, endStream: false); await SendRstStreamAsync(1); await WaitForAllStreamsAsync(); var connectionWindowUpdateFrame = await ExpectAsync(Http2FrameType.WINDOW_UPDATE, withLength: 4, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 0); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); Assert.Contains(1, _abortedStreamIds); Assert.Equal(_maxData.Length * 3, connectionWindowUpdateFrame.WindowUpdateSizeIncrement); } [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: 1, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.RST_STREAM, streamId: 1, headersStreamId: 1)); } [Fact] public async Task SETTINGS_KestrelDefaults_Sent() { _connectionTask = _connection.ProcessRequestsAsync(new DummyApplication(_noopApplication)); await SendPreambleAsync().ConfigureAwait(false); await SendSettingsAsync(); var frame = await ExpectAsync(Http2FrameType.SETTINGS, withLength: Http2Frame.SettingSize * 2, withFlags: 0, withStreamId: 0); // Only non protocol defaults are sent Assert.Equal(2, frame.SettingsCount); var settings = frame.GetSettings(); Assert.Equal(2, settings.Count); var setting = settings[0]; Assert.Equal(Http2SettingsParameter.SETTINGS_MAX_CONCURRENT_STREAMS, setting.Parameter); Assert.Equal(100u, setting.Value); setting = settings[1]; Assert.Equal(Http2SettingsParameter.SETTINGS_MAX_HEADER_LIST_SIZE, setting.Parameter); Assert.Equal(32 * 1024u, setting.Value); await ExpectAsync(Http2FrameType.SETTINGS, withLength: 0, withFlags: (byte)Http2SettingsFrameFlags.ACK, withStreamId: 0); await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false); } [Fact] public async Task SETTINGS_Custom_Sent() { _connection.ServerSettings.MaxConcurrentStreams = 1; _connection.ServerSettings.MaxHeaderListSize = 4 * 1024; _connectionTask = _connection.ProcessRequestsAsync(new DummyApplication(_noopApplication)); await SendPreambleAsync().ConfigureAwait(false); await SendSettingsAsync(); var frame = await ExpectAsync(Http2FrameType.SETTINGS, withLength: 6 * 2, withFlags: 0, withStreamId: 0); // Only non protocol defaults are sent Assert.Equal(2, frame.SettingsCount); var settings = frame.GetSettings(); Assert.Equal(2, settings.Count); var setting = settings[0]; Assert.Equal(Http2SettingsParameter.SETTINGS_MAX_CONCURRENT_STREAMS, setting.Parameter); Assert.Equal(1u, setting.Value); setting = settings[1]; Assert.Equal(Http2SettingsParameter.SETTINGS_MAX_HEADER_LIST_SIZE, setting.Parameter); Assert.Equal(4 * 1024u, setting.Value); await ExpectAsync(Http2FrameType.SETTINGS, withLength: 0, withFlags: (byte)Http2SettingsFrameFlags.ACK, withStreamId: 0); await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false); } [Fact] public async Task SETTINGS_Received_Sends_ACK() { await InitializeConnectionAsync(_noopApplication); await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false); } [Fact] public async Task SETTINGS_ACK_Received_DoesNotSend_ACK() { await InitializeConnectionAsync(_noopApplication); var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize); frame.PrepareSettings(Http2SettingsFrameFlags.ACK); await SendAsync(frame.Raw); 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: 1, 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 SETTINGS_Received_WithInitialWindowSizePushingStreamWindowOverMax_ConnectionError() { await InitializeConnectionAsync(_waitForAbortApplication); await StartStreamAsync(1, _browserRequestHeaders, endStream: false); await SendWindowUpdateAsync(1, (int)(Http2PeerSettings.MaxWindowSize - _clientSettings.InitialWindowSize)); _clientSettings.InitialWindowSize += 1; await SendSettingsAsync(); await ExpectAsync(Http2FrameType.SETTINGS, withLength: 0, withFlags: (byte)Http2SettingsFrameFlags.ACK, withStreamId: 0); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.FLOW_CONTROL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorInitialWindowSizeInvalid); } [Fact] public async Task SETTINGS_Received_ChangesAllowedResponseMaxFrameSize() { // This includes the default response headers such as :status, etc var defaultResponseHeaderLength = 37; var headerValueLength = Http2PeerSettings.MinAllowedMaxFrameSize; // First byte is always 0 // Second byte is the length of header name which is 1 // Third byte is the header name which is A/B // Next three bytes are the 7-bit integer encoding representation of the header length which is 16*1024 var encodedHeaderLength = 1 + 1 + 1 + 3 + headerValueLength; // Adding 10 additional bytes for encoding overhead var payloadLength = defaultResponseHeaderLength + encodedHeaderLength; await InitializeConnectionAsync(context => { context.Response.Headers["A"] = new string('a', headerValueLength); context.Response.Headers["B"] = new string('b', headerValueLength); return context.Response.Body.WriteAsync(new byte[payloadLength], 0, payloadLength); }); // Update client settings _clientSettings.MaxFrameSize = (uint)payloadLength; await SendSettingsAsync(); // ACK await ExpectAsync(Http2FrameType.SETTINGS, withLength: 0, withFlags: (byte)Http2SettingsFrameFlags.ACK, withStreamId: 0); // Start request await StartStreamAsync(1, _browserRequestHeaders, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, withLength: defaultResponseHeaderLength + encodedHeaderLength, withFlags: (byte)Http2HeadersFrameFlags.NONE, withStreamId: 1); await ExpectAsync(Http2FrameType.CONTINUATION, withLength: encodedHeaderLength, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); await ExpectAsync(Http2FrameType.DATA, withLength: payloadLength, 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); } [Fact] public async Task SETTINGS_Received_ChangesHeaderTableSize() { await InitializeConnectionAsync(_noopApplication); // Update client settings _clientSettings.HeaderTableSize = 65536; // Chrome's default, larger than the 4kb spec default await SendSettingsAsync(); // ACK await ExpectAsync(Http2FrameType.SETTINGS, withLength: 0, withFlags: (byte)Http2SettingsFrameFlags.ACK, withStreamId: 0); await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false); } [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: 1, 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_SetsConnectionStateToClosingAndWaitForAllStreamsToComplete() { await InitializeConnectionAsync(_echoApplication); // Start some streams await StartStreamAsync(1, _browserRequestHeaders, endStream: false); await StartStreamAsync(3, _browserRequestHeaders, endStream: false); await SendGoAwayAsync(); await _closingStateReached.Task.DefaultTimeout(); await SendDataAsync(1, _helloBytes, 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); await SendDataAsync(3, _helloBytes, true); await ExpectAsync(Http2FrameType.HEADERS, withLength: 37, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 3); await ExpectAsync(Http2FrameType.DATA, withLength: 5, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 3); await ExpectAsync(Http2FrameType.DATA, withLength: 0, withFlags: (byte)Http2DataFrameFlags.END_STREAM, withStreamId: 3); await WaitForConnectionStopAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); await _closedStateReached.Task.DefaultTimeout(); } [Fact] public async Task GOAWAY_Received_ContinuesAppsAwaitingConnectionOutputFlowControl() { var writeTasks = new Task[6]; var expectedFullFrameCountBeforeBackpressure = Http2PeerSettings.DefaultInitialWindowSize / _maxData.Length; var remainingBytesBeforeBackpressure = (int)Http2PeerSettings.DefaultInitialWindowSize % _maxData.Length; // Double the stream window to be 128KiB so it doesn't interfere with the rest of the test. _clientSettings.InitialWindowSize = Http2PeerSettings.DefaultInitialWindowSize * 2; await InitializeConnectionAsync(async context => { var streamId = context.Features.Get().StreamId; var abortedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var writeTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); context.RequestAborted.Register(() => { lock (_abortedStreamIdsLock) { _abortedStreamIds.Add(streamId); abortedTcs.SetResult(null); } }); try { writeTasks[streamId] = writeTcs.Task; // Flush headers even if the body can't yet be written because of flow control. await context.Response.Body.FlushAsync(); for (var i = 0; i < expectedFullFrameCountBeforeBackpressure; i++) { await context.Response.Body.WriteAsync(_maxData, 0, _maxData.Length); } await context.Response.Body.WriteAsync(_maxData, 0, remainingBytesBeforeBackpressure + 1); writeTcs.SetResult(null); await abortedTcs.Task; _runningStreams[streamId].SetResult(null); } catch (Exception ex) { _runningStreams[streamId].SetException(ex); throw; } }); // Start one stream that consumes the entire connection output window. await StartStreamAsync(1, _browserRequestHeaders, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, withLength: 37, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); for (var i = 0; i < expectedFullFrameCountBeforeBackpressure; i++) { await ExpectAsync(Http2FrameType.DATA, withLength: _maxData.Length, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 1); } await ExpectAsync(Http2FrameType.DATA, withLength: remainingBytesBeforeBackpressure, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 1); Assert.False(writeTasks[1].IsCompleted); // Start two more streams that immediately experience backpressure. // The headers, but not the data for the stream, can still be sent. await StartStreamAsync(3, _browserRequestHeaders, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, withLength: 37, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 3); await StartStreamAsync(5, _browserRequestHeaders, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, withLength: 37, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 5); // Close all pipes and wait for response to drain _pair.Application.Output.Complete(); _pair.Transport.Input.Complete(); _pair.Transport.Output.Complete(); await WaitForConnectionStopAsync(expectedLastStreamId: 5, ignoreNonGoAwayFrames: false); await WaitForAllStreamsAsync(); Assert.Contains(1, _abortedStreamIds); Assert.Contains(3, _abortedStreamIds); Assert.Contains(5, _abortedStreamIds); } [Fact] public async Task GOAWAY_Received_ContinuesAppsAwaitingStreamOutputFlowControle() { var writeTasks = new Task[6]; var initialWindowSize = _helloWorldBytes.Length / 2; // This only affects the stream windows. The connection-level window is always initialized at 64KiB. _clientSettings.InitialWindowSize = (uint)initialWindowSize; await InitializeConnectionAsync(async context => { var streamId = context.Features.Get().StreamId; var abortedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var writeTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); context.RequestAborted.Register(() => { lock (_abortedStreamIdsLock) { _abortedStreamIds.Add(streamId); abortedTcs.SetResult(null); } }); try { writeTasks[streamId] = writeTcs.Task; await context.Response.Body.WriteAsync(_helloWorldBytes, 0, _helloWorldBytes.Length); writeTcs.SetResult(null); await abortedTcs.Task; _runningStreams[streamId].SetResult(null); } catch (Exception ex) { _runningStreams[streamId].SetException(ex); throw; } }); async Task VerifyStreamBackpressure(int streamId) { await StartStreamAsync(streamId, _browserRequestHeaders, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, withLength: 37, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: streamId); var dataFrame = await ExpectAsync(Http2FrameType.DATA, withLength: initialWindowSize, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: streamId); Assert.True(_helloWorldBytes.AsSpan(0, initialWindowSize).SequenceEqual(dataFrame.DataPayload)); Assert.False(writeTasks[streamId].IsCompleted); } await VerifyStreamBackpressure(1); await VerifyStreamBackpressure(3); await VerifyStreamBackpressure(5); // Close all pipes and wait for response to drain _pair.Application.Output.Complete(); _pair.Transport.Input.Complete(); _pair.Transport.Output.Complete(); await WaitForConnectionStopAsync(expectedLastStreamId: 5, ignoreNonGoAwayFrames: false); 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: 1, 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: 1, 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 WINDOW_UPDATE_Received_OnConnection_IncreasesWindowAboveMaxValue_ConnectionError() { var maxIncrement = (int)(Http2PeerSettings.MaxWindowSize - Http2PeerSettings.DefaultInitialWindowSize); await InitializeConnectionAsync(_noopApplication); await SendWindowUpdateAsync(0, sizeIncrement: maxIncrement); await SendWindowUpdateAsync(0, sizeIncrement: 1); await WaitForConnectionErrorAsync( ignoreNonGoAwayFrames: false, expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.FLOW_CONTROL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorWindowUpdateSizeInvalid); } [Fact] public async Task WINDOW_UPDATE_Received_OnStream_IncreasesWindowAboveMaxValue_StreamError() { var maxIncrement = (int)(Http2PeerSettings.MaxWindowSize - Http2PeerSettings.DefaultInitialWindowSize); await InitializeConnectionAsync(_waitForAbortApplication); await StartStreamAsync(1, _browserRequestHeaders, endStream: true); await SendWindowUpdateAsync(1, sizeIncrement: maxIncrement); await SendWindowUpdateAsync(1, sizeIncrement: 1); await WaitForStreamErrorAsync( expectedStreamId: 1, expectedErrorCode: Http2ErrorCode.FLOW_CONTROL_ERROR, expectedErrorMessage: CoreStrings.Http2ErrorWindowUpdateSizeInvalid); await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); } [Fact] public async Task WINDOW_UPDATE_Received_OnConnection_Respected() { var expectedFullFrameCountBeforeBackpressure = Http2PeerSettings.DefaultInitialWindowSize / _maxData.Length; // Use this semaphore to wait until a new data frame is expected before trying to send it. // This way we're sure that if Response.Body.WriteAsync returns an incomplete task, it's because // of the flow control window and not Pipe backpressure. var expectingDataSem = new SemaphoreSlim(0); var backpressureObservedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var backpressureReleasedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); // Double the stream window to be 128KiB so it doesn't interfere with the rest of the test. _clientSettings.InitialWindowSize = Http2PeerSettings.DefaultInitialWindowSize * 2; await InitializeConnectionAsync(async context => { try { // Flush the headers so expectingDataSem is released. await context.Response.Body.FlushAsync(); for (var i = 0; i < expectedFullFrameCountBeforeBackpressure; i++) { await expectingDataSem.WaitAsync(); Assert.True(context.Response.Body.WriteAsync(_maxData, 0, _maxData.Length).IsCompleted); } await expectingDataSem.WaitAsync(); var lastWriteTask = context.Response.Body.WriteAsync(_maxData, 0, _maxData.Length); Assert.False(lastWriteTask.IsCompleted); backpressureObservedTcs.TrySetResult(null); await lastWriteTask; backpressureReleasedTcs.TrySetResult(null); } catch (Exception ex) { backpressureObservedTcs.TrySetException(ex); backpressureReleasedTcs.TrySetException(ex); throw; } }); await StartStreamAsync(1, _browserRequestHeaders, endStream: true); await ExpectAsync(Http2FrameType.HEADERS, withLength: 37, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 1); for (var i = 0; i < expectedFullFrameCountBeforeBackpressure; i++) { expectingDataSem.Release(); await ExpectAsync(Http2FrameType.DATA, withLength: _maxData.Length, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 1); } var remainingBytesBeforeBackpressure = (int)Http2PeerSettings.DefaultInitialWindowSize % _maxData.Length; var remainingBytesAfterBackpressure = _maxData.Length - remainingBytesBeforeBackpressure; expectingDataSem.Release(); await ExpectAsync(Http2FrameType.DATA, withLength: remainingBytesBeforeBackpressure, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 1); await backpressureObservedTcs.Task.DefaultTimeout(); await SendWindowUpdateAsync(0, remainingBytesAfterBackpressure); await backpressureReleasedTcs.Task.DefaultTimeout(); // This is the remaining data that could have come in the last frame if not for the flow control window, // so there's no need to release the semaphore again. await ExpectAsync(Http2FrameType.DATA, withLength: remainingBytesAfterBackpressure, 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); } [Fact] public async Task WINDOW_UPDATE_Received_OnStream_Respected() { var initialWindowSize = _helloWorldBytes.Length / 2; // This only affects the stream windows. The connection-level window is always initialized at 64KiB. _clientSettings.InitialWindowSize = (uint)initialWindowSize; 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 dataFrame1 = await ExpectAsync(Http2FrameType.DATA, withLength: initialWindowSize, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 1); await SendWindowUpdateAsync(1, initialWindowSize); var dataFrame2 = await ExpectAsync(Http2FrameType.DATA, withLength: initialWindowSize, 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.True(_helloWorldBytes.AsSpan(0, initialWindowSize).SequenceEqual(dataFrame1.DataPayload)); Assert.True(_helloWorldBytes.AsSpan(initialWindowSize, initialWindowSize).SequenceEqual(dataFrame2.DataPayload)); } [Fact] public async Task WINDOW_UPDATE_Received_OnStream_Respected_WhenInitialWindowSizeReducedMidStream() { // This only affects the stream windows. The connection-level window is always initialized at 64KiB. _clientSettings.InitialWindowSize = 6; 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 dataFrame1 = await ExpectAsync(Http2FrameType.DATA, withLength: 6, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 1); // Reduce the initial window size for response data by 3 bytes. _clientSettings.InitialWindowSize = 3; await SendSettingsAsync(); await ExpectAsync(Http2FrameType.SETTINGS, withLength: 0, withFlags: (byte)Http2SettingsFrameFlags.ACK, withStreamId: 0); await SendWindowUpdateAsync(1, 6); var dataFrame2 = await ExpectAsync(Http2FrameType.DATA, withLength: 3, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 1); await SendWindowUpdateAsync(1, 3); var dataFrame3 = await ExpectAsync(Http2FrameType.DATA, withLength: 3, 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.True(_helloWorldBytes.AsSpan(0, 6).SequenceEqual(dataFrame1.DataPayload)); Assert.True(_helloWorldBytes.AsSpan(6, 3).SequenceEqual(dataFrame2.DataPayload)); Assert.True(_helloWorldBytes.AsSpan(9, 3).SequenceEqual(dataFrame3.DataPayload)); } [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_Discarded(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); // Make sure the trailers are missing. https://github.com/aspnet/KestrelHttpServer/issues/2630 Assert.False(_receivedHeaders.ContainsKey("trailer-1")); Assert.False(_receivedHeaders.ContainsKey("trailer-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: 1, 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: 1, 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( 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[HeaderNames.Status]); Assert.Equal("0", _decodedHeaders["content-length"]); Assert.Equal(_4kHeaderValue, _decodedHeaders["a"]); Assert.Equal(_4kHeaderValue, _decodedHeaders["b"]); Assert.Equal(_4kHeaderValue, _decodedHeaders["c"]); Assert.Equal(_4kHeaderValue, _decodedHeaders["d"]); Assert.Equal(_4kHeaderValue, _decodedHeaders["e"]); Assert.Equal(_4kHeaderValue, _decodedHeaders["f"]); Assert.Equal(_4kHeaderValue, _decodedHeaders["g"]); Assert.Equal(_4kHeaderValue, _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: 1, 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)); await StopConnectionAsync(1, ignoreNonGoAwayFrames: false); Assert.Single(TestApplicationErrorLogger.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(TestApplicationErrorLogger.Messages, m => m.Exception is ConnectionResetException); } [Fact] public async Task OnInputOrOutputCompletedSendsFinalGOAWAY() { await InitializeConnectionAsync(_noopApplication); _connection.OnInputOrOutputCompleted(); await _closedStateReached.Task.DefaultTimeout(); VerifyGoAway(await ReceiveFrameAsync(), 0, Http2ErrorCode.NO_ERROR); } [Fact] public async Task AbortSendsFinalGOAWAY() { await InitializeConnectionAsync(_noopApplication); _connection.Abort(new ConnectionAbortedException()); await _closedStateReached.Task.DefaultTimeout(); VerifyGoAway(await ReceiveFrameAsync(), 0, Http2ErrorCode.INTERNAL_ERROR); } [Fact] public async Task CompletionSendsFinalGOAWAY() { await InitializeConnectionAsync(_noopApplication); // Completes ProcessRequestsAsync _pair.Application.Output.Complete(); await _closedStateReached.Task.DefaultTimeout(); VerifyGoAway(await ReceiveFrameAsync(), 0, Http2ErrorCode.NO_ERROR); } [Fact] public async Task StopProcessingNextRequestSendsGracefulGOAWAYAndWaitsForStreamsToComplete() { var task = Task.CompletedTask; await InitializeConnectionAsync(context => task); // Send and receive an unblocked request 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); // Send a blocked request var tcs = new TaskCompletionSource(TaskContinuationOptions.RunContinuationsAsynchronously); task = tcs.Task; await StartStreamAsync(3, _browserRequestHeaders, endStream: false); // Close pipe _pair.Application.Output.Complete(); // Assert connection closed await _closedStateReached.Task.DefaultTimeout(); VerifyGoAway(await ReceiveFrameAsync(), 3, Http2ErrorCode.NO_ERROR); // Assert connection shutdown is still blocked // ProcessRequestsAsync completes the connection's Input pipe var readTask = _pair.Application.Input.ReadAsync(); _pair.Application.Input.CancelPendingRead(); var result = await readTask; Assert.False(result.IsCompleted); // Unblock the request and ProcessRequestsAsync tcs.TrySetResult(null); await _connectionTask; // Assert connection's Input pipe is completed readTask = _pair.Application.Input.ReadAsync(); _pair.Application.Input.CancelPendingRead(); result = await readTask; Assert.True(result.IsCompleted); } [Fact] public async Task StopProcessingNextRequestSendsGracefulGOAWAYThenFinalGOAWAYWhenAllStreamsComplete() { await InitializeConnectionAsync(_echoApplication); await StartStreamAsync(1, _browserRequestHeaders, endStream: false); _connection.StopProcessingNextRequest(); await _closingStateReached.Task.DefaultTimeout(); VerifyGoAway(await ReceiveFrameAsync(), Int32.MaxValue, Http2ErrorCode.NO_ERROR); await SendDataAsync(1, _helloBytes, 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); await _closedStateReached.Task.DefaultTimeout(); VerifyGoAway(await ReceiveFrameAsync(), 1, Http2ErrorCode.NO_ERROR); } [Fact] public async Task AcceptNewStreamsDuringClosingConnection() { await InitializeConnectionAsync(_echoApplication); await StartStreamAsync(1, _browserRequestHeaders, endStream: false); _connection.StopProcessingNextRequest(); VerifyGoAway(await ReceiveFrameAsync(), Int32.MaxValue, Http2ErrorCode.NO_ERROR); await _closingStateReached.Task.DefaultTimeout(); await StartStreamAsync(3, _browserRequestHeaders, endStream: false); await SendDataAsync(1, _helloBytes, 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); await SendDataAsync(3, _helloBytes, true); await ExpectAsync(Http2FrameType.HEADERS, withLength: 37, withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS, withStreamId: 3); await ExpectAsync(Http2FrameType.DATA, withLength: 5, withFlags: (byte)Http2DataFrameFlags.NONE, withStreamId: 3); await ExpectAsync(Http2FrameType.DATA, withLength: 0, withFlags: (byte)Http2DataFrameFlags.END_STREAM, withStreamId: 3); await WaitForConnectionStopAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false); } [Fact] public async Task IgnoreNewStreamsDuringClosedConnection() { await InitializeConnectionAsync(_echoApplication); await StartStreamAsync(1, _browserRequestHeaders, endStream: false); _connection.OnInputOrOutputCompleted(); await _closedStateReached.Task.DefaultTimeout(); await StartStreamAsync(3, _browserRequestHeaders, endStream: false); await WaitForConnectionStopAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); } [Fact] public void IOExceptionDuringFrameProcessingLoggedAsInfo() { var ioException = new IOException(); _pair.Application.Output.Complete(ioException); Assert.Equal(TaskStatus.RanToCompletion, _connection.ProcessRequestsAsync(new DummyApplication(_noopApplication)).Status); var logMessage = TestApplicationErrorLogger.Messages.Single(m => m.LogLevel >= LogLevel.Information); Assert.Equal(LogLevel.Information, logMessage.LogLevel); Assert.Equal("Connection id \"(null)\" request processing ended abnormally.", logMessage.Message); Assert.Same(ioException, logMessage.Exception); } [Fact] public void UnexpectedExceptionDuringFrameProcessingLoggedAWarning() { var exception = new Exception(); _pair.Application.Output.Complete(exception); Assert.Equal(TaskStatus.RanToCompletion, _connection.ProcessRequestsAsync(new DummyApplication(_noopApplication)).Status); var logMessage = TestApplicationErrorLogger.Messages.Single(m => m.LogLevel >= LogLevel.Information); Assert.Equal(LogLevel.Warning, logMessage.LogLevel); Assert.Equal(CoreStrings.RequestProcessingEndError, logMessage.Message); Assert.Same(exception, logMessage.Exception); } 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(HeaderNames.Method, "GET"), new KeyValuePair(HeaderNames.Path, "/"), new KeyValuePair(HeaderNames.Authority, "127.0.0.1"), new KeyValuePair(HeaderNames.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(HeaderNames.Method, "GET"), new KeyValuePair(HeaderNames.Path, "/"), new KeyValuePair(HeaderNames.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 KeyValuePair(HeaderNames.Method, "CONNECT"); var headers = new[] { methodHeader }; data.Add(headers); return data; } } public static TheoryData>> PseudoHeaderFieldAfterRegularHeadersData { get { var data = new TheoryData>>(); var requestHeaders = new[] { new KeyValuePair(HeaderNames.Method, "GET"), new KeyValuePair(HeaderNames.Path, "/"), new KeyValuePair(HeaderNames.Authority, "127.0.0.1"), new KeyValuePair(HeaderNames.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; } } } }