File: Http3\Http3StreamTests.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;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Runtime.ExceptionServices;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3;
using Microsoft.AspNetCore.InternalTesting;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Net.Http.Headers;
using Xunit;
 
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests;
 
public class Http3StreamTests : Http3TestBase
{
    [Fact]
    public async Task HelloWorldTest()
    {
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "Custom"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>(InternalHeaderNames.Authority, "localhost:80"),
        };
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(_echoApplication, headers);
        await requestStream.SendDataAsync(Encoding.ASCII.GetBytes("Hello world"), endStream: true);
 
        var responseHeaders = await requestStream.ExpectHeadersAsync();
        Assert.Equal("200", responseHeaders[InternalHeaderNames.Status]);
 
        var responseData = await requestStream.ExpectDataAsync();
        Assert.Equal("Hello world", Encoding.ASCII.GetString(responseData.ToArray()));
    }
 
    [Fact]
    public async Task UnauthorizedHttpStatusResponse()
    {
        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.Authority, "localhost:80"),
        };
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(context =>
        {
            context.Response.StatusCode = 401;
            return Task.CompletedTask;
        }, headers, endStream: true);
 
        var responseHeaders = await requestStream.ExpectHeadersAsync();
        Assert.Equal("401", responseHeaders[InternalHeaderNames.Status]);
 
        await requestStream.ExpectReceiveEndOfStream();
    }
 
    [Fact]
    public async Task EmptyMethod_Reset()
    {
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, ""),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>(InternalHeaderNames.Authority, "localhost:80"),
        };
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(_echoApplication, headers);
        await requestStream.WaitForStreamErrorAsync(
            Http3ErrorCode.ProtocolError,
            AssertExpectedErrorMessages,
            CoreStrings.FormatHttp3ErrorMethodInvalid(""));
    }
 
    [Fact]
    public async Task InvalidCustomMethod_Reset()
    {
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "Hello,World"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>(InternalHeaderNames.Authority, "localhost:80"),
        };
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(_echoApplication, headers);
        await requestStream.WaitForStreamErrorAsync(
            Http3ErrorCode.ProtocolError,
            AssertExpectedErrorMessages,
            CoreStrings.FormatHttp3ErrorMethodInvalid("Hello,World"));
    }
 
    [Fact]
    public async Task CustomMethod_Accepted()
    {
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "Custom"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>(InternalHeaderNames.Authority, "localhost:80"),
        };
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(_echoMethod, headers, endStream: true);
 
        var responseHeaders = await requestStream.ExpectHeadersAsync();
 
        Assert.Equal(4, responseHeaders.Count);
        Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("200", responseHeaders[InternalHeaderNames.Status]);
        Assert.Equal("Custom", responseHeaders["Method"]);
        Assert.Equal("0", responseHeaders["content-length"]);
    }
 
    [Fact]
    public async Task RequestHeadersMaxRequestHeaderFieldSize_EndsStream()
    {
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "Custom"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>(InternalHeaderNames.Authority, "localhost:80"),
            new KeyValuePair<string, string>("test", new string('a', 1024 * 32 + 1))
        };
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(_echoApplication, headers);
 
        await requestStream.WaitForStreamErrorAsync(
            Http3ErrorCode.InternalError,
            AssertExpectedErrorMessages,
            $"The HTTP headers length exceeded the set limit of {1024 * 32} bytes.");
    }
 
    [Fact]
    public async Task ConnectMethod_Accepted()
    {
        // :path and :scheme are not allowed, :authority is optional
        var headers = new[] { new KeyValuePair<string, string>(InternalHeaderNames.Method, "CONNECT") };
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(_echoMethod, headers, endStream: true);
 
        var responseHeaders = await requestStream.ExpectHeadersAsync();
 
        Assert.Equal(3, responseHeaders.Count);
        Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("200", responseHeaders[InternalHeaderNames.Status]);
        Assert.Equal("CONNECT", responseHeaders["Method"]);
    }
 
    [Fact]
    public async Task OptionsStar_LeftOutOfPath()
    {
        var headers = new[] { new KeyValuePair<string, string>(InternalHeaderNames.Method, "OPTIONS"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "*")};
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(_echoPath, headers, endStream: true);
 
        var responseHeaders = await requestStream.ExpectHeadersAsync();
 
        Assert.Equal(5, responseHeaders.Count);
        Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("200", responseHeaders[InternalHeaderNames.Status]);
        Assert.Equal("", responseHeaders["path"]);
        Assert.Equal("*", responseHeaders["rawtarget"]);
        Assert.Equal("0", responseHeaders["content-length"]);
    }
 
    [Fact]
    public async Task OptionsSlash_Accepted()
    {
        var headers = new[] { new KeyValuePair<string, string>(InternalHeaderNames.Method, "OPTIONS"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/")};
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(_echoPath, headers, endStream: true);
 
        var responseHeaders = await requestStream.ExpectHeadersAsync();
 
        Assert.Equal(5, responseHeaders.Count);
        Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("200", responseHeaders[InternalHeaderNames.Status]);
        Assert.Equal("/", responseHeaders["path"]);
        Assert.Equal("/", responseHeaders["rawtarget"]);
        Assert.Equal("0", responseHeaders["content-length"]);
    }
 
    [Fact]
    public async Task PathAndQuery_Separated()
    {
        // :path and :scheme are not allowed, :authority is optional
        var headers = new[] { new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/a/path?a&que%35ry")};
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(context =>
        {
            context.Response.Headers["path"] = context.Request.Path.Value;
            context.Response.Headers["query"] = context.Request.QueryString.Value;
            context.Response.Headers["rawtarget"] = context.Features.Get<IHttpRequestFeature>().RawTarget;
            return Task.CompletedTask;
        }, headers, endStream: true);
 
        var responseHeaders = await requestStream.ExpectHeadersAsync();
 
        Assert.Equal(6, responseHeaders.Count);
        Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("200", responseHeaders[InternalHeaderNames.Status]);
        Assert.Equal("/a/path", responseHeaders["path"]);
        Assert.Equal("?a&que%35ry", responseHeaders["query"]);
        Assert.Equal("/a/path?a&que%35ry", responseHeaders["rawtarget"]);
        Assert.Equal("0", responseHeaders["content-length"]);
    }
 
    [Theory]
    [InlineData("/", "/")]
    [InlineData("/a%5E", "/a^")]
    [InlineData("/a%E2%82%AC", "/a€")]
    [InlineData("/a%2Fb", "/a%2Fb")] // Forward slash, not decoded
    [InlineData("/a%b", "/a%b")] // Incomplete encoding, not decoded
    [InlineData("/a/b/c/../d", "/a/b/d")] // Navigation processed
    [InlineData("/a/b/c/../../../../d", "/d")] // Navigation escape prevented
    [InlineData("/a/b/c/.%2E/d", "/a/b/d")] // Decode before navigation processing
    public async Task Path_DecodedAndNormalized(string input, string expected)
    {
        // :path and :scheme are not allowed, :authority is optional
        var headers = new[] { new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, input)};
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(context =>
        {
            Assert.Equal(expected, context.Request.Path.Value);
            Assert.Equal(input, context.Features.Get<IHttpRequestFeature>().RawTarget);
            return Task.CompletedTask;
        }, headers, endStream: true);
 
        var responseHeaders = await requestStream.ExpectHeadersAsync();
 
        Assert.Equal(3, responseHeaders.Count);
        Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("200", responseHeaders[InternalHeaderNames.Status]);
        Assert.Equal("0", responseHeaders["content-length"]);
    }
 
    [Theory]
    [InlineData(":path", "/")]
    [InlineData(":scheme", "http")]
    public async Task ConnectMethod_WithSchemeOrPath_Reset(string headerName, string value)
    {
        // :path and :scheme are not allowed, :authority is optional
        var headers = new[] { new KeyValuePair<string, string>(InternalHeaderNames.Method, "CONNECT"),
            new KeyValuePair<string, string>(headerName, value) };
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(_noopApplication, headers, endStream: true);
 
        await requestStream.WaitForStreamErrorAsync(
            Http3ErrorCode.ProtocolError,
            AssertExpectedErrorMessages,
            CoreStrings.Http3ErrorConnectMustNotSendSchemeOrPath);
    }
 
    [Theory]
    [InlineData("https")]
    [InlineData("ftp")]
    public async Task SchemeMismatch_Reset(string scheme)
    {
        var headers = new[] { new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, scheme) }; // Not the expected "http"
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(_noopApplication, headers, endStream: true);
 
        await requestStream.WaitForStreamErrorAsync(
            Http3ErrorCode.ProtocolError,
            AssertExpectedErrorMessages,
            CoreStrings.FormatHttp3StreamErrorSchemeMismatch(scheme, "http"));
    }
 
    [Theory]
    [InlineData("https")]
    [InlineData("ftp")]
    public async Task SchemeMismatchAllowed_Processed(string scheme)
    {
        _serviceContext.ServerOptions.AllowAlternateSchemes = true;
 
        var headers = new[] { new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, scheme) }; // Not the expected "http"
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(context =>
        {
            Assert.Equal(scheme, context.Request.Scheme);
            return Task.CompletedTask;
        }, headers, endStream: true);
 
        var responseHeaders = await requestStream.ExpectHeadersAsync();
 
        Assert.Equal(3, responseHeaders.Count);
        Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("200", responseHeaders[InternalHeaderNames.Status]);
        Assert.Equal("0", responseHeaders["content-length"]);
    }
 
    [Theory]
    [InlineData("https,http")]
    [InlineData("http://fakehost/")]
    public async Task SchemeMismatchAllowed_InvalidScheme_Reset(string scheme)
    {
        _serviceContext.ServerOptions.AllowAlternateSchemes = true;
 
        var headers = new[] { new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, scheme) }; // Not the expected "http"
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(_noopApplication, headers, endStream: true);
 
        await requestStream.WaitForStreamErrorAsync(
            Http3ErrorCode.ProtocolError,
            AssertExpectedErrorMessages,
            CoreStrings.FormatHttp3StreamErrorSchemeMismatch(scheme, "http"));
    }
 
    [Fact]
    public async Task MissingAuthority_200Status()
    {
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
        };
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(_noopApplication, headers, endStream: true);
 
        var responseHeaders = await requestStream.ExpectHeadersAsync();
 
        Assert.Equal(3, responseHeaders.Count);
        Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("200", responseHeaders[InternalHeaderNames.Status]);
        Assert.Equal("0", responseHeaders["content-length"]);
    }
 
    [Fact]
    public async Task EmptyAuthority_200Status()
    {
        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.Authority, ""),
        };
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(_noopApplication, headers, endStream: true);
 
        var responseHeaders = await requestStream.ExpectHeadersAsync();
 
        Assert.Equal(3, responseHeaders.Count);
        Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("200", responseHeaders[InternalHeaderNames.Status]);
        Assert.Equal("0", responseHeaders["content-length"]);
    }
 
    [Fact]
    public async Task MissingAuthorityFallsBackToHost_200Status()
    {
        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>("Host", "abc"),
        };
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(_echoHost, headers, endStream: true);
 
        var responseHeaders = await requestStream.ExpectHeadersAsync();
 
        Assert.Equal(4, responseHeaders.Count);
        Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("200", responseHeaders[InternalHeaderNames.Status]);
        Assert.Equal("0", responseHeaders[HeaderNames.ContentLength]);
        Assert.Equal("abc", responseHeaders[HeaderNames.Host]);
    }
 
    [Fact]
    public async Task EmptyAuthorityIgnoredOverHost_200Status()
    {
        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.Authority, ""),
            new KeyValuePair<string, string>("Host", "abc"),
        };
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(_echoHost, headers, endStream: true);
 
        var responseHeaders = await requestStream.ExpectHeadersAsync();
 
        Assert.Equal(4, responseHeaders.Count);
        Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("200", responseHeaders[InternalHeaderNames.Status]);
        Assert.Equal("0", responseHeaders[HeaderNames.ContentLength]);
        Assert.Equal("abc", responseHeaders[HeaderNames.Host]);
    }
 
    [Fact]
    public async Task AuthorityOverridesHost_200Status()
    {
        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.Authority, "def"),
            new KeyValuePair<string, string>("Host", "abc"),
        };
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(_echoHost, headers, endStream: true);
 
        var responseHeaders = await requestStream.ExpectHeadersAsync();
 
        Assert.Equal(4, responseHeaders.Count);
        Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("200", responseHeaders[InternalHeaderNames.Status]);
        Assert.Equal("0", responseHeaders[HeaderNames.ContentLength]);
        Assert.Equal("def", responseHeaders[HeaderNames.Host]);
    }
 
    [Fact]
    public async Task AuthorityOverridesInvalidHost_200Status()
    {
        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.Authority, "def"),
            new KeyValuePair<string, string>("Host", "a=bc"),
        };
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(_echoHost, headers, endStream: true);
 
        var responseHeaders = await requestStream.ExpectHeadersAsync();
 
        Assert.Equal(4, responseHeaders.Count);
        Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("200", responseHeaders[InternalHeaderNames.Status]);
        Assert.Equal("0", responseHeaders[HeaderNames.ContentLength]);
        Assert.Equal("def", responseHeaders[HeaderNames.Host]);
    }
 
    [Fact]
    public async Task InvalidAuthority_Reset()
    {
        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.Authority, "local=host:80"),
        };
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(_noopApplication, headers, endStream: true);
 
        await requestStream.WaitForStreamErrorAsync(
            Http3ErrorCode.ProtocolError,
            AssertExpectedErrorMessages,
            CoreStrings.FormatBadRequest_InvalidHostHeader_Detail("local=host:80"));
    }
 
    [Fact]
    public async Task InvalidAuthorityWithValidHost_Reset()
    {
        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.Authority, "d=ef"),
            new KeyValuePair<string, string>("Host", "abc"),
        };
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(_noopApplication, headers, endStream: true);
 
        await requestStream.WaitForStreamErrorAsync(
            Http3ErrorCode.ProtocolError,
            AssertExpectedErrorMessages,
            CoreStrings.FormatBadRequest_InvalidHostHeader_Detail("d=ef"));
    }
 
    [Fact]
    public async Task TwoHosts_StreamReset()
    {
        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>("Host", "host1"),
            new KeyValuePair<string, string>("Host", "host2"),
        };
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(_noopApplication, headers, endStream: true);
 
        await requestStream.WaitForStreamErrorAsync(
            Http3ErrorCode.ProtocolError,
            AssertExpectedErrorMessages,
            CoreStrings.FormatBadRequest_InvalidHostHeader_Detail("host1,host2"));
    }
 
    [Fact]
    public async Task MaxRequestLineSize_Reset()
    {
        // Default 8kb limit
        // This test has to work around the HPack parser limit for incoming field sizes over 4kb. That's going to be a problem for people with long urls.
        // https://github.com/aspnet/KestrelHttpServer/issues/2872
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET" + new string('a', 1024 * 3)),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/Hello/How/Are/You/" + new string('a', 1024 * 3)),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>(InternalHeaderNames.Authority, "localhost" + new string('a', 1024 * 3) + ":80"),
        };
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(_noopApplication, headers, endStream: true);
 
        await requestStream.WaitForStreamErrorAsync(
            Http3ErrorCode.RequestRejected,
            AssertExpectedErrorMessages,
            CoreStrings.BadRequest_RequestLineTooLong);
    }
 
    [Fact]
    public async Task ContentLength_Received_SingleDataFrame_Verified()
    {
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>(HeaderNames.ContentLength, "12"),
        };
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async context =>
        {
            var buffer = new byte[100];
            var read = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length);
            Assert.Equal(12, read);
            read = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length);
            Assert.Equal(0, read);
        }, headers, endStream: false);
        await requestStream.SendDataAsync(new byte[12], endStream: true);
 
        var responseHeaders = await requestStream.ExpectHeadersAsync();
 
        Assert.Equal(3, responseHeaders.Count);
        Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("200", responseHeaders[InternalHeaderNames.Status]);
        Assert.Equal("0", responseHeaders[HeaderNames.ContentLength]);
    }
 
    [Fact]
    public async Task ContentLength_Received_MultipleDataFrame_Verified()
    {
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>(HeaderNames.ContentLength, "12"),
        };
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async context =>
        {
            var buffer = new byte[100];
            var read = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length);
            var total = read;
            while (read > 0)
            {
                read = await context.Request.Body.ReadAsync(buffer, total, buffer.Length - total);
                total += read;
            }
            Assert.Equal(12, total);
        }, headers, endStream: false);
 
        await requestStream.SendDataAsync(new byte[1], endStream: false);
        await requestStream.SendDataAsync(new byte[3], endStream: false);
        await requestStream.SendDataAsync(new byte[8], endStream: true);
 
        var responseHeaders = await requestStream.ExpectHeadersAsync();
 
        Assert.Equal(3, responseHeaders.Count);
        Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("200", responseHeaders[InternalHeaderNames.Status]);
        Assert.Equal("0", responseHeaders[HeaderNames.ContentLength]);
    }
 
    [Fact]
    public async Task ContentLength_Received_MultipleDataFrame_ReadViaPipe_Verified()
    {
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>(HeaderNames.ContentLength, "12"),
        };
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async context =>
        {
            var readResult = await context.Request.BodyReader.ReadAsync();
            while (!readResult.IsCompleted)
            {
                context.Request.BodyReader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
                readResult = await context.Request.BodyReader.ReadAsync();
            }
 
            Assert.Equal(12, readResult.Buffer.Length);
            context.Request.BodyReader.AdvanceTo(readResult.Buffer.End);
        }, headers, endStream: false);
 
        await requestStream.SendDataAsync(new byte[1], endStream: false);
        await requestStream.SendDataAsync(new byte[3], endStream: false);
        await requestStream.SendDataAsync(new byte[8], endStream: true);
 
        var responseHeaders = await requestStream.ExpectHeadersAsync();
 
        Assert.Equal(3, responseHeaders.Count);
        Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("200", responseHeaders[InternalHeaderNames.Status]);
        Assert.Equal("0", responseHeaders[HeaderNames.ContentLength]);
    }
 
    [Fact]
    public async Task RemoveConnectionSpecificHeaders()
    {
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "Custom"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>(InternalHeaderNames.Authority, "localhost:80"),
        };
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async context =>
        {
            var response = context.Response;
 
            response.Headers.Add(HeaderNames.TransferEncoding, "chunked");
            response.Headers.Add(HeaderNames.Upgrade, "websocket");
            response.Headers.Add(HeaderNames.Connection, "Keep-Alive");
            response.Headers.Add(HeaderNames.KeepAlive, "timeout=5, max=1000");
            response.Headers.Add(HeaderNames.ProxyConnection, "keep-alive");
 
            await response.WriteAsync("Hello world");
        }, headers, endStream: true);
 
        var responseHeaders = await requestStream.ExpectHeadersAsync();
        Assert.Equal(2, responseHeaders.Count);
 
        var responseData = await requestStream.ExpectDataAsync();
        Assert.Equal("Hello world", Encoding.ASCII.GetString(responseData.ToArray()));
 
        Assert.Contains(LogMessages, m => m.Message.Equals("One or more of the following response headers have been removed because they are invalid for HTTP/2 and HTTP/3 responses: 'Connection', 'Transfer-Encoding', 'Keep-Alive', 'Upgrade' and 'Proxy-Connection'."));
    }
 
    [Fact]
    public async Task ContentLength_Received_NoDataFrames_Reset()
    {
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>(HeaderNames.ContentLength, "12"),
        };
 
        var requestDelegateCalled = false;
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(c =>
        {
            // Bad content-length + end stream means the request delegate
            // is never called by the server.
            requestDelegateCalled = true;
            return Task.CompletedTask;
        }, headers, endStream: true);
 
        await requestStream.WaitForStreamErrorAsync(
            Http3ErrorCode.ProtocolError,
            AssertExpectedErrorMessages,
            CoreStrings.Http3StreamErrorLessDataThanLength);
 
        Assert.False(requestDelegateCalled);
    }
 
    [Fact]
    public async Task EndRequestStream_ContinueReadingFromResponse()
    {
        var headersTcs = 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"),
        };
 
        var data = new byte[] { 1, 2, 3, 4, 5, 6 };
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async context =>
        {
            await context.Response.BodyWriter.FlushAsync();
 
            await headersTcs.Task;
 
            for (var i = 0; i < data.Length; i++)
            {
                await Task.Delay(50);
                await context.Response.BodyWriter.WriteAsync(new byte[] { data[i] });
            }
        }, headers, endStream: true);
        await requestStream.ExpectHeadersAsync();
 
        headersTcs.SetResult();
 
        var receivedData = new List<byte>();
        while (receivedData.Count < data.Length)
        {
            var frameData = await requestStream.ExpectDataAsync();
            receivedData.AddRange(frameData.ToArray());
        }
 
        Assert.Equal(data, receivedData);
 
        await requestStream.ExpectReceiveEndOfStream();
    }
 
    [Fact]
    public async Task FlushPipeAsync_OnStoppedHttp3Stream_ReturnsFlushResultWithIsCompletedTrue()
    {
        var appTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        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 requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async context =>
        {
            try
            {
                context.Abort();
 
                var payload = Encoding.ASCII.GetBytes("hello world");
                var result = await context.Response.BodyWriter.WriteAsync(payload);
 
                Assert.True(result.IsCompleted);
                appTcs.SetResult();
            }
            catch (Exception ex)
            {
                appTcs.SetException(ex);
            }
        }, requestHeaders, endStream: true);
 
        await requestStream.ExpectReceiveEndOfStream();
        await appTcs.Task;
    }
 
    [Fact]
    public async Task FlushPipeAsync_OnCanceledPendingFlush_ReturnsFlushResultWithIsCanceledTrue()
    {
        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 requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async context =>
        {
            context.Response.BodyWriter.CancelPendingFlush();
            var payload = Encoding.ASCII.GetBytes("hello,");
            var cancelledResult = await context.Response.BodyWriter.WriteAsync(payload);
            Assert.True(cancelledResult.IsCanceled);
 
            var secondPayload = Encoding.ASCII.GetBytes(" world");
            var goodResult = await context.Response.BodyWriter.WriteAsync(secondPayload);
            Assert.False(goodResult.IsCanceled);
        }, requestHeaders, endStream: true);
        await requestStream.ExpectHeadersAsync();
 
        var response = await requestStream.ExpectDataAsync();
        Assert.Equal("hello,", Encoding.UTF8.GetString(response.Span));
 
        var secondResponse = await requestStream.ExpectDataAsync();
        Assert.Equal(" world", Encoding.UTF8.GetString(secondResponse.Span));
 
        await requestStream.ExpectReceiveEndOfStream();
    }
 
    [Fact]
    public async Task ResponseTrailers_WithoutData_Sent()
    {
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "Custom"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>(InternalHeaderNames.Authority, "localhost:80"),
        };
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(context =>
        {
            var trailersFeature = context.Features.Get<IHttpResponseTrailersFeature>();
 
            trailersFeature.Trailers.Add("Trailer1", "Value1");
            trailersFeature.Trailers.Add("Trailer2", "Value2");
 
            return Task.CompletedTask;
        }, headers, endStream: true);
 
        var responseHeaders = await requestStream.ExpectHeadersAsync();
 
        var responseTrailers = await requestStream.ExpectHeadersAsync();
 
        Assert.Equal(2, responseTrailers.Count);
        Assert.Equal("Value1", responseTrailers["Trailer1"]);
        Assert.Equal("Value2", responseTrailers["Trailer2"]);
    }
 
    [Fact]
    public async Task ResponseHeaders_WithNonAscii_Throws()
    {
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "Custom"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>(InternalHeaderNames.Authority, "localhost:80"),
        };
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async context =>
        {
            var trailersFeature = context.Features.Get<IHttpResponseTrailersFeature>();
 
            Assert.Throws<InvalidOperationException>(() => context.Response.Headers.Append("Custom你好Name", "Custom Value"));
            Assert.Throws<InvalidOperationException>(() => context.Response.ContentType = "Custom 你好 Type");
            Assert.Throws<InvalidOperationException>(() => context.Response.Headers.Append("CustomName", "Custom 你好 Value"));
            Assert.Throws<InvalidOperationException>(() => context.Response.Headers.Append("CustomName", "Custom \r Value"));
            await context.Response.WriteAsync("Hello World");
        }, headers, endStream: true);
 
        var responseHeaders = await requestStream.ExpectHeadersAsync();
        var responseData = await requestStream.ExpectDataAsync();
        Assert.Equal("Hello World", Encoding.ASCII.GetString(responseData.ToArray()));
 
        Assert.Equal(2, responseHeaders.Count);
        Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("200", responseHeaders[InternalHeaderNames.Status]);
    }
 
    [Fact]
    public async Task ResponseHeaders_WithNonAsciiAndCustomEncoder_Works()
    {
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "Custom"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>(InternalHeaderNames.Authority, "localhost:80"),
        };
 
        _serviceContext.ServerOptions.ResponseHeaderEncodingSelector = _ => Encoding.UTF8;
        _serviceContext.ServerOptions.RequestHeaderEncodingSelector = _ => Encoding.UTF8; // Used for decoding response.
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async context =>
        {
            var trailersFeature = context.Features.Get<IHttpResponseTrailersFeature>();
 
            Assert.Throws<InvalidOperationException>(() => context.Response.Headers.Append("Custom你好Name", "Custom Value"));
            Assert.Throws<InvalidOperationException>(() => context.Response.Headers.Append("CustomName", "Custom \r Value"));
            context.Response.ContentType = "Custom 你好 Type";
            context.Response.Headers.Append("CustomName", "Custom 你好 Value");
            await context.Response.WriteAsync("Hello World");
        }, headers, endStream: true);
 
        var responseHeaders = await requestStream.ExpectHeadersAsync();
        var responseData = await requestStream.ExpectDataAsync();
        Assert.Equal("Hello World", Encoding.ASCII.GetString(responseData.ToArray()));
 
        Assert.Equal(4, responseHeaders.Count);
        Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("200", responseHeaders[InternalHeaderNames.Status]);
        Assert.Equal("Custom 你好 Type", responseHeaders[HeaderNames.ContentType]);
        Assert.Equal("Custom 你好 Value", responseHeaders["CustomName"]);
    }
 
    [Fact]
    public async Task ResponseHeaders_WithInvalidValuesAndCustomEncoder_AbortsConnection()
    {
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "Custom"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>(InternalHeaderNames.Authority, "localhost:80"),
        };
 
        var encoding = Encoding.GetEncoding(Encoding.Latin1.CodePage, EncoderFallback.ExceptionFallback,
            DecoderFallback.ExceptionFallback);
        _serviceContext.ServerOptions.ResponseHeaderEncodingSelector = _ => encoding;
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async context =>
        {
            context.Response.Headers.Append("CustomName", "Custom 你好 Value");
            await context.Response.WriteAsync("Hello World");
        }, headers, endStream: true);
 
        await requestStream.WaitForStreamErrorAsync(
            Http3ErrorCode.InternalError,
            AssertExpectedErrorMessages,
            "");
    }
 
    [Fact]
    public async Task ResponseTrailers_WithData_Sent()
    {
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "Custom"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>(InternalHeaderNames.Authority, "localhost:80"),
        };
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async context =>
        {
            var trailersFeature = context.Features.Get<IHttpResponseTrailersFeature>();
 
            trailersFeature.Trailers.Add("Trailer1", "Value1");
            trailersFeature.Trailers.Add("Trailer2", "Value2");
 
            await context.Response.WriteAsync("Hello world");
        }, headers, endStream: true);
 
        var responseHeaders = await requestStream.ExpectHeadersAsync();
        var responseData = await requestStream.ExpectDataAsync();
        Assert.Equal("Hello world", Encoding.ASCII.GetString(responseData.ToArray()));
 
        var responseTrailers = await requestStream.ExpectHeadersAsync();
 
        Assert.Equal(2, responseTrailers.Count);
        Assert.Equal("Value1", responseTrailers["Trailer1"]);
        Assert.Equal("Value2", responseTrailers["Trailer2"]);
    }
 
    [Fact]
    public async Task ResponseTrailers_WithExeption500_Cleared()
    {
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "Custom"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>(InternalHeaderNames.Authority, "localhost:80"),
        };
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(context =>
        {
            var trailersFeature = context.Features.Get<IHttpResponseTrailersFeature>();
 
            trailersFeature.Trailers.Add("Trailer1", "Value1");
            trailersFeature.Trailers.Add("Trailer2", "Value2");
 
            throw new NotImplementedException("Test Exception");
        }, headers, endStream: true);
 
        var responseHeaders = await requestStream.ExpectHeadersAsync();
 
        await requestStream.ExpectReceiveEndOfStream();
    }
 
    [Fact]
    public async Task ResponseTrailers_WithNonAscii_Throws()
    {
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "Custom"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>(InternalHeaderNames.Authority, "localhost:80"),
        };
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async context =>
        {
            await context.Response.WriteAsync("Hello World");
            Assert.Throws<InvalidOperationException>(() => context.Response.AppendTrailer("Custom你好Name", "Custom Value"));
            Assert.Throws<InvalidOperationException>(() => context.Response.AppendTrailer("CustomName", "Custom 你好 Value"));
            Assert.Throws<InvalidOperationException>(() => context.Response.AppendTrailer("CustomName", "Custom \r Value"));
            // ETag is one of the few special cased trailers. Accept is not.
            Assert.Throws<InvalidOperationException>(() => context.Features.Get<IHttpResponseTrailersFeature>().Trailers.ETag = "Custom 你好 Tag");
            Assert.Throws<InvalidOperationException>(() => context.Features.Get<IHttpResponseTrailersFeature>().Trailers.Accept = "Custom 你好 Tag");
        }, headers, endStream: true);
 
        var responseHeaders = await requestStream.ExpectHeadersAsync();
        var responseData = await requestStream.ExpectDataAsync();
        Assert.Equal("Hello World", Encoding.ASCII.GetString(responseData.ToArray()));
        await requestStream.ExpectReceiveEndOfStream();
    }
 
    [Fact]
    public async Task ResponseTrailers_WithNonAsciiAndCustomEncoder_Works()
    {
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "Custom"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>(InternalHeaderNames.Authority, "localhost:80"),
        };
 
        _serviceContext.ServerOptions.ResponseHeaderEncodingSelector = _ => Encoding.UTF8;
        _serviceContext.ServerOptions.RequestHeaderEncodingSelector = _ => Encoding.UTF8; // Used for decoding response.
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async context =>
        {
            await context.Response.WriteAsync("Hello World");
            Assert.Throws<InvalidOperationException>(() => context.Response.AppendTrailer("Custom你好Name", "Custom Value"));
            Assert.Throws<InvalidOperationException>(() => context.Response.AppendTrailer("CustomName", "Custom \r Value"));
            context.Response.AppendTrailer("CustomName", "Custom 你好 Value");
            // ETag is one of the few special cased trailers. Accept is not.
            context.Features.Get<IHttpResponseTrailersFeature>().Trailers.ETag = "Custom 你好 Tag";
            context.Features.Get<IHttpResponseTrailersFeature>().Trailers.Accept = "Custom 你好 Accept";
        }, headers, endStream: true);
 
        var responseHeaders = await requestStream.ExpectHeadersAsync();
        var responseData = await requestStream.ExpectDataAsync();
        Assert.Equal("Hello World", Encoding.ASCII.GetString(responseData.ToArray()));
 
        var responseTrailers = await requestStream.ExpectHeadersAsync();
        Assert.Equal(3, responseTrailers.Count);
        Assert.Equal("Custom 你好 Value", responseTrailers["CustomName"]);
        Assert.Equal("Custom 你好 Tag", responseTrailers[HeaderNames.ETag]);
        Assert.Equal("Custom 你好 Accept", responseTrailers[HeaderNames.Accept]);
 
        await requestStream.ExpectReceiveEndOfStream();
    }
 
    [Fact]
    public async Task ResponseTrailers_WithInvalidValuesAndCustomEncoder_AbortsConnection()
    {
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "Custom"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>(InternalHeaderNames.Authority, "localhost:80"),
        };
 
        var encoding = Encoding.GetEncoding(Encoding.Latin1.CodePage, EncoderFallback.ExceptionFallback,
            DecoderFallback.ExceptionFallback);
        _serviceContext.ServerOptions.ResponseHeaderEncodingSelector = _ => encoding;
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async context =>
        {
            await context.Response.WriteAsync("Hello World");
            context.Response.AppendTrailer("CustomName", "Custom 你好 Value");
        }, headers, endStream: true);
 
        var responseHeaders = await requestStream.ExpectHeadersAsync();
        var responseData = await requestStream.ExpectDataAsync();
        Assert.Equal("Hello World", Encoding.ASCII.GetString(responseData.ToArray()));
 
        await requestStream.WaitForStreamErrorAsync(
            Http3ErrorCode.InternalError,
            AssertExpectedErrorMessages,
            "");
    }
 
    [Fact]
    public async Task ResetStream_ReturnStreamError()
    {
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "Custom"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>(InternalHeaderNames.Authority, "localhost:80"),
        };
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(context =>
        {
            var resetFeature = context.Features.Get<IHttpResetFeature>();
 
            resetFeature.Reset((int)Http3ErrorCode.RequestCancelled);
 
            return Task.CompletedTask;
        }, headers, endStream: true);
 
        await requestStream.WaitForStreamErrorAsync(
            Http3ErrorCode.RequestCancelled,
            AssertExpectedErrorMessages,
            CoreStrings.FormatHttp3StreamResetByApplication(Http3Formatting.ToFormattedErrorCode(Http3ErrorCode.RequestCancelled)));
    }
 
    [Fact]
    public async Task CompleteAsync_BeforeBodyStarted_SendsHeadersWithEndStream()
    {
        var startingTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var appTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var clientTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
        };
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async context =>
        {
            try
            {
                context.Response.OnStarting(() => { startingTcs.SetResult(); return Task.CompletedTask; });
                await context.Response.CompleteAsync().DefaultTimeout();
 
                Assert.True(startingTcs.Task.IsCompletedSuccessfully); // OnStarting got called.
                Assert.True(context.Response.Headers.IsReadOnly);
                Assert.True(context.Features.Get<IHttpResponseTrailersFeature>().Trailers.IsReadOnly);
 
                // Make sure the client gets our results from CompleteAsync instead of from the request delegate exiting.
                await clientTcs.Task.DefaultTimeout();
                appTcs.SetResult();
            }
            catch (Exception ex)
            {
                appTcs.SetException(ex);
            }
        }, headers, endStream: true);
 
        var decodedHeaders = await requestStream.ExpectHeadersAsync();
 
        clientTcs.SetResult();
        await appTcs.Task;
 
        Assert.Equal(3, decodedHeaders.Count);
        Assert.Contains("date", decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("200", decodedHeaders[InternalHeaderNames.Status]);
        Assert.Equal("0", decodedHeaders["content-length"]);
 
        await requestStream.ExpectReceiveEndOfStream();
    }
 
    [Fact]
    public async Task CompleteAsync_BeforeBodyStarted_WithTrailers_SendsHeadersAndTrailersWithEndStream()
    {
        var startingTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var appTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var clientTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
        };
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async context =>
        {
            try
            {
                context.Response.OnStarting(() => { startingTcs.SetResult(); return Task.CompletedTask; });
                context.Response.AppendTrailer("CustomName", "Custom Value");
 
                await context.Response.CompleteAsync().DefaultTimeout();
                await context.Response.CompleteAsync().DefaultTimeout(); // Can be called twice, no-ops
 
                Assert.True(startingTcs.Task.IsCompletedSuccessfully); // OnStarting got called.
                Assert.True(context.Response.Headers.IsReadOnly);
                Assert.True(context.Features.Get<IHttpResponseTrailersFeature>().Trailers.IsReadOnly);
 
                // Make sure the client gets our results from CompleteAsync instead of from the request delegate exiting.
                await clientTcs.Task.DefaultTimeout();
                appTcs.SetResult();
            }
            catch (Exception ex)
            {
                appTcs.SetException(ex);
            }
        }, headers, endStream: true);
 
        var decodedHeaders = await requestStream.ExpectHeadersAsync();
        var decodedTrailers = await requestStream.ExpectHeadersAsync();
 
        clientTcs.SetResult();
        await appTcs.Task;
 
        await requestStream.ExpectReceiveEndOfStream();
 
        Assert.Equal(3, decodedHeaders.Count);
        Assert.Contains("date", decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("200", decodedHeaders[InternalHeaderNames.Status]);
        Assert.Equal("0", decodedHeaders["content-length"]);
 
        Assert.Single(decodedTrailers);
        Assert.Equal("Custom Value", decodedTrailers["CustomName"]);
    }
 
    [Fact]
    public async Task CompleteAsync_BeforeBodyStarted_WithTrailers_TruncatedContentLength_ThrowsAnd500()
    {
        var startingTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var appTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
        };
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async context =>
        {
            try
            {
                context.Response.OnStarting(() => { startingTcs.SetResult(); return Task.CompletedTask; });
 
                context.Response.ContentLength = 25;
                context.Response.AppendTrailer("CustomName", "Custom Value");
 
                var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => context.Response.CompleteAsync().DefaultTimeout());
                Assert.Equal(CoreStrings.FormatTooFewBytesWritten(0, 25), ex.Message);
 
                Assert.True(startingTcs.Task.IsCompletedSuccessfully);
                Assert.False(context.Response.Headers.IsReadOnly);
                Assert.False(context.Features.Get<IHttpResponseTrailersFeature>().Trailers.IsReadOnly);
 
                appTcs.SetResult();
            }
            catch (Exception ex)
            {
                appTcs.SetException(ex);
            }
        }, headers, endStream: true);
 
        var decodedHeaders = await requestStream.ExpectHeadersAsync();
 
        await appTcs.Task;
 
        await requestStream.ExpectReceiveEndOfStream();
 
        Assert.Equal(3, decodedHeaders.Count);
        Assert.Contains("date", decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("500", decodedHeaders[InternalHeaderNames.Status]);
        Assert.Equal("0", decodedHeaders[HeaderNames.ContentLength]);
    }
 
    [Fact]
    public async Task CompleteAsync_AfterBodyStarted_SendsBodyWithEndStream()
    {
        var startingTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var appTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var clientTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
        };
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async context =>
        {
            try
            {
                context.Response.OnStarting(() => { startingTcs.SetResult(); return Task.CompletedTask; });
 
                await context.Response.WriteAsync("Hello World");
                Assert.True(startingTcs.Task.IsCompletedSuccessfully); // OnStarting got called.
                Assert.True(context.Response.Headers.IsReadOnly);
 
                await context.Response.CompleteAsync().DefaultTimeout();
                await context.Response.CompleteAsync().DefaultTimeout(); // Can be called twice, no-ops
 
                Assert.True(context.Features.Get<IHttpResponseTrailersFeature>().Trailers.IsReadOnly);
 
                // Make sure the client gets our results from CompleteAsync instead of from the request delegate exiting.
                await clientTcs.Task.DefaultTimeout();
                appTcs.SetResult();
            }
            catch (Exception ex)
            {
                appTcs.SetException(ex);
            }
        }, headers, endStream: true);
 
        var decodedHeaders = await requestStream.ExpectHeadersAsync();
 
        Assert.Equal(2, decodedHeaders.Count);
        Assert.Contains("date", decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("200", decodedHeaders[InternalHeaderNames.Status]);
 
        var data = await requestStream.ExpectDataAsync();
 
        Assert.Equal("Hello World", Encoding.UTF8.GetString(data.Span));
 
        clientTcs.SetResult();
        await appTcs.Task;
 
        await requestStream.ExpectReceiveEndOfStream();
    }
 
    [Fact]
    public async Task CompleteAsync_WriteAfterComplete_Throws()
    {
        var startingTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var appTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var clientTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
        };
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async context =>
        {
            try
            {
                context.Response.OnStarting(() => { startingTcs.SetResult(); return Task.CompletedTask; });
                await context.Response.CompleteAsync().DefaultTimeout();
 
                Assert.True(startingTcs.Task.IsCompletedSuccessfully); // OnStarting got called.
                Assert.True(context.Response.Headers.IsReadOnly);
                Assert.True(context.Features.Get<IHttpResponseTrailersFeature>().Trailers.IsReadOnly);
 
                var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => context.Response.WriteAsync("2 Hello World").DefaultTimeout());
                Assert.Equal("Writing is not allowed after writer was completed.", ex.Message);
 
                // Make sure the client gets our results from CompleteAsync instead of from the request delegate exiting.
                await clientTcs.Task.DefaultTimeout();
                appTcs.SetResult();
            }
            catch (Exception ex)
            {
                appTcs.SetException(ex);
            }
        }, headers, endStream: true);
 
        var decodedHeaders = await requestStream.ExpectHeadersAsync();
 
        Assert.Equal(3, decodedHeaders.Count);
        Assert.Contains("date", decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("200", decodedHeaders[InternalHeaderNames.Status]);
        Assert.Equal("0", decodedHeaders[HeaderNames.ContentLength]);
 
        clientTcs.SetResult();
        await appTcs.Task;
 
        await requestStream.ExpectReceiveEndOfStream();
    }
 
    [Fact]
    public async Task CompleteAsync_WriteAgainAfterComplete_Throws()
    {
        var startingTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var appTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var clientTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
        };
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async context =>
        {
            try
            {
                context.Response.OnStarting(() => { startingTcs.SetResult(); return Task.CompletedTask; });
 
                await context.Response.WriteAsync("Hello World").DefaultTimeout();
                Assert.True(startingTcs.Task.IsCompletedSuccessfully); // OnStarting got called.
                Assert.True(context.Response.Headers.IsReadOnly);
 
                await context.Response.CompleteAsync().DefaultTimeout();
 
                Assert.True(context.Features.Get<IHttpResponseTrailersFeature>().Trailers.IsReadOnly);
 
                var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => context.Response.WriteAsync("2 Hello World").DefaultTimeout());
                Assert.Equal("Writing is not allowed after writer was completed.", ex.Message);
 
                // Make sure the client gets our results from CompleteAsync instead of from the request delegate exiting.
                await clientTcs.Task.DefaultTimeout();
                appTcs.SetResult();
            }
            catch (Exception ex)
            {
                appTcs.SetException(ex);
            }
        }, headers, endStream: true);
 
        var decodedHeaders = await requestStream.ExpectHeadersAsync();
 
        Assert.Equal(2, decodedHeaders.Count);
        Assert.Contains("date", decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("200", decodedHeaders[InternalHeaderNames.Status]);
 
        var data = await requestStream.ExpectDataAsync();
        Assert.Equal("Hello World", Encoding.UTF8.GetString(data.Span));
 
        clientTcs.SetResult();
        await appTcs.Task;
 
        await requestStream.ExpectReceiveEndOfStream();
    }
 
    [Fact]
    public async Task CompleteAsync_AdvanceAfterComplete_AdvanceThrows()
    {
        var tcs = new TaskCompletionSource();
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
        };
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async context =>
        {
            var memory = context.Response.BodyWriter.GetMemory(12);
            await context.Response.CompleteAsync();
            try
            {
                context.Response.BodyWriter.Advance(memory.Length);
            }
            catch (InvalidOperationException)
            {
                tcs.SetResult();
                return;
            }
 
            Assert.True(false);
        }, headers, endStream: true);
 
        var decodedHeaders = await requestStream.ExpectHeadersAsync();
 
        Assert.Equal(3, decodedHeaders.Count);
        Assert.Contains("date", decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("200", decodedHeaders[InternalHeaderNames.Status]);
        Assert.Equal("0", decodedHeaders["content-length"]);
 
        await tcs.Task.DefaultTimeout();
 
        await requestStream.ExpectReceiveEndOfStream();
    }
 
    [Fact]
    public async Task CompleteAsync_AfterPipeWrite_WithTrailers_SendsBodyAndTrailersWithEndStream()
    {
        var startingTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var appTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var clientTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
        };
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async context =>
        {
            try
            {
                context.Response.OnStarting(() => { startingTcs.SetResult(); return Task.CompletedTask; });
 
                var buffer = context.Response.BodyWriter.GetMemory();
                var length = Encoding.UTF8.GetBytes("Hello World", buffer.Span);
                context.Response.BodyWriter.Advance(length);
 
                Assert.False(startingTcs.Task.IsCompletedSuccessfully); // OnStarting did not get called.
                Assert.False(context.Response.Headers.IsReadOnly);
 
                context.Response.AppendTrailer("CustomName", "Custom Value");
 
                await context.Response.CompleteAsync().DefaultTimeout();
                Assert.True(startingTcs.Task.IsCompletedSuccessfully); // OnStarting got called.
                Assert.True(context.Response.Headers.IsReadOnly);
 
                Assert.True(context.Features.Get<IHttpResponseTrailersFeature>().Trailers.IsReadOnly);
 
                // Make sure the client gets our results from CompleteAsync instead of from the request delegate exiting.
                await clientTcs.Task.DefaultTimeout();
                appTcs.SetResult();
            }
            catch (Exception ex)
            {
                appTcs.SetException(ex);
            }
        }, headers, endStream: true);
 
        var decodedHeaders = await requestStream.ExpectHeadersAsync();
        Assert.Equal(2, decodedHeaders.Count);
        Assert.Contains("date", decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("200", decodedHeaders[InternalHeaderNames.Status]);
 
        var data = await requestStream.ExpectDataAsync();
        Assert.Equal("Hello World", Encoding.UTF8.GetString(data.Span));
 
        var decodedTrailers = await requestStream.ExpectHeadersAsync();
        Assert.Equal("Custom Value", decodedTrailers["CustomName"]);
 
        clientTcs.SetResult();
        await appTcs.Task;
 
        await requestStream.ExpectReceiveEndOfStream();
    }
 
    [Fact]
    public async Task CompleteAsync_AfterBodyStarted_WithTrailers_SendsBodyAndTrailersWithEndStream()
    {
        var startingTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var appTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var clientTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
        };
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async context =>
        {
            try
            {
                context.Response.OnStarting(() => { startingTcs.SetResult(); return Task.CompletedTask; });
 
                await context.Response.WriteAsync("Hello World");
                Assert.True(startingTcs.Task.IsCompletedSuccessfully); // OnStarting got called.
                Assert.True(context.Response.Headers.IsReadOnly);
 
                context.Response.AppendTrailer("CustomName", "Custom Value");
 
                await context.Response.CompleteAsync().DefaultTimeout();
 
                Assert.True(context.Features.Get<IHttpResponseTrailersFeature>().Trailers.IsReadOnly);
 
                // Make sure the client gets our results from CompleteAsync instead of from the request delegate exiting.
                await clientTcs.Task.DefaultTimeout();
                appTcs.SetResult();
            }
            catch (Exception ex)
            {
                appTcs.SetException(ex);
            }
        }, headers, endStream: true);
 
        var decodedHeaders = await requestStream.ExpectHeadersAsync();
        Assert.Equal(2, decodedHeaders.Count);
        Assert.Contains("date", decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("200", decodedHeaders[InternalHeaderNames.Status]);
 
        var data = await requestStream.ExpectDataAsync();
        Assert.Equal("Hello World", Encoding.UTF8.GetString(data.Span));
 
        var decodedTrailers = await requestStream.ExpectHeadersAsync();
        Assert.Equal("Custom Value", decodedTrailers["CustomName"]);
 
        clientTcs.SetResult();
        await appTcs.Task;
 
        await requestStream.ExpectReceiveEndOfStream();
    }
 
    [Fact]
    public async Task CompleteAsync_AfterBodyStarted_WithTrailers_TruncatedContentLength_ThrowsAndReset()
    {
        var startingTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var appTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var clientTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
        };
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async context =>
        {
            try
            {
                context.Response.OnStarting(() => { startingTcs.SetResult(); return Task.CompletedTask; });
 
                context.Response.ContentLength = 25;
                await context.Response.WriteAsync("Hello World");
                Assert.True(startingTcs.Task.IsCompletedSuccessfully); // OnStarting got called.
                Assert.True(context.Response.Headers.IsReadOnly);
 
                context.Response.AppendTrailer("CustomName", "Custom Value");
 
                var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => context.Response.CompleteAsync().DefaultTimeout());
                Assert.Equal(CoreStrings.FormatTooFewBytesWritten(11, 25), ex.Message);
 
                Assert.False(context.Features.Get<IHttpResponseTrailersFeature>().Trailers.IsReadOnly);
 
                // Make sure the client gets our results from CompleteAsync instead of from the request delegate exiting.
                await clientTcs.Task.DefaultTimeout();
                appTcs.SetResult();
            }
            catch (Exception ex)
            {
                appTcs.SetException(ex);
            }
        }, headers, endStream: true);
 
        var decodedHeaders = await requestStream.ExpectHeadersAsync();
        Assert.Equal(3, decodedHeaders.Count);
        Assert.Contains("date", decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("200", decodedHeaders[InternalHeaderNames.Status]);
        Assert.Equal("25", decodedHeaders[HeaderNames.ContentLength]);
 
        var data = await requestStream.ExpectDataAsync();
        Assert.Equal("Hello World", Encoding.UTF8.GetString(data.Span));
 
        clientTcs.SetResult();
 
        await requestStream.WaitForStreamErrorAsync(Http3ErrorCode.InternalError,
            expectedErrorMessage: CoreStrings.FormatTooFewBytesWritten(11, 25));
 
        await appTcs.Task;
    }
 
    [Fact]
    public async Task PipeWriterComplete_AfterBodyStarted_WithTrailers_TruncatedContentLength_ThrowsAndReset()
    {
        var startingTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var appTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var clientTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
        };
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async context =>
        {
            try
            {
                context.Response.OnStarting(() => { startingTcs.SetResult(); return Task.CompletedTask; });
 
                context.Response.ContentLength = 25;
                await context.Response.WriteAsync("Hello World");
                Assert.True(startingTcs.Task.IsCompletedSuccessfully); // OnStarting got called.
                Assert.True(context.Response.Headers.IsReadOnly);
 
                context.Response.AppendTrailer("CustomName", "Custom Value");
 
                var ex = Assert.Throws<InvalidOperationException>(() => context.Response.BodyWriter.Complete());
                Assert.Equal(CoreStrings.FormatTooFewBytesWritten(11, 25), ex.Message);
 
                Assert.False(context.Features.Get<IHttpResponseTrailersFeature>().Trailers.IsReadOnly);
 
                // Make sure the client gets our results from CompleteAsync instead of from the request delegate exiting.
                await clientTcs.Task.DefaultTimeout();
                appTcs.SetResult();
            }
            catch (Exception ex)
            {
                appTcs.SetException(ex);
            }
        }, headers, endStream: true);
 
        var decodedHeaders = await requestStream.ExpectHeadersAsync();
        Assert.Equal(3, decodedHeaders.Count);
        Assert.Contains("date", decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("200", decodedHeaders[InternalHeaderNames.Status]);
        Assert.Equal("25", decodedHeaders[HeaderNames.ContentLength]);
 
        var data = await requestStream.ExpectDataAsync();
        Assert.Equal("Hello World", Encoding.UTF8.GetString(data.Span));
 
        clientTcs.SetResult();
 
        await requestStream.WaitForStreamErrorAsync(Http3ErrorCode.InternalError,
            expectedErrorMessage: CoreStrings.FormatTooFewBytesWritten(11, 25));
 
        await appTcs.Task;
    }
 
    [Fact]
    public async Task AbortAfterCompleteAsync_GETWithResponseBodyAndTrailers_ResetsAfterResponse()
    {
        var startingTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var appTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var clientTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
        };
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async context =>
        {
            try
            {
                context.Response.OnStarting(() => { startingTcs.SetResult(); return Task.CompletedTask; });
 
                await context.Response.WriteAsync("Hello World");
                Assert.True(startingTcs.Task.IsCompletedSuccessfully); // OnStarting got called.
                Assert.True(context.Response.Headers.IsReadOnly);
 
                context.Response.AppendTrailer("CustomName", "Custom Value");
 
                await context.Response.CompleteAsync().DefaultTimeout();
 
                Assert.True(context.Features.Get<IHttpResponseTrailersFeature>().Trailers.IsReadOnly);
 
                // RequestAborted will no longer fire after CompleteAsync.
                Assert.False(context.RequestAborted.CanBeCanceled);
                context.Abort();
 
                // Make sure the client gets our results from CompleteAsync instead of from the request delegate exiting.
                await clientTcs.Task.DefaultTimeout();
                appTcs.SetResult();
            }
            catch (Exception ex)
            {
                appTcs.SetException(ex);
            }
        }, headers, endStream: true);
 
        var decodedHeaders = await requestStream.ExpectHeadersAsync();
        Assert.Equal(2, decodedHeaders.Count);
        Assert.Contains("date", decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("200", decodedHeaders[InternalHeaderNames.Status]);
 
        var data = await requestStream.ExpectDataAsync();
        Assert.Equal("Hello World", Encoding.UTF8.GetString(data.Span));
 
        var decodedTrailers = await requestStream.ExpectHeadersAsync();
        Assert.Equal("Custom Value", decodedTrailers["CustomName"]);
 
        await requestStream.WaitForStreamErrorAsync(Http3ErrorCode.InternalError, expectedErrorMessage: null);
 
        clientTcs.SetResult();
        await appTcs.Task;
    }
 
    [Fact]
    public async Task AbortAfterCompleteAsync_POSTWithResponseBodyAndTrailers_RequestBodyThrows()
    {
        var startingTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var appTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var clientTcs = 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"),
        };
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async context =>
        {
            try
            {
                var requestBodyTask = context.Request.BodyReader.ReadAsync();
 
                context.Response.OnStarting(() => { startingTcs.SetResult(); return Task.CompletedTask; });
 
                await context.Response.WriteAsync("Hello World");
                Assert.True(startingTcs.Task.IsCompletedSuccessfully); // OnStarting got called.
                Assert.True(context.Response.Headers.IsReadOnly);
 
                context.Response.AppendTrailer("CustomName", "Custom Value");
 
                await context.Response.CompleteAsync().DefaultTimeout();
 
                Assert.True(context.Features.Get<IHttpResponseTrailersFeature>().Trailers.IsReadOnly);
 
                // RequestAborted will no longer fire after CompleteAsync.
                Assert.False(context.RequestAborted.CanBeCanceled);
                context.Abort();
 
                await Assert.ThrowsAsync<TaskCanceledException>(async () => await requestBodyTask);
                await Assert.ThrowsAsync<ConnectionAbortedException>(async () => await context.Request.BodyReader.ReadAsync());
 
                // Make sure the client gets our results from CompleteAsync instead of from the request delegate exiting.
                await clientTcs.Task.DefaultTimeout();
                appTcs.SetResult();
            }
            catch (Exception ex)
            {
                appTcs.SetException(ex);
            }
        }, headers, endStream: false);
 
        var decodedHeaders = await requestStream.ExpectHeadersAsync();
        Assert.Equal(2, decodedHeaders.Count);
        Assert.Contains("date", decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("200", decodedHeaders[InternalHeaderNames.Status]);
 
        var data = await requestStream.ExpectDataAsync();
        Assert.Equal("Hello World", Encoding.UTF8.GetString(data.Span));
 
        var decodedTrailers = await requestStream.ExpectHeadersAsync();
        Assert.Equal("Custom Value", decodedTrailers["CustomName"]);
 
        await requestStream.WaitForStreamErrorAsync(Http3ErrorCode.InternalError, expectedErrorMessage: null);
 
        clientTcs.SetResult();
        await appTcs.Task;
    }
 
    [Fact]
    public async Task ResetAfterCompleteAsync_GETWithResponseBodyAndTrailers_ResetsAfterResponse()
    {
        var startingTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var appTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var clientTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
        };
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async context =>
        {
            try
            {
                context.Response.OnStarting(() => { startingTcs.SetResult(); return Task.CompletedTask; });
 
                await context.Response.WriteAsync("Hello World");
                Assert.True(startingTcs.Task.IsCompletedSuccessfully); // OnStarting got called.
                Assert.True(context.Response.Headers.IsReadOnly);
 
                context.Response.AppendTrailer("CustomName", "Custom Value");
 
                await context.Response.CompleteAsync().DefaultTimeout();
 
                Assert.True(context.Features.Get<IHttpResponseTrailersFeature>().Trailers.IsReadOnly);
 
                // RequestAborted will no longer fire after CompleteAsync.
                Assert.False(context.RequestAborted.CanBeCanceled);
                var resetFeature = context.Features.Get<IHttpResetFeature>();
                Assert.NotNull(resetFeature);
                resetFeature.Reset((int)Http3ErrorCode.NoError);
 
                // Make sure the client gets our results from CompleteAsync instead of from the request delegate exiting.
                await clientTcs.Task.DefaultTimeout();
                appTcs.SetResult();
            }
            catch (Exception ex)
            {
                appTcs.SetException(ex);
            }
        }, headers, endStream: true);
 
        var decodedHeaders = await requestStream.ExpectHeadersAsync();
        Assert.Equal(2, decodedHeaders.Count);
        Assert.Contains("date", decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("200", decodedHeaders[InternalHeaderNames.Status]);
 
        var data = await requestStream.ExpectDataAsync();
        Assert.Equal("Hello World", Encoding.UTF8.GetString(data.Span));
 
        var decodedTrailers = await requestStream.ExpectHeadersAsync();
        Assert.Equal("Custom Value", decodedTrailers["CustomName"]);
 
        await requestStream.WaitForStreamErrorAsync(
            Http3ErrorCode.NoError,
            expectedErrorMessage: "The HTTP/3 stream was reset by the application with error code H3_NO_ERROR.");
 
        clientTcs.SetResult();
        await appTcs.Task;
    }
 
    [Fact]
    public async Task ResetAfterCompleteAsync_POSTWithResponseBodyAndTrailers_RequestBodyThrows()
    {
        var startingTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var appTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var clientTcs = 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"),
        };
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async context =>
        {
            try
            {
                var requestBodyTask = context.Request.BodyReader.ReadAsync();
 
                context.Response.OnStarting(() => { startingTcs.SetResult(); return Task.CompletedTask; });
 
                await context.Response.WriteAsync("Hello World");
                Assert.True(startingTcs.Task.IsCompletedSuccessfully); // OnStarting got called.
                Assert.True(context.Response.Headers.IsReadOnly);
 
                context.Response.AppendTrailer("CustomName", "Custom Value");
 
                await context.Response.CompleteAsync().DefaultTimeout();
 
                Assert.True(context.Features.Get<IHttpResponseTrailersFeature>().Trailers.IsReadOnly);
 
                // RequestAborted will no longer fire after CompleteAsync.
                Assert.False(context.RequestAborted.CanBeCanceled);
                var resetFeature = context.Features.Get<IHttpResetFeature>();
                Assert.NotNull(resetFeature);
                resetFeature.Reset((int)Http3ErrorCode.NoError);
 
                await Assert.ThrowsAsync<TaskCanceledException>(async () => await requestBodyTask);
                await Assert.ThrowsAsync<ConnectionAbortedException>(async () => await context.Request.BodyReader.ReadAsync());
 
                // Make sure the client gets our results from CompleteAsync instead of from the request delegate exiting.
                await clientTcs.Task.DefaultTimeout();
                appTcs.SetResult();
            }
            catch (Exception ex)
            {
                appTcs.SetException(ex);
            }
        }, headers, endStream: false);
 
        var decodedHeaders = await requestStream.ExpectHeadersAsync();
        Assert.Equal(2, decodedHeaders.Count);
        Assert.Contains("date", decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("200", decodedHeaders[InternalHeaderNames.Status]);
 
        var data = await requestStream.ExpectDataAsync();
        Assert.Equal("Hello World", Encoding.UTF8.GetString(data.Span));
 
        var decodedTrailers = await requestStream.ExpectHeadersAsync();
        Assert.Equal("Custom Value", decodedTrailers["CustomName"]);
 
        await requestStream.WaitForStreamErrorAsync(
            Http3ErrorCode.NoError,
            expectedErrorMessage: "The HTTP/3 stream was reset by the application with error code H3_NO_ERROR.");
 
        clientTcs.SetResult();
        await appTcs.Task;
    }
 
    [Theory]
    [InlineData(true)]
    [InlineData(false)]
    public async Task DataBeforeHeaders_UnexpectedFrameError(bool pendingStreamsEnabled)
    {
        Http3Api._serviceContext.ServerOptions.EnableWebTransportAndH3Datagrams = pendingStreamsEnabled;
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(_noopApplication, null);
 
        await (pendingStreamsEnabled ? requestStream.OnUnidentifiedStreamCreatedTask : requestStream.OnStreamCreatedTask);
 
        await requestStream.SendDataAsync(Encoding.UTF8.GetBytes("This is invalid."));
 
        await requestStream.WaitForStreamErrorAsync(
            Http3ErrorCode.UnexpectedFrame,
            expectedErrorMessage: CoreStrings.Http3StreamErrorDataReceivedBeforeHeaders);
    }
 
    [Fact]
    public async Task RequestTrailers_CanReadTrailersFromRequest()
    {
        string testValue = null;
 
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
        };
        var trailers = new[]
        {
                new KeyValuePair<string, string>("TestName", "TestValue"),
            };
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async c =>
        {
            await c.Request.Body.DrainAsync(default);
 
            testValue = c.Request.GetTrailer("TestName");
        }, headers, endStream: false);
        await requestStream.SendDataAsync(Encoding.UTF8.GetBytes("Hello world"));
        await requestStream.SendHeadersAsync(trailers, endStream: true);
 
        await requestStream.ExpectHeadersAsync();
 
        Assert.Equal("TestValue", testValue);
 
        await requestStream.ExpectReceiveEndOfStream();
    }
 
    [Fact]
    public async Task FrameAfterTrailers_UnexpectedFrameError()
    {
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
        };
        var trailers = new[]
        {
                new KeyValuePair<string, string>("TestName", "TestValue"),
            };
        var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async c =>
        {
            // Send headers
            await c.Response.Body.FlushAsync();
 
            await tcs.Task;
        }, headers, endStream: false);
 
        await requestStream.ExpectHeadersAsync();
 
        await requestStream.SendDataAsync(Encoding.UTF8.GetBytes("Hello world"));
        await requestStream.SendHeadersAsync(trailers, endStream: false);
        await requestStream.SendDataAsync(Encoding.UTF8.GetBytes("This is invalid."));
 
        await requestStream.WaitForStreamErrorAsync(
            Http3ErrorCode.UnexpectedFrame,
            expectedErrorMessage: CoreStrings.FormatHttp3StreamErrorFrameReceivedAfterTrailers(Http3Formatting.ToFormattedType(Http3FrameType.Data)));
 
        tcs.SetResult();
    }
 
    [Fact]
    public async Task TrailersWithoutEndingStream_ErrorAccessingTrailers()
    {
        var readTrailersTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var syncPoint = new SyncPoint();
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
        };
        var trailers = new[]
        {
                new KeyValuePair<string, string>("TestName", "TestValue"),
            };
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async c =>
        {
            var data = new byte[1024];
            await c.Request.Body.ReadAsync(data);
 
            await syncPoint.WaitToContinue();
 
            try
            {
                c.Request.GetTrailer("TestName");
            }
            catch (Exception ex)
            {
                readTrailersTcs.TrySetException(ex);
                throw;
            }
        }, headers, endStream: false);
        await requestStream.SendDataAsync(Encoding.UTF8.GetBytes("Hello world"));
        await requestStream.SendHeadersAsync(trailers, endStream: false);
 
        await syncPoint.WaitForSyncPoint().DefaultTimeout();
        syncPoint.Continue();
 
        // Stream not ended after trailing headers.
        var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => readTrailersTcs.Task).DefaultTimeout();
        Assert.Equal("The request trailers are not available yet. They may not be available until the full request body is read.", ex.Message);
    }
 
    [Theory]
    [InlineData(nameof(Http3FrameType.MaxPushId), true)]
    [InlineData(nameof(Http3FrameType.Settings), true)]
    [InlineData(nameof(Http3FrameType.CancelPush), true)]
    [InlineData(nameof(Http3FrameType.GoAway), true)]
    [InlineData(nameof(Http3FrameType.MaxPushId), false)]
    [InlineData(nameof(Http3FrameType.Settings), false)]
    [InlineData(nameof(Http3FrameType.CancelPush), false)]
    [InlineData(nameof(Http3FrameType.GoAway), false)]
    public async Task UnexpectedRequestFrame(string frameType, bool pendingStreamsEnabled)
    {
        Http3Api._serviceContext.ServerOptions.EnableWebTransportAndH3Datagrams = pendingStreamsEnabled;
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(_echoApplication, null);
 
        await (pendingStreamsEnabled ? requestStream.OnUnidentifiedStreamCreatedTask : requestStream.OnStreamCreatedTask);
 
        var f = Enum.Parse<Http3FrameType>(frameType);
        await requestStream.SendFrameAsync(f, Memory<byte>.Empty);
 
        await requestStream.WaitForStreamErrorAsync(
            Http3ErrorCode.UnexpectedFrame,
            expectedErrorMessage: CoreStrings.FormatHttp3ErrorUnsupportedFrameOnRequestStream(Http3Formatting.ToFormattedType(f)));
 
        await Http3Api.WaitForConnectionErrorAsync<Http3ConnectionErrorException>(
            ignoreNonGoAwayFrames: true,
            expectedLastStreamId: 4,
            expectedErrorCode: Http3ErrorCode.UnexpectedFrame,
            matchExpectedErrorMessage: AssertExpectedErrorMessages,
            expectedErrorMessage: CoreStrings.FormatHttp3ErrorUnsupportedFrameOnRequestStream(Http3Formatting.ToFormattedType(f)));
    }
 
    [Theory]
    [InlineData(nameof(Http3FrameType.PushPromise))]
    public async Task UnexpectedServerFrame(string frameType)
    {
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
        };
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(_echoApplication, headers);
 
        await requestStream.OnStreamCreatedTask;
 
        var f = Enum.Parse<Http3FrameType>(frameType);
        await requestStream.SendFrameAsync(f, Memory<byte>.Empty);
 
        await requestStream.WaitForStreamErrorAsync(
            Http3ErrorCode.UnexpectedFrame,
            expectedErrorMessage: CoreStrings.FormatHttp3ErrorUnsupportedFrameOnServer(Http3Formatting.ToFormattedType(f)));
    }
 
    [Fact]
    public async Task RequestIncomplete()
    {
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(_echoApplication, null);
 
        await requestStream.EndStreamAsync();
 
        await requestStream.WaitForStreamErrorAsync(
            Http3ErrorCode.RequestIncomplete,
            expectedErrorMessage: CoreStrings.Http3StreamErrorRequestEndedNoHeaders);
    }
 
    [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_StreamError(headers, expectedErrorMessage: CoreStrings.HttpErrorUnknownPseudoHeaderField);
    }
 
    [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_StreamError(headers, expectedErrorMessage: CoreStrings.HttpErrorResponsePseudoHeaderField);
    }
 
    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>>> 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<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;
        }
    }
 
    [Theory]
    [MemberData(nameof(DuplicatePseudoHeaderFieldData))]
    public Task HEADERS_Received_HeaderBlockContainsDuplicatePseudoHeaderField_ConnectionError(IEnumerable<KeyValuePair<string, string>> headers)
    {
        return HEADERS_Received_InvalidHeaderFields_StreamError(headers, expectedErrorMessage: CoreStrings.HttpErrorDuplicatePseudoHeaderField);
    }
 
    [Theory]
    [MemberData(nameof(ConnectMissingPseudoHeaderFieldData))]
    public async Task HEADERS_Received_HeaderBlockDoesNotContainMandatoryPseudoHeaderField_MethodIsCONNECT_NoError(IEnumerable<KeyValuePair<string, string>> headers)
    {
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(_noopApplication, headers, endStream: true);
 
        await requestStream.ExpectHeadersAsync();
 
        await requestStream.ExpectReceiveEndOfStream();
    }
 
    [Theory]
    [MemberData(nameof(PseudoHeaderFieldAfterRegularHeadersData))]
    public Task HEADERS_Received_HeaderBlockContainsPseudoHeaderFieldAfterRegularHeaders_ConnectionError(IEnumerable<KeyValuePair<string, string>> headers)
    {
        return HEADERS_Received_InvalidHeaderFields_StreamError(headers, expectedErrorMessage: CoreStrings.HttpErrorPseudoHeaderFieldAfterRegularHeaders);
    }
 
    private async Task HEADERS_Received_InvalidHeaderFields_StreamError(IEnumerable<KeyValuePair<string, string>> headers, string expectedErrorMessage, Http3ErrorCode? errorCode = null)
    {
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(_noopApplication, headers, endStream: true);
 
        await requestStream.WaitForStreamErrorAsync(
            errorCode ?? Http3ErrorCode.MessageError,
            AssertExpectedErrorMessages,
            expectedErrorMessage);
    }
 
    [Theory]
    [MemberData(nameof(MissingPseudoHeaderFieldData))]
    public async Task HEADERS_Received_HeaderBlockDoesNotContainMandatoryPseudoHeaderField_StreamError(IEnumerable<KeyValuePair<string, string>> headers)
    {
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(_noopApplication, headers, endStream: true);
        await requestStream.WaitForStreamErrorAsync(
             Http3ErrorCode.MessageError,
             expectedErrorMessage: CoreStrings.HttpErrorMissingMandatoryPseudoHeaderFields);
    }
 
    [Fact]
    public async Task HEADERS_Received_HeaderBlockOverLimit_431()
    {
        // > 32kb
        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),
        };
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(_notImplementedApp, headers, endStream: true);
 
        var receivedHeaders = await requestStream.ExpectHeadersAsync();
 
        await requestStream.ExpectReceiveEndOfStream();
 
        Assert.Equal(3, receivedHeaders.Count);
        Assert.Contains("date", receivedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("431", receivedHeaders[InternalHeaderNames.Status]);
        Assert.Equal("0", receivedHeaders[HeaderNames.ContentLength]);
    }
 
    [Fact]
    public Task HEADERS_Received_HeaderBlockOverLimitx2_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_StreamError(headers, CoreStrings.BadRequest_HeadersExceedMaxTotalSize, Http3ErrorCode.RequestRejected);
    }
 
    [Fact]
    public async Task HEADERS_Received_TooManyHeaders_431()
    {
        // > MaxRequestHeaderCount (100)
        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 < 100; i++)
        {
            headers.Add(new KeyValuePair<string, string>(i.ToString(CultureInfo.InvariantCulture), i.ToString(CultureInfo.InvariantCulture)));
        }
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(_notImplementedApp, headers, endStream: true);
 
        var receivedHeaders = await requestStream.ExpectHeadersAsync();
 
        await requestStream.ExpectReceiveEndOfStream();
 
        Assert.Equal(3, receivedHeaders.Count);
        Assert.Contains("date", receivedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("431", receivedHeaders[InternalHeaderNames.Status]);
        Assert.Equal("0", receivedHeaders[HeaderNames.ContentLength]);
    }
 
    [Fact]
    public Task HEADERS_Received_TooManyHeadersx2_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_StreamError(headers, CoreStrings.BadRequest_TooManyHeaders);
    }
 
    [Fact]
    public Task HEADERS_Received_InvalidCharacters_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_StreamError(headers, CoreStrings.BadRequest_MalformedRequestInvalidHeaders);
    }
 
    [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_StreamError(headers, CoreStrings.HttpErrorConnectionSpecificHeaderField);
    }
 
    [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_StreamError(headers, CoreStrings.HttpErrorConnectionSpecificHeaderField);
    }
 
    [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")
        };
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(_noopApplication, headers, endStream: true);
 
        await requestStream.ExpectHeadersAsync();
 
        await requestStream.ExpectReceiveEndOfStream();
    }
 
    [Fact]
    public async Task MaxRequestBodySize_ContentLengthUnder_200()
    {
        _serviceContext.ServerOptions.Limits.MaxRequestBodySize = 15;
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>(HeaderNames.ContentLength, "12"),
        };
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async context =>
        {
            var buffer = new byte[100];
            var read = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length);
            Assert.Equal(12, read);
            read = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length);
            Assert.Equal(0, read);
        }, headers, endStream: false);
        await requestStream.SendDataAsync(new byte[12], endStream: true);
 
        var receivedHeaders = await requestStream.ExpectHeadersAsync();
 
        await requestStream.ExpectReceiveEndOfStream();
 
        Assert.Equal(3, receivedHeaders.Count);
        Assert.Contains("date", receivedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("200", receivedHeaders[InternalHeaderNames.Status]);
        Assert.Equal("0", receivedHeaders[HeaderNames.ContentLength]);
    }
 
    [Fact]
    public async Task MaxRequestBodySize_ContentLengthOver_413()
    {
#pragma warning disable CS0618 // Type or member is obsolete
        BadHttpRequestException exception = null;
#pragma warning restore CS0618 // Type or member is obsolete
        _serviceContext.ServerOptions.Limits.MaxRequestBodySize = 10;
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>(HeaderNames.ContentLength, "12"),
        };
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async context =>
        {
#pragma warning disable CS0618 // Type or member is obsolete
            exception = await Assert.ThrowsAsync<BadHttpRequestException>(async () =>
#pragma warning restore CS0618 // Type or member is obsolete
            {
                var buffer = new byte[100];
                while (await context.Request.Body.ReadAsync(buffer, 0, buffer.Length) > 0) { }
            });
            ExceptionDispatchInfo.Capture(exception).Throw();
        }, headers, endStream: false);
 
        var receivedHeaders = await requestStream.ExpectHeadersAsync();
 
        await requestStream.ExpectReceiveEndOfStream();
 
        await requestStream.OnStreamCompletedTask.DefaultTimeout();
 
        Assert.Contains(LogMessages, m => m.Message.Contains("the application completed without reading the entire request body."));
        Assert.Equal("The application completed without reading the entire request body.", requestStream.AbortReadException.Message);
 
        Assert.Equal(3, receivedHeaders.Count);
        Assert.Contains("date", receivedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("413", receivedHeaders[InternalHeaderNames.Status]);
        Assert.Equal("0", receivedHeaders[HeaderNames.ContentLength]);
 
        Assert.NotNull(exception);
    }
 
    [Fact]
    public async Task MaxRequestBodySize_NoContentLength_Under_200()
    {
        _serviceContext.ServerOptions.Limits.MaxRequestBodySize = 15;
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
        };
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async context =>
        {
            var buffer = new byte[100];
            var read = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length);
            Assert.Equal(12, read);
            read = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length);
            Assert.Equal(0, read);
        }, headers, endStream: false);
        await requestStream.SendDataAsync(new byte[12], endStream: true);
 
        var receivedHeaders = await requestStream.ExpectHeadersAsync();
 
        await requestStream.ExpectReceiveEndOfStream();
 
        Assert.Equal(3, receivedHeaders.Count);
        Assert.Contains("date", receivedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("200", receivedHeaders[InternalHeaderNames.Status]);
        Assert.Equal("0", receivedHeaders[HeaderNames.ContentLength]);
    }
 
    [Fact]
    public async Task MaxRequestBodySize_NoContentLength_Over_413()
    {
#pragma warning disable CS0618 // Type or member is obsolete
        BadHttpRequestException exception = null;
#pragma warning restore CS0618 // Type or member is obsolete
        _serviceContext.ServerOptions.Limits.MaxRequestBodySize = 10;
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
        };
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async context =>
        {
#pragma warning disable CS0618 // Type or member is obsolete
            exception = await Assert.ThrowsAsync<BadHttpRequestException>(async () =>
#pragma warning restore CS0618 // Type or member is obsolete
            {
                var buffer = new byte[100];
                while (await context.Request.Body.ReadAsync(buffer, 0, buffer.Length) > 0) { }
            });
            ExceptionDispatchInfo.Capture(exception).Throw();
        }, headers, endStream: false);
        await requestStream.SendDataAsync(new byte[6], endStream: false);
        await requestStream.SendDataAsync(new byte[6], endStream: false);
 
        var receivedHeaders = await requestStream.ExpectHeadersAsync();
 
        await requestStream.ExpectReceiveEndOfStream();
 
        await requestStream.OnStreamCompletedTask.DefaultTimeout();
        Assert.Contains(LogMessages, m => m.Message.Contains("the application completed without reading the entire request body."));
 
        Assert.Equal(3, receivedHeaders.Count);
        Assert.Contains("date", receivedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("413", receivedHeaders[InternalHeaderNames.Status]);
        Assert.Equal("0", receivedHeaders[HeaderNames.ContentLength]);
 
        Assert.NotNull(exception);
    }
 
    [Theory]
    [InlineData(true)]
    [InlineData(false)]
    public async Task MaxRequestBodySize_AppCanLowerLimit(bool includeContentLength)
    {
#pragma warning disable CS0618 // Type or member is obsolete
        BadHttpRequestException exception = null;
#pragma warning restore CS0618 // Type or member is obsolete
        _serviceContext.ServerOptions.Limits.MaxRequestBodySize = 20;
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
        };
        if (includeContentLength)
        {
            headers.Concat(new[]
                {
                        new KeyValuePair<string, string>(HeaderNames.ContentLength, "18"),
                    });
        }
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async context =>
        {
            Assert.False(context.Features.Get<IHttpMaxRequestBodySizeFeature>().IsReadOnly);
            context.Features.Get<IHttpMaxRequestBodySizeFeature>().MaxRequestBodySize = 17;
#pragma warning disable CS0618 // Type or member is obsolete
            exception = await Assert.ThrowsAsync<BadHttpRequestException>(async () =>
#pragma warning restore CS0618 // Type or member is obsolete
            {
                var buffer = new byte[100];
                while (await context.Request.Body.ReadAsync(buffer, 0, buffer.Length) > 0) { }
            });
            Assert.True(context.Features.Get<IHttpMaxRequestBodySizeFeature>().IsReadOnly);
            ExceptionDispatchInfo.Capture(exception).Throw();
        }, headers, endStream: false);
        await requestStream.SendDataAsync(new byte[6], endStream: false);
        await requestStream.SendDataAsync(new byte[6], endStream: false);
        await requestStream.SendDataAsync(new byte[6], endStream: false);
 
        var receivedHeaders = await requestStream.ExpectHeadersAsync();
 
        await requestStream.ExpectReceiveEndOfStream();
 
        await requestStream.OnStreamCompletedTask.DefaultTimeout();
        Assert.Contains(LogMessages, m => m.Message.Contains("the application completed without reading the entire request body."));
 
        Assert.Equal(3, receivedHeaders.Count);
        Assert.Contains("date", receivedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("413", receivedHeaders[InternalHeaderNames.Status]);
        Assert.Equal("0", receivedHeaders[HeaderNames.ContentLength]);
 
        Assert.NotNull(exception);
    }
 
    [Theory]
    [InlineData(true)]
    [InlineData(false)]
    public async Task MaxRequestBodySize_AppCanRaiseLimit(bool includeContentLength)
    {
        _serviceContext.ServerOptions.Limits.MaxRequestBodySize = 10;
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
        };
        if (includeContentLength)
        {
            headers.Concat(new[]
                {
                    new KeyValuePair<string, string>(HeaderNames.ContentLength, "12"),
                });
        }
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async context =>
        {
            Assert.False(context.Features.Get<IHttpMaxRequestBodySizeFeature>().IsReadOnly);
            context.Features.Get<IHttpMaxRequestBodySizeFeature>().MaxRequestBodySize = 12;
            var buffer = new byte[100];
            var read = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length);
            Assert.Equal(12, read);
            Assert.True(context.Features.Get<IHttpMaxRequestBodySizeFeature>().IsReadOnly);
            read = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length);
            Assert.Equal(0, read);
        }, headers, endStream: false);
        await requestStream.SendDataAsync(new byte[12], endStream: true);
 
        var receivedHeaders = await requestStream.ExpectHeadersAsync();
 
        await requestStream.ExpectReceiveEndOfStream();
 
        Assert.Equal(3, receivedHeaders.Count);
        Assert.Contains("date", receivedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
        Assert.Equal("200", receivedHeaders[InternalHeaderNames.Status]);
        Assert.Equal("0", receivedHeaders[HeaderNames.ContentLength]);
    }
 
    [Fact]
    public Task HEADERS_Received_RequestLineLength_Error()
    {
        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")
        };
 
        return HEADERS_Received_InvalidHeaderFields_StreamError(headers, CoreStrings.BadRequest_RequestLineTooLong, Http3ErrorCode.RequestRejected);
    }
 
    [Theory]
    [InlineData(1)]
    [InlineData(4)]
    [InlineData(int.MaxValue)]
    public async Task UnsupportedControlStreamType(int typeId)
    {
        await Http3Api.InitializeConnectionAsync(_noopApplication);
 
        var outboundControlStream = await Http3Api.CreateControlStream().DefaultTimeout();
        await outboundControlStream.SendSettingsAsync(new List<Http3PeerSetting>());
 
        var inboundControlStream = await Http3Api.GetInboundControlStream();
        await inboundControlStream.ExpectSettingsAsync();
 
        // Create unsupported control stream
        var invalidStream = await Http3Api.CreateControlStream(typeId).DefaultTimeout();
        await invalidStream.WaitForStreamErrorAsync(
            Http3ErrorCode.StreamCreationError,
            AssertExpectedErrorMessages,
            CoreStrings.FormatHttp3ControlStreamErrorUnsupportedType(typeId)).DefaultTimeout();
 
        // Connection is still alive and available for requests
        var requestStream = await Http3Api.CreateRequestStream(new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
            new KeyValuePair<string, string>(InternalHeaderNames.Authority, "localhost:80"),
        }, endStream: true).DefaultTimeout();
 
        await requestStream.ExpectHeadersAsync().DefaultTimeout();
        await requestStream.ExpectReceiveEndOfStream().DefaultTimeout();
    }
 
    [Fact]
    public async Task HEADERS_ExceedsClientMaxFieldSectionSize_ErrorOnServer()
    {
        await Http3Api.InitializeConnectionAsync(context =>
        {
            context.Response.Headers["BigHeader"] = new string('!', 100);
            return Task.CompletedTask;
        });
 
        var outboundcontrolStream = await Http3Api.CreateControlStream();
        await outboundcontrolStream.SendSettingsAsync(new List<Http3PeerSetting>
            {
                new Http3PeerSetting(Core.Internal.Http3.Http3SettingType.MaxFieldSectionSize, 100)
            });
 
        var maxFieldSetting = await Http3Api.ServerReceivedSettingsReader.ReadAsync().DefaultTimeout();
 
        Assert.Equal(Core.Internal.Http3.Http3SettingType.MaxFieldSectionSize, maxFieldSetting.Key);
        Assert.Equal(100, maxFieldSetting.Value);
 
        var requestStream = await Http3Api.CreateRequestStream(new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
            new KeyValuePair<string, string>(InternalHeaderNames.Authority, "localhost:80"),
        }, endStream: true).DefaultTimeout();
 
        await requestStream.WaitForStreamErrorAsync(
            Http3ErrorCode.InternalError,
            AssertExpectedErrorMessages,
            "The encoded HTTP headers length exceeds the limit specified by the peer of 100 bytes.");
    }
 
    [Fact]
    public async Task PostRequest_ServerReadsPartialAndFinishes_SendsBodyWithEndStream()
    {
        var startingTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var appTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var clientTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async context =>
        {
            var buffer = new byte[1024];
            try
            {
                // Read 100 bytes
                var readCount = 0;
                while (readCount < 100)
                {
                    readCount += await context.Request.Body.ReadAsync(buffer.AsMemory(readCount, 100 - readCount));
                }
 
                await context.Response.Body.WriteAsync(buffer.AsMemory(0, 100));
                await clientTcs.Task.DefaultTimeout();
                appTcs.SetResult();
            }
            catch (Exception ex)
            {
                appTcs.SetException(ex);
            }
        }, new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
        });
 
        var sourceData = new byte[1024];
        for (var i = 0; i < sourceData.Length; i++)
        {
            sourceData[i] = (byte)(i % byte.MaxValue);
        }
 
        await requestStream.SendDataAsync(sourceData);
        var decodedHeaders = await requestStream.ExpectHeadersAsync();
        Assert.Equal(2, decodedHeaders.Count);
        Assert.Equal("200", decodedHeaders[InternalHeaderNames.Status]);
 
        var data = await requestStream.ExpectDataAsync();
 
        Assert.Equal(sourceData.AsMemory(0, 100).ToArray(), data.ToArray());
 
        clientTcs.SetResult();
        await appTcs.Task;
 
        await requestStream.ExpectReceiveEndOfStream();
 
        await requestStream.OnStreamCompletedTask.DefaultTimeout();
 
        Assert.Contains(LogMessages, m => m.Message.Contains("the application completed without reading the entire request body."));
        Assert.Equal("The application completed without reading the entire request body.", requestStream.AbortReadException.Message);
    }
 
    [Fact]
    public async Task HEADERS_WriteLargeResponseHeaderSection_Success()
    {
        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.Authority, "localhost:80"),
        };
 
        var headerText = string.Create(6 * 1024, new object(), (chars, state) =>
        {
            for (var i = 0; i < chars.Length; i++)
            {
                chars[i] = (char)('0' + i % 10);
            }
        });
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(c =>
        {
            for (var i = 0; i < 10; i++)
            {
                c.Response.Headers["Header" + i] = i + "-" + headerText;
            }
 
            return Task.CompletedTask;
        }, headers);
 
        var responseHeaders = await requestStream.ExpectHeadersAsync();
        Assert.Equal("200", responseHeaders[InternalHeaderNames.Status]);
 
        for (var i = 0; i < 10; i++)
        {
            Assert.Equal(i + "-" + headerText, responseHeaders["Header" + i]);
        }
 
        await requestStream.ExpectReceiveEndOfStream();
    }
 
    [Fact]
    public async Task HEADERS_WriteLargeResponseHeaderSectionTrailers_Success()
    {
        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.Authority, "localhost:80"),
        };
 
        var headerText = string.Create(6 * 1024, new object(), (chars, state) =>
        {
            for (var i = 0; i < chars.Length; i++)
            {
                chars[i] = (char)('0' + i % 10);
            }
        });
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(c =>
        {
            for (var i = 0; i < 10; i++)
            {
                c.Response.AppendTrailer("Header" + i, i + "-" + headerText);
            }
 
            return Task.CompletedTask;
        }, headers);
 
        var responseHeaders = await requestStream.ExpectHeadersAsync();
        Assert.Equal("200", responseHeaders[InternalHeaderNames.Status]);
 
        var responseTrailers = await requestStream.ExpectHeadersAsync();
        for (var i = 0; i < 10; i++)
        {
            Assert.Equal(i + "-" + headerText, responseTrailers["Header" + i]);
        }
 
        await requestStream.ExpectReceiveEndOfStream();
    }
 
    [Fact]
    public async Task HEADERS_NoResponseBody_RequestEndsOnHeaders()
    {
        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.Authority, "localhost:80"),
        };
 
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(c =>
        {
            return Task.CompletedTask;
        }, headers);
 
        var responseHeaders = await requestStream.ExpectHeadersAsync(expectEnd: true);
        Assert.Equal("200", responseHeaders[InternalHeaderNames.Status]);
    }
 
    [Theory]
    [InlineData(1000)]
    [InlineData(4096)]
    [InlineData(8000)] // Greater than the default max pool size (4096)
    public async Task GetMemory_AfterAbort_GetsFakeMemory(int sizeHint)
    {
        var tcs = new TaskCompletionSource();
        var headers = new[]
        {
            new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
            new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
            new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
        };
        var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async context =>
        {
            context.Abort();
 
            var memory = context.Response.BodyWriter.GetMemory(sizeHint);
 
            Assert.True(memory.Length >= sizeHint);
            await context.Response.CompleteAsync();
            context.Response.BodyWriter.Advance(memory.Length);
        }, headers);
    }
}