From 278b3b0a1da40507b33bba59649d06410eddfa9b Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Mon, 22 Nov 2021 21:33:09 +1300 Subject: [PATCH 1/3] HTTP/3: Write static header names --- src/Hosting/Hosting/src/Internal/WebHost.cs | 14 +- .../src/Internal/Http3/Http3FrameWriter.cs | 4 +- .../Internal/Http3/Http3HeadersEnumerator.cs | 87 ++- .../Core/src/Internal/Http3/Http3Stream.cs | 2 +- .../src/Internal/Http3/QPackHeaderWriter.cs | 45 +- .../test/{ => Http1}/Http1ConnectionTests.cs | 0 .../{ => Http1}/Http1ConnectionTestsBase.cs | 0 ...Http1HttpProtocolFeatureCollectionTests.cs | 0 .../{ => Http1}/Http1OutputProducerTests.cs | 0 .../test/{ => Http2}/Http2FrameWriterTests.cs | 0 .../{ => Http2}/Http2HPackEncoderTests.cs | 0 .../Http2HeadersEnumeratorTests.cs | 0 ...Http2HttpProtocolFeatureCollectionTests.cs | 0 .../test/{ => Http3}/Http3FrameWriterTests.cs | 0 .../Http3HeadersEnumeratorTests.cs | 34 +- ...Http3HttpProtocolFeatureCollectionTests.cs | 0 .../Core/test/Http3/Http3QPackEncoderTests.cs | 660 ++++++++++++++++++ .../Microbenchmarks/QPackDecoderBenchmark.cs | 127 ++++ .../shared/test/Http3/Http3InMemory.cs | 6 +- .../runtime/Http3/QPack/QPackDecoder.cs | 6 +- 20 files changed, 934 insertions(+), 51 deletions(-) rename src/Servers/Kestrel/Core/test/{ => Http1}/Http1ConnectionTests.cs (100%) rename src/Servers/Kestrel/Core/test/{ => Http1}/Http1ConnectionTestsBase.cs (100%) rename src/Servers/Kestrel/Core/test/{ => Http1}/Http1HttpProtocolFeatureCollectionTests.cs (100%) rename src/Servers/Kestrel/Core/test/{ => Http1}/Http1OutputProducerTests.cs (100%) rename src/Servers/Kestrel/Core/test/{ => Http2}/Http2FrameWriterTests.cs (100%) rename src/Servers/Kestrel/Core/test/{ => Http2}/Http2HPackEncoderTests.cs (100%) rename src/Servers/Kestrel/Core/test/{ => Http2}/Http2HeadersEnumeratorTests.cs (100%) rename src/Servers/Kestrel/Core/test/{ => Http2}/Http2HttpProtocolFeatureCollectionTests.cs (100%) rename src/Servers/Kestrel/Core/test/{ => Http3}/Http3FrameWriterTests.cs (100%) rename src/Servers/Kestrel/Core/test/{ => Http3}/Http3HeadersEnumeratorTests.cs (82%) rename src/Servers/Kestrel/Core/test/{ => Http3}/Http3HttpProtocolFeatureCollectionTests.cs (100%) create mode 100644 src/Servers/Kestrel/Core/test/Http3/Http3QPackEncoderTests.cs create mode 100644 src/Servers/Kestrel/perf/Microbenchmarks/QPackDecoderBenchmark.cs diff --git a/src/Hosting/Hosting/src/Internal/WebHost.cs b/src/Hosting/Hosting/src/Internal/WebHost.cs index f0c91f56cde1..6209842a4dfe 100644 --- a/src/Hosting/Hosting/src/Internal/WebHost.cs +++ b/src/Hosting/Hosting/src/Internal/WebHost.cs @@ -79,16 +79,14 @@ public WebHost( _hostingServiceProvider = hostingServiceProvider; _applicationServiceCollection.AddSingleton(); // There's no way to to register multiple service types per definition. See https://github.com/aspnet/DependencyInjection/issues/360 -#pragma warning disable CS8634 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'class' constraint. - _applicationServiceCollection.AddSingleton(services - => services.GetService() as IHostApplicationLifetime); + _applicationServiceCollection.AddSingleton(services + => services.GetService()!); #pragma warning disable CS0618 // Type or member is obsolete - _applicationServiceCollection.AddSingleton(services - => services.GetService() as AspNetCore.Hosting.IApplicationLifetime); - _applicationServiceCollection.AddSingleton(services - => services.GetService() as Extensions.Hosting.IApplicationLifetime); + _applicationServiceCollection.AddSingleton(services + => services.GetService()!); + _applicationServiceCollection.AddSingleton(services + => services.GetService()!); #pragma warning restore CS0618 // Type or member is obsolete -#pragma warning restore CS8634 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'class' constraint. _applicationServiceCollection.AddSingleton(); } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3FrameWriter.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3FrameWriter.cs index 9d978a6df667..7902de78f824 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3FrameWriter.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3FrameWriter.cs @@ -315,7 +315,7 @@ public ValueTask WriteResponseTrailersAsync(long streamId, HttpResp _outgoingFrame.PrepareHeaders(); var buffer = _headerEncodingBuffer.GetSpan(HeaderBufferSize); - var done = QPackHeaderWriter.BeginEncode(_headersEnumerator, buffer, ref _headersTotalSize, out var payloadLength); + var done = QPackHeaderWriter.BeginEncodeHeaders(_headersEnumerator, buffer, ref _headersTotalSize, out var payloadLength); FinishWritingHeaders(payloadLength, done); } // Any exception from the QPack encoder can leave the dynamic table in a corrupt state. @@ -370,7 +370,7 @@ internal void WriteResponseHeaders(int statusCode, HttpResponseHeaders headers) _outgoingFrame.PrepareHeaders(); var buffer = _headerEncodingBuffer.GetSpan(HeaderBufferSize); - var done = QPackHeaderWriter.BeginEncode(statusCode, _headersEnumerator, buffer, ref _headersTotalSize, out var payloadLength); + var done = QPackHeaderWriter.BeginEncodeHeaders(statusCode, _headersEnumerator, buffer, ref _headersTotalSize, out var payloadLength); FinishWritingHeaders(payloadLength, done); } // Any exception from the QPack encoder can leave the dynamic table in a corrupt state. diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3HeadersEnumerator.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3HeadersEnumerator.cs index 2caec53a91c6..f8b02961ecea 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3HeadersEnumerator.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3HeadersEnumerator.cs @@ -4,6 +4,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Net.Http.QPack; using System.Text; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.Extensions.Primitives; @@ -147,7 +148,89 @@ public void Dispose() internal static int GetResponseHeaderStaticTableId(KnownHeaderType responseHeaderType) { - // Not Implemented - return -1; + // Not every header in the QPACK static table is known. + // These are missing from this test and the full header name is written. + // Missing: + // - link + // - location + // - strict-transport-security + // - x-content-type-options + // - x-xss-protection + // - content-security-policy + // - early-data + // - expect-ct + // - purpose + // - timing-allow-origin + // - x-forwarded-for + // - x-frame-options + switch (responseHeaderType) + { + case KnownHeaderType.Age: + return H3StaticTable.Age0; + case KnownHeaderType.ContentLength: + return H3StaticTable.ContentLength0; + case KnownHeaderType.Date: + return H3StaticTable.Date; + case KnownHeaderType.Cookie: + return H3StaticTable.Cookie; + case KnownHeaderType.ETag: + return H3StaticTable.ETag; + case KnownHeaderType.IfModifiedSince: + return H3StaticTable.IfModifiedSince; + case KnownHeaderType.IfNoneMatch: + return H3StaticTable.IfNoneMatch; + case KnownHeaderType.LastModified: + return H3StaticTable.LastModified; + case KnownHeaderType.Location: + return H3StaticTable.Location; + case KnownHeaderType.Referer: + return H3StaticTable.Referer; + case KnownHeaderType.SetCookie: + return H3StaticTable.SetCookie; + case KnownHeaderType.Method: + return H3StaticTable.MethodConnect; + case KnownHeaderType.Accept: + return H3StaticTable.AcceptAny; + case KnownHeaderType.AcceptEncoding: + return H3StaticTable.AcceptEncodingGzipDeflateBr; + case KnownHeaderType.AcceptRanges: + return H3StaticTable.AcceptRangesBytes; + case KnownHeaderType.AccessControlAllowHeaders: + return H3StaticTable.AccessControlAllowHeadersCacheControl; + case KnownHeaderType.AccessControlAllowOrigin: + return H3StaticTable.AccessControlAllowOriginAny; + case KnownHeaderType.CacheControl: + return H3StaticTable.CacheControlMaxAge0; + case KnownHeaderType.ContentEncoding: + return H3StaticTable.ContentEncodingBr; + case KnownHeaderType.ContentType: + return H3StaticTable.ContentTypeApplicationDnsMessage; + case KnownHeaderType.Range: + return H3StaticTable.RangeBytes0ToAll; + case KnownHeaderType.Vary: + return H3StaticTable.VaryAcceptEncoding; + case KnownHeaderType.AcceptLanguage: + return H3StaticTable.AcceptLanguage; + case KnownHeaderType.AccessControlAllowCredentials: + return H3StaticTable.AccessControlAllowCredentials; + case KnownHeaderType.AccessControlAllowMethods: + return H3StaticTable.AccessControlAllowMethodsGet; + case KnownHeaderType.AltSvc: + return H3StaticTable.AltSvcClear; + case KnownHeaderType.Authorization: + return H3StaticTable.Authorization; + case KnownHeaderType.IfRange: + return H3StaticTable.IfRange; + case KnownHeaderType.Origin: + return H3StaticTable.Origin; + case KnownHeaderType.Server: + return H3StaticTable.Server; + case KnownHeaderType.UpgradeInsecureRequests: + return H3StaticTable.UpgradeInsecureRequests1; + case KnownHeaderType.UserAgent: + return H3StaticTable.UserAgent; + default: + return -1; + } } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs index 37f7bb029e82..dc06197e7b03 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs @@ -636,7 +636,7 @@ private async Task ProcessHeadersFrameAsync(IHttpApplication try { - QPackDecoder.Decode(payload, handler: this); + QPackDecoder.Decode(payload, endHeaders: true, handler: this); QPackDecoder.Reset(); } catch (QPackDecodingException ex) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/QPackHeaderWriter.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/QPackHeaderWriter.cs index 5346b1c3f5c2..0748760b2817 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/QPackHeaderWriter.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/QPackHeaderWriter.cs @@ -4,12 +4,13 @@ using System; using System.Diagnostics; using System.Net.Http.QPack; +using System.Text; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3; internal static class QPackHeaderWriter { - public static bool BeginEncode(Http3HeadersEnumerator enumerator, Span buffer, ref int totalHeaderSize, out int length) + public static bool BeginEncodeHeaders(Http3HeadersEnumerator enumerator, Span buffer, ref int totalHeaderSize, out int length) { bool hasValue = enumerator.MoveNext(); Debug.Assert(hasValue == true); @@ -24,10 +25,9 @@ public static bool BeginEncode(Http3HeadersEnumerator enumerator, Span buf return doneEncode; } - public static bool BeginEncode(int statusCode, Http3HeadersEnumerator enumerator, Span buffer, ref int totalHeaderSize, out int length) + public static bool BeginEncodeHeaders(int statusCode, Http3HeadersEnumerator headersEnumerator, Span buffer, ref int totalHeaderSize, out int length) { - bool hasValue = enumerator.MoveNext(); - Debug.Assert(hasValue == true); + length = 0; // https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#header-prefix buffer[0] = 0; @@ -35,29 +35,37 @@ public static bool BeginEncode(int statusCode, Http3HeadersEnumerator enumerator int statusCodeLength = EncodeStatusCode(statusCode, buffer.Slice(2)); totalHeaderSize += 42; // name (:status) + value (xxx) + overhead (32) + length += statusCodeLength + 2; - bool done = Encode(enumerator, buffer.Slice(statusCodeLength + 2), throwIfNoneEncoded: false, ref totalHeaderSize, out int headersLength); - length = statusCodeLength + headersLength + 2; + if (!headersEnumerator.MoveNext()) + { + return true; + } + + bool done = Encode(headersEnumerator, buffer.Slice(statusCodeLength + 2), throwIfNoneEncoded: false, ref totalHeaderSize, out int headersLength); + length += headersLength; return done; } - public static bool Encode(Http3HeadersEnumerator enumerator, Span buffer, ref int totalHeaderSize, out int length) + public static bool Encode(Http3HeadersEnumerator headersEnumerator, Span buffer, ref int totalHeaderSize, out int length) { - return Encode(enumerator, buffer, throwIfNoneEncoded: true, ref totalHeaderSize, out length); + return Encode(headersEnumerator, buffer, throwIfNoneEncoded: true, ref totalHeaderSize, out length); } - private static bool Encode(Http3HeadersEnumerator enumerator, Span buffer, bool throwIfNoneEncoded, ref int totalHeaderSize, out int length) + private static bool Encode(Http3HeadersEnumerator headersEnumerator, Span buffer, bool throwIfNoneEncoded, ref int totalHeaderSize, out int length) { length = 0; do { - var current = enumerator.Current; - var valueEncoding = ReferenceEquals(enumerator.EncodingSelector, KestrelServerOptions.DefaultHeaderEncodingSelector) - ? null : enumerator.EncodingSelector(current.Key); + var staticTableId = headersEnumerator.QPackStaticTableId; + var name = headersEnumerator.Current.Key; + var value = headersEnumerator.Current.Value; + var valueEncoding = ReferenceEquals(headersEnumerator.EncodingSelector, KestrelServerOptions.DefaultHeaderEncodingSelector) + ? null : headersEnumerator.EncodingSelector(name); - if (!QPackEncoder.EncodeLiteralHeaderFieldWithoutNameReference(current.Key, current.Value, valueEncoding, buffer.Slice(length), out int headerLength)) + if (!EncodeHeader(buffer.Slice(length), staticTableId, name, value, valueEncoding, out var headerLength)) { if (length == 0 && throwIfNoneEncoded) { @@ -67,13 +75,20 @@ private static bool Encode(Http3HeadersEnumerator enumerator, Span buffer, } // https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-4.1.1.3 - totalHeaderSize += HeaderField.GetLength(current.Key.Length, current.Value.Length); + totalHeaderSize += HeaderField.GetLength(name.Length, value.Length); length += headerLength; - } while (enumerator.MoveNext()); + } while (headersEnumerator.MoveNext()); return true; } + private static bool EncodeHeader(Span buffer, int staticTableId, string name, string value, Encoding? valueEncoding, out int headerLength) + { + return staticTableId == -1 + ? QPackEncoder.EncodeLiteralHeaderFieldWithoutNameReference(name, value, valueEncoding, buffer, out headerLength) + : QPackEncoder.EncodeLiteralHeaderFieldWithStaticNameReference(staticTableId, value, valueEncoding, buffer, out headerLength); + } + private static int EncodeStatusCode(int statusCode, Span buffer) { switch (statusCode) diff --git a/src/Servers/Kestrel/Core/test/Http1ConnectionTests.cs b/src/Servers/Kestrel/Core/test/Http1/Http1ConnectionTests.cs similarity index 100% rename from src/Servers/Kestrel/Core/test/Http1ConnectionTests.cs rename to src/Servers/Kestrel/Core/test/Http1/Http1ConnectionTests.cs diff --git a/src/Servers/Kestrel/Core/test/Http1ConnectionTestsBase.cs b/src/Servers/Kestrel/Core/test/Http1/Http1ConnectionTestsBase.cs similarity index 100% rename from src/Servers/Kestrel/Core/test/Http1ConnectionTestsBase.cs rename to src/Servers/Kestrel/Core/test/Http1/Http1ConnectionTestsBase.cs diff --git a/src/Servers/Kestrel/Core/test/Http1HttpProtocolFeatureCollectionTests.cs b/src/Servers/Kestrel/Core/test/Http1/Http1HttpProtocolFeatureCollectionTests.cs similarity index 100% rename from src/Servers/Kestrel/Core/test/Http1HttpProtocolFeatureCollectionTests.cs rename to src/Servers/Kestrel/Core/test/Http1/Http1HttpProtocolFeatureCollectionTests.cs diff --git a/src/Servers/Kestrel/Core/test/Http1OutputProducerTests.cs b/src/Servers/Kestrel/Core/test/Http1/Http1OutputProducerTests.cs similarity index 100% rename from src/Servers/Kestrel/Core/test/Http1OutputProducerTests.cs rename to src/Servers/Kestrel/Core/test/Http1/Http1OutputProducerTests.cs diff --git a/src/Servers/Kestrel/Core/test/Http2FrameWriterTests.cs b/src/Servers/Kestrel/Core/test/Http2/Http2FrameWriterTests.cs similarity index 100% rename from src/Servers/Kestrel/Core/test/Http2FrameWriterTests.cs rename to src/Servers/Kestrel/Core/test/Http2/Http2FrameWriterTests.cs diff --git a/src/Servers/Kestrel/Core/test/Http2HPackEncoderTests.cs b/src/Servers/Kestrel/Core/test/Http2/Http2HPackEncoderTests.cs similarity index 100% rename from src/Servers/Kestrel/Core/test/Http2HPackEncoderTests.cs rename to src/Servers/Kestrel/Core/test/Http2/Http2HPackEncoderTests.cs diff --git a/src/Servers/Kestrel/Core/test/Http2HeadersEnumeratorTests.cs b/src/Servers/Kestrel/Core/test/Http2/Http2HeadersEnumeratorTests.cs similarity index 100% rename from src/Servers/Kestrel/Core/test/Http2HeadersEnumeratorTests.cs rename to src/Servers/Kestrel/Core/test/Http2/Http2HeadersEnumeratorTests.cs diff --git a/src/Servers/Kestrel/Core/test/Http2HttpProtocolFeatureCollectionTests.cs b/src/Servers/Kestrel/Core/test/Http2/Http2HttpProtocolFeatureCollectionTests.cs similarity index 100% rename from src/Servers/Kestrel/Core/test/Http2HttpProtocolFeatureCollectionTests.cs rename to src/Servers/Kestrel/Core/test/Http2/Http2HttpProtocolFeatureCollectionTests.cs diff --git a/src/Servers/Kestrel/Core/test/Http3FrameWriterTests.cs b/src/Servers/Kestrel/Core/test/Http3/Http3FrameWriterTests.cs similarity index 100% rename from src/Servers/Kestrel/Core/test/Http3FrameWriterTests.cs rename to src/Servers/Kestrel/Core/test/Http3/Http3FrameWriterTests.cs diff --git a/src/Servers/Kestrel/Core/test/Http3HeadersEnumeratorTests.cs b/src/Servers/Kestrel/Core/test/Http3/Http3HeadersEnumeratorTests.cs similarity index 82% rename from src/Servers/Kestrel/Core/test/Http3HeadersEnumeratorTests.cs rename to src/Servers/Kestrel/Core/test/Http3/Http3HeadersEnumeratorTests.cs index c419ffbd3450..e2f3b242fc54 100644 --- a/src/Servers/Kestrel/Core/test/Http3HeadersEnumeratorTests.cs +++ b/src/Servers/Kestrel/Core/test/Http3/Http3HeadersEnumeratorTests.cs @@ -38,17 +38,17 @@ public void CanIterateOverResponseHeaders() Assert.Equal(new[] { - CreateHeaderResult(-1, "Date", "Date!"), - CreateHeaderResult(-1, "Accept-Ranges", "AcceptRanges!"), - CreateHeaderResult(-1, "Age", "1"), - CreateHeaderResult(-1, "Age", "2"), - CreateHeaderResult(-1, "Grpc-Encoding", "Identity!"), - CreateHeaderResult(-1, "Content-Length", "9"), - CreateHeaderResult(-1, "Name1", "Value1"), - CreateHeaderResult(-1, "Name2", "Value2-1"), - CreateHeaderResult(-1, "Name2", "Value2-2"), - CreateHeaderResult(-1, "Name3", "Value3"), - }, headers); + CreateHeaderResult(6, "Date", "Date!"), + CreateHeaderResult(32, "Accept-Ranges", "AcceptRanges!"), + CreateHeaderResult(2, "Age", "1"), + CreateHeaderResult(2, "Age", "2"), + CreateHeaderResult(-1, "Grpc-Encoding", "Identity!"), + CreateHeaderResult(4, "Content-Length", "9"), + CreateHeaderResult(-1, "Name1", "Value1"), + CreateHeaderResult(-1, "Name2", "Value2-1"), + CreateHeaderResult(-1, "Name2", "Value2-2"), + CreateHeaderResult(-1, "Name3", "Value3"), + }, headers); } [Fact] @@ -71,12 +71,12 @@ public void CanIterateOverResponseTrailers() Assert.Equal(new[] { - CreateHeaderResult(-1, "ETag", "ETag!"), - CreateHeaderResult(-1, "Name1", "Value1"), - CreateHeaderResult(-1, "Name2", "Value2-1"), - CreateHeaderResult(-1, "Name2", "Value2-2"), - CreateHeaderResult(-1, "Name3", "Value3"), - }, headers); + CreateHeaderResult(7, "ETag", "ETag!"), + CreateHeaderResult(-1, "Name1", "Value1"), + CreateHeaderResult(-1, "Name2", "Value2-1"), + CreateHeaderResult(-1, "Name2", "Value2-2"), + CreateHeaderResult(-1, "Name3", "Value3"), + }, headers); } [Fact] diff --git a/src/Servers/Kestrel/Core/test/Http3HttpProtocolFeatureCollectionTests.cs b/src/Servers/Kestrel/Core/test/Http3/Http3HttpProtocolFeatureCollectionTests.cs similarity index 100% rename from src/Servers/Kestrel/Core/test/Http3HttpProtocolFeatureCollectionTests.cs rename to src/Servers/Kestrel/Core/test/Http3/Http3HttpProtocolFeatureCollectionTests.cs diff --git a/src/Servers/Kestrel/Core/test/Http3/Http3QPackEncoderTests.cs b/src/Servers/Kestrel/Core/test/Http3/Http3QPackEncoderTests.cs new file mode 100644 index 000000000000..fa908a583f41 --- /dev/null +++ b/src/Servers/Kestrel/Core/test/Http3/Http3QPackEncoderTests.cs @@ -0,0 +1,660 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests; + +public class Http3QPackEncoderTests +{ + [Fact] + public void BeginEncodeHeaders_StatusWithoutIndexedValue_WriteIndexNameAndFullValue() + { + Span buffer = new byte[1024 * 16]; + + var totalHeaderSize = 0; + var headers = new HttpResponseHeaders(); + var enumerator = new Http3HeadersEnumerator(); + enumerator.Initialize(headers); + + Assert.True(QPackHeaderWriter.BeginEncodeHeaders(302, enumerator, buffer, ref totalHeaderSize, out var length)); + + var result = buffer.Slice(0, length).ToArray(); + var hex = BitConverter.ToString(result); + Assert.Equal("00-00-5F-30-03-33-30-32", hex); + } + + [Fact] + public void BeginEncodeHeaders_StatusWithIndexedValue_WriteIndex() + { + Span buffer = new byte[1024 * 16]; + + var totalHeaderSize = 0; + var headers = new HttpResponseHeaders(); + var enumerator = new Http3HeadersEnumerator(); + enumerator.Initialize(headers); + + Assert.True(QPackHeaderWriter.BeginEncodeHeaders(200, enumerator, buffer, ref totalHeaderSize, out var length)); + + var result = buffer.Slice(0, length).ToArray(); + var hex = BitConverter.ToString(result); + Assert.Equal("00-00-D9", hex); + } + + [Fact] + public void BeginEncodeHeaders_NonStaticKey_WriteFullNameAndFullValue() + { + Span buffer = new byte[1024 * 16]; + + var headers = (IHeaderDictionary)new HttpResponseHeaders(); + headers.Translate = "private"; + + var totalHeaderSize = 0; + var enumerator = new Http3HeadersEnumerator(); + enumerator.Initialize(headers); + + Assert.True(QPackHeaderWriter.BeginEncodeHeaders(302, enumerator, buffer, ref totalHeaderSize, out var length)); + + var result = buffer.Slice(8, length - 8).ToArray(); + var hex = BitConverter.ToString(result); + Assert.Equal("37-02-74-72-61-6E-73-6C-61-74-65-07-70-72-69-76-61-74-65", hex); + } + + [Fact] + public void BeginEncodeHeaders_NoStatus_NonStaticKey_WriteFullNameAndFullValue() + { + Span buffer = new byte[1024 * 16]; + + var headers = (IHeaderDictionary)new HttpResponseHeaders(); + headers.Translate = "private"; + + var totalHeaderSize = 0; + var enumerator = new Http3HeadersEnumerator(); + enumerator.Initialize(headers); + + Assert.True(QPackHeaderWriter.BeginEncodeHeaders(enumerator, buffer, ref totalHeaderSize, out var length)); + + var result = buffer.Slice(2, length - 2).ToArray(); + var hex = BitConverter.ToString(result); + Assert.Equal("37-02-74-72-61-6E-73-6C-61-74-65-07-70-72-69-76-61-74-65", hex); + } + + //[Fact] + //public void BeginEncodeHeaders_MaxHeaderTableSizeExceeded_EvictionsToFit() + //{ + // // Test follows example https://tools.ietf.org/html/rfc7541#appendix-C.5 + + // Span 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); + //} + + //[Fact] + //public void BeginEncodeHeadersCustomEncoding_MaxHeaderTableSizeExceeded_EvictionsToFit() + //{ + // // Test follows example https://tools.ietf.org/html/rfc7541#appendix-C.5 + + // Span 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.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-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.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("你好你好你好你.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.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("你好你好你好你.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 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)); + //} + + //[Fact] + //public void BeginEncodeHeaders_HeaderExceedHeaderTableSize_NoIndexAndNoHeaderEntry() + //{ + // Span 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)); + //} + + //public static TheoryData[], byte[], int?> SinglePayloadData + //{ + // get + // { + // var data = new TheoryData[], byte[], int?>(); + + // // Lowercase header name letters only + // data.Add( + // new[] + // { + // new KeyValuePair("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("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("date", "Mon, 24 Jul 2017 19:22:30 GMT"), + // new KeyValuePair("content-type", "text/html; charset=utf-8"), + // new KeyValuePair("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[] 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(payload, 0, length)); + //} + + //[Theory] + //[InlineData(true)] + //[InlineData(false)] + //public void EncodesHeadersInMultiplePayloadsWhenSpaceNotAvailable(bool exactSize) + //{ + // var statusCode = 200; + // var headers = new[] + // { + // new KeyValuePair("date", "Mon, 24 Jul 2017 19:22:30 GMT"), + // new KeyValuePair("content-type", "text/html; charset=utf-8"), + // new KeyValuePair("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 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()); + //} + + //[Fact] + //public void BeginEncodeHeaders_MaxHeaderTableSizeUpdated_SizeUpdateInHeaders() + //{ + // Span buffer = new byte[1024 * 16]; + + // var hpackEncoder = new DynamicHPackEncoder(); + // hpackEncoder.UpdateMaxHeaderTableSize(100); + + // var enumerator = new Http2HeadersEnumerator(); + + // // First request + // enumerator.Initialize(new Dictionary()); + // 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()); + // Assert.True(HPackHeaderWriter.BeginEncodeHeaders(hpackEncoder, enumerator, buffer, out length)); + + // Assert.Equal(0, length); + //} + + //private static Http2HeadersEnumerator GetHeadersEnumerator(IEnumerable> 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 GetHeaderEntries(DynamicHPackEncoder encoder) + //{ + // var headers = new List(); + + // var entry = encoder.Head; + // while (entry.Before != encoder.Head) + // { + // entry = entry.Before; + // headers.Add(entry); + // }; + + // return headers; + //} +} diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/QPackDecoderBenchmark.cs b/src/Servers/Kestrel/perf/Microbenchmarks/QPackDecoderBenchmark.cs new file mode 100644 index 000000000000..e459add05441 --- /dev/null +++ b/src/Servers/Kestrel/perf/Microbenchmarks/QPackDecoderBenchmark.cs @@ -0,0 +1,127 @@ +// 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.QPack; +using System.Text; +using BenchmarkDotNet.Attributes; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; + +namespace Microsoft.AspNetCore.Server.Kestrel.Microbenchmarks; + +public class QPackDecoderBenchmark +{ + // Indexed Header Field Representation - Dynamic Table - Index 62 (first index in dynamic table) + private static readonly byte[] _indexedHeaderDynamic = new byte[] { 0xbe }; + + private static readonly byte[] _literalHeaderFieldWithoutIndexingNewName = new byte[] { 0x00 }; + + private const string _headerNameString = "new-header"; + + private static readonly byte[] _headerNameBytes = Encoding.ASCII.GetBytes(_headerNameString); + + private static readonly byte[] _headerName = new byte[] { (byte)_headerNameBytes.Length } + .Concat(_headerNameBytes) + .ToArray(); + + private const string _headerValueString = "value"; + + private static readonly byte[] _headerValueBytes = Encoding.ASCII.GetBytes(_headerValueString); + + private static readonly byte[] _headerValue = new byte[] { (byte)_headerValueBytes.Length } + .Concat(_headerValueBytes) + .ToArray(); + + private static readonly byte[] _literalHeaderFieldNeverIndexed_NewName = _literalHeaderFieldWithoutIndexingNewName + .Concat(_headerName) + .Concat(_headerValue) + .ToArray(); + + private static readonly byte[] _literalHeaderFieldNeverIndexed_NewName_Large; + private static readonly byte[] _literalHeaderFieldNeverIndexed_NewName_Multiple; + private static readonly byte[] _indexedHeaderDynamic_Multiple; + + static QPackDecoderBenchmark() + { + string string8193 = new string('a', 8193); + + _literalHeaderFieldNeverIndexed_NewName_Large = _literalHeaderFieldWithoutIndexingNewName + .Concat(new byte[] { 0x7f, 0x82, 0x3f }) // 8193 encoded with 7-bit prefix, no Huffman encoding + .Concat(Encoding.ASCII.GetBytes(string8193)) + .Concat(new byte[] { 0x7f, 0x82, 0x3f }) // 8193 encoded with 7-bit prefix, no Huffman encoding + .Concat(Encoding.ASCII.GetBytes(string8193)) + .ToArray(); + + _literalHeaderFieldNeverIndexed_NewName_Multiple = _literalHeaderFieldNeverIndexed_NewName + .Concat(_literalHeaderFieldNeverIndexed_NewName) + .Concat(_literalHeaderFieldNeverIndexed_NewName) + .Concat(_literalHeaderFieldNeverIndexed_NewName) + .Concat(_literalHeaderFieldNeverIndexed_NewName) + .ToArray(); + + _indexedHeaderDynamic_Multiple = _indexedHeaderDynamic + .Concat(_indexedHeaderDynamic) + .Concat(_indexedHeaderDynamic) + .Concat(_indexedHeaderDynamic) + .Concat(_indexedHeaderDynamic) + .ToArray(); + } + + private QPackDecoder _decoder; + private TestHeadersHandler _testHeadersHandler; + + [GlobalSetup] + public void GlobalSetup() + { + _decoder = new QPackDecoder(maxHeadersLength: 65536); + _testHeadersHandler = new TestHeadersHandler(); + } + + [Benchmark] + public void DecodesLiteralHeaderFieldNeverIndexed_NewName() + { + _decoder.Decode(_literalHeaderFieldNeverIndexed_NewName, endHeaders: true, handler: _testHeadersHandler); + } + + [Benchmark] + public void DecodesLiteralHeaderFieldNeverIndexed_NewName_Large() + { + _decoder.Decode(_literalHeaderFieldNeverIndexed_NewName_Large, endHeaders: true, handler: _testHeadersHandler); + } + + [Benchmark] + public void DecodesLiteralHeaderFieldNeverIndexed_NewName_Multiple() + { + _decoder.Decode(_literalHeaderFieldNeverIndexed_NewName_Multiple, endHeaders: true, handler: _testHeadersHandler); + } + + //[Benchmark] + //public void DecodesIndexedHeaderField_DynamicTable() + //{ + // _decoder.Decode(_indexedHeaderDynamic, endHeaders: true, handler: _testHeadersHandler); + //} + + //[Benchmark] + //public void DecodesIndexedHeaderField_DynamicTable_Multiple() + //{ + // _decoder.Decode(_indexedHeaderDynamic_Multiple, endHeaders: true, handler: _testHeadersHandler); + //} + + private class TestHeadersHandler : IHttpHeadersHandler + { + public void OnHeader(ReadOnlySpan name, ReadOnlySpan value) + { + } + + public void OnHeadersComplete(bool endStream) + { + } + + public void OnStaticIndexedHeader(int index) + { + } + + public void OnStaticIndexedHeader(int index, ReadOnlySpan value) + { + } + } +} diff --git a/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs b/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs index 91c29266ba95..68679be7f2c7 100644 --- a/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs +++ b/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs @@ -637,7 +637,7 @@ public async Task SendHeadersAsync(Http3HeadersEnumerator headers, bool endStrea var headersTotalSize = 0; var buffer = _headerHandler.HeaderEncodingBuffer.AsMemory(); - var done = QPackHeaderWriter.BeginEncode(headers, buffer.Span, ref headersTotalSize, out var length); + var done = QPackHeaderWriter.BeginEncodeHeaders(headers, buffer.Span, ref headersTotalSize, out var length); if (!done) { throw new InvalidOperationException("Headers not sent."); @@ -676,7 +676,7 @@ internal async ValueTask> ExpectHeadersAsync(bool exp Http3InMemory.AssertFrameType(http3WithPayload.Type, Http3FrameType.Headers); _headerHandler.DecodedHeaders.Clear(); - _headerHandler.QpackDecoder.Decode(http3WithPayload.PayloadSequence, this); + _headerHandler.QpackDecoder.Decode(http3WithPayload.PayloadSequence, endHeaders: true, this); _headerHandler.QpackDecoder.Reset(); return _headerHandler.DecodedHeaders.ToDictionary(kvp => kvp.Key, kvp => kvp.Value, _headerHandler.DecodedHeaders.Comparer); } @@ -693,7 +693,7 @@ internal async ValueTask> ExpectTrailersAsync() Http3InMemory.AssertFrameType(http3WithPayload.Type, Http3FrameType.Headers); _headerHandler.DecodedHeaders.Clear(); - _headerHandler.QpackDecoder.Decode(http3WithPayload.PayloadSequence, this); + _headerHandler.QpackDecoder.Decode(http3WithPayload.PayloadSequence, endHeaders: true, this); _headerHandler.QpackDecoder.Reset(); return _headerHandler.DecodedHeaders.ToDictionary(kvp => kvp.Key, kvp => kvp.Value, _headerHandler.DecodedHeaders.Comparer); } diff --git a/src/Shared/runtime/Http3/QPack/QPackDecoder.cs b/src/Shared/runtime/Http3/QPack/QPackDecoder.cs index 77a3baa8a3de..8488d0a07132 100644 --- a/src/Shared/runtime/Http3/QPack/QPackDecoder.cs +++ b/src/Shared/runtime/Http3/QPack/QPackDecoder.cs @@ -176,15 +176,15 @@ public void Reset() _state = State.RequiredInsertCount; } - public void Decode(in ReadOnlySequence headerBlock, IHttpHeadersHandler handler) + public void Decode(in ReadOnlySequence headerBlock, bool endHeaders, IHttpHeadersHandler handler) { foreach (ReadOnlyMemory segment in headerBlock) { - Decode(segment.Span, handler); + Decode(segment.Span, endHeaders: false, handler); } } - public void Decode(ReadOnlySpan headerBlock, IHttpHeadersHandler handler) + public void Decode(ReadOnlySpan headerBlock, bool endHeaders, IHttpHeadersHandler handler) { foreach (byte b in headerBlock) { From 37c4c2e1418e75ffbe52d1570e4f9540e8584c0f Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Mon, 22 Nov 2021 21:52:37 +1300 Subject: [PATCH 2/3] Clean up --- .../Core/test/Http3/Http3QPackEncoderTests.cs | 577 ------------------ 1 file changed, 577 deletions(-) diff --git a/src/Servers/Kestrel/Core/test/Http3/Http3QPackEncoderTests.cs b/src/Servers/Kestrel/Core/test/Http3/Http3QPackEncoderTests.cs index fa908a583f41..5b99147615b2 100644 --- a/src/Servers/Kestrel/Core/test/Http3/Http3QPackEncoderTests.cs +++ b/src/Servers/Kestrel/Core/test/Http3/Http3QPackEncoderTests.cs @@ -80,581 +80,4 @@ public void BeginEncodeHeaders_NoStatus_NonStaticKey_WriteFullNameAndFullValue() var hex = BitConverter.ToString(result); Assert.Equal("37-02-74-72-61-6E-73-6C-61-74-65-07-70-72-69-76-61-74-65", hex); } - - //[Fact] - //public void BeginEncodeHeaders_MaxHeaderTableSizeExceeded_EvictionsToFit() - //{ - // // Test follows example https://tools.ietf.org/html/rfc7541#appendix-C.5 - - // Span 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); - //} - - //[Fact] - //public void BeginEncodeHeadersCustomEncoding_MaxHeaderTableSizeExceeded_EvictionsToFit() - //{ - // // Test follows example https://tools.ietf.org/html/rfc7541#appendix-C.5 - - // Span 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.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-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.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("你好你好你好你.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.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("你好你好你好你.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 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)); - //} - - //[Fact] - //public void BeginEncodeHeaders_HeaderExceedHeaderTableSize_NoIndexAndNoHeaderEntry() - //{ - // Span 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)); - //} - - //public static TheoryData[], byte[], int?> SinglePayloadData - //{ - // get - // { - // var data = new TheoryData[], byte[], int?>(); - - // // Lowercase header name letters only - // data.Add( - // new[] - // { - // new KeyValuePair("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("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("date", "Mon, 24 Jul 2017 19:22:30 GMT"), - // new KeyValuePair("content-type", "text/html; charset=utf-8"), - // new KeyValuePair("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[] 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(payload, 0, length)); - //} - - //[Theory] - //[InlineData(true)] - //[InlineData(false)] - //public void EncodesHeadersInMultiplePayloadsWhenSpaceNotAvailable(bool exactSize) - //{ - // var statusCode = 200; - // var headers = new[] - // { - // new KeyValuePair("date", "Mon, 24 Jul 2017 19:22:30 GMT"), - // new KeyValuePair("content-type", "text/html; charset=utf-8"), - // new KeyValuePair("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 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()); - //} - - //[Fact] - //public void BeginEncodeHeaders_MaxHeaderTableSizeUpdated_SizeUpdateInHeaders() - //{ - // Span buffer = new byte[1024 * 16]; - - // var hpackEncoder = new DynamicHPackEncoder(); - // hpackEncoder.UpdateMaxHeaderTableSize(100); - - // var enumerator = new Http2HeadersEnumerator(); - - // // First request - // enumerator.Initialize(new Dictionary()); - // 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()); - // Assert.True(HPackHeaderWriter.BeginEncodeHeaders(hpackEncoder, enumerator, buffer, out length)); - - // Assert.Equal(0, length); - //} - - //private static Http2HeadersEnumerator GetHeadersEnumerator(IEnumerable> 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 GetHeaderEntries(DynamicHPackEncoder encoder) - //{ - // var headers = new List(); - - // var entry = encoder.Head; - // while (entry.Before != encoder.Head) - // { - // entry = entry.Before; - // headers.Add(entry); - // }; - - // return headers; - //} } From 8c8443dfa36c68ba58d0880e4aec6d1f67081142 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Mon, 22 Nov 2021 21:53:16 +1300 Subject: [PATCH 3/3] Clean up --- .../Microbenchmarks/QPackDecoderBenchmark.cs | 127 ------------------ 1 file changed, 127 deletions(-) delete mode 100644 src/Servers/Kestrel/perf/Microbenchmarks/QPackDecoderBenchmark.cs diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/QPackDecoderBenchmark.cs b/src/Servers/Kestrel/perf/Microbenchmarks/QPackDecoderBenchmark.cs deleted file mode 100644 index e459add05441..000000000000 --- a/src/Servers/Kestrel/perf/Microbenchmarks/QPackDecoderBenchmark.cs +++ /dev/null @@ -1,127 +0,0 @@ -// 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.QPack; -using System.Text; -using BenchmarkDotNet.Attributes; -using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; - -namespace Microsoft.AspNetCore.Server.Kestrel.Microbenchmarks; - -public class QPackDecoderBenchmark -{ - // Indexed Header Field Representation - Dynamic Table - Index 62 (first index in dynamic table) - private static readonly byte[] _indexedHeaderDynamic = new byte[] { 0xbe }; - - private static readonly byte[] _literalHeaderFieldWithoutIndexingNewName = new byte[] { 0x00 }; - - private const string _headerNameString = "new-header"; - - private static readonly byte[] _headerNameBytes = Encoding.ASCII.GetBytes(_headerNameString); - - private static readonly byte[] _headerName = new byte[] { (byte)_headerNameBytes.Length } - .Concat(_headerNameBytes) - .ToArray(); - - private const string _headerValueString = "value"; - - private static readonly byte[] _headerValueBytes = Encoding.ASCII.GetBytes(_headerValueString); - - private static readonly byte[] _headerValue = new byte[] { (byte)_headerValueBytes.Length } - .Concat(_headerValueBytes) - .ToArray(); - - private static readonly byte[] _literalHeaderFieldNeverIndexed_NewName = _literalHeaderFieldWithoutIndexingNewName - .Concat(_headerName) - .Concat(_headerValue) - .ToArray(); - - private static readonly byte[] _literalHeaderFieldNeverIndexed_NewName_Large; - private static readonly byte[] _literalHeaderFieldNeverIndexed_NewName_Multiple; - private static readonly byte[] _indexedHeaderDynamic_Multiple; - - static QPackDecoderBenchmark() - { - string string8193 = new string('a', 8193); - - _literalHeaderFieldNeverIndexed_NewName_Large = _literalHeaderFieldWithoutIndexingNewName - .Concat(new byte[] { 0x7f, 0x82, 0x3f }) // 8193 encoded with 7-bit prefix, no Huffman encoding - .Concat(Encoding.ASCII.GetBytes(string8193)) - .Concat(new byte[] { 0x7f, 0x82, 0x3f }) // 8193 encoded with 7-bit prefix, no Huffman encoding - .Concat(Encoding.ASCII.GetBytes(string8193)) - .ToArray(); - - _literalHeaderFieldNeverIndexed_NewName_Multiple = _literalHeaderFieldNeverIndexed_NewName - .Concat(_literalHeaderFieldNeverIndexed_NewName) - .Concat(_literalHeaderFieldNeverIndexed_NewName) - .Concat(_literalHeaderFieldNeverIndexed_NewName) - .Concat(_literalHeaderFieldNeverIndexed_NewName) - .ToArray(); - - _indexedHeaderDynamic_Multiple = _indexedHeaderDynamic - .Concat(_indexedHeaderDynamic) - .Concat(_indexedHeaderDynamic) - .Concat(_indexedHeaderDynamic) - .Concat(_indexedHeaderDynamic) - .ToArray(); - } - - private QPackDecoder _decoder; - private TestHeadersHandler _testHeadersHandler; - - [GlobalSetup] - public void GlobalSetup() - { - _decoder = new QPackDecoder(maxHeadersLength: 65536); - _testHeadersHandler = new TestHeadersHandler(); - } - - [Benchmark] - public void DecodesLiteralHeaderFieldNeverIndexed_NewName() - { - _decoder.Decode(_literalHeaderFieldNeverIndexed_NewName, endHeaders: true, handler: _testHeadersHandler); - } - - [Benchmark] - public void DecodesLiteralHeaderFieldNeverIndexed_NewName_Large() - { - _decoder.Decode(_literalHeaderFieldNeverIndexed_NewName_Large, endHeaders: true, handler: _testHeadersHandler); - } - - [Benchmark] - public void DecodesLiteralHeaderFieldNeverIndexed_NewName_Multiple() - { - _decoder.Decode(_literalHeaderFieldNeverIndexed_NewName_Multiple, endHeaders: true, handler: _testHeadersHandler); - } - - //[Benchmark] - //public void DecodesIndexedHeaderField_DynamicTable() - //{ - // _decoder.Decode(_indexedHeaderDynamic, endHeaders: true, handler: _testHeadersHandler); - //} - - //[Benchmark] - //public void DecodesIndexedHeaderField_DynamicTable_Multiple() - //{ - // _decoder.Decode(_indexedHeaderDynamic_Multiple, endHeaders: true, handler: _testHeadersHandler); - //} - - private class TestHeadersHandler : IHttpHeadersHandler - { - public void OnHeader(ReadOnlySpan name, ReadOnlySpan value) - { - } - - public void OnHeadersComplete(bool endStream) - { - } - - public void OnStaticIndexedHeader(int index) - { - } - - public void OnStaticIndexedHeader(int index, ReadOnlySpan value) - { - } - } -}