File: Http2\Http2HPackEncoderTests.cs
Web Access
Project: src\src\Servers\Kestrel\Core\test\Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj (Microsoft.AspNetCore.Server.Kestrel.Core.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Net.Http.HPack;
using System.Text;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
using Microsoft.Extensions.Primitives;
 
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests;
 
public class Http2HPackEncoderTests
{
    [Fact]
    public void BeginEncodeHeaders_Status302_NewIndexValue()
    {
        Span<byte> buffer = new byte[1024 * 16];
 
        var headers = new HttpResponseHeaders();
        var enumerator = new Http2HeadersEnumerator();
        enumerator.Initialize(headers);
 
        var hpackEncoder = new DynamicHPackEncoder();
        Assert.Equal(HeaderWriteResult.Done, HPackHeaderWriter.BeginEncodeHeaders(302, hpackEncoder, enumerator, buffer, out var length));
 
        var result = buffer.Slice(0, length).ToArray();
        var hex = BitConverter.ToString(result);
        Assert.Equal("48-03-33-30-32", hex);
 
        var statusHeader = GetHeaderEntry(hpackEncoder, 0);
        Assert.Equal(":status", statusHeader.Name);
        Assert.Equal("302", statusHeader.Value);
    }
 
    [Fact]
    public void BeginEncodeHeaders_CacheControlPrivate_NewIndexValue()
    {
        Span<byte> buffer = new byte[1024 * 16];
 
        var headers = (IHeaderDictionary)new HttpResponseHeaders();
        headers.CacheControl = "private";
 
        var enumerator = new Http2HeadersEnumerator();
        enumerator.Initialize(headers);
 
        var hpackEncoder = new DynamicHPackEncoder();
        Assert.Equal(HeaderWriteResult.Done, HPackHeaderWriter.BeginEncodeHeaders(302, hpackEncoder, enumerator, buffer, out var length));
 
        var result = buffer.Slice(5, length - 5).ToArray();
        var hex = BitConverter.ToString(result);
        Assert.Equal("58-07-70-72-69-76-61-74-65", hex);
 
        var statusHeader = GetHeaderEntry(hpackEncoder, 0);
        Assert.Equal("Cache-Control", statusHeader.Name);
        Assert.Equal("private", statusHeader.Value);
    }
 
    [Fact]
    public void BeginEncodeHeaders_MaxHeaderTableSizeExceeded_EvictionsToFit()
    {
        // Test follows example https://tools.ietf.org/html/rfc7541#appendix-C.5
        Span<byte> buffer = new byte[1024 * 16];
 
        var headers = (IHeaderDictionary)new HttpResponseHeaders();
        headers.CacheControl = "private";
        headers.Date = "Mon, 21 Oct 2013 20:13:21 GMT";
        headers.Location = "https://www.example.com";
 
        var enumerator = new Http2HeadersEnumerator();
 
        var hpackEncoder = new DynamicHPackEncoder(maxHeaderTableSize: 256);
 
        // First response
        enumerator.Initialize(headers);
        Assert.Equal(HeaderWriteResult.Done, HPackHeaderWriter.BeginEncodeHeaders(302, hpackEncoder, enumerator, buffer, out var length));
 
        var result = buffer.Slice(0, length).ToArray();
        var hex = BitConverter.ToString(result);
        Assert.Equal(
            "48-03-33-30-32-61-1D-4D-6F-6E-2C-20-32-31-20-4F-" +
            "63-74-20-32-30-31-33-20-32-30-3A-31-33-3A-32-31-" +
            "20-47-4D-54-58-07-70-72-69-76-61-74-65-6E-17-68-" +
            "74-74-70-73-3A-2F-2F-77-77-77-2E-65-78-61-6D-70-" +
            "6C-65-2E-63-6F-6D", hex);
 
        var entries = GetHeaderEntries(hpackEncoder);
        Assert.Collection(entries,
            e =>
            {
                Assert.Equal("Location", e.Name);
                Assert.Equal("https://www.example.com", e.Value);
                Assert.Equal(63u, e.Size);
            },
            e =>
            {
                Assert.Equal("Cache-Control", e.Name);
                Assert.Equal("private", e.Value);
                Assert.Equal(52u, e.Size);
            },
            e =>
            {
                Assert.Equal("Date", e.Name);
                Assert.Equal("Mon, 21 Oct 2013 20:13:21 GMT", e.Value);
                Assert.Equal(65u, e.Size);
            },
            e =>
            {
                Assert.Equal(":status", e.Name);
                Assert.Equal("302", e.Value);
                Assert.Equal(42u, e.Size);
            });
 
        Assert.Equal(222u, hpackEncoder.TableSize);
 
        // Second response
        enumerator.Initialize(headers);
        Assert.Equal(HeaderWriteResult.Done, HPackHeaderWriter.BeginEncodeHeaders(307, hpackEncoder, enumerator, buffer, out length));
 
        result = buffer.Slice(0, length).ToArray();
        hex = BitConverter.ToString(result);
        Assert.Equal("48-03-33-30-37-C1-C0-BF", hex);
 
        entries = GetHeaderEntries(hpackEncoder);
        Assert.Collection(entries,
            e =>
            {
                Assert.Equal(":status", e.Name);
                Assert.Equal("307", e.Value);
                Assert.Equal(42u, e.Size);
            },
            e =>
            {
                Assert.Equal("Location", e.Name);
                Assert.Equal("https://www.example.com", e.Value);
                Assert.Equal(63u, e.Size);
            },
            e =>
            {
                Assert.Equal("Cache-Control", e.Name);
                Assert.Equal("private", e.Value);
                Assert.Equal(52u, e.Size);
            },
            e =>
            {
                Assert.Equal("Date", e.Name);
                Assert.Equal("Mon, 21 Oct 2013 20:13:21 GMT", e.Value);
                Assert.Equal(65u, e.Size);
            });
 
        Assert.Equal(222u, hpackEncoder.TableSize);
 
        // Third response
        headers.Date = "Mon, 21 Oct 2013 20:13:22 GMT";
        headers.ContentEncoding = "gzip";
        headers.SetCookie = "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1";
 
        enumerator.Initialize(headers);
        Assert.Equal(HeaderWriteResult.Done, HPackHeaderWriter.BeginEncodeHeaders(200, hpackEncoder, enumerator, buffer, out length));
 
        result = buffer.Slice(0, length).ToArray();
        hex = BitConverter.ToString(result);
        Assert.Equal(
            "88-61-1D-4D-6F-6E-2C-20-32-31-20-4F-63-74-20-32-" +
            "30-31-33-20-32-30-3A-31-33-3A-32-32-20-47-4D-54-" +
            "C1-5A-04-67-7A-69-70-C1-1F-28-38-66-6F-6F-3D-41-" +
            "53-44-4A-4B-48-51-4B-42-5A-58-4F-51-57-45-4F-50-" +
            "49-55-41-58-51-57-45-4F-49-55-3B-20-6D-61-78-2D-" +
            "61-67-65-3D-33-36-30-30-3B-20-76-65-72-73-69-6F-" +
            "6E-3D-31", hex);
 
        entries = GetHeaderEntries(hpackEncoder);
        Assert.Collection(entries,
            e =>
            {
                Assert.Equal("Content-Encoding", e.Name);
                Assert.Equal("gzip", e.Value);
                Assert.Equal(52u, e.Size);
            },
            e =>
            {
                Assert.Equal("Date", e.Name);
                Assert.Equal("Mon, 21 Oct 2013 20:13:22 GMT", e.Value);
                Assert.Equal(65u, e.Size);
            },
            e =>
            {
                Assert.Equal(":status", e.Name);
                Assert.Equal("307", e.Value);
                Assert.Equal(42u, e.Size);
            },
            e =>
            {
                Assert.Equal("Location", e.Name);
                Assert.Equal("https://www.example.com", e.Value);
                Assert.Equal(63u, e.Size);
            });
 
        Assert.Equal(222u, hpackEncoder.TableSize);
    }
 
    [Fact]
    public void BeginEncodeHeadersCustomEncoding_MaxHeaderTableSizeExceeded_EvictionsToFit()
    {
        // Test follows example https://tools.ietf.org/html/rfc7541#appendix-C.5
 
        Span<byte> buffer = new byte[1024 * 16];
 
        var headers = (IHeaderDictionary)new HttpResponseHeaders(_ => Encoding.UTF8);
        headers.CacheControl = "你好e";
        headers.Date = "Mon, 21 Oct 2013 20:13:21 GMT";
        headers.Location = "你好你好你好你.c";
 
        var enumerator = new Http2HeadersEnumerator();
 
        var hpackEncoder = new DynamicHPackEncoder(maxHeaderTableSize: 256);
 
        // First response
        enumerator.Initialize((HttpResponseHeaders)headers);
        Assert.Equal(HeaderWriteResult.Done, HPackHeaderWriter.BeginEncodeHeaders(302, hpackEncoder, enumerator, buffer, out var length));
 
        var result = buffer.Slice(0, length).ToArray();
        var hex = BitConverter.ToString(result);
        Assert.Equal(
            "48-03-33-30-32-61-1D-4D-6F-6E-2C-20-32-31-20-4F-" +
            "63-74-20-32-30-31-33-20-32-30-3A-31-33-3A-32-31-" +
            "20-47-4D-54-58-07-E4-BD-A0-E5-A5-BD-65-6E-17-E4-" +
            "BD-A0-E5-A5-BD-E4-BD-A0-E5-A5-BD-E4-BD-A0-E5-A5-" +
            "BD-E4-BD-A0-2E-63", hex);
 
        var entries = GetHeaderEntries(hpackEncoder);
        Assert.Collection(entries,
            e =>
            {
                Assert.Equal("Location", e.Name);
                Assert.Equal("你好你好你好你.c", e.Value);
                Assert.Equal(63u, e.Size);
            },
            e =>
            {
                Assert.Equal("Cache-Control", e.Name);
                Assert.Equal("你好e", e.Value);
                Assert.Equal(52u, e.Size);
            },
            e =>
            {
                Assert.Equal("Date", e.Name);
                Assert.Equal("Mon, 21 Oct 2013 20:13:21 GMT", e.Value);
                Assert.Equal(65u, e.Size);
            },
            e =>
            {
                Assert.Equal(":status", e.Name);
                Assert.Equal("302", e.Value);
                Assert.Equal(42u, e.Size);
            });
 
        Assert.Equal(222u, hpackEncoder.TableSize);
 
        // Second response
        enumerator.Initialize(headers);
        Assert.Equal(HeaderWriteResult.Done, HPackHeaderWriter.BeginEncodeHeaders(307, hpackEncoder, enumerator, buffer, out length));
 
        result = buffer.Slice(0, length).ToArray();
        hex = BitConverter.ToString(result);
        Assert.Equal("48-03-33-30-37-C1-C0-BF", hex);
 
        entries = GetHeaderEntries(hpackEncoder);
        Assert.Collection(entries,
            e =>
            {
                Assert.Equal(":status", e.Name);
                Assert.Equal("307", e.Value);
                Assert.Equal(42u, e.Size);
            },
            e =>
            {
                Assert.Equal("Location", e.Name);
                Assert.Equal("你好你好你好你.c", e.Value);
                Assert.Equal(63u, e.Size);
            },
            e =>
            {
                Assert.Equal("Cache-Control", e.Name);
                Assert.Equal("你好e", e.Value);
                Assert.Equal(52u, e.Size);
            },
            e =>
            {
                Assert.Equal("Date", e.Name);
                Assert.Equal("Mon, 21 Oct 2013 20:13:21 GMT", e.Value);
                Assert.Equal(65u, e.Size);
            });
 
        Assert.Equal(222u, hpackEncoder.TableSize);
 
        // Third response
        headers.Date = "Mon, 21 Oct 2013 20:13:22 GMT";
        headers.ContentEncoding = "gzip";
        headers.SetCookie = "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1";
 
        enumerator.Initialize(headers);
        Assert.Equal(HeaderWriteResult.Done, HPackHeaderWriter.BeginEncodeHeaders(200, hpackEncoder, enumerator, buffer, out length));
 
        result = buffer.Slice(0, length).ToArray();
        hex = BitConverter.ToString(result);
        Assert.Equal(
            "88-61-1D-4D-6F-6E-2C-20-32-31-20-4F-63-74-20-32-" +
            "30-31-33-20-32-30-3A-31-33-3A-32-32-20-47-4D-54-" +
            "C1-5A-04-67-7A-69-70-C1-1F-28-38-66-6F-6F-3D-41-" +
            "53-44-4A-4B-48-51-4B-42-5A-58-4F-51-57-45-4F-50-" +
            "49-55-41-58-51-57-45-4F-49-55-3B-20-6D-61-78-2D-" +
            "61-67-65-3D-33-36-30-30-3B-20-76-65-72-73-69-6F-" +
            "6E-3D-31", hex);
 
        entries = GetHeaderEntries(hpackEncoder);
        Assert.Collection(entries,
            e =>
            {
                Assert.Equal("Content-Encoding", e.Name);
                Assert.Equal("gzip", e.Value);
                Assert.Equal(52u, e.Size);
            },
            e =>
            {
                Assert.Equal("Date", e.Name);
                Assert.Equal("Mon, 21 Oct 2013 20:13:22 GMT", e.Value);
                Assert.Equal(65u, e.Size);
            },
            e =>
            {
                Assert.Equal(":status", e.Name);
                Assert.Equal("307", e.Value);
                Assert.Equal(42u, e.Size);
            },
            e =>
            {
                Assert.Equal("Location", e.Name);
                Assert.Equal("你好你好你好你.c", e.Value);
                Assert.Equal(63u, e.Size);
            });
 
        Assert.Equal(222u, hpackEncoder.TableSize);
    }
 
    [Theory]
    [InlineData("Set-Cookie", true)]
    [InlineData("Content-Disposition", true)]
    [InlineData("Content-Length", false)]
    public void BeginEncodeHeaders_ExcludedHeaders_NotAddedToTable(string headerName, bool neverIndex)
    {
        Span<byte> buffer = new byte[1024 * 16];
 
        var headers = new HttpResponseHeaders();
        headers.Append(headerName, "1");
 
        var enumerator = new Http2HeadersEnumerator();
        enumerator.Initialize(headers);
 
        var hpackEncoder = new DynamicHPackEncoder(maxHeaderTableSize: Http2PeerSettings.DefaultHeaderTableSize);
        Assert.Equal(HeaderWriteResult.Done, HPackHeaderWriter.BeginEncodeHeaders(hpackEncoder, enumerator, buffer, out _));
 
        if (neverIndex)
        {
            Assert.Equal(0x10, buffer[0] & 0x10);
        }
        else
        {
            Assert.Equal(0, buffer[0] & 0x40);
        }
 
        Assert.Empty(GetHeaderEntries(hpackEncoder));
    }
 
    [Fact]
    public void BeginEncodeHeaders_HeaderExceedHeaderTableSize_NoIndexAndNoHeaderEntry()
    {
        Span<byte> buffer = new byte[1024 * 16];
 
        var headers = new HttpResponseHeaders();
        headers.Append("x-Custom", new string('!', (int)Http2PeerSettings.DefaultHeaderTableSize));
 
        var enumerator = new Http2HeadersEnumerator();
        enumerator.Initialize(headers);
 
        var hpackEncoder = new DynamicHPackEncoder();
        Assert.Equal(HeaderWriteResult.Done, HPackHeaderWriter.BeginEncodeHeaders(200, hpackEncoder, enumerator, buffer, out var length));
 
        Assert.Empty(GetHeaderEntries(hpackEncoder));
    }
 
    public static TheoryData<KeyValuePair<string, string>[], byte[], int?> SinglePayloadData
    {
        get
        {
            var data = new TheoryData<KeyValuePair<string, string>[], byte[], int?>();
 
            // Lowercase header name letters only
            data.Add(
                new[]
                {
                        new KeyValuePair<string, string>("CustomHeader", "CustomValue"),
                },
                new byte[]
                {
                        //      12     c     u     s     t     o     m
                        0x40, 0x0c, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d,
                        // h     e     a     d     e     r    11     C
                        0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x0b, 0x43,
                        // u     s     t     o     m     V     a     l
                        0x75, 0x73, 0x74, 0x6f, 0x6d, 0x56, 0x61, 0x6c,
                        // u     e
                        0x75, 0x65
                },
                null);
            // Lowercase header name letters only
            data.Add(
                new[]
                {
                        new KeyValuePair<string, string>("CustomHeader!#$%&'*+-.^_`|~", "CustomValue"),
                },
                new byte[]
                {
                        //      27     c     u     s     t     o     m
                        0x40, 0x1b, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d,
                        // h     e     a     d     e     r     !     #
                        0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x21, 0x23,
                        // $     %     &     '     *     +     -     .
                        0x24, 0x25, 0x26, 0x27, 0x2a, 0x2b, 0x2d, 0x2e,
                        // ^     _     `     |     ~    11     C     u
                        0x5e, 0x5f, 0x60, 0x7c, 0x7e, 0x0b, 0x43, 0x75,
                        // s     t     o     m     V     a     l     u
                        0x73, 0x74, 0x6f, 0x6d, 0x56, 0x61, 0x6c, 0x75,
                        // e
                        0x65
                },
                null);
            // Single Payload
            data.Add(
                new[]
                {
                        new KeyValuePair<string, string>("date", "Mon, 24 Jul 2017 19:22:30 GMT"),
                        new KeyValuePair<string, string>("content-type", "text/html; charset=utf-8"),
                        new KeyValuePair<string, string>("server", "Kestrel")
                },
                new byte[]
                {
                        0x88, 0x40, 0x04, 0x64, 0x61, 0x74, 0x65, 0x1d,
                        0x4d, 0x6f, 0x6e, 0x2c, 0x20, 0x32, 0x34, 0x20,
                        0x4a, 0x75, 0x6c, 0x20, 0x32, 0x30, 0x31, 0x37,
                        0x20, 0x31, 0x39, 0x3a, 0x32, 0x32, 0x3a, 0x33,
                        0x30, 0x20, 0x47, 0x4d, 0x54, 0x40, 0x0c, 0x63,
                        0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x74,
                        0x79, 0x70, 0x65, 0x18, 0x74, 0x65, 0x78, 0x74,
                        0x2f, 0x68, 0x74, 0x6d, 0x6c, 0x3b, 0x20, 0x63,
                        0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x75,
                        0x74, 0x66, 0x2d, 0x38, 0x40, 0x06, 0x73, 0x65,
                        0x72, 0x76, 0x65, 0x72, 0x07, 0x4b, 0x65, 0x73,
                        0x74, 0x72, 0x65, 0x6c
                },
                200);
 
            return data;
        }
    }
 
    [Theory]
    [MemberData(nameof(SinglePayloadData))]
    public void EncodesHeadersInSinglePayloadWhenSpaceAvailable(KeyValuePair<string, string>[] headers, byte[] expectedPayload, int? statusCode)
    {
        var hpackEncoder = new DynamicHPackEncoder();
        var payload = new byte[1024];
        var length = 0;
        if (statusCode.HasValue)
        {
            Assert.Equal(HeaderWriteResult.Done, HPackHeaderWriter.BeginEncodeHeaders(statusCode.Value, hpackEncoder, GetHeadersEnumerator(headers), payload, out length));
        }
        else
        {
            Assert.Equal(HeaderWriteResult.Done, HPackHeaderWriter.BeginEncodeHeaders(hpackEncoder, GetHeadersEnumerator(headers), payload, out length));
        }
        Assert.Equal(expectedPayload.Length, length);
 
        for (var i = 0; i < length; i++)
        {
            Assert.True(expectedPayload[i] == payload[i], $"{expectedPayload[i]} != {payload[i]} at {i} (len {length})");
        }
 
        Assert.Equal(expectedPayload, new ArraySegment<byte>(payload, 0, length).ToArray());
    }
 
    [Theory]
    [InlineData(true)]
    [InlineData(false)]
    public void EncodesHeadersInMultiplePayloadsWhenSpaceNotAvailable(bool exactSize)
    {
        var statusCode = 200;
        var headers = new[]
        {
                new KeyValuePair<string, string>("date", "Mon, 24 Jul 2017 19:22:30 GMT"),
                new KeyValuePair<string, string>("content-type", "text/html; charset=utf-8"),
                new KeyValuePair<string, string>("server", "Kestrel")
            };
 
        var expectedStatusCodePayload = new byte[]
        {
                0x88
        };
 
        var expectedDateHeaderPayload = new byte[]
        {
                0x40, 0x04, 0x64, 0x61, 0x74, 0x65, 0x1d, 0x4d,
                0x6f, 0x6e, 0x2c, 0x20, 0x32, 0x34, 0x20, 0x4a,
                0x75, 0x6c, 0x20, 0x32, 0x30, 0x31, 0x37, 0x20,
                0x31, 0x39, 0x3a, 0x32, 0x32, 0x3a, 0x33, 0x30,
                0x20, 0x47, 0x4d, 0x54
        };
 
        var expectedContentTypeHeaderPayload = new byte[]
        {
                0x40, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e,
                0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x18, 0x74,
                0x65, 0x78, 0x74, 0x2f, 0x68, 0x74, 0x6d, 0x6c,
                0x3b, 0x20, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65,
                0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38
        };
 
        var expectedServerHeaderPayload = new byte[]
        {
                0x40, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
                0x07, 0x4b, 0x65, 0x73, 0x74, 0x72, 0x65, 0x6c
        };
 
        var hpackEncoder = new DynamicHPackEncoder();
 
        Span<byte> payload = new byte[1024];
        var offset = 0;
        var headerEnumerator = GetHeadersEnumerator(headers);
 
        // When !exactSize, slices are one byte short of fitting the next header
        var sliceLength = expectedStatusCodePayload.Length + (exactSize ? 0 : expectedDateHeaderPayload.Length - 1);
        Assert.Equal(HeaderWriteResult.MoreHeaders, HPackHeaderWriter.BeginEncodeHeaders(statusCode, hpackEncoder, headerEnumerator, payload.Slice(offset, sliceLength), out var length));
        Assert.Equal(expectedStatusCodePayload.Length, length);
        Assert.Equal(expectedStatusCodePayload, payload.Slice(0, length).ToArray());
 
        offset += length;
 
        sliceLength = expectedDateHeaderPayload.Length + (exactSize ? 0 : expectedContentTypeHeaderPayload.Length - 1);
        Assert.Equal(HeaderWriteResult.MoreHeaders, HPackHeaderWriter.ContinueEncodeHeaders(hpackEncoder, headerEnumerator, payload.Slice(offset, sliceLength), out length));
        Assert.Equal(expectedDateHeaderPayload.Length, length);
        Assert.Equal(expectedDateHeaderPayload, payload.Slice(offset, length).ToArray());
 
        offset += length;
 
        sliceLength = expectedContentTypeHeaderPayload.Length + (exactSize ? 0 : expectedServerHeaderPayload.Length - 1);
        Assert.Equal(HeaderWriteResult.MoreHeaders, HPackHeaderWriter.ContinueEncodeHeaders(hpackEncoder, headerEnumerator, payload.Slice(offset, sliceLength), out length));
        Assert.Equal(expectedContentTypeHeaderPayload.Length, length);
        Assert.Equal(expectedContentTypeHeaderPayload, payload.Slice(offset, length).ToArray());
 
        offset += length;
 
        sliceLength = expectedServerHeaderPayload.Length;
        Assert.Equal(HeaderWriteResult.Done, HPackHeaderWriter.ContinueEncodeHeaders(hpackEncoder, headerEnumerator, payload.Slice(offset, sliceLength), out length));
        Assert.Equal(expectedServerHeaderPayload.Length, length);
        Assert.Equal(expectedServerHeaderPayload, payload.Slice(offset, length).ToArray());
    }
 
    [Fact]
    public void BeginEncodeHeaders_MaxHeaderTableSizeUpdated_SizeUpdateInHeaders()
    {
        Span<byte> buffer = new byte[1024 * 16];
 
        var hpackEncoder = new DynamicHPackEncoder();
        hpackEncoder.UpdateMaxHeaderTableSize(100);
 
        var enumerator = new Http2HeadersEnumerator();
 
        // First request
        enumerator.Initialize(new Dictionary<string, StringValues>());
        Assert.Equal(HeaderWriteResult.Done, HPackHeaderWriter.BeginEncodeHeaders(hpackEncoder, enumerator, buffer, out var length));
 
        Assert.Equal(2, length);
 
        const byte DynamicTableSizeUpdateMask = 0xe0;
 
        var integerDecoder = new IntegerDecoder();
        Assert.False(integerDecoder.BeginTryDecode((byte)(buffer[0] & ~DynamicTableSizeUpdateMask), prefixLength: 5, out _));
        Assert.True(integerDecoder.TryDecode(buffer[1], out var result));
 
        Assert.Equal(100, result);
 
        // Second request
        enumerator.Initialize(new Dictionary<string, StringValues>());
        Assert.Equal(HeaderWriteResult.Done, HPackHeaderWriter.BeginEncodeHeaders(hpackEncoder, enumerator, buffer, out length));
 
        Assert.Equal(0, length);
    }
 
    [Fact]
    public void WithStatusCode_TooLargeHeader_ReturnsMoreHeaders()
    {
        Span<byte> buffer = new byte[1024 * 16];
 
        IHeaderDictionary headers = new HttpResponseHeaders();
        headers.Cookie = new string('a', buffer.Length + 1);
        var enumerator = new Http2HeadersEnumerator();
        enumerator.Initialize(headers);
 
        var hpackEncoder = new DynamicHPackEncoder();
        Assert.Equal(HeaderWriteResult.MoreHeaders, HPackHeaderWriter.BeginEncodeHeaders(200, hpackEncoder, enumerator, buffer, out var length));
        Assert.Equal(1, length);
    }
 
    [Fact]
    public void NoStatusCodeLargeHeader_ReturnsOversized()
    {
        Span<byte> buffer = new byte[1024 * 16];
 
        IHeaderDictionary headers = new HttpResponseHeaders();
        headers.Cookie = new string('a', buffer.Length + 1);
        var enumerator = new Http2HeadersEnumerator();
        enumerator.Initialize(headers);
 
        var hpackEncoder = new DynamicHPackEncoder();
        Assert.Equal(HeaderWriteResult.BufferTooSmall, HPackHeaderWriter.BeginEncodeHeaders(hpackEncoder, enumerator, buffer, out var length));
        Assert.Equal(0, length);
    }
 
    [Fact]
    public void WithStatusCode_JustFittingHeaderNoSpace_ReturnsMoreHeaders()
    {
        Span<byte> buffer = new byte[1024 * 16];
 
        IHeaderDictionary headers = new HttpResponseHeaders();
        headers.Cookie = new string('a', buffer.Length - 1);
        var enumerator = new Http2HeadersEnumerator();
        enumerator.Initialize(headers);
 
        var hpackEncoder = new DynamicHPackEncoder();
        Assert.Equal(HeaderWriteResult.MoreHeaders, HPackHeaderWriter.BeginEncodeHeaders(200, hpackEncoder, enumerator, buffer, out var length));
        Assert.Equal(1, length);
    }
 
    [Fact]
    public void NoStatusCode_JustFittingHeaderNoSpace_ReturnsMoreHeaders()
    {
        Span<byte> buffer = new byte[1024 * 16];
 
        IHeaderDictionary headers = new HttpResponseHeaders();
        headers.Accept = "application/json;";
        headers.Cookie = new string('a', buffer.Length - 1);
        var enumerator = new Http2HeadersEnumerator();
        enumerator.Initialize(headers);
 
        var hpackEncoder = new DynamicHPackEncoder();
        Assert.Equal(HeaderWriteResult.MoreHeaders, HPackHeaderWriter.BeginEncodeHeaders(hpackEncoder, enumerator, buffer, out var length));
        Assert.Equal(26, length);
    }
 
    private static Http2HeadersEnumerator GetHeadersEnumerator(IEnumerable<KeyValuePair<string, string>> headers)
    {
        var groupedHeaders = headers
            .GroupBy(k => k.Key)
            .ToDictionary(g => g.Key, g => new StringValues(g.Select(gg => gg.Value).ToArray()));
 
        var enumerator = new Http2HeadersEnumerator();
        enumerator.Initialize(groupedHeaders);
        return enumerator;
    }
 
    private EncoderHeaderEntry GetHeaderEntry(DynamicHPackEncoder encoder, int index)
    {
        var entry = encoder.Head;
        while (index-- >= 0)
        {
            entry = entry.Before;
        }
        return entry;
    }
 
    private List<EncoderHeaderEntry> GetHeaderEntries(DynamicHPackEncoder encoder)
    {
        var headers = new List<EncoderHeaderEntry>();
 
        var entry = encoder.Head;
        while (entry.Before != encoder.Head)
        {
            entry = entry.Before;
            headers.Add(entry);
        };
 
        return headers;
    }
}