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..5b99147615b2 --- /dev/null +++ b/src/Servers/Kestrel/Core/test/Http3/Http3QPackEncoderTests.cs @@ -0,0 +1,83 @@ +// 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); + } +} 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) {