コード例 #1
0
        public virtual void GlobalSetup()
        {
            _memoryPool = PinnedBlockMemoryPoolFactory.Create();
            _httpFrame  = new Http2Frame();

            var options = new PipeOptions(_memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false);

            _connectionPair = DuplexPipe.CreateConnectionPair(options, options);

            _httpRequestHeaders = new HttpRequestHeaders();
            _httpRequestHeaders[HeaderNames.Method]    = new StringValues("GET");
            _httpRequestHeaders[HeaderNames.Path]      = new StringValues("/");
            _httpRequestHeaders[HeaderNames.Scheme]    = new StringValues("http");
            _httpRequestHeaders[HeaderNames.Authority] = new StringValues("localhost:80");

            _headersBuffer = new byte[1024 * 16];
            _hpackEncoder  = new DynamicHPackEncoder();

            var serviceContext = TestContextFactory.CreateServiceContext(
                serverOptions: new KestrelServerOptions(),
                dateHeaderValueManager: new DateHeaderValueManager(),
                systemClock: new MockSystemClock(),
                log: new MockTrace());

            serviceContext.DateHeaderValueManager.OnHeartbeat(default);
コード例 #2
0
        public Http2FrameWriter(
            PipeWriter outputPipeWriter,
            BaseConnectionContext connectionContext,
            Http2Connection http2Connection,
            OutputFlowControl connectionOutputFlowControl,
            ITimeoutControl timeoutControl,
            MinDataRate?minResponseDataRate,
            string connectionId,
            MemoryPool <byte> memoryPool,
            ServiceContext serviceContext)
        {
            // Allow appending more data to the PipeWriter when a flush is pending.
            _outputWriter                = new ConcurrentPipeWriter(outputPipeWriter, memoryPool, _writeLock);
            _connectionContext           = connectionContext;
            _http2Connection             = http2Connection;
            _connectionOutputFlowControl = connectionOutputFlowControl;
            _connectionId                = connectionId;
            _log                 = serviceContext.Log;
            _timeoutControl      = timeoutControl;
            _minResponseDataRate = minResponseDataRate;
            _flusher             = new TimingPipeFlusher(timeoutControl, serviceContext.Log);
            _flusher.Initialize(_outputWriter);
            _outgoingFrame        = new Http2Frame();
            _headerEncodingBuffer = new byte[_maxFrameSize];

            _scheduleInline = serviceContext.Scheduler == PipeScheduler.Inline;

            _hpackEncoder = new DynamicHPackEncoder(serviceContext.ServerOptions.AllowResponseHeaderCompression);
        }
コード例 #3
0
        public void GlobalSetup()
        {
            _http2HeadersEnumerator = new Http2HeadersEnumerator();
            _hpackEncoder           = new DynamicHPackEncoder();
            _buffer = new byte[1024 * 1024];

            _knownResponseHeaders = new HttpResponseHeaders
            {
                HeaderServer           = "Kestrel",
                HeaderContentType      = "application/json",
                HeaderDate             = "Date!",
                HeaderContentLength    = "0",
                HeaderAcceptRanges     = "Ranges!",
                HeaderTransferEncoding = "Encoding!",
                HeaderVia             = "Via!",
                HeaderVary            = "Vary!",
                HeaderWWWAuthenticate = "Authenticate!",
                HeaderLastModified    = "Modified!",
                HeaderExpires         = "Expires!",
                HeaderAge             = "Age!"
            };

            _unknownResponseHeaders = new HttpResponseHeaders();
            for (var i = 0; i < 10; i++)
            {
                _unknownResponseHeaders.Append("Unknown" + i, "Value" + i);
            }
        }
コード例 #4
0
    public void GlobalSetup()
    {
        _http2HeadersEnumerator = new Http2HeadersEnumerator();
        _hpackEncoder           = new DynamicHPackEncoder();
        _buffer = new byte[1024 * 1024];

        _knownResponseHeaders = new HttpResponseHeaders();

        var knownHeaders = (IHeaderDictionary)_knownResponseHeaders;

        knownHeaders.Server           = "Kestrel";
        knownHeaders.ContentType      = "application/json";
        knownHeaders.Date             = "Date!";
        knownHeaders.ContentLength    = 0;
        knownHeaders.AcceptRanges     = "Ranges!";
        knownHeaders.TransferEncoding = "Encoding!";
        knownHeaders.Via             = "Via!";
        knownHeaders.Vary            = "Vary!";
        knownHeaders.WWWAuthenticate = "Authenticate!";
        knownHeaders.LastModified    = "Modified!";
        knownHeaders.Expires         = "Expires!";
        knownHeaders.Age             = "Age!";

        _unknownResponseHeaders = new HttpResponseHeaders();
        for (var i = 0; i < 10; i++)
        {
            _unknownResponseHeaders.Append("Unknown" + i, "Value" + i);
        }
    }
コード例 #5
0
    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.True(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.True(HPackHeaderWriter.BeginEncodeHeaders(hpackEncoder, enumerator, buffer, out length));

        Assert.Equal(0, length);
    }
コード例 #6
0
    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.True(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);
    }
コード例 #7
0
    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.True(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));
    }
コード例 #8
0
    /// <summary>
    /// Begin encoding headers in the first HEADERS frame.
    /// </summary>
    public static bool BeginEncodeHeaders(int statusCode, DynamicHPackEncoder hpackEncoder, Http2HeadersEnumerator headersEnumerator, Span <byte> buffer, out int length)
    {
        length = 0;

        if (!hpackEncoder.EnsureDynamicTableSizeUpdate(buffer, out var sizeUpdateLength))
        {
            throw new HPackEncodingException(SR.net_http_hpack_encode_failure);
        }
        length += sizeUpdateLength;

        if (!EncodeStatusHeader(statusCode, hpackEncoder, buffer.Slice(length), out var statusCodeLength))
        {
            throw new HPackEncodingException(SR.net_http_hpack_encode_failure);
        }
        length += statusCodeLength;

        if (!headersEnumerator.MoveNext())
        {
            return(true);
        }

        // We're ok with not throwing if no headers were encoded because we've already encoded the status.
        // There is a small chance that the header will encode if there is no other content in the next HEADERS frame.
        var done = EncodeHeadersCore(hpackEncoder, headersEnumerator, buffer.Slice(length), throwIfNoneEncoded: false, out var headersLength);

        length += headersLength;
        return(done);
    }
コード例 #9
0
    private EncoderHeaderEntry GetHeaderEntry(DynamicHPackEncoder encoder, int index)
    {
        var entry = encoder.Head;

        while (index-- >= 0)
        {
            entry = entry.Before;
        }
        return(entry);
    }
コード例 #10
0
 private static bool EncodeStatusHeader(int statusCode, DynamicHPackEncoder hpackEncoder, Span <byte> buffer, out int length)
 {
     if (H2StaticTable.TryGetStatusIndex(statusCode, out var index))
     {
         // Status codes which exist in the HTTP/2 StaticTable.
         return(HPackEncoder.EncodeIndexedHeaderField(index, buffer, out length));
     }
     else
     {
         const string name  = ":status";
         var          value = StatusCodes.ToStatusString(statusCode);
         return(hpackEncoder.EncodeHeader(buffer, H2StaticTable.Status200, HeaderEncodingHint.Index, name, value, valueEncoding: null, out length));
     }
 }
コード例 #11
0
    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);
    }
コード例 #12
0
    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.True(HPackHeaderWriter.BeginEncodeHeaders(200, hpackEncoder, enumerator, buffer, out var length));

        Assert.Empty(GetHeaderEntries(hpackEncoder));
    }
コード例 #13
0
    /// <summary>
    /// Begin encoding headers in the first HEADERS frame.
    /// </summary>
    public static bool BeginEncodeHeaders(DynamicHPackEncoder hpackEncoder, Http2HeadersEnumerator headersEnumerator, Span <byte> buffer, out int length)
    {
        length = 0;

        if (!hpackEncoder.EnsureDynamicTableSizeUpdate(buffer, out var sizeUpdateLength))
        {
            throw new HPackEncodingException(SR.net_http_hpack_encode_failure);
        }
        length += sizeUpdateLength;

        if (!headersEnumerator.MoveNext())
        {
            return(true);
        }

        var done = EncodeHeadersCore(hpackEncoder, headersEnumerator, buffer.Slice(length), throwIfNoneEncoded: true, out var headersLength);

        length += headersLength;
        return(done);
    }
コード例 #14
0
        private static bool EncodeStatusHeader(int statusCode, DynamicHPackEncoder hpackEncoder, Span <byte> buffer, out int length)
        {
            switch (statusCode)
            {
            case 200:
            case 204:
            case 206:
            case 304:
            case 400:
            case 404:
            case 500:
                // Status codes which exist in the HTTP/2 StaticTable.
                return(HPackEncoder.EncodeIndexedHeaderField(H2StaticTable.GetStatusIndex(statusCode), buffer, out length));

            default:
                const string name  = ":status";
                var          value = StatusCodes.ToStatusString(statusCode);
                return(hpackEncoder.EncodeHeader(buffer, H2StaticTable.Status200, HeaderEncodingHint.Index, name, value, out length));
            }
        }
コード例 #15
0
    private static bool EncodeHeadersCore(DynamicHPackEncoder hpackEncoder, Http2HeadersEnumerator headersEnumerator, Span <byte> buffer, bool throwIfNoneEncoded, out int length)
    {
        var currentLength = 0;

        do
        {
            var staticTableId = headersEnumerator.HPackStaticTableId;
            var name          = headersEnumerator.Current.Key;
            var value         = headersEnumerator.Current.Value;
            var valueEncoding =
                ReferenceEquals(headersEnumerator.EncodingSelector, KestrelServerOptions.DefaultHeaderEncodingSelector)
                ? null : headersEnumerator.EncodingSelector(name);

            var hint = ResolveHeaderEncodingHint(staticTableId, name);

            if (!hpackEncoder.EncodeHeader(
                    buffer.Slice(currentLength),
                    staticTableId,
                    hint,
                    name,
                    value,
                    valueEncoding,
                    out var headerLength))
            {
                // If the header wasn't written, and no headers have been written, then the header is too large.
                // Throw an error to avoid an infinite loop of attempting to write large header.
                if (currentLength == 0 && throwIfNoneEncoded)
                {
                    throw new HPackEncodingException(SR.net_http_hpack_encode_failure);
                }

                length = currentLength;
                return(false);
            }

            currentLength += headerLength;
        }while (headersEnumerator.MoveNext());

        length = currentLength;
        return(true);
    }
コード例 #16
0
    public static void WriteStartStream(this PipeWriter writer, int streamId, DynamicHPackEncoder hpackEncoder, Http2HeadersEnumerator headers, byte[] headerEncodingBuffer, bool endStream, Http2Frame frame = null)
    {
        frame ??= new Http2Frame();
        frame.PrepareHeaders(Http2HeadersFrameFlags.NONE, streamId);

        var buffer = headerEncodingBuffer.AsSpan();
        var done   = HPackHeaderWriter.BeginEncodeHeaders(hpackEncoder, headers, buffer, out var length);

        frame.PayloadLength = length;

        if (done)
        {
            frame.HeadersFlags = Http2HeadersFrameFlags.END_HEADERS;
        }

        if (endStream)
        {
            frame.HeadersFlags |= Http2HeadersFrameFlags.END_STREAM;
        }

        Http2FrameWriter.WriteHeader(frame, writer);
        writer.Write(buffer.Slice(0, length));

        while (!done)
        {
            frame.PrepareContinuation(Http2ContinuationFrameFlags.NONE, streamId);

            done = HPackHeaderWriter.ContinueEncodeHeaders(hpackEncoder, headers, buffer, out length);
            frame.PayloadLength = length;

            if (done)
            {
                frame.ContinuationFlags = Http2ContinuationFrameFlags.END_HEADERS;
            }

            Http2FrameWriter.WriteHeader(frame, writer);
            writer.Write(buffer.Slice(0, length));
        }
    }
コード例 #17
0
    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.True(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);
    }
コード例 #18
0
    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.True(HPackHeaderWriter.BeginEncodeHeaders(statusCode.Value, hpackEncoder, GetHeadersEnumerator(headers), payload, out length));
        }
        else
        {
            Assert.True(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));
    }
コード例 #19
0
    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.False(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.False(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.False(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.True(HPackHeaderWriter.ContinueEncodeHeaders(hpackEncoder, headerEnumerator, payload.Slice(offset, sliceLength), out length));
        Assert.Equal(expectedServerHeaderPayload.Length, length);
        Assert.Equal(expectedServerHeaderPayload, payload.Slice(offset, length).ToArray());
    }
コード例 #20
0
 /// <summary>
 /// Continue encoding headers in the next HEADERS frame. The enumerator should already have a current value.
 /// </summary>
 public static bool ContinueEncodeHeaders(DynamicHPackEncoder hpackEncoder, Http2HeadersEnumerator headersEnumerator, Span <byte> buffer, out int length)
 {
     return(EncodeHeadersCore(hpackEncoder, headersEnumerator, buffer, throwIfNoneEncoded: true, out length));
 }
コード例 #21
0
    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.True(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.True(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.True(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);
    }