File: Http2\Http2ConnectionTests.cs
Web Access
Project: src\src\Servers\Kestrel\test\InMemory.FunctionalTests\InMemory.FunctionalTests.csproj (InMemory.FunctionalTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Buffers;
using System.Collections;
using System.Globalization;
using System.Net.Http;
using System.Net.Http.HPack;
using System.Reflection;
using System.Security.Authentication;
using System.Text;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.InternalTesting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
using Moq;
using Microsoft.Extensions.Diagnostics.Metrics.Testing;
 
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests;
 
public class Http2ConnectionTests : Http2TestBase
{
    [Fact]
    public async Task MaxConcurrentStreamsLogging_ReachLimit_MessageLogged()
    {
        await InitializeConnectionAsync(_echoApplication);
 
        _connection.ServerSettings.MaxConcurrentStreams = 2;
 
        await StartStreamAsync(1, _browserRequestHeaders, endStream: false);
        Assert.Equal(0, LogMessages.Count(m => m.EventId.Name == "Http2MaxConcurrentStreamsReached"));
 
        // Log message because we've reached the stream limit
        await StartStreamAsync(3, _browserRequestHeaders, endStream: false);
        Assert.Equal(1, LogMessages.Count(m => m.EventId.Name == "Http2MaxConcurrentStreamsReached"));
 
        // This stream will error because it exceeds max concurrent streams
        await StartStreamAsync(5, _browserRequestHeaders, endStream: true);
        await WaitForStreamErrorAsync(5, Http2ErrorCode.REFUSED_STREAM, CoreStrings.Http2ErrorMaxStreams);
        Assert.Equal(1, LogMessages.Count(m => m.EventId.Name == "Http2MaxConcurrentStreamsReached"));
 
        await StopConnectionAsync(expectedLastStreamId: 5, ignoreNonGoAwayFrames: false);
        AssertConnectionNoError();
    }
 
    [Fact]
    public async Task FlowControl_NoAvailability_ResponseHeadersStillFlushed()
    {
        _clientSettings.InitialWindowSize = 0;
 
        await InitializeConnectionAsync(c =>
        {
            return c.Response.Body.WriteAsync(new byte[1]).AsTask();
        });
 
        await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 32,
            withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
            withStreamId: 1);
 
        await SendWindowUpdateAsync(streamId: 1, 1);
 
        await ExpectAsync(Http2FrameType.DATA,
            withLength: 1,
            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);
        AssertConnectionNoError();
    }
 
    [Fact]
    public async Task FlowControl_OneStream_CorrectlyAwaited()
    {
        await InitializeConnectionAsync(async c =>
        {
            // Send headers
            await c.Response.Body.FlushAsync();
 
            // Send large data (1 larger than window size)
            await c.Response.Body.WriteAsync(new byte[65540]);
        });
 
        // Ensure the connection window size is large enough
        await SendWindowUpdateAsync(streamId: 0, 65537);
 
        await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 32,
            withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
            withStreamId: 1);
        await ExpectAsync(Http2FrameType.DATA,
            withLength: 16384,
            withFlags: (byte)Http2DataFrameFlags.NONE,
            withStreamId: 1);
        await ExpectAsync(Http2FrameType.DATA,
            withLength: 16384,
            withFlags: (byte)Http2DataFrameFlags.NONE,
            withStreamId: 1);
        await ExpectAsync(Http2FrameType.DATA,
            withLength: 16384,
            withFlags: (byte)Http2DataFrameFlags.NONE,
            withStreamId: 1);
        await ExpectAsync(Http2FrameType.DATA,
            withLength: 16383,
            withFlags: (byte)Http2DataFrameFlags.NONE,
            withStreamId: 1);
 
        // 2 bytes remaining
 
        await SendWindowUpdateAsync(streamId: 1, 1);
        await ExpectAsync(Http2FrameType.DATA,
            withLength: 1,
            withFlags: (byte)Http2DataFrameFlags.NONE,
            withStreamId: 1);
 
        await SendWindowUpdateAsync(streamId: 1, 1);
        await ExpectAsync(Http2FrameType.DATA,
            withLength: 1,
            withFlags: (byte)Http2DataFrameFlags.NONE,
            withStreamId: 1);
 
        await SendWindowUpdateAsync(streamId: 1, 1);
        await ExpectAsync(Http2FrameType.DATA,
            withLength: 1,
            withFlags: (byte)Http2DataFrameFlags.NONE,
            withStreamId: 1);
 
        await SendWindowUpdateAsync(streamId: 1, 1);
        await ExpectAsync(Http2FrameType.DATA,
            withLength: 1,
            withFlags: (byte)Http2DataFrameFlags.NONE,
            withStreamId: 1);
 
        await SendWindowUpdateAsync(streamId: 1, 1);
        await ExpectAsync(Http2FrameType.DATA,
            withLength: 1,
            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);
        AssertConnectionNoError();
    }
 
    [Fact]
    public async Task RequestHeaderStringReuse_MultipleStreams_KnownHeaderReused()
    {
        var requestHeaders = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/hello"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>(InternalHeaderNames.Authority, "localhost:80"),
            new KeyValuePair<string, string>(HeaderNames.ContentType, "application/json")
        };
 
        await InitializeConnectionAsync(_readHeadersApplication);
 
        await StartStreamAsync(1, requestHeaders, endStream: true);
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 36,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
            withStreamId: 1);
 
        var contentType1 = _receivedHeaders["Content-Type"];
        var authority1 = _receivedRequestFields.Authority;
        var path1 = _receivedRequestFields.Path;
 
        // TriggerTick will trigger the stream to be returned to the pool so we can assert it
        TriggerTick();
 
        // Stream has been returned to the pool
        Assert.Equal(1, _connection.StreamPool.Count);
 
        await StartStreamAsync(3, requestHeaders, endStream: true);
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 6,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
            withStreamId: 3);
 
        var contentType2 = _receivedHeaders["Content-Type"];
        var authority2 = _receivedRequestFields.Authority;
        var path2 = _receivedRequestFields.Path;
 
        Assert.Same(contentType1, contentType2);
        Assert.Same(authority1, authority2);
        Assert.Same(path1, path2);
 
        await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
        AssertConnectionNoError();
    }
 
    [Fact]
    public async Task RequestHeaderStringReuse_MultipleStreams_KnownHeaderClearedIfNotReused()
    {
        const BindingFlags privateFlags = BindingFlags.NonPublic | BindingFlags.Instance;
 
        var requestHeaders1 = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/hello"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>(InternalHeaderNames.Authority, "localhost:80"),
            new KeyValuePair<string, string>(HeaderNames.ContentType, "application/json")
        };
 
        // Note: No content-type
        var requestHeaders2 = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/hello"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>(InternalHeaderNames.Authority, "localhost:80")
        };
 
        await InitializeConnectionAsync(_noopApplication);
 
        await StartStreamAsync(1, requestHeaders1, endStream: true);
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 36,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
            withStreamId: 1);
 
        // TriggerTick will trigger the stream to be returned to the pool so we can assert it
        TriggerTick();
 
        // Stream has been returned to the pool
        Assert.Equal(1, _connection.StreamPool.Count);
        Assert.True(_connection.StreamPool.TryPeek(out var stream1));
 
        // Hacky but required because header references is private.
        var headerReferences1 = typeof(HttpRequestHeaders).GetField("_headers", privateFlags).GetValue(stream1.RequestHeaders);
        var contentTypeValue1 = (StringValues)headerReferences1.GetType().GetField("_ContentType").GetValue(headerReferences1);
 
        await StartStreamAsync(3, requestHeaders2, endStream: true);
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 6,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
            withStreamId: 3);
 
        // TriggerTick will trigger the stream to be returned to the pool so we can assert it
        TriggerTick();
 
        // Stream has been returned to the pool
        Assert.Equal(1, _connection.StreamPool.Count);
        Assert.True(_connection.StreamPool.TryPeek(out var stream2));
 
        // Hacky but required because header references is private.
        var headerReferences2 = typeof(HttpRequestHeaders).GetField("_headers", privateFlags).GetValue(stream2.RequestHeaders);
        var contentTypeValue2 = (StringValues)headerReferences2.GetType().GetField("_ContentType").GetValue(headerReferences2);
 
        Assert.Equal("application/json", contentTypeValue1);
        Assert.Equal(StringValues.Empty, contentTypeValue2);
 
        await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
        AssertConnectionNoError();
    }
 
    private class ResponseTrailersWrapper : IHeaderDictionary
    {
        readonly IHeaderDictionary _innerHeaders;
 
        public ResponseTrailersWrapper(IHeaderDictionary headers)
        {
            _innerHeaders = headers;
        }
 
        public StringValues this[string key] { get => _innerHeaders[key]; set => _innerHeaders[key] = value; }
        public long? ContentLength { get => _innerHeaders.ContentLength; set => _innerHeaders.ContentLength = value; }
        public ICollection<string> Keys => _innerHeaders.Keys;
        public ICollection<StringValues> Values => _innerHeaders.Values;
        public int Count => _innerHeaders.Count;
        public bool IsReadOnly => _innerHeaders.IsReadOnly;
        public void Add(string key, StringValues value) => _innerHeaders.Add(key, value);
        public void Add(KeyValuePair<string, StringValues> item) => _innerHeaders.Add(item);
        public void Clear() => _innerHeaders.Clear();
        public bool Contains(KeyValuePair<string, StringValues> item) => _innerHeaders.Contains(item);
        public bool ContainsKey(string key) => _innerHeaders.ContainsKey(key);
        public void CopyTo(KeyValuePair<string, StringValues>[] array, int arrayIndex) => _innerHeaders.CopyTo(array, arrayIndex);
        public IEnumerator<KeyValuePair<string, StringValues>> GetEnumerator() => _innerHeaders.GetEnumerator();
        public bool Remove(string key) => _innerHeaders.Remove(key);
        public bool Remove(KeyValuePair<string, StringValues> item) => _innerHeaders.Remove(item);
        public bool TryGetValue(string key, out StringValues value) => _innerHeaders.TryGetValue(key, out value);
        IEnumerator IEnumerable.GetEnumerator() => _innerHeaders.GetEnumerator();
    }
 
    [Fact]
    public async Task ResponseTrailers_MultipleStreams_Reset()
    {
        var requestHeaders = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/hello"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>(InternalHeaderNames.Authority, "localhost:80"),
            new KeyValuePair<string, string>(HeaderNames.ContentType, "application/json")
        };
 
        var requestCount = 0;
        IHeaderDictionary trailersFirst = null;
        IHeaderDictionary trailersLast = null;
        await InitializeConnectionAsync(context =>
        {
            requestCount++;
 
            var trailersFeature = context.Features.Get<IHttpResponseTrailersFeature>();
            if (requestCount == 1)
            {
                trailersFirst = new ResponseTrailersWrapper(trailersFeature.Trailers);
                trailersFeature.Trailers = trailersFirst;
            }
            else
            {
                trailersLast = trailersFeature.Trailers;
            }
            trailersFeature.Trailers["trailer-" + requestCount] = "true";
            return Task.CompletedTask;
        });
 
        await StartStreamAsync(1, requestHeaders, endStream: true);
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 36,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS),
            withStreamId: 1);
 
        var trailersFrame = await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 16,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
            withStreamId: 1);
 
        _hpackDecoder.Decode(trailersFrame.PayloadSequence, endHeaders: true, handler: this);
 
        Assert.Single(_decodedHeaders);
        Assert.Equal("true", _decodedHeaders["trailer-1"]);
 
        _decodedHeaders.Clear();
 
        for (int i = 1; i < 3; i++)
        {
            int streamId = i * 2 + 1;
            // TriggerTick will trigger the stream to be returned to the pool so we can assert it
            TriggerTick();
 
            // Stream has been returned to the pool
            Assert.Equal(1, _connection.StreamPool.Count);
 
            await StartStreamAsync(streamId, requestHeaders, endStream: true);
 
            await ExpectAsync(Http2FrameType.HEADERS,
                withLength: 6,
                withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS),
                withStreamId: streamId);
 
            trailersFrame = await ExpectAsync(Http2FrameType.HEADERS,
                withLength: 16,
                withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
                withStreamId: streamId);
 
            _hpackDecoder.Decode(trailersFrame.PayloadSequence, endHeaders: true, handler: this);
 
            Assert.Single(_decodedHeaders);
            Assert.Equal("true", _decodedHeaders[$"trailer-{i + 1}"]);
 
            _decodedHeaders.Clear();
 
        }
 
        Assert.NotNull(trailersFirst);
        Assert.NotNull(trailersLast);
        Assert.NotSame(trailersFirst, trailersLast);
 
        await StopConnectionAsync(expectedLastStreamId: 5, ignoreNonGoAwayFrames: false);
        AssertConnectionNoError();
    }
 
    [Fact]
    public async Task StreamPool_SingleStream_ReturnedToPool()
    {
        // Add stream to Http2Connection._completedStreams inline with SetResult().
        var serverTcs = new TaskCompletionSource();
 
        await InitializeConnectionAsync(async context =>
        {
            await serverTcs.Task;
            await _echoApplication(context);
        });
 
        Assert.Equal(0, _connection.StreamPool.Count);
 
        await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
 
        var stream = _connection._streams[1];
        serverTcs.SetResult();
 
        // TriggerTick will trigger the stream to be returned to the pool so we can assert it
        TriggerTick();
 
        // Stream has been returned to the pool
        Assert.Equal(1, _connection.StreamPool.Count);
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 36,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
            withStreamId: 1);
 
        await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
        AssertConnectionNoError();
    }
 
    [Fact]
    public async Task StreamPool_MultipleStreamsConcurrent_StreamsReturnedToPool()
    {
        await InitializeConnectionAsync(_echoApplication);
 
        Assert.Equal(0, _connection.StreamPool.Count);
 
        await StartStreamAsync(1, _browserRequestHeaders, endStream: false);
        await StartStreamAsync(3, _browserRequestHeaders, endStream: false);
 
        await SendDataAsync(1, _helloBytes, endStream: true);
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 32,
            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, endStream: true);
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 2,
            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 WaitForAllStreamsAsync().DefaultTimeout();
 
        // TriggerTick will trigger the stream to be returned to the pool so we can assert it
        TriggerTick();
 
        // Streams have been returned to the pool
        Assert.Equal(2, _connection.StreamPool.Count);
 
        await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
    }
 
    [Fact]
    public async Task StreamPool_MultipleStreamsInSequence_PooledStreamReused()
    {
        TaskCompletionSource appDelegateTcs = null;
        object persistedState = null;
        var requestCount = 0;
 
        await InitializeConnectionAsync(async context =>
        {
            requestCount++;
            var persistentStateCollection = context.Features.Get<IPersistentStateFeature>().State;
            if (persistentStateCollection.TryGetValue("Counter", out var value))
            {
                persistedState = value;
            }
            persistentStateCollection["Counter"] = requestCount;
            await appDelegateTcs.Task;
        });
 
        Assert.Equal(0, _connection.StreamPool.Count);
 
        // Add stream to Http2Connection._completedStreams inline with SetResult().
        appDelegateTcs = new TaskCompletionSource();
        await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
 
        // Get the in progress stream
        var stream = _connection._streams[1];
 
        appDelegateTcs.TrySetResult();
 
        // TriggerTick will trigger the stream to be returned to the pool so we can assert it
        TriggerTick();
 
        // Stream has been returned to the pool
        Assert.Equal(1, _connection.StreamPool.Count);
        Assert.True(_connection.StreamPool.TryPeek(out var pooledStream));
        Assert.Equal(stream, pooledStream);
 
        // First request has no persisted state
        Assert.Null(persistedState);
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 36,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
            withStreamId: 1);
 
        // Add stream to Http2Connection._completedStreams inline with SetResult().
        appDelegateTcs = new TaskCompletionSource();
        await StartStreamAsync(3, _browserRequestHeaders, endStream: true);
 
        // New stream has been taken from the pool
        Assert.Equal(0, _connection.StreamPool.Count);
 
        appDelegateTcs.TrySetResult();
 
        // TriggerTick will trigger the stream to be returned to the pool so we can assert it
        TriggerTick();
 
        // Stream was reused and returned to the pool
        Assert.Equal(1, _connection.StreamPool.Count);
        Assert.True(_connection.StreamPool.TryPeek(out pooledStream));
        Assert.Equal(stream, pooledStream);
 
        // State persisted on first request was available on the second request
        Assert.Equal(1, (int)persistedState);
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 6,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
            withStreamId: 3);
 
        await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
        AssertConnectionNoError();
    }
 
    [Fact]
    public async Task StreamPool_StreamIsInvalidState_DontReturnedToPool()
    {
        // Add (or don't add) stream to Http2Connection._completedStreams inline with SetResult().
        var serverTcs = new TaskCompletionSource();
 
        await InitializeConnectionAsync(async context =>
        {
            await serverTcs.Task.DefaultTimeout();
 
            await context.Response.WriteAsync("Content");
            throw new InvalidOperationException("Put the stream into an invalid state by throwing after writing to response.");
        });
 
        await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
 
        var stream = _connection._streams[1];
        serverTcs.SetResult();
 
        // Wait for the stream to be completed
        await WaitForStreamAsync(stream.StreamId).DefaultTimeout();
 
        // TriggerTick will trigger the stream to be returned to the pool so we can assert it
        TriggerTick();
 
        var output = (Http2OutputProducer)stream.Output;
        Assert.True(output._disposed);
 
        // Stream is not returned to the pool
        Assert.Equal(0, _connection.StreamPool.Count);
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 32,
            withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
            withStreamId: 1);
        await ExpectAsync(Http2FrameType.DATA,
            withLength: 7,
            withFlags: (byte)Http2DataFrameFlags.NONE,
            withStreamId: 1);
        await WaitForStreamErrorAsync(1, Http2ErrorCode.INTERNAL_ERROR, null);
 
        await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
    }
 
    [Fact]
    public async Task StreamPool_EndedStreamErrorsOnStart_NotReturnedToPool()
    {
        await InitializeConnectionAsync(_echoApplication);
 
        _connection.ServerSettings.MaxConcurrentStreams = 1;
 
        await StartStreamAsync(1, _browserRequestHeaders, endStream: false);
 
        // This stream will error because it exceeds max concurrent streams
        await StartStreamAsync(3, _browserRequestHeaders, endStream: true);
        await WaitForStreamErrorAsync(3, Http2ErrorCode.REFUSED_STREAM, CoreStrings.Http2ErrorMaxStreams);
 
        // TriggerTick will trigger the stream to be returned to the pool so we can assert it
        TriggerTick();
 
        // Stream not returned to the pool
        Assert.Equal(0, _connection.StreamPool.Count);
 
        await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
        AssertConnectionNoError();
    }
 
    [Fact]
    public async Task StreamPool_UnendedStreamErrorsOnStart_NotReturnedToPool()
    {
        _serviceContext.ServerOptions.Limits.MinRequestBodyDataRate = null;
 
        await InitializeConnectionAsync(_echoApplication);
 
        _connection.ServerSettings.MaxConcurrentStreams = 1;
 
        await StartStreamAsync(1, _browserRequestHeaders, endStream: false);
 
        // This stream will error because it exceeds max concurrent streams
        await StartStreamAsync(3, _browserRequestHeaders, endStream: false);
        await WaitForStreamErrorAsync(3, Http2ErrorCode.REFUSED_STREAM, CoreStrings.Http2ErrorMaxStreams);
 
        // TriggerTick will trigger the stream to be returned to the pool so we can assert it
        TriggerTick();
 
        AdvanceTime(TimeSpan.FromTicks(Constants.RequestBodyDrainTimeout.Ticks + 1));
 
        // TriggerTick will trigger the stream to attempt to be returned to the pool
        TriggerTick();
 
        // Drain timeout has past but the stream was not returned because it is unfinished
        Assert.Equal(0, _connection.StreamPool.Count);
 
        await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
        AssertConnectionNoError();
    }
 
    [Fact]
    public async Task StreamPool_UnusedExpiredStream_RemovedFromPool()
    {
        await InitializeConnectionAsync(async context =>
        {
            await _echoApplication(context);
        });
 
        Assert.Equal(0, _connection.StreamPool.Count);
 
        await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 36,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
            withStreamId: 1);
 
        // TriggerTick will trigger the stream to be returned to the pool so we can assert it
        TriggerTick();
 
        // Stream has been returned to the pool
        Assert.Equal(1, _connection.StreamPool.Count);
 
        _connection.StreamPool.TryPeek(out var pooledStream);
 
        AdvanceTime(TimeSpan.FromSeconds(1));
 
        // Stream has not expired and is still in pool
        Assert.Equal(1, _connection.StreamPool.Count);
 
        AdvanceTime(TimeSpan.FromSeconds(6));
 
        // Stream has expired and has been removed from pool
        Assert.Equal(0, _connection.StreamPool.Count);
 
        // Removed stream should have been disposed
        Assert.True(((Http2OutputProducer)pooledStream.Output)._disposed);
 
        await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
        AssertConnectionNoError();
    }
 
    [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], endStream: true);
 
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: true,
            expectedLastStreamId: 1,
            expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorFrameOverLimit(length, Http2PeerSettings.MinAllowedMaxFrameSize));
        AssertConnectionEndReason(ConnectionEndReason.MaxFrameLengthExceeded);
    }
 
    [Fact]
    public async Task ServerSettings_ChangesRequestMaxFrameSize()
    {
        var length = Http2PeerSettings.MinAllowedMaxFrameSize + 10;
        _serviceContext.ServerOptions.Limits.Http2.MaxFrameSize = length;
 
        await InitializeConnectionAsync(_echoApplication, expectedSettingsCount: 5);
 
        await StartStreamAsync(1, _browserRequestHeaders, endStream: false);
        await SendDataAsync(1, new byte[length], endStream: true);
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 32,
            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);
        AssertConnectionNoError();
    }
 
    [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: 32,
            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);
        AssertConnectionNoError();
 
        Assert.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.PayloadSequence.ToArray()));
    }
 
    [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: 32,
            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);
        AssertConnectionNoError();
 
        Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame.PayloadSequence.ToArray()));
    }
 
    [Fact]
    public async Task DATA_Received_GreaterThanInitialWindowSize_ReadByStream()
    {
        var initialStreamWindowSize = _serviceContext.ServerOptions.Limits.Http2.InitialStreamWindowSize;
        var framesInStreamWindow = initialStreamWindowSize / Http2PeerSettings.DefaultMaxFrameSize;
        var initialConnectionWindowSize = _serviceContext.ServerOptions.Limits.Http2.InitialConnectionWindowSize;
        var framesInConnectionWindow = initialConnectionWindowSize / Http2PeerSettings.DefaultMaxFrameSize;
 
        // Grow the client stream windows so no stream WINDOW_UPDATEs need to be sent.
        _clientSettings.InitialWindowSize = int.MaxValue;
 
        await InitializeConnectionAsync(_echoApplication);
 
        // Grow the client connection windows so no connection WINDOW_UPDATEs need to be sent.
        await SendWindowUpdateAsync(0, int.MaxValue - (int)Http2PeerSettings.DefaultInitialWindowSize);
 
        await StartStreamAsync(1, _browserRequestHeaders, endStream: false);
 
        // Rounds down so we don't go over the half window size and trigger an update
        for (var i = 0; i < framesInStreamWindow / 2; i++)
        {
            await SendDataAsync(1, _maxData, endStream: false);
        }
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 32,
            withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
            withStreamId: 1);
 
        var dataFrames = new List<Http2FrameWithPayload>();
 
        for (var i = 0; i < framesInStreamWindow / 2; i++)
        {
            var dataFrame1 = await ExpectAsync(Http2FrameType.DATA,
                withLength: _maxData.Length,
                withFlags: (byte)Http2DataFrameFlags.NONE,
                withStreamId: 1);
            dataFrames.Add(dataFrame1);
        }
 
        // 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 dataFrame2 = await ExpectAsync(Http2FrameType.DATA,
            withLength: _maxData.Length,
            withFlags: (byte)Http2DataFrameFlags.NONE,
            withStreamId: 1);
        dataFrames.Add(dataFrame2);
 
        // Write a few more frames to get close to the connection window threshold
        var additionalFrames = (framesInConnectionWindow / 2) - (framesInStreamWindow / 2) - 1;
        for (var i = 0; i < additionalFrames; i++)
        {
            await SendDataAsync(1, _maxData, endStream: false);
 
            var dataFrame1 = await ExpectAsync(Http2FrameType.DATA,
                withLength: _maxData.Length,
                withFlags: (byte)Http2DataFrameFlags.NONE,
                withStreamId: 1);
            dataFrames.Add(dataFrame1);
        }
 
        // Write one more to cross the connection window update threshold
        await SendDataAsync(1, _maxData, endStream: false);
 
        var connectionWindowUpdateFrame1 = await ExpectAsync(Http2FrameType.WINDOW_UPDATE,
            withLength: 4,
            withFlags: (byte)Http2DataFrameFlags.NONE,
            withStreamId: 0);
 
        var dataFrame3 = await ExpectAsync(Http2FrameType.DATA,
            withLength: _maxData.Length,
            withFlags: (byte)Http2DataFrameFlags.NONE,
            withStreamId: 1);
        dataFrames.Add(dataFrame3);
 
        // End
        await SendDataAsync(1, new Memory<byte>(), endStream: true);
 
        await ExpectAsync(Http2FrameType.DATA,
            withLength: 0,
            withFlags: (byte)Http2DataFrameFlags.END_STREAM,
            withStreamId: 1);
 
        await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
        AssertConnectionNoError();
 
        foreach (var frame in dataFrames)
        {
            Assert.True(_maxData.AsSpan().SequenceEqual(frame.PayloadSequence.ToArray()));
        }
        var updateSize = ((framesInStreamWindow / 2) + 1) * _maxData.Length;
        Assert.Equal(updateSize, streamWindowUpdateFrame1.WindowUpdateSizeIncrement);
        updateSize = ((framesInConnectionWindow / 2) + 1) * _maxData.Length;
        Assert.Equal(updateSize, connectionWindowUpdateFrame1.WindowUpdateSizeIncrement);
    }
 
    [Fact]
    public async Task DATA_Received_RightAtWindowLimit_DoesNotPausePipe()
    {
        var initialStreamWindowSize = _serviceContext.ServerOptions.Limits.Http2.InitialStreamWindowSize;
        var framesInStreamWindow = initialStreamWindowSize / Http2PeerSettings.DefaultMaxFrameSize;
        var initialConnectionWindowSize = _serviceContext.ServerOptions.Limits.Http2.InitialConnectionWindowSize;
        var framesInConnectionWindow = initialConnectionWindowSize / Http2PeerSettings.DefaultMaxFrameSize;
 
        await InitializeConnectionAsync(_waitForAbortApplication);
 
        await StartStreamAsync(1, _browserRequestHeaders, endStream: false);
 
        // Rounds down so we don't go over the limit
        for (var i = 0; i < framesInStreamWindow; i++)
        {
            await SendDataAsync(1, _maxData, endStream: false);
        }
 
        var remainder = initialStreamWindowSize % (int)Http2PeerSettings.DefaultMaxFrameSize;
 
        // Write just to the limit.
        // This should not produce a async task from the request body pipe. See the Debug.Assert in Http2Stream.OnDataAsync
        await SendDataAsync(1, new Memory<byte>(_maxData, 0, remainder), endStream: false);
 
        // End
        await SendDataAsync(1, new Memory<byte>(), endStream: true);
 
        await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
        AssertConnectionNoError();
    }
 
    [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<byte>(_helloWorldBytes, i, 1), endStream: false);
        }
 
        await SendDataAsync(1, _noData, endStream: true);
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 32,
            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);
        AssertConnectionNoError();
 
        Assert.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.PayloadSequence.ToArray()));
    }
 
    [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: 32,
            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: 2,
            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);
        AssertConnectionNoError();
 
        Assert.True(_helloBytes.AsSpan().SequenceEqual(stream1DataFrame1.PayloadSequence.ToArray()));
        Assert.True(_worldBytes.AsSpan().SequenceEqual(stream1DataFrame2.PayloadSequence.ToArray()));
        Assert.True(_helloBytes.AsSpan().SequenceEqual(stream3DataFrame1.PayloadSequence.ToArray()));
        Assert.True(_worldBytes.AsSpan().SequenceEqual(stream3DataFrame2.PayloadSequence.ToArray()));
    }
 
    [Fact]
    public async Task DATA_Received_Multiplexed_GreaterThanInitialWindowSize_ReadByStream()
    {
        var initialStreamWindowSize = _serviceContext.ServerOptions.Limits.Http2.InitialStreamWindowSize;
        var initialConnectionWindowSize = _serviceContext.ServerOptions.Limits.Http2.InitialConnectionWindowSize;
        var framesInStreamWindow = initialStreamWindowSize / Http2PeerSettings.DefaultMaxFrameSize;
        var framesInConnectionWindow = initialConnectionWindowSize / Http2PeerSettings.DefaultMaxFrameSize;
 
        // Grow the client stream windows so no stream WINDOW_UPDATEs need to be sent.
        _clientSettings.InitialWindowSize = int.MaxValue;
 
        await InitializeConnectionAsync(_echoApplication);
 
        // Grow the client connection windows so no connection WINDOW_UPDATEs need to be sent.
        await SendWindowUpdateAsync(0, int.MaxValue - (int)Http2PeerSettings.DefaultInitialWindowSize);
 
        await StartStreamAsync(1, _browserRequestHeaders, endStream: false);
 
        // Rounds down so we don't go over the half window size and trigger an update
        for (var i = 0; i < framesInStreamWindow / 2; i++)
        {
            await SendDataAsync(1, _maxData, endStream: false);
        }
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 32,
            withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
            withStreamId: 1);
 
        var dataFrames = new List<Http2FrameWithPayload>();
 
        for (var i = 0; i < framesInStreamWindow / 2; i++)
        {
            var dataFrame1 = await ExpectAsync(Http2FrameType.DATA,
                withLength: _maxData.Length,
                withFlags: (byte)Http2DataFrameFlags.NONE,
                withStreamId: 1);
            dataFrames.Add(dataFrame1);
        }
 
        // Writing over half the initial window size induces a 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 dataFrame2 = await ExpectAsync(Http2FrameType.DATA,
            withLength: _maxData.Length,
            withFlags: (byte)Http2DataFrameFlags.NONE,
            withStreamId: 1);
        dataFrames.Add(dataFrame2);
 
        // No update expected for these
        var additionalFrames = (framesInConnectionWindow / 2) - (framesInStreamWindow / 2) - 1;
        for (var i = 0; i < additionalFrames; i++)
        {
            await SendDataAsync(1, _maxData, endStream: false);
 
            var dataFrame3 = await ExpectAsync(Http2FrameType.DATA,
                withLength: _maxData.Length,
                withFlags: (byte)Http2DataFrameFlags.NONE,
                withStreamId: 1);
            dataFrames.Add(dataFrame3);
        }
 
        // 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 connectionWindowUpdateFrame = await ExpectAsync(Http2FrameType.WINDOW_UPDATE,
            withLength: 4,
            withFlags: (byte)Http2DataFrameFlags.NONE,
            withStreamId: 0);
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 2,
            withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
            withStreamId: 3);
 
        var dataFrame4 = await ExpectAsync(Http2FrameType.DATA,
            withLength: _maxData.Length,
            withFlags: (byte)Http2DataFrameFlags.NONE,
            withStreamId: 3);
        dataFrames.Add(dataFrame4);
        await ExpectAsync(Http2FrameType.DATA,
            withLength: 0,
            withFlags: (byte)Http2DataFrameFlags.END_STREAM,
            withStreamId: 3);
 
        // Would trigger a stream window update, except it's the last frame.
        await SendDataAsync(1, _maxData, endStream: true);
 
        var dataFrame5 = await ExpectAsync(Http2FrameType.DATA,
            withLength: _maxData.Length,
            withFlags: (byte)Http2DataFrameFlags.NONE,
            withStreamId: 1);
        dataFrames.Add(dataFrame5);
        await ExpectAsync(Http2FrameType.DATA,
            withLength: 0,
            withFlags: (byte)Http2DataFrameFlags.END_STREAM,
            withStreamId: 1);
 
        await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
        AssertConnectionNoError();
 
        foreach (var frame in dataFrames)
        {
            Assert.True(_maxData.AsSpan().SequenceEqual(frame.PayloadSequence.ToArray()));
        }
        var updateSize = ((framesInStreamWindow / 2) + 1) * _maxData.Length;
        Assert.Equal(updateSize, streamWindowUpdateFrame.WindowUpdateSizeIncrement);
        updateSize = ((framesInConnectionWindow / 2) + 1) * _maxData.Length;
        Assert.Equal(updateSize, connectionWindowUpdateFrame.WindowUpdateSizeIncrement);
    }
 
    [Fact]
    public async Task DATA_Received_Multiplexed_AppMustNotBlockOtherFrames()
    {
        var stream1Read = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var stream1ReadFinished = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var stream3Read = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var stream3ReadFinished = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
 
        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<IHttp2StreamIdFeature>().StreamId == 1)
            {
                stream1Read.TrySetResult();
 
                await stream1ReadFinished.Task.DefaultTimeout();
            }
            else
            {
                stream3Read.TrySetResult();
 
                await stream3ReadFinished.Task.DefaultTimeout();
            }
            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: true);
        await stream1Read.Task.DefaultTimeout();
 
        await SendDataAsync(3, _helloBytes, endStream: true);
        await stream3Read.Task.DefaultTimeout();
 
        stream3ReadFinished.TrySetResult();
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 32,
            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.TrySetResult();
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 2,
            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);
        AssertConnectionNoError();
    }
 
    [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: 32,
            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);
        AssertConnectionNoError();
 
        Assert.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.PayloadSequence.ToArray()));
    }
 
    [Theory]
    [InlineData(0)]
    [InlineData(1)]
    [InlineData(255)]
    public async Task DATA_Received_WithPadding_CountsTowardsInputFlowControl(byte padLength)
    {
        var initialWindowSize = _serviceContext.ServerOptions.Limits.Http2.InitialStreamWindowSize;
        var framesInWindow = initialWindowSize / Http2PeerSettings.DefaultMaxFrameSize;
        var maxDataMinusPadding = _maxData.AsMemory(0, _maxData.Length - padLength - 1);
 
        // Grow the client stream windows so no stream WINDOW_UPDATEs need to be sent.
        _clientSettings.InitialWindowSize = int.MaxValue;
 
        await InitializeConnectionAsync(_echoApplication);
 
        // Grow the client connection windows so no connection WINDOW_UPDATEs need to be sent.
        await SendWindowUpdateAsync(0, int.MaxValue - (int)Http2PeerSettings.DefaultInitialWindowSize);
 
        await StartStreamAsync(1, _browserRequestHeaders, endStream: false);
        var dataSent = 0;
        // Rounds down so we don't go over the half window size and trigger an update
        for (var i = 0; i < framesInWindow / 2; i++)
        {
            await SendDataWithPaddingAsync(1, maxDataMinusPadding, padLength, endStream: false);
            dataSent += maxDataMinusPadding.Length;
        }
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 32,
            withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
            withStreamId: 1);
 
        // The frames come back in various sizes depending on the pipe buffers, and without the padding we sent.
        while (dataSent > 0)
        {
            var frame = await ReceiveFrameAsync();
            Assert.Equal(Http2FrameType.DATA, frame.Type);
            Assert.True(dataSent >= frame.PayloadLength);
            Assert.Equal(Http2DataFrameFlags.NONE, frame.DataFlags);
            Assert.Equal(1, frame.StreamId);
 
            dataSent -= frame.PayloadLength;
        }
 
        // Writing over half the initial window size induces a stream-level window update.
        await SendDataAsync(1, _maxData, endStream: false);
 
        var connectionWindowUpdateFrame = await ExpectAsync(Http2FrameType.WINDOW_UPDATE,
            withLength: 4,
            withFlags: (byte)Http2DataFrameFlags.NONE,
            withStreamId: 1);
 
        var dataFrame3 = await ExpectAsync(Http2FrameType.DATA,
            withLength: _maxData.Length,
            withFlags: (byte)Http2DataFrameFlags.NONE,
            withStreamId: 1);
 
        await SendDataAsync(1, new Memory<byte>(), endStream: true);
 
        await ExpectAsync(Http2FrameType.DATA,
            withLength: 0,
            withFlags: (byte)Http2DataFrameFlags.END_STREAM,
            withStreamId: 1);
 
        await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
        AssertConnectionNoError();
 
        Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame3.PayloadSequence.ToArray()));
 
        var updateSize = ((framesInWindow / 2) + 1) * _maxData.Length;
        Assert.Equal(updateSize, connectionWindowUpdateFrame.WindowUpdateSizeIncrement);
    }
 
    [Fact]
    public async Task DATA_Received_ButNotConsumedByApp_CountsTowardsInputFlowControl()
    {
        var initialConnectionWindowSize = _serviceContext.ServerOptions.Limits.Http2.InitialConnectionWindowSize;
        var framesConnectionInWindow = initialConnectionWindowSize / Http2PeerSettings.DefaultMaxFrameSize;
 
        await InitializeConnectionAsync(_noopApplication);
 
        await StartStreamAsync(1, _browserRequestHeaders, endStream: false);
        for (var i = 0; i < framesConnectionInWindow / 2; i++)
        {
            await SendDataAsync(1, _maxData, endStream: false);
        }
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 36,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
            withStreamId: 1);
 
        await WaitForStreamErrorAsync(expectedStreamId: 1, Http2ErrorCode.NO_ERROR, null);
        // Logged without an exception.
        Assert.Contains(LogMessages, m => m.Message.Contains("the application completed without reading the entire request body."));
 
        // Writing over half the initial window size induces a connection-level window update.
        // But no stream window update since this is the last frame.
        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);
        AssertConnectionNoError();
 
        var updateSize = ((framesConnectionInWindow / 2) + 1) * _maxData.Length;
        Assert.Equal(updateSize, connectionWindowUpdateFrame.WindowUpdateSizeIncrement);
    }
 
    [Fact]
    public async Task DATA_BufferRequestBodyLargerThanStreamSizeSmallerThanConnectionPipe_Works()
    {
        // This test was not written to allow for arbitrary initial stream sizes. It was tuned to the old defaults.
        _serviceContext.ServerOptions.Limits.Http2.InitialConnectionWindowSize = 128 * 1024;
        _serviceContext.ServerOptions.Limits.Http2.InitialStreamWindowSize = 96 * 1024;
 
        var initialStreamWindowSize = _serviceContext.ServerOptions.Limits.Http2.InitialStreamWindowSize;
        var framesInStreamWindow = initialStreamWindowSize / Http2PeerSettings.DefaultMaxFrameSize;
        var initialConnectionWindowSize = _serviceContext.ServerOptions.Limits.Http2.InitialConnectionWindowSize;
        var framesInConnectionWindow = initialConnectionWindowSize / Http2PeerSettings.DefaultMaxFrameSize;
 
        // Grow the client stream windows so no stream WINDOW_UPDATEs need to be sent.
        _clientSettings.InitialWindowSize = int.MaxValue;
 
        await InitializeConnectionAsync(async context =>
        {
            await context.Response.BodyWriter.FlushAsync();
            var readResult = await context.Request.BodyReader.ReadAsync();
            while (readResult.Buffer.Length != _maxData.Length * 4)
            {
                context.Request.BodyReader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
                readResult = await context.Request.BodyReader.ReadAsync();
            }
 
            context.Request.BodyReader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
 
            readResult = await context.Request.BodyReader.ReadAsync();
            Assert.Equal(readResult.Buffer.Length, _maxData.Length * 5);
 
            await context.Response.BodyWriter.WriteAsync(readResult.Buffer.ToArray());
 
            context.Request.BodyReader.AdvanceTo(readResult.Buffer.End);
        });
 
        // Grow the client connection windows so no connection WINDOW_UPDATEs need to be sent.
        await SendWindowUpdateAsync(0, int.MaxValue - (int)Http2PeerSettings.DefaultInitialWindowSize);
 
        await StartStreamAsync(1, _browserRequestHeaders, endStream: false);
 
        // Rounds down so we don't go over the half window size and trigger an update
        for (var i = 0; i < framesInStreamWindow / 2; i++)
        {
            await SendDataAsync(1, _maxData, endStream: false);
        }
 
        // trip over the update size.
        await SendDataAsync(1, _maxData, endStream: false);
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 32,
            withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
            withStreamId: 1);
 
        var dataFrames = new List<Http2FrameWithPayload>();
 
        var streamWindowUpdateFrame1 = await ExpectAsync(Http2FrameType.WINDOW_UPDATE,
            withLength: 4,
            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);
 
        for (var i = 0; i < framesInStreamWindow / 2 + 2; i++)
        {
            var dataFrame3 = await ExpectAsync(Http2FrameType.DATA,
                withLength: _maxData.Length,
                withFlags: (byte)Http2DataFrameFlags.NONE,
                withStreamId: 1);
            dataFrames.Add(dataFrame3);
        }
 
        var connectionWindowUpdateFrame = await ExpectAsync(Http2FrameType.WINDOW_UPDATE,
            withLength: 4,
            withFlags: (byte)Http2DataFrameFlags.NONE,
            withStreamId: 0);
        // End
 
        await ExpectAsync(Http2FrameType.DATA,
            withLength: 0,
            withFlags: (byte)Http2DataFrameFlags.END_STREAM,
            withStreamId: 1);
 
        await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
        AssertConnectionNoError();
 
        foreach (var frame in dataFrames)
        {
            Assert.True(_maxData.AsSpan().SequenceEqual(frame.PayloadSequence.ToArray()));
        }
 
        var updateSize = ((framesInStreamWindow / 2) + 1) * _maxData.Length;
        Assert.Equal(updateSize, streamWindowUpdateFrame1.WindowUpdateSizeIncrement);
        updateSize = ((framesInConnectionWindow / 2) + 1) * _maxData.Length;
        Assert.Equal(updateSize, connectionWindowUpdateFrame.WindowUpdateSizeIncrement);
    }
 
    [Fact]
    public async Task DATA_Received_StreamIdZero_ConnectionError()
    {
        await InitializeConnectionAsync(_noopApplication);
 
        await SendDataAsync(0, _noData, endStream: false);
 
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 0,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdZero(Http2FrameType.DATA));
        AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId);
    }
 
    [Fact]
    public async Task DATA_Received_StreamIdEven_ConnectionError()
    {
        await InitializeConnectionAsync(_noopApplication);
 
        await SendDataAsync(2, _noData, endStream: false);
 
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 0,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdEven(Http2FrameType.DATA, streamId: 2));
        AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId);
    }
 
    [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<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: true,
            expectedLastStreamId: 1,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorPaddingTooLong(Http2FrameType.DATA));
        AssertConnectionEndReason(ConnectionEndReason.InvalidDataPadding);
    }
 
    [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<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: true,
            expectedLastStreamId: 1,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorPaddingTooLong(Http2FrameType.DATA));
        AssertConnectionEndReason(ConnectionEndReason.InvalidDataPadding);
    }
 
    [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<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: true,
            expectedLastStreamId: 1,
            expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.DATA, expectedLength: 1));
        AssertConnectionEndReason(ConnectionEndReason.InvalidFrameLength);
    }
 
    [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<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 1,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.DATA, streamId: 1, headersStreamId: 1));
        AssertConnectionEndReason(ConnectionEndReason.UnexpectedFrame);
    }
 
    [Fact]
    public async Task DATA_Received_StreamIdle_ConnectionError()
    {
        await InitializeConnectionAsync(_noopApplication);
 
        await SendDataAsync(1, _helloWorldBytes, endStream: false);
 
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 0,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdle(Http2FrameType.DATA, streamId: 1));
        AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId);
    }
 
    [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<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 1,
            expectedErrorCode: Http2ErrorCode.STREAM_CLOSED,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.DATA, streamId: 1));
        AssertConnectionEndReason(ConnectionEndReason.FrameAfterStreamClose);
    }
 
    [Fact]
    public async Task DATA_Received_StreamClosed_ConnectionError()
    {
        await InitializeConnectionAsync(_noopApplication);
 
        await StartStreamAsync(1, _postRequestHeaders, endStream: true);
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 36,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
            withStreamId: 1);
 
        await SendDataAsync(1, _helloWorldBytes, endStream: false);
 
        // There's a race where either of these messages could be logged, depending on if the stream cleanup has finished yet.
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 1,
            expectedErrorCode: Http2ErrorCode.STREAM_CLOSED,
            expectedErrorMessage: new[] {
                    CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.DATA, streamId: 1),
                    CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.DATA, streamId: 1)
            });
        AssertConnectionEndReason(ConnectionEndReason.UnknownStream);
    }
 
    [Fact]
    public async Task Frame_MultipleStreams_CanBeCreatedIfClientCountIsLessThanActualMaxStreamCount()
    {
        _serviceContext.ServerOptions.Limits.Http2.MaxStreamsPerConnection = 1;
        var firstRequestBlock = new TaskCompletionSource();
        var firstRequestReceived = new TaskCompletionSource();
        var makeFirstRequestWait = false;
        await InitializeConnectionAsync(async context =>
        {
            if (!makeFirstRequestWait)
            {
                makeFirstRequestWait = true;
                firstRequestReceived.SetResult();
                await firstRequestBlock.Task.DefaultTimeout();
            }
        });
 
        await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
        await SendRstStreamAsync(1);
 
        await firstRequestReceived.Task.DefaultTimeout();
 
        await StartStreamAsync(3, _browserRequestHeaders, endStream: true);
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 36,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
            withStreamId: 3);
 
        firstRequestBlock.SetResult();
 
        await StopConnectionAsync(3, ignoreNonGoAwayFrames: false);
        AssertConnectionNoError();
    }
 
    [Fact]
    public async Task MaxTrackedStreams_SmallMaxConcurrentStreams_LowerLimitOf100Async()
    {
        _serviceContext.ServerOptions.Limits.Http2.MaxStreamsPerConnection = 1;
 
        await InitializeConnectionAsync(_noopApplication);
 
        Assert.Equal((uint)100, _connection.MaxTrackedStreams);
 
        await StopConnectionAsync(0, ignoreNonGoAwayFrames: false);
        AssertConnectionNoError();
    }
 
    [Fact]
    public async Task MaxTrackedStreams_DefaultMaxConcurrentStreams_DoubleLimit()
    {
        _serviceContext.ServerOptions.Limits.Http2.MaxStreamsPerConnection = 100;
 
        await InitializeConnectionAsync(_noopApplication);
 
        Assert.Equal((uint)200, _connection.MaxTrackedStreams);
 
        await StopConnectionAsync(0, ignoreNonGoAwayFrames: false);
        AssertConnectionNoError();
    }
 
    [Fact]
    public async Task MaxTrackedStreams_LargeMaxConcurrentStreams_DoubleLimit()
    {
        _serviceContext.ServerOptions.Limits.Http2.MaxStreamsPerConnection = int.MaxValue;
 
        await InitializeConnectionAsync(_noopApplication);
 
        Assert.Equal((uint)int.MaxValue * 2, _connection.MaxTrackedStreams);
 
        await StopConnectionAsync(0, ignoreNonGoAwayFrames: false);
        AssertConnectionNoError();
    }
 
    [Fact]
    public void MinimumMaxTrackedStreams()
    {
        _serviceContext.ServerOptions.Limits.Http2.MaxStreamsPerConnection = 1;
        CreateConnection();
        // Kestrel always tracks at least 100 streams
        Assert.Equal(100u, _connection.MaxTrackedStreams);
        _connection.Abort(new ConnectionAbortedException(), ConnectionEndReason.AbortedByApp);
        AssertConnectionEndReason(ConnectionEndReason.AbortedByApp);
    }
 
    [Fact]
    public Task Frame_MultipleStreams_RequestsNotFinished_LowMaxStreamsPerConnection_EnhanceYourCalmAfter100()
    {
        // Kestrel always tracks at least 100 streams
        return RequestUntilEnhanceYourCalm(maxStreamsPerConnection: 1, sentStreams: 101);
    }
 
    [Fact]
    public Task Frame_MultipleStreams_RequestsNotFinished_DefaultMaxStreamsPerConnection_EnhanceYourCalmAfterDoubleMaxStreams()
    {
        // Kestrel tracks max streams per connection * 2
        return RequestUntilEnhanceYourCalm(maxStreamsPerConnection: 100, sentStreams: 201);
    }
 
    [Fact]
    public async Task AbortConnectionAfterTooManyEnhanceYourCalms()
    {
        var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        await InitializeConnectionAsync(_ => Task.FromException(new InvalidOperationException("No requests should be processed")));
 
        // Reject *every* stream
        _connection.SendEnhanceYourCalmOnStartStream = true;
 
        var enhanceYourCalmMaximumCount = Http2Connection.EnhanceYourCalmTickWindowCount * Http2Connection.EnhanceYourCalmMaximumCount;
        var streamId = 1;
 
        // There's a burst of activity, but the limit isn't exceeded
 
        for (var i = 0; i < enhanceYourCalmMaximumCount; i++)
        {
            await StartStreamAsync(streamId, _browserRequestHeaders, endStream: false);
            await WaitForStreamErrorAsync(
                expectedStreamId: streamId,
                expectedErrorCode: Http2ErrorCode.ENHANCE_YOUR_CALM,
                expectedErrorMessage: CoreStrings.Http2TellClientToCalmDown);
            streamId += 2;
        }
 
        // The window expires
 
        for (var i = 0; i < Http2Connection.EnhanceYourCalmTickWindowCount; i++)
        {
            TriggerTick();
        }
 
        // We have room for more streams
 
        for (var i = 0; i < enhanceYourCalmMaximumCount; i++)
        {
            await StartStreamAsync(streamId, _browserRequestHeaders, endStream: false);
            await WaitForStreamErrorAsync(
                expectedStreamId: streamId,
                expectedErrorCode: Http2ErrorCode.ENHANCE_YOUR_CALM,
                expectedErrorMessage: CoreStrings.Http2TellClientToCalmDown);
            streamId += 2;
        }
 
        // The last stream exceeds the limit
 
        await StartStreamAsync(streamId, _browserRequestHeaders, endStream: false);
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: true,
            expectedLastStreamId: int.MaxValue,
            expectedErrorCode: Http2ErrorCode.ENHANCE_YOUR_CALM,
            expectedErrorMessage: CoreStrings.Http2ConnectionFaulted);
 
        AssertConnectionEndReason(ConnectionEndReason.StreamResetLimitExceeded);
    }
 
    private async Task RequestUntilEnhanceYourCalm(int maxStreamsPerConnection, int sentStreams)
    {
        _serviceContext.ServerOptions.Limits.Http2.MaxStreamsPerConnection = maxStreamsPerConnection;
        var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        await InitializeConnectionAsync(async context =>
        {
            await tcs.Task.DefaultTimeout();
        });
 
        var streamId = 1;
        for (var i = 0; i < sentStreams - 1; i++)
        {
            await StartStreamAsync(streamId, _browserRequestHeaders, endStream: true);
            await SendRstStreamAsync(streamId);
 
            streamId += 2;
        }
 
        await StartStreamAsync(streamId, _browserRequestHeaders, endStream: true);
        await WaitForStreamErrorAsync(
            expectedStreamId: streamId,
            expectedErrorCode: Http2ErrorCode.ENHANCE_YOUR_CALM,
            expectedErrorMessage: CoreStrings.Http2TellClientToCalmDown);
 
        tcs.SetResult();
 
        await StopConnectionAsync(streamId, ignoreNonGoAwayFrames: false);
        AssertConnectionNoError();
    }
 
    [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: 36,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
            withStreamId: 3);
 
        await SendDataAsync(1, _helloWorldBytes, endStream: true);
 
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 3,
            expectedErrorCode: Http2ErrorCode.STREAM_CLOSED,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.DATA, streamId: 1));
 
        AssertConnectionEndReason(ConnectionEndReason.UnknownStream);
    }
 
    [Fact]
    public async Task DATA_Received_NoStreamWindowSpace_ConnectionError()
    {
        var initialWindowSize = _serviceContext.ServerOptions.Limits.Http2.InitialStreamWindowSize;
        var framesInWindow = (initialWindowSize / Http2PeerSettings.DefaultMaxFrameSize) + 1; // Round up to overflow the window
 
        await InitializeConnectionAsync(_waitForAbortApplication);
 
        await StartStreamAsync(1, _browserRequestHeaders, endStream: false);
 
        for (var i = 0; i < framesInWindow; i++)
        {
            await SendDataAsync(1, _maxData, endStream: false);
        }
 
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 1,
            expectedErrorCode: Http2ErrorCode.FLOW_CONTROL_ERROR,
            expectedErrorMessage: CoreStrings.Http2ErrorFlowControlWindowExceeded);
 
        AssertConnectionEndReason(ConnectionEndReason.FlowControlWindowExceeded);
    }
 
    [Fact]
    public async Task DATA_Received_NoConnectionWindowSpace_ConnectionError()
    {
        var initialWindowSize = _serviceContext.ServerOptions.Limits.Http2.InitialConnectionWindowSize;
        var framesInWindow = initialWindowSize / Http2PeerSettings.DefaultMaxFrameSize;
 
        await InitializeConnectionAsync(_waitForAbortApplication);
 
        await StartStreamAsync(1, _browserRequestHeaders, endStream: false);
        for (var i = 0; i < framesInWindow / 2; i++)
        {
            await SendDataAsync(1, _maxData, endStream: false);
        }
 
        await StartStreamAsync(3, _browserRequestHeaders, endStream: false);
        for (var i = 0; i < framesInWindow / 2; i++)
        {
            await SendDataAsync(3, _maxData, endStream: false);
        }
        // One extra to overflow the connection window
        await SendDataAsync(3, _maxData, endStream: false);
 
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 3,
            expectedErrorCode: Http2ErrorCode.FLOW_CONTROL_ERROR,
            expectedErrorMessage: CoreStrings.Http2ErrorFlowControlWindowExceeded);
 
        AssertConnectionEndReason(ConnectionEndReason.FlowControlWindowExceeded);
    }
 
    [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<IHttp2StreamIdFeature>().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();
            }
            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: 32,
            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: 6,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.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();
 
        AssertConnectionNoError();
    }
 
    [Fact]
    [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/44415")]
    public async Task OutputFlowControl_ConnectionAndRequestAborted_NoException()
    {
        // Ensure the stream window size is bigger than the connection window size
        _clientSettings.InitialWindowSize = _clientSettings.InitialWindowSize * 2;
 
        var connectionAbortedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var requestAbortedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
 
        await InitializeConnectionAsync(async context =>
        {
            // Exceed connection window size
            await context.Response.WriteAsync(new string('!', 65536));
 
            await connectionAbortedTcs.Task;
 
            try
            {
                context.Abort();
                requestAbortedTcs.SetResult();
            }
            catch (Exception ex)
            {
                requestAbortedTcs.SetException(ex);
            }
        }).DefaultTimeout();
 
        await StartStreamAsync(1, _browserRequestHeaders, endStream: true).DefaultTimeout();
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 32,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS),
            withStreamId: 1).DefaultTimeout();
 
        await ExpectAsync(Http2FrameType.DATA,
            withLength: 16384,
            withFlags: (byte)(Http2DataFrameFlags.NONE),
            withStreamId: 1).DefaultTimeout();
 
        await ExpectAsync(Http2FrameType.DATA,
            withLength: 16384,
            withFlags: (byte)(Http2DataFrameFlags.NONE),
            withStreamId: 1).DefaultTimeout();
 
        await ExpectAsync(Http2FrameType.DATA,
            withLength: 16384,
            withFlags: (byte)(Http2DataFrameFlags.NONE),
            withStreamId: 1).DefaultTimeout();
 
        await ExpectAsync(Http2FrameType.DATA,
            withLength: 16383,
            withFlags: (byte)(Http2DataFrameFlags.NONE),
            withStreamId: 1).DefaultTimeout();
 
        _connection.HandleReadDataRateTimeout();
 
        connectionAbortedTcs.SetResult();
 
        // Task completing successfully means HttpContext.Abort didn't throw
        await requestAbortedTcs.Task.DefaultTimeout();
    }
 
    [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: 36,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
            withStreamId: 1);
 
        await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
 
        AssertConnectionNoError();
    }
 
    [Fact]
    public async Task StreamWindow_BiggerThan_ConnectionWindow()
    {
        // This only affects the stream windows. The connection-level window is always initialized at 64KiB.
        _clientSettings.InitialWindowSize = 16384 * 5;
 
        await InitializeConnectionAsync(async context =>
        {
            var data = new byte[16384 * 4];
            data.AsSpan().Fill(1);
            await context.Response.Body.WriteAsync(data);
        });
 
        await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 32,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS),
            withStreamId: 1).DefaultTimeout();
 
        for (int i = 0; i < 3; i++)
        {
            await ExpectAsync(Http2FrameType.DATA,
                withLength: 16384,
                withFlags: (byte)(Http2DataFrameFlags.NONE),
                withStreamId: 1).DefaultTimeout();
        }
 
        // Now we've consumed the entire connection window, and there's one more frame to write
        await ExpectAsync(Http2FrameType.DATA,
            withLength: 16383,
            withFlags: (byte)(Http2DataFrameFlags.NONE),
            withStreamId: 1).DefaultTimeout();
 
        // Send more than enough bytes for the window update
        await SendWindowUpdateAsync(0, 1);
 
        // Expect the last frame
        await ExpectAsync(Http2FrameType.DATA,
            withLength: 1,
            withFlags: (byte)(Http2DataFrameFlags.NONE),
            withStreamId: 1).DefaultTimeout();
 
        // 0 length end of stream frame
        await ExpectAsync(Http2FrameType.DATA,
            withLength: 0,
            withFlags: (byte)Http2DataFrameFlags.END_STREAM,
            withStreamId: 1);
 
        await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
 
        AssertConnectionNoError();
    }
 
    [Fact]
    public async Task HEADERS_Received_Decoded()
    {
        await InitializeConnectionAsync(_readHeadersApplication);
 
        await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 36,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
            withStreamId: 1);
 
        VerifyDecodedRequestHeaders(_browserRequestHeaders);
 
        await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
 
        AssertConnectionNoError();
    }
 
    [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: 36,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
            withStreamId: 1);
 
        VerifyDecodedRequestHeaders(_browserRequestHeaders);
 
        await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
 
        AssertConnectionNoError();
    }
 
    [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: 36,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
            withStreamId: 1);
 
        VerifyDecodedRequestHeaders(_browserRequestHeaders);
 
        await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
 
        AssertConnectionNoError();
    }
 
    [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: 36,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
            withStreamId: 1);
 
        VerifyDecodedRequestHeaders(_browserRequestHeaders);
 
        await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
 
        AssertConnectionNoError();
    }
 
    [Theory]
    [InlineData(true)]
    [InlineData(false)]
    public async Task HEADERS_Received_WithTrailers_Available(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: 36,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.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: 6,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
            withStreamId: 1);
 
        VerifyDecodedRequestHeaders(_browserRequestHeaders);
 
        // Make sure the trailers are in the trailers collection.
        foreach (var header in _requestTrailers)
        {
            Assert.False(_receivedHeaders.ContainsKey(header.Key));
            Assert.True(_receivedTrailers.ContainsKey(header.Key));
            Assert.Equal(header.Value, _receivedTrailers[header.Key]);
        }
 
        await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
 
        AssertConnectionNoError();
    }
 
    [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);
 
        Assert.Equal(new byte[] { 0x08, 0x03, (byte)'1', (byte)'0', (byte)'0' }, frame.PayloadSequence.ToArray());
 
        await SendDataAsync(1, _helloBytes, endStream: true);
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 32,
            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: 1, ignoreNonGoAwayFrames: false);
 
        AssertConnectionNoError();
    }
 
    [Fact]
    public async Task HEADERS_Received_AppCannotBlockOtherFrames()
    {
        var firstRequestReceived = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var finishFirstRequest = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var secondRequestReceived = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var finishSecondRequest = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
 
        await InitializeConnectionAsync(async context =>
        {
            if (!firstRequestReceived.Task.IsCompleted)
            {
                firstRequestReceived.TrySetResult();
 
                await finishFirstRequest.Task.DefaultTimeout();
            }
            else
            {
                secondRequestReceived.TrySetResult();
 
                await finishSecondRequest.Task.DefaultTimeout();
            }
        });
 
        await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
 
        await firstRequestReceived.Task.DefaultTimeout();
 
        await StartStreamAsync(3, _browserRequestHeaders, endStream: true);
 
        await secondRequestReceived.Task.DefaultTimeout();
 
        finishSecondRequest.TrySetResult();
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 36,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
            withStreamId: 3);
 
        finishFirstRequest.TrySetResult();
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 6,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
            withStreamId: 1);
 
        await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
 
        AssertConnectionNoError();
    }
 
    [Fact]
    public async Task HEADERS_HeaderTableSizeLimitZero_Received_DynamicTableUpdate()
    {
        _serviceContext.ServerOptions.Limits.Http2.HeaderTableSize = 0;
 
        await InitializeConnectionAsync(_noopApplication, expectedSettingsCount: 5);
 
        await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
 
        _hpackEncoder.UpdateMaxHeaderTableSize(0);
 
        var headerFrame = await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 38,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
            withStreamId: 1);
 
        const byte DynamicTableSizeUpdateMask = 0xe0;
 
        var integerDecoder = new IntegerDecoder();
        Assert.True(integerDecoder.BeginTryDecode((byte)(headerFrame.Payload.Span[0] & ~DynamicTableSizeUpdateMask), prefixLength: 5, out var result));
 
        // Dynamic table update from the server
        Assert.Equal(0, result);
 
        await StartStreamAsync(3, _browserRequestHeaders, endStream: true);
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 37,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
            withStreamId: 3);
 
        await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
 
        AssertConnectionNoError();
    }
 
    [Fact]
    public async Task HEADERS_ResponseSetsIgnoreIndexAndNeverIndexValues_HeadersParsed()
    {
        await InitializeConnectionAsync(c =>
        {
            c.Response.ContentLength = 0;
            c.Response.Headers.SetCookie = "SetCookie!";
            c.Response.Headers.ContentDisposition = "ContentDisposition!";
 
            return Task.CompletedTask;
        });
 
        await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
 
        var frame = await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 90,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
            withStreamId: 1);
 
        var handler = new TestHttpHeadersHandler();
 
        var hpackDecoder = new HPackDecoder();
        hpackDecoder.Decode(new ReadOnlySequence<byte>(frame.Payload), endHeaders: true, handler);
        hpackDecoder.CompleteDecode();
 
        Assert.Equal("200", handler.Headers[":status"]);
        Assert.Equal("SetCookie!", handler.Headers[HeaderNames.SetCookie]);
        Assert.Equal("ContentDisposition!", handler.Headers[HeaderNames.ContentDisposition]);
        Assert.Equal("0", handler.Headers[HeaderNames.ContentLength]);
 
        await StartStreamAsync(3, _browserRequestHeaders, endStream: true);
 
        frame = await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 60,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
            withStreamId: 3);
 
        handler = new TestHttpHeadersHandler();
 
        hpackDecoder.Decode(new ReadOnlySequence<byte>(frame.Payload), endHeaders: true, handler);
        hpackDecoder.CompleteDecode();
 
        Assert.Equal("200", handler.Headers[":status"]);
        Assert.Equal("SetCookie!", handler.Headers[HeaderNames.SetCookie]);
        Assert.Equal("ContentDisposition!", handler.Headers[HeaderNames.ContentDisposition]);
        Assert.Equal("0", handler.Headers[HeaderNames.ContentLength]);
 
        await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
    }
 
    private class TestHttpHeadersHandler : IHttpStreamHeadersHandler
    {
        public readonly Dictionary<string, StringValues> Headers = new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase);
 
        public void OnDynamicIndexedHeader(int? index, ReadOnlySpan<byte> name, ReadOnlySpan<byte> value)
        {
            OnHeader(name, value);
        }
 
        public void OnHeader(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value)
        {
            var nameString = Encoding.ASCII.GetString(name);
            var valueString = Encoding.ASCII.GetString(value);
 
            if (Headers.TryGetValue(nameString, out var values))
            {
                var l = values.ToList();
                l.Add(valueString);
 
                Headers[nameString] = new StringValues(l.ToArray());
            }
            else
            {
                Headers[nameString] = new StringValues(valueString);
            }
        }
 
        public void OnHeadersComplete(bool endStream)
        {
            throw new NotImplementedException();
        }
 
        public void OnStaticIndexedHeader(int index)
        {
            ref readonly var entry = ref H2StaticTable.Get(index - 1);
            OnHeader(entry.Name, entry.Value);
        }
 
        public void OnStaticIndexedHeader(int index, ReadOnlySpan<byte> value)
        {
            OnHeader(H2StaticTable.Get(index - 1).Name, value);
        }
    }
 
    [Fact]
    public async Task HEADERS_DisableDynamicHeaderCompression_HeadersNotCompressed()
    {
        _serviceContext.ServerOptions.AllowResponseHeaderCompression = false;
 
        await InitializeConnectionAsync(_noopApplication);
 
        await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 37,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
            withStreamId: 1);
 
        await StartStreamAsync(3, _browserRequestHeaders, endStream: true);
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 37,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
            withStreamId: 3);
 
        await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
    }
 
    [Fact]
    public async Task HEADERS_OverMaxStreamLimit_Refused()
    {
        CreateConnection();
 
        _connection.ServerSettings.MaxConcurrentStreams = 1;
 
        var requestBlocker = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        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();
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 36,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.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<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 0,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdZero(Http2FrameType.HEADERS));
 
        AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId);
    }
 
    [Fact]
    public async Task HEADERS_Received_StreamIdEven_ConnectionError()
    {
        await InitializeConnectionAsync(_noopApplication);
 
        await StartStreamAsync(2, _browserRequestHeaders, endStream: true);
 
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 0,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdEven(Http2FrameType.HEADERS, streamId: 2));
 
        AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId);
    }
 
    [Fact]
    public async Task HEADERS_Received_StreamClosed_ConnectionError()
    {
        await InitializeConnectionAsync(_noopApplication);
 
        await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 36,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.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);
 
        // There's a race where either of these messages could be logged, depending on if the stream cleanup has finished yet.
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 1,
            expectedErrorCode: Http2ErrorCode.STREAM_CLOSED,
            expectedErrorMessage: new[] {
                    CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1),
                    CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.HEADERS, streamId: 1)
            });
 
        AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId);
    }
 
    [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<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 1,
            expectedErrorCode: Http2ErrorCode.STREAM_CLOSED,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.HEADERS, streamId: 1));
 
        AssertConnectionEndReason(ConnectionEndReason.FrameAfterStreamClose);
    }
 
    [Fact]
    public async Task HEADERS_Received_StreamClosedImplicitly_ConnectionError()
    {
        await InitializeConnectionAsync(_noopApplication);
 
        await StartStreamAsync(3, _browserRequestHeaders, endStream: true);
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 36,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.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<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 3,
            expectedErrorCode: Http2ErrorCode.STREAM_CLOSED,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1));
 
        AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId);
    }
 
    [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<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: true,
            expectedLastStreamId: 0,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorPaddingTooLong(Http2FrameType.HEADERS));
 
        AssertConnectionEndReason(ConnectionEndReason.InvalidDataPadding);
    }
 
    [Fact]
    public async Task HEADERS_Received_PaddingFieldMissing_ConnectionError()
    {
        await InitializeConnectionAsync(_noopApplication);
 
        await SendInvalidHeadersFrameAsync(1, payloadLength: 0, padLength: 1);
 
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: true,
            expectedLastStreamId: 0,
            expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.HEADERS, expectedLength: 1));
 
        AssertConnectionEndReason(ConnectionEndReason.InvalidFrameLength);
    }
 
    [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<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: true,
            expectedLastStreamId: 0,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorPaddingTooLong(Http2FrameType.HEADERS));
 
        AssertConnectionEndReason(ConnectionEndReason.InvalidDataPadding);
    }
 
    [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<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 1,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.HEADERS, streamId: 3, headersStreamId: 1));
 
        AssertConnectionEndReason(ConnectionEndReason.UnexpectedFrame);
    }
 
    [Fact]
    public async Task HEADERS_Received_WithPriority_StreamDependencyOnSelf_ConnectionError()
    {
        await InitializeConnectionAsync(_readHeadersApplication);
 
        await SendHeadersWithPriorityAsync(1, _browserRequestHeaders, priority: 42, streamDependency: 1, endStream: true);
 
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 0,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamSelfDependency(Http2FrameType.HEADERS, streamId: 1));
 
        AssertConnectionEndReason(ConnectionEndReason.StreamSelfDependency);
    }
 
    [Fact]
    public async Task HEADERS_Received_IncompleteHeaderBlock_ConnectionError()
    {
        await InitializeConnectionAsync(_noopApplication);
 
        await SendIncompleteHeadersFrameAsync(streamId: 1);
 
        await WaitForConnectionErrorAsync<HPackDecodingException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 1,
            expectedErrorCode: Http2ErrorCode.COMPRESSION_ERROR,
            expectedErrorMessage: SR.net_http_hpack_incomplete_header_block);
 
        AssertConnectionEndReason(ConnectionEndReason.ErrorReadingHeaders);
    }
 
    [Fact]
    public async Task HEADERS_Received_IntegerOverLimit_ConnectionError()
    {
        await InitializeConnectionAsync(_noopApplication);
 
        var outputWriter = _pair.Application.Output;
        var frame = new Http2Frame();
 
        frame.PrepareHeaders(Http2HeadersFrameFlags.END_HEADERS, 1);
        frame.PayloadLength = 7;
        var payload = new byte[]
        {
                // Set up an incomplete Literal Header Field w/ Incremental Indexing frame,
                0x00,
                // with an name of size that's greater than int.MaxValue
                0x7f, 0x80, 0x80, 0x80, 0x80, 0x7f
        };
 
        Http2FrameWriter.WriteHeader(frame, outputWriter);
        await SendAsync(payload);
 
        await WaitForConnectionErrorAsync<HPackDecodingException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 1,
            expectedErrorCode: Http2ErrorCode.COMPRESSION_ERROR,
            expectedErrorMessage: SR.net_http_hpack_bad_integer);
 
        AssertConnectionEndReason(ConnectionEndReason.ErrorReadingHeaders);
    }
 
    [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<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 1,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: expectedErrorMessage);
 
        AssertConnectionEndReason(ConnectionEndReason.InvalidRequestHeaders);
    }
 
    [Theory]
    [InlineData((int)Http2HeadersFrameFlags.NONE)]
    [InlineData((int)Http2HeadersFrameFlags.END_HEADERS)]
    public async Task HEADERS_Received_WithTrailers_EndStreamNotSet_ConnectionError(int intFlags)
    {
        var flags = (Http2HeadersFrameFlags)intFlags;
        await InitializeConnectionAsync(_readTrailersApplication);
 
        await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, _browserRequestHeaders);
        await SendHeadersAsync(1, flags, _requestTrailers);
 
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 1,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: CoreStrings.Http2ErrorHeadersWithTrailersNoEndStream);
 
        AssertConnectionEndReason(ConnectionEndReason.MissingStreamEnd);
    }
 
    [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<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 1,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: CoreStrings.HttpErrorHeaderNameUppercase);
 
        AssertConnectionEndReason(ConnectionEndReason.InvalidRequestHeaders);
    }
 
    [Fact]
    public Task HEADERS_Received_HeaderBlockContainsUnknownPseudoHeaderField_ConnectionError()
    {
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>(":unknown", "0"),
        };
 
        return HEADERS_Received_InvalidHeaderFields_ConnectionError(
            headers,
            expectedErrorMessage: CoreStrings.HttpErrorUnknownPseudoHeaderField,
            expectedEndReason: ConnectionEndReason.InvalidRequestHeaders);
    }
 
    [Fact]
    public Task HEADERS_Received_HeaderBlockContainsResponsePseudoHeaderField_ConnectionError()
    {
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>(InternalHeaderNames.Status, "200"),
        };
 
        return HEADERS_Received_InvalidHeaderFields_ConnectionError(
            headers,
            expectedErrorMessage: CoreStrings.HttpErrorResponsePseudoHeaderField,
            expectedEndReason: ConnectionEndReason.InvalidRequestHeaders);
    }
 
    [Theory]
    [MemberData(nameof(DuplicatePseudoHeaderFieldData))]
    public Task HEADERS_Received_HeaderBlockContainsDuplicatePseudoHeaderField_ConnectionError(IEnumerable<KeyValuePair<string, string>> headers)
    {
        return HEADERS_Received_InvalidHeaderFields_ConnectionError(
            headers,
            expectedErrorMessage: CoreStrings.HttpErrorDuplicatePseudoHeaderField,
            expectedEndReason: ConnectionEndReason.InvalidRequestHeaders);
    }
 
    [Theory]
    [MemberData(nameof(ConnectMissingPseudoHeaderFieldData))]
    public async Task HEADERS_Received_HeaderBlockDoesNotContainMandatoryPseudoHeaderField_MethodIsCONNECT_NoError(IEnumerable<KeyValuePair<string, string>> headers)
    {
        await InitializeConnectionAsync(_noopApplication);
 
        await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, headers);
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 32,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
            withStreamId: 1);
 
        await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
 
        AssertConnectionNoError();
    }
 
    [Theory]
    [MemberData(nameof(PseudoHeaderFieldAfterRegularHeadersData))]
    public Task HEADERS_Received_HeaderBlockContainsPseudoHeaderFieldAfterRegularHeaders_ConnectionError(IEnumerable<KeyValuePair<string, string>> headers)
    {
        return HEADERS_Received_InvalidHeaderFields_ConnectionError(
            headers,
            expectedErrorMessage: CoreStrings.HttpErrorPseudoHeaderFieldAfterRegularHeaders,
            expectedEndReason: ConnectionEndReason.InvalidRequestHeaders);
    }
 
    private async Task HEADERS_Received_InvalidHeaderFields_ConnectionError(IEnumerable<KeyValuePair<string, string>> headers, string expectedErrorMessage, ConnectionEndReason expectedEndReason)
    {
        await InitializeConnectionAsync(_noopApplication);
        await StartStreamAsync(1, headers, endStream: true);
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 1,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: expectedErrorMessage);
 
        AssertConnectionEndReason(expectedEndReason);
    }
 
    [Theory]
    [MemberData(nameof(MissingPseudoHeaderFieldData))]
    public async Task HEADERS_Received_HeaderBlockDoesNotContainMandatoryPseudoHeaderField_StreamError(IEnumerable<KeyValuePair<string, string>> headers)
    {
        await InitializeConnectionAsync(_noopApplication);
 
        await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, headers);
        await WaitForStreamErrorAsync(
             expectedStreamId: 1,
             expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
             expectedErrorMessage: CoreStrings.HttpErrorMissingMandatoryPseudoHeaderFields);
 
        // Verify that the stream ID can't be re-used
        await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, _browserRequestHeaders);
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 1,
            expectedErrorCode: Http2ErrorCode.STREAM_CLOSED,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1));
 
        AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId);
    }
 
    [Fact]
    public Task HEADERS_Received_HeaderBlockOverLimit_ConnectionError()
    {
        // > 32kb * 2 to exceed graceful handling limit
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>("a", _4kHeaderValue),
            new KeyValuePair<string, string>("b", _4kHeaderValue),
            new KeyValuePair<string, string>("c", _4kHeaderValue),
            new KeyValuePair<string, string>("d", _4kHeaderValue),
            new KeyValuePair<string, string>("e", _4kHeaderValue),
            new KeyValuePair<string, string>("f", _4kHeaderValue),
            new KeyValuePair<string, string>("g", _4kHeaderValue),
            new KeyValuePair<string, string>("h", _4kHeaderValue),
            new KeyValuePair<string, string>("i", _4kHeaderValue),
            new KeyValuePair<string, string>("j", _4kHeaderValue),
            new KeyValuePair<string, string>("k", _4kHeaderValue),
            new KeyValuePair<string, string>("l", _4kHeaderValue),
            new KeyValuePair<string, string>("m", _4kHeaderValue),
            new KeyValuePair<string, string>("n", _4kHeaderValue),
            new KeyValuePair<string, string>("o", _4kHeaderValue),
            new KeyValuePair<string, string>("p", _4kHeaderValue),
        };
 
        return HEADERS_Received_InvalidHeaderFields_ConnectionError(
            headers,
            CoreStrings.BadRequest_HeadersExceedMaxTotalSize,
            expectedEndReason: ConnectionEndReason.MaxRequestHeadersTotalSizeExceeded);
    }
 
    [Fact]
    public Task HEADERS_Received_TooManyHeaders_ConnectionError()
    {
        // > MaxRequestHeaderCount (100) * 2 to exceed graceful handling limit
        var headers = new List<KeyValuePair<string, string>>();
        headers.AddRange(new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
        });
        for (var i = 0; i < 200; i++)
        {
            headers.Add(new KeyValuePair<string, string>(i.ToString(CultureInfo.InvariantCulture), i.ToString(CultureInfo.InvariantCulture)));
        }
 
        return HEADERS_Received_InvalidHeaderFields_ConnectionError(
            headers,
            CoreStrings.BadRequest_TooManyHeaders,
            expectedEndReason: ConnectionEndReason.MaxRequestHeaderCountExceeded);
    }
 
    [Fact]
    public Task HEADERS_Received_InvalidCharactersInHeaderValue_ConnectionError()
    {
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>("Custom", "val\0ue"),
        };
 
        return HEADERS_Received_InvalidHeaderFields_ConnectionError(
            headers,
            CoreStrings.BadRequest_MalformedRequestInvalidHeaders,
            expectedEndReason: ConnectionEndReason.InvalidRequestHeaders);
    }
 
    [Fact]
    public Task HEADERS_Received_InvalidCharactersInHeaderName_ConnectionError()
    {
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>("Cus\0tom", "value"),
        };
 
        return HEADERS_Received_InvalidHeaderFields_ConnectionError(headers, CoreStrings.BadRequest_InvalidCharactersInHeaderName,
            expectedEndReason: ConnectionEndReason.InvalidRequestHeaders);
    }
 
    [Fact]
    public Task HEADERS_Received_HeaderBlockContainsConnectionHeader_ConnectionError()
    {
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>("connection", "keep-alive")
        };
 
        return HEADERS_Received_InvalidHeaderFields_ConnectionError(
            headers,
            CoreStrings.HttpErrorConnectionSpecificHeaderField,
            expectedEndReason: ConnectionEndReason.InvalidRequestHeaders);
    }
 
    [Fact]
    public Task HEADERS_Received_HeaderBlockContainsTEHeader_ValueIsNotTrailers_ConnectionError()
    {
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>("te", "trailers, deflate")
        };
 
        return HEADERS_Received_InvalidHeaderFields_ConnectionError(
            headers,
            CoreStrings.HttpErrorConnectionSpecificHeaderField,
            expectedEndReason: ConnectionEndReason.InvalidRequestHeaders);
    }
 
    [Fact]
    public async Task HEADERS_Received_HeaderBlockContainsTEHeader_ValueIsTrailers_NoError()
    {
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>("te", "trailers")
        };
 
        await InitializeConnectionAsync(_noopApplication);
 
        await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, headers);
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 36,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
            withStreamId: 1);
 
        await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
 
        AssertConnectionNoError();
    }
 
    [Fact]
    public async Task HEADERS_Received_RequestLineLength_StreamError()
    {
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, new string('A', 8192 / 2)),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/" + new string('A', 8192 / 2)),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http")
        };
 
        await InitializeConnectionAsync(_noopApplication);
        await StartStreamAsync(1, headers, endStream: true);
 
        await WaitForStreamErrorAsync(1, Http2ErrorCode.PROTOCOL_ERROR, CoreStrings.BadRequest_RequestLineTooLong);
 
        await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
 
        AssertConnectionNoError();
    }
 
    [Fact]
    public async Task HEADERS_CookiesMergedIntoOne()
    {
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>(HeaderNames.Cookie, "a=0"),
            new KeyValuePair<string, string>(HeaderNames.Cookie, "b=1"),
            new KeyValuePair<string, string>(HeaderNames.Cookie, "c=2"),
        };
 
        await InitializeConnectionAsync(_readHeadersApplication);
 
        await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, headers);
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 36,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
            withStreamId: 1);
 
        Assert.Equal("a=0; b=1; c=2", _receivedHeaders[HeaderNames.Cookie]);
 
        await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
    }
 
    [Fact]
    public async Task PRIORITY_Received_StreamIdZero_ConnectionError()
    {
        await InitializeConnectionAsync(_noopApplication);
 
        await SendPriorityAsync(0);
 
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 0,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdZero(Http2FrameType.PRIORITY));
 
        AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId);
    }
 
    [Fact]
    public async Task PRIORITY_Received_StreamIdEven_ConnectionError()
    {
        await InitializeConnectionAsync(_noopApplication);
 
        await SendPriorityAsync(2);
 
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 0,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdEven(Http2FrameType.PRIORITY, streamId: 2));
 
        AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId);
    }
 
    [Theory]
    [InlineData(4)]
    [InlineData(6)]
    public async Task PRIORITY_Received_LengthNotFive_ConnectionError(int length)
    {
        await InitializeConnectionAsync(_noopApplication);
 
        await SendInvalidPriorityFrameAsync(1, length);
 
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 0,
            expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.PRIORITY, expectedLength: 5));
 
        AssertConnectionEndReason(ConnectionEndReason.InvalidFrameLength);
    }
 
    [Fact]
    public async Task PRIORITY_Received_InterleavedWithHeaders_ConnectionError()
    {
        await InitializeConnectionAsync(_noopApplication);
 
        await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders);
        await SendPriorityAsync(1);
 
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 1,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.PRIORITY, streamId: 1, headersStreamId: 1));
 
        AssertConnectionEndReason(ConnectionEndReason.UnexpectedFrame);
    }
 
    [Fact]
    public async Task PRIORITY_Received_StreamDependencyOnSelf_ConnectionError()
    {
        await InitializeConnectionAsync(_readHeadersApplication);
 
        await SendPriorityAsync(1, streamDependency: 1);
 
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 0,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamSelfDependency(Http2FrameType.PRIORITY, 1));
 
        AssertConnectionEndReason(ConnectionEndReason.StreamSelfDependency);
    }
 
    [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<IHttp2StreamIdFeature>().StreamId;
 
            var abortedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
            var writeTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
 
            context.RequestAborted.Register(() =>
            {
                lock (_abortedStreamIdsLock)
                {
                    _abortedStreamIds.Add(streamId);
                    abortedTcs.SetResult();
                }
            });
 
            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();
 
                await abortedTcs.Task;
 
                _runningStreams[streamId].SetResult();
            }
            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: 32,
            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: 2,
            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 preceding 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);
 
        AssertConnectionNoError();
    }
 
    [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<IHttp2StreamIdFeature>().StreamId;
 
            var abortedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
            var writeTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
 
            context.RequestAborted.Register(() =>
            {
                lock (_abortedStreamIdsLock)
                {
                    _abortedStreamIds.Add(streamId);
                    abortedTcs.SetResult();
                }
            });
 
            try
            {
                writeTasks[streamId] = writeTcs.Task;
                await context.Response.Body.WriteAsync(_helloWorldBytes, 0, _helloWorldBytes.Length);
                writeTcs.SetResult();
 
                await abortedTcs.Task;
 
                _runningStreams[streamId].SetResult();
            }
            catch (Exception ex)
            {
                _runningStreams[streamId].SetException(ex);
                throw;
            }
        });
 
        async Task VerifyStreamBackpressure(int streamId, int headersLength)
        {
            await StartStreamAsync(streamId, _browserRequestHeaders, endStream: true);
 
            await ExpectAsync(Http2FrameType.HEADERS,
                withLength: headersLength,
                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.PayloadSequence.ToArray()));
            Assert.False(writeTasks[streamId].IsCompleted);
        }
 
        await VerifyStreamBackpressure(1, 32);
        await VerifyStreamBackpressure(3, 2);
        await VerifyStreamBackpressure(5, 2);
 
        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);
 
        AssertConnectionNoError();
    }
 
    [Fact]
    public async Task RST_STREAM_Received_ReturnsSpaceToConnectionInputFlowControlWindow()
    {
        var initialConnectionWindowSize = _serviceContext.ServerOptions.Limits.Http2.InitialConnectionWindowSize;
        var framesInConnectionWindow = initialConnectionWindowSize / Http2PeerSettings.DefaultMaxFrameSize;
 
        await InitializeConnectionAsync(_waitForAbortApplication);
 
        await StartStreamAsync(1, _browserRequestHeaders, endStream: false);
 
        // Rounds down so we don't go over the half window size and trigger an update
        for (var i = 0; i < framesInConnectionWindow / 2; i++)
        {
            await SendDataAsync(1, _maxData, endStream: false);
        }
 
        // Go over the threshold and trigger an update
        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);
        var updateSize = ((framesInConnectionWindow / 2) + 1) * _maxData.Length;
        Assert.Equal(updateSize, connectionWindowUpdateFrame.WindowUpdateSizeIncrement);
    }
 
    [Fact]
    public async Task RST_STREAM_Received_StreamIdZero_ConnectionError()
    {
        await InitializeConnectionAsync(_noopApplication);
 
        await SendRstStreamAsync(0);
 
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 0,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdZero(Http2FrameType.RST_STREAM));
 
        AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId);
    }
 
    [Fact]
    public async Task RST_STREAM_Received_StreamIdEven_ConnectionError()
    {
        await InitializeConnectionAsync(_noopApplication);
 
        await SendRstStreamAsync(2);
 
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 0,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdEven(Http2FrameType.RST_STREAM, streamId: 2));
 
        AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId);
    }
 
    [Fact]
    public async Task RST_STREAM_Received_StreamIdle_ConnectionError()
    {
        await InitializeConnectionAsync(_noopApplication);
 
        await SendRstStreamAsync(1);
 
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 0,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdle(Http2FrameType.RST_STREAM, streamId: 1));
 
        AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId);
    }
 
    [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<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: true,
            expectedLastStreamId: 1,
            expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.RST_STREAM, expectedLength: 4));
 
        AssertConnectionEndReason(ConnectionEndReason.InvalidFrameLength);
    }
 
    [Fact]
    public async Task RST_STREAM_Received_InterleavedWithHeaders_ConnectionError()
    {
        await InitializeConnectionAsync(_noopApplication);
 
        await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders);
        await SendRstStreamAsync(1);
 
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 1,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.RST_STREAM, streamId: 1, headersStreamId: 1));
 
        AssertConnectionEndReason(ConnectionEndReason.UnexpectedFrame);
    }
 
    // Compare to h2spec http2/5.1/8
    [Fact]
    public async Task RST_STREAM_IncompleteRequest_AdditionalDataFrames_ConnectionAborted()
    {
        var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
 
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
        };
        await InitializeConnectionAsync(context => tcs.Task);
 
        await StartStreamAsync(1, headers, endStream: false);
        await SendDataAsync(1, new byte[1], endStream: false);
        await SendDataAsync(1, new byte[2], endStream: false);
        await SendRstStreamAsync(1);
        await SendDataAsync(1, new byte[10], endStream: false);
        tcs.TrySetResult();
 
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(ignoreNonGoAwayFrames: false, expectedLastStreamId: 1,
            Http2ErrorCode.STREAM_CLOSED, CoreStrings.FormatHttp2ErrorStreamAborted(Http2FrameType.DATA, 1));
 
        AssertConnectionEndReason(ConnectionEndReason.FrameAfterStreamClose);
    }
 
    [Fact]
    public async Task RST_STREAM_IncompleteRequest_AdditionalTrailerFrames_ConnectionAborted()
    {
        var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
 
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
        };
        await InitializeConnectionAsync(context => tcs.Task);
 
        await StartStreamAsync(1, headers, endStream: false);
        await SendDataAsync(1, new byte[1], endStream: false);
        await SendDataAsync(1, new byte[2], endStream: false);
        await SendRstStreamAsync(1);
        await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, _requestTrailers);
        tcs.TrySetResult();
 
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(ignoreNonGoAwayFrames: false, expectedLastStreamId: 1,
            Http2ErrorCode.STREAM_CLOSED, CoreStrings.FormatHttp2ErrorStreamAborted(Http2FrameType.HEADERS, 1));
 
        AssertConnectionEndReason(ConnectionEndReason.FrameAfterStreamClose);
    }
 
    [Fact]
    public async Task RST_STREAM_IncompleteRequest_AdditionalResetFrame_IgnoreAdditionalReset()
    {
        var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
 
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
        };
        await InitializeConnectionAsync(context => tcs.Task);
 
        await StartStreamAsync(1, headers, endStream: false);
        await SendDataAsync(1, new byte[1], endStream: false);
        await SendRstStreamAsync(1);
        await SendRstStreamAsync(1);
        tcs.TrySetResult();
 
        await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
 
        AssertConnectionNoError();
    }
 
    [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/53744")]
    [Fact]
    public async Task RST_STREAM_IncompleteRequest_AdditionalWindowUpdateFrame_ConnectionAborted()
    {
        var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
 
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
        };
        await InitializeConnectionAsync(context => tcs.Task);
 
        await StartStreamAsync(1, headers, endStream: false);
        await SendDataAsync(1, new byte[1], endStream: false);
        await SendRstStreamAsync(1);
        await SendWindowUpdateAsync(1, 1024);
 
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(ignoreNonGoAwayFrames: false, expectedLastStreamId: 1,
            Http2ErrorCode.STREAM_CLOSED, CoreStrings.FormatHttp2ErrorStreamAborted(Http2FrameType.WINDOW_UPDATE, 1));
 
        tcs.TrySetResult(); // Don't let the response start until after the abort
    }
 
    [Fact]
    public async Task SETTINGS_KestrelDefaults_Sent()
    {
        CreateConnection();
 
        _connectionTask = _connection.ProcessRequestsAsync(new DummyApplication(_noopApplication));
 
        await SendPreambleAsync().ConfigureAwait(false);
        await SendSettingsAsync();
 
        var frame = await ExpectAsync(Http2FrameType.SETTINGS,
            withLength: Http2FrameReader.SettingSize * 4,
            withFlags: 0,
            withStreamId: 0);
 
        // Only non protocol defaults are sent
        var settings = Http2FrameReader.ReadSettings(frame.PayloadSequence);
        Assert.Equal(4, 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_INITIAL_WINDOW_SIZE, setting.Parameter);
        Assert.Equal(768 * 1024u, setting.Value);
 
        setting = settings[2];
        Assert.Equal(Http2SettingsParameter.SETTINGS_MAX_HEADER_LIST_SIZE, setting.Parameter);
        Assert.Equal(32 * 1024u, setting.Value);
 
        setting = settings[3];
        Assert.Equal(Http2SettingsParameter.SETTINGS_ENABLE_CONNECT_PROTOCOL, setting.Parameter);
        Assert.Equal(1u, setting.Value);
 
        var update = await ExpectAsync(Http2FrameType.WINDOW_UPDATE,
            withLength: 4,
            withFlags: (byte)Http2SettingsFrameFlags.NONE,
            withStreamId: 0);
 
        Assert.Equal(1024 * 1024 - (int)Http2PeerSettings.DefaultInitialWindowSize, update.WindowUpdateSizeIncrement);
 
        await ExpectAsync(Http2FrameType.SETTINGS,
            withLength: 0,
            withFlags: (byte)Http2SettingsFrameFlags.ACK,
            withStreamId: 0);
 
        await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false);
 
        AssertConnectionNoError();
    }
 
    [Fact]
    public async Task SETTINGS_Custom_Sent()
    {
        CreateConnection();
 
        _connection.ServerSettings.HeaderTableSize = 0;
        _connection.ServerSettings.MaxConcurrentStreams = 1;
        _connection.ServerSettings.MaxHeaderListSize = 4 * 1024;
        _connection.ServerSettings.InitialWindowSize = 1024 * 1024 * 10;
 
        _connectionTask = _connection.ProcessRequestsAsync(new DummyApplication(_noopApplication));
 
        await SendPreambleAsync().ConfigureAwait(false);
        await SendSettingsAsync();
 
        var frame = await ExpectAsync(Http2FrameType.SETTINGS,
            withLength: Http2FrameReader.SettingSize * 5,
            withFlags: 0,
            withStreamId: 0);
 
        // Only non protocol defaults are sent
        var settings = Http2FrameReader.ReadSettings(frame.PayloadSequence);
        Assert.Equal(5, settings.Count);
 
        var setting = settings[0];
        Assert.Equal(Http2SettingsParameter.SETTINGS_HEADER_TABLE_SIZE, setting.Parameter);
        Assert.Equal(0u, setting.Value);
 
        setting = settings[1];
        Assert.Equal(Http2SettingsParameter.SETTINGS_MAX_CONCURRENT_STREAMS, setting.Parameter);
        Assert.Equal(1u, setting.Value);
 
        setting = settings[2];
        Assert.Equal(Http2SettingsParameter.SETTINGS_INITIAL_WINDOW_SIZE, setting.Parameter);
        Assert.Equal(1024 * 1024 * 10u, setting.Value);
 
        setting = settings[3];
        Assert.Equal(Http2SettingsParameter.SETTINGS_MAX_HEADER_LIST_SIZE, setting.Parameter);
        Assert.Equal(4 * 1024u, setting.Value);
 
        setting = settings[4];
        Assert.Equal(Http2SettingsParameter.SETTINGS_ENABLE_CONNECT_PROTOCOL, setting.Parameter);
        Assert.Equal(1u, setting.Value);
 
        var update = await ExpectAsync(Http2FrameType.WINDOW_UPDATE,
            withLength: 4,
            withFlags: (byte)Http2SettingsFrameFlags.NONE,
            withStreamId: 0);
 
        Assert.Equal(1024 * 1024u - Http2PeerSettings.DefaultInitialWindowSize, (uint)update.WindowUpdateSizeIncrement);
 
        await ExpectAsync(Http2FrameType.SETTINGS,
            withLength: 0,
            withFlags: (byte)Http2SettingsFrameFlags.ACK,
            withStreamId: 0);
 
        await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false);
 
        AssertConnectionNoError();
    }
 
    [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();
        frame.PrepareSettings(Http2SettingsFrameFlags.ACK);
        Http2FrameWriter.WriteHeader(frame, _pair.Application.Output);
        await FlushAsync(_pair.Application.Output);
 
        await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false);
    }
 
    [Fact]
    public async Task SETTINGS_Received_StreamIdNotZero_ConnectionError()
    {
        await InitializeConnectionAsync(_noopApplication);
 
        await SendSettingsWithInvalidStreamIdAsync(1);
 
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 0,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdNotZero(Http2FrameType.SETTINGS));
 
        AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId);
    }
 
    [Theory]
    [InlineData((int)(Http2SettingsParameter.SETTINGS_ENABLE_PUSH), 2, (int)(Http2ErrorCode.PROTOCOL_ERROR))]
    [InlineData((int)(Http2SettingsParameter.SETTINGS_ENABLE_PUSH), uint.MaxValue, (int)(Http2ErrorCode.PROTOCOL_ERROR))]
    [InlineData((int)(Http2SettingsParameter.SETTINGS_INITIAL_WINDOW_SIZE), (uint)int.MaxValue + 1, (int)(Http2ErrorCode.FLOW_CONTROL_ERROR))]
    [InlineData((int)(Http2SettingsParameter.SETTINGS_INITIAL_WINDOW_SIZE), uint.MaxValue, (int)(Http2ErrorCode.FLOW_CONTROL_ERROR))]
    [InlineData((int)(Http2SettingsParameter.SETTINGS_MAX_FRAME_SIZE), 0, (int)(Http2ErrorCode.PROTOCOL_ERROR))]
    [InlineData((int)(Http2SettingsParameter.SETTINGS_MAX_FRAME_SIZE), 1, (int)(Http2ErrorCode.PROTOCOL_ERROR))]
    [InlineData((int)(Http2SettingsParameter.SETTINGS_MAX_FRAME_SIZE), 16 * 1024 - 1, (int)(Http2ErrorCode.PROTOCOL_ERROR))]
    [InlineData((int)(Http2SettingsParameter.SETTINGS_MAX_FRAME_SIZE), 16 * 1024 * 1024, (int)(Http2ErrorCode.PROTOCOL_ERROR))]
    [InlineData((int)(Http2SettingsParameter.SETTINGS_MAX_FRAME_SIZE), uint.MaxValue, (int)(Http2ErrorCode.PROTOCOL_ERROR))]
    public async Task SETTINGS_Received_InvalidParameterValue_ConnectionError(int intParameter, uint value, int intExpectedErrorCode)
    {
        var parameter = (Http2SettingsParameter)intParameter;
        var expectedErrorCode = (Http2ErrorCode)intExpectedErrorCode;
 
        await InitializeConnectionAsync(_noopApplication);
 
        await SendSettingsWithInvalidParameterValueAsync(parameter, value);
 
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 0,
            expectedErrorCode: expectedErrorCode,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorSettingsParameterOutOfRange(parameter));
 
        AssertConnectionEndReason(ConnectionEndReason.InvalidSettings);
    }
 
    [Fact]
    public async Task SETTINGS_Received_InterleavedWithHeaders_ConnectionError()
    {
        await InitializeConnectionAsync(_noopApplication);
 
        await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders);
        await SendSettingsAsync();
 
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 1,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.SETTINGS, streamId: 0, headersStreamId: 1));
 
        AssertConnectionEndReason(ConnectionEndReason.UnexpectedFrame);
    }
 
    [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<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 0,
            expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR,
            expectedErrorMessage: CoreStrings.Http2ErrorSettingsAckLengthNotZero);
 
        AssertConnectionEndReason(ConnectionEndReason.InvalidFrameLength);
    }
 
    [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<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 0,
            expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR,
            expectedErrorMessage: CoreStrings.Http2ErrorSettingsLengthNotMultipleOfSix);
 
        AssertConnectionEndReason(ConnectionEndReason.InvalidFrameLength);
    }
 
    [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<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 1,
            expectedErrorCode: Http2ErrorCode.FLOW_CONTROL_ERROR,
            expectedErrorMessage: CoreStrings.Http2ErrorInitialWindowSizeInvalid);
 
        AssertConnectionEndReason(ConnectionEndReason.InvalidSettings);
    }
 
    [Fact]
    public async Task SETTINGS_Received_ChangesAllowedResponseMaxFrameSize()
    {
        CreateConnection();
 
        _connection.ServerSettings.MaxFrameSize = Http2PeerSettings.MaxAllowedMaxFrameSize;
        // This includes the default response headers such as :status, etc
        var defaultResponseHeaderLength = 32;
        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);
        }, expectedSettingsCount: 5);
 
        // 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);
 
        AssertConnectionNoError();
    }
 
    [Fact]
    public async Task SETTINGS_Received_ClientMaxFrameSizeCannotExceedServerMaxFrameSize()
    {
        var serverMaxFrame = Http2PeerSettings.MinAllowedMaxFrameSize + 1024;
 
        CreateConnection();
 
        _connection.ServerSettings.MaxFrameSize = Http2PeerSettings.MinAllowedMaxFrameSize + 1024;
        var clientMaxFrame = serverMaxFrame + 1024 * 5;
        _clientSettings.MaxFrameSize = (uint)clientMaxFrame;
 
        await InitializeConnectionAsync(context =>
        {
            return context.Response.Body.WriteAsync(new byte[clientMaxFrame], 0, clientMaxFrame);
        }, expectedSettingsCount: 5);
 
        // Start request
        await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 32,
            withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
            withStreamId: 1);
        await ExpectAsync(Http2FrameType.DATA,
            withLength: serverMaxFrame,
            withFlags: (byte)Http2DataFrameFlags.NONE,
            withStreamId: 1);
        await ExpectAsync(Http2FrameType.DATA,
            withLength: clientMaxFrame - serverMaxFrame,
            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);
 
        AssertConnectionNoError();
    }
 
    [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);
 
        // Start request
        await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
 
        var headerFrame = await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 36,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
            withStreamId: 1);
 
        // Headers start with :status = 200
        Assert.Equal(0x88, headerFrame.Payload.Span[0]);
 
        await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
    }
 
    [Fact]
    public async Task SETTINGS_Received_WithLargeHeaderTableSizeLimit_ChangesHeaderTableSize()
    {
        _serviceContext.ServerOptions.Limits.Http2.HeaderTableSize = 40000;
 
        await InitializeConnectionAsync(_noopApplication, expectedSettingsCount: 5);
 
        // 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);
 
        // Start request
        await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
 
        var headerFrame = await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 40,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
            withStreamId: 1);
 
        const byte DynamicTableSizeUpdateMask = 0xe0;
 
        var integerDecoder = new IntegerDecoder();
        Assert.False(integerDecoder.BeginTryDecode((byte)(headerFrame.Payload.Span[0] & ~DynamicTableSizeUpdateMask), prefixLength: 5, out _));
        Assert.False(integerDecoder.TryDecode(headerFrame.Payload.Span[1], out _));
        Assert.False(integerDecoder.TryDecode(headerFrame.Payload.Span[2], out _));
        Assert.True(integerDecoder.TryDecode(headerFrame.Payload.Span[3], out var result));
 
        Assert.Equal(40000, result);
 
        await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
    }
 
    [Fact]
    public async Task PUSH_PROMISE_Received_ConnectionError()
    {
        await InitializeConnectionAsync(_noopApplication);
 
        await SendPushPromiseFrameAsync();
 
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 0,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: CoreStrings.Http2ErrorPushPromiseReceived);
 
        AssertConnectionEndReason(ConnectionEndReason.UnexpectedFrame);
    }
 
    [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<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 1,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.PING, streamId: 0, headersStreamId: 1));
 
        AssertConnectionEndReason(ConnectionEndReason.UnexpectedFrame);
    }
 
    [Fact]
    public async Task PING_Received_StreamIdNotZero_ConnectionError()
    {
        await InitializeConnectionAsync(_noopApplication);
 
        await SendPingWithInvalidStreamIdAsync(streamId: 1);
 
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 0,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdNotZero(Http2FrameType.PING));
 
        AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId);
    }
 
    [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<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 0,
            expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.PING, expectedLength: 8));
 
        AssertConnectionEndReason(ConnectionEndReason.InvalidFrameLength);
    }
 
    [Fact]
    public async Task GOAWAY_Received_ConnectionStops()
    {
        await InitializeConnectionAsync(_noopApplication);
 
        await SendGoAwayAsync();
 
        await WaitForConnectionStopAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false);
 
        AssertConnectionNoError();
    }
 
    [Fact]
    public async Task GOAWAY_Received_ConnectionLifetimeNotification_Cancelled()
    {
        await InitializeConnectionAsync(_noopApplication, addKestrelFeatures: true);
        var lifetime = _connection.ConnectionFeatures.Get<IConnectionLifetimeNotificationFeature>();
        Assert.False(lifetime.ConnectionClosedRequested.IsCancellationRequested);
 
        await SendGoAwayAsync();
 
        Assert.True(lifetime.ConnectionClosedRequested.IsCancellationRequested);
        await WaitForConnectionStopAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false);
 
        AssertConnectionNoError();
    }
 
    [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: 32,
            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: 2,
            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);
 
        TriggerTick();
        await WaitForConnectionStopAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
        await _closedStateReached.Task.DefaultTimeout();
 
        AssertConnectionNoError();
    }
 
    [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<IHttp2StreamIdFeature>().StreamId;
 
            var abortedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
            var writeTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
 
            context.RequestAborted.Register(() =>
            {
                lock (_abortedStreamIdsLock)
                {
                    _abortedStreamIds.Add(streamId);
                    abortedTcs.SetResult();
                }
            });
 
            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();
 
                await abortedTcs.Task;
 
                _runningStreams[streamId].SetResult();
            }
            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: 32,
            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: 2,
            withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
            withStreamId: 3);
 
        await StartStreamAsync(5, _browserRequestHeaders, endStream: true);
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 2,
            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);
 
        AssertConnectionNoError();
    }
 
    [Fact]
    public async Task GOAWAY_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<IHttp2StreamIdFeature>().StreamId;
 
            var abortedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
            var writeTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
 
            context.RequestAborted.Register(() =>
            {
                lock (_abortedStreamIdsLock)
                {
                    _abortedStreamIds.Add(streamId);
                    abortedTcs.SetResult();
                }
            });
 
            try
            {
                writeTasks[streamId] = writeTcs.Task;
                await context.Response.Body.WriteAsync(_helloWorldBytes, 0, _helloWorldBytes.Length);
                writeTcs.SetResult();
 
                await abortedTcs.Task;
 
                _runningStreams[streamId].SetResult();
            }
            catch (Exception ex)
            {
                _runningStreams[streamId].SetException(ex);
                throw;
            }
        });
 
        async Task VerifyStreamBackpressure(int streamId, int headersLength)
        {
            await StartStreamAsync(streamId, _browserRequestHeaders, endStream: true);
 
            await ExpectAsync(Http2FrameType.HEADERS,
                withLength: headersLength,
                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.PayloadSequence.ToArray()));
            Assert.False(writeTasks[streamId].IsCompleted);
        }
 
        await VerifyStreamBackpressure(1, 32);
        await VerifyStreamBackpressure(3, 2);
        await VerifyStreamBackpressure(5, 2);
 
        // 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);
 
        AssertConnectionNoError();
    }
 
    [Fact]
    public async Task GOAWAY_Received_StreamIdNotZero_ConnectionError()
    {
        await InitializeConnectionAsync(_noopApplication);
 
        await SendInvalidGoAwayFrameAsync();
 
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 0,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdNotZero(Http2FrameType.GOAWAY));
 
        AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId);
    }
 
    [Fact]
    public async Task GOAWAY_Received_InterleavedWithHeaders_ConnectionError()
    {
        await InitializeConnectionAsync(_noopApplication);
 
        await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _browserRequestHeaders);
        await SendGoAwayAsync();
 
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 1,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.GOAWAY, streamId: 0, headersStreamId: 1));
 
        AssertConnectionEndReason(ConnectionEndReason.UnexpectedFrame);
    }
 
    [Fact]
    public async Task WINDOW_UPDATE_Received_StreamIdEven_ConnectionError()
    {
        await InitializeConnectionAsync(_noopApplication);
 
        await SendWindowUpdateAsync(2, sizeIncrement: 42);
 
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 0,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdEven(Http2FrameType.WINDOW_UPDATE, streamId: 2));
 
        AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId);
    }
 
    [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<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 1,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.WINDOW_UPDATE, streamId: 1, headersStreamId: 1));
 
        AssertConnectionEndReason(ConnectionEndReason.UnexpectedFrame);
    }
 
    [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<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 0,
            expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.WINDOW_UPDATE, expectedLength: 4));
 
        AssertConnectionEndReason(ConnectionEndReason.InvalidFrameLength);
    }
 
    [Fact]
    public async Task WINDOW_UPDATE_Received_OnConnection_SizeIncrementZero_ConnectionError()
    {
        await InitializeConnectionAsync(_noopApplication);
 
        await SendWindowUpdateAsync(0, sizeIncrement: 0);
 
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 0,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: CoreStrings.Http2ErrorWindowUpdateIncrementZero);
 
        AssertConnectionEndReason(ConnectionEndReason.InvalidWindowUpdateSize);
    }
 
    [Fact]
    public async Task WINDOW_UPDATE_Received_OnStream_SizeIncrementZero_ConnectionError()
    {
        await InitializeConnectionAsync(_waitForAbortApplication);
 
        await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
        await SendWindowUpdateAsync(1, sizeIncrement: 0);
 
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 1,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: CoreStrings.Http2ErrorWindowUpdateIncrementZero);
 
        AssertConnectionEndReason(ConnectionEndReason.InvalidWindowUpdateSize);
    }
 
    [Fact]
    public async Task WINDOW_UPDATE_Received_StreamIdle_ConnectionError()
    {
        await InitializeConnectionAsync(_waitForAbortApplication);
 
        await SendWindowUpdateAsync(1, sizeIncrement: 1);
 
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 0,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamIdle(Http2FrameType.WINDOW_UPDATE, streamId: 1));
 
        AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId);
    }
 
    [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<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 0,
            expectedErrorCode: Http2ErrorCode.FLOW_CONTROL_ERROR,
            expectedErrorMessage: CoreStrings.Http2ErrorWindowUpdateSizeInvalid);
 
        AssertConnectionEndReason(ConnectionEndReason.InvalidWindowUpdateSize);
    }
 
    [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);
 
        AssertConnectionNoError();
    }
 
    [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();
                    await context.Response.Body.WriteAsync(_maxData, 0, _maxData.Length);
                }
 
                await expectingDataSem.WaitAsync();
                var lastWriteTask = context.Response.Body.WriteAsync(_maxData, 0, _maxData.Length);
 
                Assert.False(lastWriteTask.IsCompleted);
                backpressureObservedTcs.TrySetResult();
 
                await lastWriteTask;
                backpressureReleasedTcs.TrySetResult();
            }
            catch (Exception ex)
            {
                backpressureObservedTcs.TrySetException(ex);
                backpressureReleasedTcs.TrySetException(ex);
                throw;
            }
        });
 
        await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 32,
            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);
 
        AssertConnectionNoError();
    }
 
    [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: 32,
            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.PayloadSequence.ToArray()));
        Assert.True(_helloWorldBytes.AsSpan(initialWindowSize, initialWindowSize).SequenceEqual(dataFrame2.PayloadSequence.ToArray()));
    }
 
    [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: 32,
            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.PayloadSequence.ToArray()));
        Assert.True(_helloWorldBytes.AsSpan(6, 3).SequenceEqual(dataFrame2.PayloadSequence.ToArray()));
        Assert.True(_helloWorldBytes.AsSpan(9, 3).SequenceEqual(dataFrame3.PayloadSequence.ToArray()));
    }
 
    [Fact]
    public async Task WINDOW_UPDATE_Received_OnStream_Resumed_WhenInitialWindowSizeNegativeMidStream()
    {
        const int windowSize = 3;
        _clientSettings.InitialWindowSize = windowSize;
        var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        await InitializeConnectionAsync(async context =>
        {
            var bodyControlFeature = context.Features.Get<IHttpBodyControlFeature>();
            bodyControlFeature.AllowSynchronousIO = true;
            await context.Response.Body.WriteAsync(new byte[windowSize - 1], 0, windowSize - 1);
            await tcs.Task;
            await context.Response.Body.WriteAsync(new byte[windowSize], 0, windowSize);
        });
        await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 32,
            withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
            withStreamId: 1);
 
        // Decrease window size after server has already sent the current window - 1 size of data
        _clientSettings.InitialWindowSize = windowSize - 2;
        await SendSettingsAsync();
        await ExpectAsync(Http2FrameType.DATA,
            withLength: windowSize - 1,
            withFlags: (byte)Http2DataFrameFlags.NONE,
            withStreamId: 1);
        await ExpectAsync(Http2FrameType.SETTINGS,
            withLength: 0,
            withFlags: (byte)Http2DataFrameFlags.END_STREAM,
            withStreamId: 0);
        tcs.SetResult();
 
        // send window update to receive the next frame data
        await SendWindowUpdateAsync(1, windowSize + 1);
        await ExpectAsync(Http2FrameType.DATA,
            withLength: windowSize,
            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 CONTINUATION_Received_Decoded()
    {
        await InitializeConnectionAsync(_readHeadersApplication);
 
        await StartStreamAsync(1, _twoContinuationsRequestHeaders, endStream: true);
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 36,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
            withStreamId: 1);
 
        VerifyDecodedRequestHeaders(_twoContinuationsRequestHeaders);
 
        await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
    }
 
    [Theory]
    [InlineData(true)]
    [InlineData(false)]
    public async Task CONTINUATION_Received_WithTrailers_Available(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: 36,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.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: 6,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
            withStreamId: 1);
 
        VerifyDecodedRequestHeaders(_browserRequestHeaders);
 
        // Make sure the trailers are in the trailers collection.
        Assert.False(_receivedHeaders.ContainsKey("trailer-1"));
        Assert.False(_receivedHeaders.ContainsKey("trailer-2"));
        Assert.True(_receivedTrailers.ContainsKey("trailer-1"));
        Assert.True(_receivedTrailers.ContainsKey("trailer-2"));
        Assert.Equal("1", _receivedTrailers["trailer-1"]);
        Assert.Equal("2", _receivedTrailers["trailer-2"]);
 
        await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
    }
 
    [Fact]
    public async Task CONTINUATION_Received_StreamIdMismatch_ConnectionError()
    {
        await InitializeConnectionAsync(_readHeadersApplication);
 
        var headersEnumerator = GetHeadersEnumerator(_oneContinuationRequestHeaders);
        await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, headersEnumerator);
        await SendContinuationAsync(3, Http2ContinuationFrameFlags.END_HEADERS, headersEnumerator);
 
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 1,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.CONTINUATION, streamId: 3, headersStreamId: 1));
 
        AssertConnectionEndReason(ConnectionEndReason.UnexpectedFrame);
    }
 
    [Fact]
    public async Task CONTINUATION_Received_IncompleteHeaderBlock_ConnectionError()
    {
        await InitializeConnectionAsync(_noopApplication);
 
        await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, _postRequestHeaders);
        await SendIncompleteContinuationFrameAsync(streamId: 1);
 
        await WaitForConnectionErrorAsync<HPackDecodingException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 1,
            expectedErrorCode: Http2ErrorCode.COMPRESSION_ERROR,
            expectedErrorMessage: SR.net_http_hpack_incomplete_header_block);
 
        AssertConnectionEndReason(ConnectionEndReason.ErrorReadingHeaders);
    }
 
    [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<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 1,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: expectedErrorMessage);
 
        AssertConnectionEndReason(ConnectionEndReason.InvalidRequestHeaders);
    }
 
    [Theory]
    [MemberData(nameof(MissingPseudoHeaderFieldData))]
    public async Task CONTINUATION_Received_HeaderBlockDoesNotContainMandatoryPseudoHeaderField_StreamError(IEnumerable<KeyValuePair<string, string>> headers)
    {
        await InitializeConnectionAsync(_noopApplication);
 
        Assert.True(await SendHeadersAsync(1, Http2HeadersFrameFlags.END_STREAM, headers));
        await SendEmptyContinuationFrameAsync(1, Http2ContinuationFrameFlags.END_HEADERS);
 
        await WaitForStreamErrorAsync(
            expectedStreamId: 1,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: CoreStrings.HttpErrorMissingMandatoryPseudoHeaderFields);
 
        // Verify that the stream ID can't be re-used
        await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, headers);
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 1,
            expectedErrorCode: Http2ErrorCode.STREAM_CLOSED,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1));
 
        AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId);
    }
 
    [Theory]
    [MemberData(nameof(ConnectMissingPseudoHeaderFieldData))]
    public async Task CONTINUATION_Received_HeaderBlockDoesNotContainMandatoryPseudoHeaderField_MethodIsCONNECT_NoError(IEnumerable<KeyValuePair<string, string>> headers)
    {
        await InitializeConnectionAsync(_noopApplication);
 
        await SendHeadersAsync(1, Http2HeadersFrameFlags.END_STREAM, headers);
        await SendEmptyContinuationFrameAsync(1, Http2ContinuationFrameFlags.END_HEADERS);
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 32,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
            withStreamId: 1);
 
        await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
 
        AssertConnectionNoError();
    }
 
    [Fact]
    public async Task CONTINUATION_Sent_WhenHeadersLargerThanFrameLength()
    {
        await InitializeConnectionAsync(_largeHeadersApplication);
 
        await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
 
        var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 12342,
            withFlags: (byte)Http2HeadersFrameFlags.END_STREAM,
            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 StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
 
        _hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
        _hpackDecoder.Decode(continuationFrame1.PayloadSequence, endHeaders: false, handler: this);
        _hpackDecoder.Decode(continuationFrame2.PayloadSequence, endHeaders: true, handler: this);
 
        Assert.Equal(11, _decodedHeaders.Count);
        Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("200", _decodedHeaders[InternalHeaderNames.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"]);
 
        AssertConnectionNoError();
    }
 
    [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);
 
        AssertConnectionNoError();
    }
 
    [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<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 1,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(frameType: 42, streamId: 1, headersStreamId: 1));
 
        AssertConnectionEndReason(ConnectionEndReason.UnexpectedFrame);
    }
 
    [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<Http2ConnectionErrorException>(
            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);
 
        AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId);
    }
 
    [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(LogMessages, m => m.Exception is ConnectionResetException);
 
        AssertConnectionEndReason(ConnectionEndReason.ConnectionReset);
    }
 
    [Fact]
    public async Task ConnectionResetNotLoggedWithNoActiveStreams()
    {
        await InitializeConnectionAsync(_waitForAbortApplication);
 
        _pair.Application.Output.Complete(new ConnectionResetException(string.Empty));
 
        await WaitForConnectionStopAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false);
        Assert.DoesNotContain(LogMessages, m => m.Exception is ConnectionResetException);
 
        AssertConnectionNoError();
    }
 
    [Fact]
    public async Task OnInputOrOutputCompletedCompletesOutput()
    {
        await InitializeConnectionAsync(_noopApplication);
 
        _connection.OnInputOrOutputCompleted();
        await _closedStateReached.Task.DefaultTimeout();
 
        var result = await _pair.Application.Input.ReadAsync().AsTask().DefaultTimeout();
        Assert.True(result.IsCompleted);
        Assert.True(result.Buffer.IsEmpty);
 
        AssertConnectionNoError();
    }
 
    [Fact]
    public async Task AbortSendsFinalGOAWAY()
    {
        await InitializeConnectionAsync(_noopApplication);
 
        _connection.Abort(new ConnectionAbortedException(), ConnectionEndReason.AbortedByApp);
        await _closedStateReached.Task.DefaultTimeout();
 
        VerifyGoAway(await ReceiveFrameAsync(), int.MaxValue, Http2ErrorCode.INTERNAL_ERROR);
 
        AssertConnectionEndReason(ConnectionEndReason.AbortedByApp);
    }
 
    [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);
 
        AssertConnectionNoError();
    }
 
    [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: 36,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
            withStreamId: 1);
 
        // Send a blocked request
        var tcs = new TaskCompletionSource(TaskCreationOptions.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();
        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);
 
        AssertConnectionNoError();
    }
 
    [Fact]
    public async Task StopProcessingNextRequestSendsGracefulGOAWAYThenFinalGOAWAYWhenAllStreamsComplete()
    {
        await InitializeConnectionAsync(_echoApplication);
 
        await StartStreamAsync(1, _browserRequestHeaders, endStream: false);
 
        _connection.StopProcessingNextRequest(ConnectionEndReason.AppShutdownTimeout);
        await _closingStateReached.Task.DefaultTimeout();
 
        VerifyGoAway(await ReceiveFrameAsync(), Int32.MaxValue, Http2ErrorCode.NO_ERROR);
 
        await SendDataAsync(1, _helloBytes, true);
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 32,
            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);
 
        TriggerTick();
        await _closedStateReached.Task.DefaultTimeout();
        VerifyGoAway(await ReceiveFrameAsync(), 1, Http2ErrorCode.NO_ERROR);
 
        AssertConnectionEndReason(ConnectionEndReason.AppShutdownTimeout);
    }
 
    [Fact]
    public async Task AcceptNewStreamsDuringClosingConnection()
    {
        await InitializeConnectionAsync(_echoApplication);
 
        await StartStreamAsync(1, _browserRequestHeaders, endStream: false);
 
        _connection.StopProcessingNextRequest(ConnectionEndReason.AppShutdownTimeout);
        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: 32,
            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: 2,
            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);
 
        TriggerTick();
 
        await WaitForConnectionStopAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
 
        AssertConnectionEndReason(ConnectionEndReason.AppShutdownTimeout);
    }
 
    [Fact]
    public async Task IgnoreNewStreamsDuringClosedConnection()
    {
        // Remove callback that completes _pair.Application.Output on abort.
        _mockConnectionContext.Reset();
 
        await InitializeConnectionAsync(_echoApplication);
 
        await StartStreamAsync(1, _browserRequestHeaders, endStream: false);
 
        _connection.OnInputOrOutputCompleted();
        await _closedStateReached.Task.DefaultTimeout();
 
        await StartStreamAsync(3, _browserRequestHeaders, endStream: false);
 
        var result = await _pair.Application.Input.ReadAsync().AsTask().DefaultTimeout();
        Assert.True(result.IsCompleted);
        Assert.True(result.Buffer.IsEmpty);
 
        AssertConnectionEndReason(ConnectionEndReason.ConnectionReset);
    }
 
    [Fact]
    public async Task IOExceptionDuringFrameProcessingIsNotLoggedHigherThanDebug()
    {
        CreateConnection();
 
        var ioException = new IOException();
        _pair.Application.Output.Complete(ioException);
 
        await _connection.ProcessRequestsAsync(new DummyApplication(_noopApplication)).DefaultTimeout();
 
        Assert.All(LogMessages, w => Assert.InRange(w.LogLevel, LogLevel.Trace, LogLevel.Debug));
 
        var logMessage = LogMessages.Single(m => m.EventId == 20);
 
        Assert.Equal("Connection id \"TestConnectionId\" request processing ended abnormally.", logMessage.Message);
        Assert.Same(ioException, logMessage.Exception);
 
        AssertConnectionEndReason(ConnectionEndReason.IOError);
    }
 
    [Fact]
    public async Task UnexpectedExceptionDuringFrameProcessingLoggedAWarning()
    {
        CreateConnection();
 
        var exception = new Exception();
        _pair.Application.Output.Complete(exception);
 
        await _connection.ProcessRequestsAsync(new DummyApplication(_noopApplication)).DefaultTimeout();
 
        var logMessage = LogMessages.Single(m => m.LogLevel >= LogLevel.Information);
 
        Assert.Equal(LogLevel.Warning, logMessage.LogLevel);
        Assert.Equal(CoreStrings.RequestProcessingEndError, logMessage.Message);
        Assert.Same(exception, logMessage.Exception);
 
        AssertConnectionEndReason(ConnectionEndReason.OtherError);
    }
 
    [Theory]
    [InlineData((int)(Http2FrameType.DATA))]
    [InlineData((int)(Http2FrameType.WINDOW_UPDATE))]
    [InlineData((int)(Http2FrameType.HEADERS))]
    [InlineData((int)(Http2FrameType.CONTINUATION))]
    public async Task AppDoesNotReadRequestBody_ResetsAndDrainsRequest(int intFinalFrameType)
    {
        var finalFrameType = (Http2FrameType)intFinalFrameType;
 
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
        };
        await InitializeConnectionAsync(_noopApplication);
 
        await StartStreamAsync(1, headers, endStream: false);
 
        await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 36,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
            withStreamId: 1);
 
        await WaitForStreamErrorAsync(1, Http2ErrorCode.NO_ERROR, null);
        // Logged without an exception.
        Assert.Contains(LogMessages, m => m.Message.Contains("the application completed without reading the entire request body."));
 
        // These would be refused if the cool-down period had expired
        switch (finalFrameType)
        {
            case Http2FrameType.DATA:
                await SendDataAsync(1, new byte[100], endStream: true);
                break;
            case Http2FrameType.WINDOW_UPDATE:
                await SendWindowUpdateAsync(1, 1024);
                break;
            case Http2FrameType.HEADERS:
                await SendHeadersAsync(1, Http2HeadersFrameFlags.END_STREAM | Http2HeadersFrameFlags.END_HEADERS, _requestTrailers);
                break;
            case Http2FrameType.CONTINUATION:
                await SendHeadersAsync(1, Http2HeadersFrameFlags.END_STREAM, _requestTrailers);
                await SendContinuationAsync(1, Http2ContinuationFrameFlags.END_HEADERS, _requestTrailers);
                break;
            default:
                throw new NotImplementedException(finalFrameType.ToString());
        }
 
        await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
 
        AssertConnectionNoError();
    }
 
    [Theory]
    [InlineData((int)(Http2FrameType.DATA))]
    [InlineData((int)(Http2FrameType.WINDOW_UPDATE))]
    [InlineData((int)(Http2FrameType.HEADERS))]
    [InlineData((int)(Http2FrameType.CONTINUATION))]
    public async Task AbortedStream_ResetsAndDrainsRequest(int intFinalFrameType)
    {
        var finalFrameType = (Http2FrameType)intFinalFrameType;
 
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
        };
        await InitializeConnectionAsync(_appAbort);
 
        await StartStreamAsync(1, headers, endStream: false);
 
        await WaitForStreamErrorAsync(1, Http2ErrorCode.INTERNAL_ERROR, "The connection was aborted by the application.");
 
        // These would be refused if the cool-down period had expired
        switch (finalFrameType)
        {
            case Http2FrameType.DATA:
                await SendDataAsync(1, new byte[100], endStream: true);
                break;
            case Http2FrameType.WINDOW_UPDATE:
                await SendWindowUpdateAsync(1, 1024);
                break;
            case Http2FrameType.HEADERS:
                await SendHeadersAsync(1, Http2HeadersFrameFlags.END_STREAM | Http2HeadersFrameFlags.END_HEADERS, _requestTrailers);
                break;
            case Http2FrameType.CONTINUATION:
                await SendHeadersAsync(1, Http2HeadersFrameFlags.END_STREAM, _requestTrailers);
                await SendContinuationAsync(1, Http2ContinuationFrameFlags.END_HEADERS, _requestTrailers);
                break;
            default:
                throw new NotImplementedException(finalFrameType.ToString());
        }
 
        await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
 
        AssertConnectionNoError();
    }
 
    [Theory]
    [InlineData((int)(Http2FrameType.DATA))]
    [InlineData((int)(Http2FrameType.WINDOW_UPDATE))]
    [InlineData((int)(Http2FrameType.HEADERS))]
    [InlineData((int)(Http2FrameType.CONTINUATION))]
    public async Task ResetStream_ResetsAndDrainsRequest(int intFinalFrameType)
    {
        var finalFrameType = (Http2FrameType)intFinalFrameType;
 
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
        };
        await InitializeConnectionAsync(_appReset);
 
        await StartStreamAsync(1, headers, endStream: false);
 
        await WaitForStreamErrorAsync(1, Http2ErrorCode.CANCEL, "The HTTP/2 stream was reset by the application with error code CANCEL.");
 
        // These would be refused if the cool-down period had expired
        switch (finalFrameType)
        {
            case Http2FrameType.DATA:
                await SendDataAsync(1, new byte[100], endStream: true);
                break;
            case Http2FrameType.WINDOW_UPDATE:
                await SendWindowUpdateAsync(1, 1024);
                break;
            case Http2FrameType.HEADERS:
                await SendHeadersAsync(1, Http2HeadersFrameFlags.END_STREAM | Http2HeadersFrameFlags.END_HEADERS, _requestTrailers);
                break;
            case Http2FrameType.CONTINUATION:
                await SendHeadersAsync(1, Http2HeadersFrameFlags.END_STREAM, _requestTrailers);
                await SendContinuationAsync(1, Http2ContinuationFrameFlags.END_HEADERS, _requestTrailers);
                break;
            default:
                throw new NotImplementedException(finalFrameType.ToString());
        }
 
        await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
 
        AssertConnectionNoError();
    }
 
    [Theory]
    [InlineData((int)(Http2FrameType.DATA))]
    [InlineData((int)(Http2FrameType.WINDOW_UPDATE))]
    [InlineData((int)(Http2FrameType.HEADERS))]
    [InlineData((int)(Http2FrameType.CONTINUATION))]
    public async Task RefusedStream_Post_ResetsAndDrainsRequest(int intFinalFrameType)
    {
        var finalFrameType = (Http2FrameType)intFinalFrameType;
 
        CreateConnection();
 
        _connection.ServerSettings.MaxConcurrentStreams = 0; // Refuse all streams
 
        var connectionTask = _connection.ProcessRequestsAsync(new DummyApplication(_noopApplication));
 
        async Task CompletePipeOnTaskCompletion()
        {
            try
            {
                await connectionTask;
            }
            finally
            {
                _pair.Transport.Input.Complete();
                _pair.Transport.Output.Complete();
            }
        }
 
        _connectionTask = CompletePipeOnTaskCompletion();
 
        await SendPreambleAsync().ConfigureAwait(false);
        await SendSettingsAsync();
 
        // Requests can be sent before receiving and acking settings.
 
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
        };
 
        await StartStreamAsync(1, headers, endStream: false);
 
        await ExpectAsync(Http2FrameType.SETTINGS,
            withLength: 4 * Http2FrameReader.SettingSize,
            withFlags: 0,
            withStreamId: 0);
 
        await ExpectAsync(Http2FrameType.WINDOW_UPDATE,
            withLength: 4,
            withFlags: 0,
            withStreamId: 0);
 
        await ExpectAsync(Http2FrameType.SETTINGS,
            withLength: 0,
            withFlags: (byte)Http2SettingsFrameFlags.ACK,
            withStreamId: 0);
 
        await WaitForStreamErrorAsync(1, Http2ErrorCode.REFUSED_STREAM, "HTTP/2 stream ID 1 error (REFUSED_STREAM): A new stream was refused because this connection has reached its stream limit.");
 
        // These frames should be drained and ignored while in cool-down mode.
        switch (finalFrameType)
        {
            case Http2FrameType.DATA:
                await SendDataAsync(1, new byte[100], endStream: true);
                break;
            case Http2FrameType.WINDOW_UPDATE:
                await SendWindowUpdateAsync(1, 1024);
                break;
            case Http2FrameType.HEADERS:
                await SendHeadersAsync(1, Http2HeadersFrameFlags.END_STREAM | Http2HeadersFrameFlags.END_HEADERS, _requestTrailers);
                break;
            case Http2FrameType.CONTINUATION:
                await SendHeadersAsync(1, Http2HeadersFrameFlags.END_STREAM, _requestTrailers);
                await SendContinuationAsync(1, Http2ContinuationFrameFlags.END_HEADERS, _requestTrailers);
                break;
            default:
                throw new NotImplementedException(finalFrameType.ToString());
        }
 
        await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
 
        AssertConnectionNoError();
    }
 
    [Fact]
    public async Task RefusedStream_Post_2xLimitRefused()
    {
        var requestBlock = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
 
        CreateConnection();
 
        _connection.ServerSettings.MaxConcurrentStreams = 1;
 
        var connectionTask = _connection.ProcessRequestsAsync(new DummyApplication(_ => requestBlock.Task));
 
        async Task CompletePipeOnTaskCompletion()
        {
            try
            {
                await connectionTask;
            }
            finally
            {
                _pair.Transport.Input.Complete();
                _pair.Transport.Output.Complete();
            }
        }
 
        _connectionTask = CompletePipeOnTaskCompletion();
 
        await SendPreambleAsync().ConfigureAwait(false);
        await SendSettingsAsync();
 
        // Requests can be sent before receiving and acking settings.
 
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
        };
 
        // This mimics gRPC, sending headers and data close together before receiving a reset.
        await StartStreamAsync(1, headers, endStream: false);
        await SendDataAsync(1, new byte[100], endStream: false);
        await StartStreamAsync(3, headers, endStream: false);
        await SendDataAsync(3, new byte[100], endStream: false);
        await StartStreamAsync(5, headers, endStream: false);
        await SendDataAsync(5, new byte[100], endStream: false);
        await StartStreamAsync(7, headers, endStream: false);
        await SendDataAsync(7, new byte[100], endStream: false);
 
        await ExpectAsync(Http2FrameType.SETTINGS,
            withLength: 4 * Http2FrameReader.SettingSize,
            withFlags: 0,
            withStreamId: 0);
 
        await ExpectAsync(Http2FrameType.WINDOW_UPDATE,
            withLength: 4,
            withFlags: 0,
            withStreamId: 0);
 
        await ExpectAsync(Http2FrameType.SETTINGS,
            withLength: 0,
            withFlags: (byte)Http2SettingsFrameFlags.ACK,
            withStreamId: 0);
 
        await WaitForStreamErrorAsync(3, Http2ErrorCode.REFUSED_STREAM, "HTTP/2 stream ID 3 error (REFUSED_STREAM): A new stream was refused because this connection has reached its stream limit.");
        await WaitForStreamErrorAsync(5, Http2ErrorCode.REFUSED_STREAM, "HTTP/2 stream ID 5 error (REFUSED_STREAM): A new stream was refused because this connection has reached its stream limit.");
        await WaitForStreamErrorAsync(7, Http2ErrorCode.REFUSED_STREAM, "HTTP/2 stream ID 7 error (REFUSED_STREAM): A new stream was refused because this connection has reached its stream limit.");
        requestBlock.SetResult();
        await StopConnectionAsync(expectedLastStreamId: 7, ignoreNonGoAwayFrames: true);
 
        AssertConnectionNoError();
    }
 
    [Fact]
    public async Task FramesInBatchAreStillProcessedAfterStreamError_WithoutHeartbeat()
    {
        // Previously, if there was a stream error, frame processing would stop and wait for either
        // the heartbeat or more data before continuing frame processing. This is testing that frames
        // continue to be processed if they were in the same read.
 
        CreateConnection();
        _connection.ServerSettings.MaxConcurrentStreams = 1;
 
        var tcs = new TaskCompletionSource<byte[]>(TaskCreationOptions.RunContinuationsAsynchronously);
 
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
        };
 
        await InitializeConnectionAsync(async context =>
        {
            var buffer = new byte[2];
            var result = await context.Request.BodyReader.ReadAsync().DefaultTimeout();
            Assert.True(result.IsCompleted);
            result.Buffer.CopyTo(buffer);
            context.Request.BodyReader.AdvanceTo(result.Buffer.Start, result.Buffer.End);
 
            tcs.SetResult(buffer);
        });
 
        var streamPayload = new byte[2] { 42, 24 };
        await StartStreamAsync(1, headers, endStream: false);
        // Send these 2 frames in a batch with the error in the first frame
        await StartStreamAsync(3, headers, endStream: false, flushFrame: false);
        await SendDataAsync(1, streamPayload, endStream: true);
 
        await WaitForStreamErrorAsync(3, Http2ErrorCode.REFUSED_STREAM, CoreStrings.Http2ErrorMaxStreams);
 
        var streamResponse = await tcs.Task.DefaultTimeout();
        Assert.Equal(streamPayload, streamResponse);
 
        await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: true);
        AssertConnectionNoError();
    }
 
    [Theory]
    [InlineData((int)(Http2FrameType.DATA))]
    [InlineData((int)(Http2FrameType.HEADERS))]
    [InlineData((int)(Http2FrameType.CONTINUATION))]
    public async Task AbortedStream_ResetsAndDrainsRequest_RefusesFramesAfterEndOfStream(int intFinalFrameType)
    {
        var finalFrameType = (Http2FrameType)intFinalFrameType;
 
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
        };
        await InitializeConnectionAsync(_appAbort);
 
        await StartStreamAsync(1, headers, endStream: false);
 
        await WaitForStreamErrorAsync(1, Http2ErrorCode.INTERNAL_ERROR, "The connection was aborted by the application.");
 
        switch (finalFrameType)
        {
            case Http2FrameType.DATA:
                await SendDataAsync(1, new byte[100], endStream: true);
                // An extra one to break it
                await SendDataAsync(1, new byte[100], endStream: true);
 
                // There's a race where either of these messages could be logged, depending on if the stream cleanup has finished yet.
                await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
                    ignoreNonGoAwayFrames: false,
                    expectedLastStreamId: 1,
                    expectedErrorCode: Http2ErrorCode.STREAM_CLOSED,
                    expectedErrorMessage: new[] {
                            CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.DATA, streamId: 1),
                            CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.DATA, streamId: 1)
                    });
                AssertConnectionEndReason(ConnectionEndReason.UnknownStream);
                break;
 
            case Http2FrameType.HEADERS:
                await SendHeadersAsync(1, Http2HeadersFrameFlags.END_STREAM | Http2HeadersFrameFlags.END_HEADERS, _requestTrailers);
                // An extra one to break it
                await SendHeadersAsync(1, Http2HeadersFrameFlags.END_STREAM | Http2HeadersFrameFlags.END_HEADERS, _requestTrailers);
 
                // There's a race where either of these messages could be logged, depending on if the stream cleanup has finished yet.
                await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
                    ignoreNonGoAwayFrames: false,
                    expectedLastStreamId: 1,
                    expectedErrorCode: Http2ErrorCode.STREAM_CLOSED,
                    expectedErrorMessage: new[] {
                            CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1),
                            CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.HEADERS, streamId: 1)
                    });
                AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId);
                break;
 
            case Http2FrameType.CONTINUATION:
                await SendHeadersAsync(1, Http2HeadersFrameFlags.END_STREAM, _requestTrailers);
                await SendContinuationAsync(1, Http2ContinuationFrameFlags.END_HEADERS, _requestTrailers);
                // An extra one to break it. It's not a Continuation because that would fail with an error that no headers were in progress.
                await SendHeadersAsync(1, Http2HeadersFrameFlags.END_STREAM, _requestTrailers);
 
                // There's a race where either of these messages could be logged, depending on if the stream cleanup has finished yet.
                await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
                    ignoreNonGoAwayFrames: false,
                    expectedLastStreamId: 1,
                    expectedErrorCode: Http2ErrorCode.STREAM_CLOSED,
                    expectedErrorMessage: new[] {
                            CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1),
                            CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(Http2FrameType.HEADERS, streamId: 1)
                    });
                AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId);
                break;
            default:
                throw new NotImplementedException(finalFrameType.ToString());
        }
    }
 
    [Theory]
    [InlineData((int)(Http2FrameType.DATA))]
    [InlineData((int)(Http2FrameType.HEADERS))]
    public async Task AbortedStream_ResetsAndDrainsRequest_RefusesFramesAfterClientReset(int intFinalFrameType)
    {
        var finalFrameType = (Http2FrameType)intFinalFrameType;
 
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
        };
        await InitializeConnectionAsync(_appAbort);
 
        await StartStreamAsync(1, headers, endStream: false);
 
        await WaitForStreamErrorAsync(1, Http2ErrorCode.INTERNAL_ERROR, "The connection was aborted by the application.");
 
        await SendRstStreamAsync(1);
 
        // Send an extra frame to make it fail
        switch (finalFrameType)
        {
            case Http2FrameType.DATA:
                await SendDataAsync(1, new byte[100], endStream: true);
                break;
 
            case Http2FrameType.HEADERS:
                await SendHeadersAsync(1, Http2HeadersFrameFlags.END_STREAM | Http2HeadersFrameFlags.END_HEADERS, _requestTrailers);
                break;
 
            default:
                throw new NotImplementedException(finalFrameType.ToString());
        }
 
        // There's a race where either of these messages could be logged, depending on if the stream cleanup has finished yet.
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 1,
            expectedErrorCode: Http2ErrorCode.STREAM_CLOSED,
            expectedErrorMessage: new[] {
                    CoreStrings.FormatHttp2ErrorStreamClosed(finalFrameType, streamId: 1),
                    CoreStrings.FormatHttp2ErrorStreamAborted(finalFrameType, streamId: 1)
            });
 
        switch (finalFrameType)
        {
            case Http2FrameType.DATA:
                AssertConnectionEndReason(ConnectionEndReason.UnknownStream);
                break;
 
            case Http2FrameType.HEADERS:
                AssertConnectionEndReason(ConnectionEndReason.InvalidStreamId);
                break;
 
            default:
                throw new NotImplementedException(finalFrameType.ToString());
        }
    }
 
    [Fact]
    public async Task StartConnection_SendPreface_ReturnSettings()
    {
        InitializeConnectionWithoutPreface(_noopApplication);
 
        await SendAsync(Http2Connection.ClientPreface);
 
        await ExpectAsync(Http2FrameType.SETTINGS,
            withLength: 4 * Http2FrameReader.SettingSize,
            withFlags: 0,
            withStreamId: 0);
 
        await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: true);
        AssertConnectionNoError();
    }
 
    [Fact]
    public async Task StartConnection_SendHttp1xRequest_ReturnHttp11Status400()
    {
        InitializeConnectionWithoutPreface(_noopApplication);
 
        await SendAsync(Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\n"));
 
        var data = await ReadAllAsync();
 
        Assert.NotNull(Http2Connection.InvalidHttp1xErrorResponseBytes);
        Assert.Equal(Http2Connection.InvalidHttp1xErrorResponseBytes, data);
        AssertConnectionEndReason(ConnectionEndReason.InvalidHttpVersion);
    }
 
    [Fact]
    public async Task StartConnection_SendHttp1xRequest_ExceedsRequestLineLimit_ProtocolError()
    {
        InitializeConnectionWithoutPreface(_noopApplication);
 
        await SendAsync(Encoding.ASCII.GetBytes($"GET /{new string('a', _connection.Limits.MaxRequestLineSize)} HTTP/1.1\r\n"));
 
        await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
            ignoreNonGoAwayFrames: false,
            expectedLastStreamId: 0,
            expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
            expectedErrorMessage: CoreStrings.Http2ErrorInvalidPreface);
        AssertConnectionEndReason(ConnectionEndReason.InvalidHandshake);
    }
 
    [Fact]
    public async Task StartTlsConnection_SendHttp1xRequest_NoError()
    {
        CreateConnection();
 
        var tlsHandshakeMock = new Mock<ITlsHandshakeFeature>();
        tlsHandshakeMock.SetupGet(m => m.Protocol).Returns(SslProtocols.Tls12);
        _connection.ConnectionFeatures.Set<ITlsHandshakeFeature>(tlsHandshakeMock.Object);
 
        InitializeConnectionWithoutPreface(_noopApplication);
 
        await SendAsync(Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\n"));
 
        await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false);
        AssertConnectionNoError();
    }
 
    [Fact]
    public async Task StartConnection_SendNothing_NoError()
    {
        InitializeConnectionWithoutPreface(_noopApplication);
 
        await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false);
        AssertConnectionNoError();
    }
 
    [Fact]
    public async Task WriteBeforeFlushingHeadersTracksBytesCorrectly()
    {
        var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        await InitializeConnectionAsync(async c =>
        {
            try
            {
                var length = 0;
                var memory = c.Response.BodyWriter.GetMemory();
                c.Response.BodyWriter.Advance(memory.Length);
                length += memory.Length;
                Assert.Equal(length, c.Response.BodyWriter.UnflushedBytes);
 
                memory = c.Response.BodyWriter.GetMemory();
                c.Response.BodyWriter.Advance(memory.Length);
                length += memory.Length;
 
                Assert.Equal(length, c.Response.BodyWriter.UnflushedBytes);
 
                await c.Response.BodyWriter.FlushAsync();
 
                Assert.Equal(0, c.Response.BodyWriter.UnflushedBytes);
 
                tcs.SetResult();
            }
            catch (Exception ex)
            {
                tcs.SetException(ex);
            }
        });
 
        await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
 
        var frame = await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 32,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS),
            withStreamId: 1);
 
        frame = await ExpectAsync(Http2FrameType.DATA,
            withLength: 8192,
            withFlags: (byte)(Http2HeadersFrameFlags.NONE),
            withStreamId: 1);
 
        await ExpectAsync(Http2FrameType.DATA,
            withLength: 0,
            withFlags: (byte)(Http2HeadersFrameFlags.END_STREAM),
            withStreamId: 1);
 
        await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
 
        await tcs.Task;
    }
 
    [Fact]
    public async Task WriteAfterFlushingHeadersTracksBytesCorrectly()
    {
        var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        await InitializeConnectionAsync(async c =>
        {
            try
            {
                await c.Response.StartAsync();
 
                var length = 0;
                var memory = c.Response.BodyWriter.GetMemory();
                c.Response.BodyWriter.Advance(memory.Length);
                length += memory.Length;
                Assert.Equal(length, c.Response.BodyWriter.UnflushedBytes);
 
                memory = c.Response.BodyWriter.GetMemory();
                c.Response.BodyWriter.Advance(memory.Length);
                length += memory.Length;
 
                Assert.Equal(length, c.Response.BodyWriter.UnflushedBytes);
 
                await c.Response.BodyWriter.FlushAsync();
 
                Assert.Equal(0, c.Response.BodyWriter.UnflushedBytes);
 
                tcs.SetResult();
            }
            catch (Exception ex)
            {
                tcs.SetException(ex);
            }
        });
 
        await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
 
        var frame = await ExpectAsync(Http2FrameType.HEADERS,
            withLength: 32,
            withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS),
            withStreamId: 1);
 
        frame = await ExpectAsync(Http2FrameType.DATA,
            withLength: 8192,
            withFlags: (byte)(Http2HeadersFrameFlags.NONE),
            withStreamId: 1);
 
        await ExpectAsync(Http2FrameType.DATA,
            withLength: 0,
            withFlags: (byte)(Http2HeadersFrameFlags.END_STREAM),
            withStreamId: 1);
 
        await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
 
        await tcs.Task;
    }
 
    public static TheoryData<byte[]> 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<byte[]>();
 
            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<IEnumerable<KeyValuePair<string, string>>> DuplicatePseudoHeaderFieldData
    {
        get
        {
            var data = new TheoryData<IEnumerable<KeyValuePair<string, string>>>();
            var requestHeaders = new[]
            {
                    new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
                    new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
                    new KeyValuePair<string, string>(InternalHeaderNames.Authority, "127.0.0.1"),
                    new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
                };
 
            foreach (var headerField in requestHeaders)
            {
                var headers = requestHeaders.Concat(new[] { new KeyValuePair<string, string>(headerField.Key, headerField.Value) });
                data.Add(headers);
            }
 
            return data;
        }
    }
 
    public static TheoryData<IEnumerable<KeyValuePair<string, string>>> MissingPseudoHeaderFieldData
    {
        get
        {
            var data = new TheoryData<IEnumerable<KeyValuePair<string, string>>>();
            var requestHeaders = new[]
            {
                    new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
                    new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
                    new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
                };
 
            foreach (var headerField in requestHeaders)
            {
                var headers = requestHeaders.Except(new[] { headerField });
                data.Add(headers);
            }
 
            return data;
        }
    }
 
    public static TheoryData<IEnumerable<KeyValuePair<string, string>>> ConnectMissingPseudoHeaderFieldData
    {
        get
        {
            var data = new TheoryData<IEnumerable<KeyValuePair<string, string>>>();
            var methodHeader = new KeyValuePair<string, string>(InternalHeaderNames.Method, "CONNECT");
            var headers = new[] { methodHeader };
            data.Add(headers);
 
            return data;
        }
    }
 
    public static TheoryData<IEnumerable<KeyValuePair<string, string>>> PseudoHeaderFieldAfterRegularHeadersData
    {
        get
        {
            var data = new TheoryData<IEnumerable<KeyValuePair<string, string>>>();
            var requestHeaders = new[]
            {
                    new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
                    new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
                    new KeyValuePair<string, string>(InternalHeaderNames.Authority, "127.0.0.1"),
                    new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
                    new KeyValuePair<string, string>("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<byte[], string> IllegalTrailerData
    {
        get
        {
            // We can't use HPackEncoder here because it will convert header names to lowercase
            var data = new TheoryData<byte[], string>();
 
            // Indexed Header Field - :method: GET
            data.Add(new byte[] { 0x82 }, CoreStrings.HttpErrorTrailersContainPseudoHeaderField);
 
            // Indexed Header Field - :path: /
            data.Add(new byte[] { 0x84 }, CoreStrings.HttpErrorTrailersContainPseudoHeaderField);
 
            // Indexed Header Field - :scheme: http
            data.Add(new byte[] { 0x86 }, CoreStrings.HttpErrorTrailersContainPseudoHeaderField);
 
            // 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.HttpErrorTrailersContainPseudoHeaderField);
 
            // 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.HttpErrorTrailerNameUppercase);
 
            return data;
        }
    }
}