Skip to content

The runtime<->aspnetcore shared src is out of sync #18943

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Tratcher opened this issue Feb 10, 2020 · 182 comments · Fixed by #19062, #19206, #19314, #19553 or #19636
Closed

The runtime<->aspnetcore shared src is out of sync #18943

Tratcher opened this issue Feb 10, 2020 · 182 comments · Fixed by #19062, #19206, #19314, #19553 or #19636
Assignees
Labels
area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions Bot: Do Not Lock Indicates that the bot should not lock this issue due to inactivity. feature-kestrel task

Comments

@Tratcher
Copy link
Member

Tratcher commented Feb 10, 2020

This is a tracking issue for when the shared src gets out of sync with it's runtime counterpart. See the ReadMe for details.

There is a github action that runs daily at https://git.1-hub.cndotnet/aspnetcore/actions and will re-open and comment on this bug if it detects any differences in the files shared between the repos. AspNetCore will triage and assign this issue to get the differences resolved.

@analogrelay
Copy link
Contributor

We are currently in-sync.

@github-actions
Copy link
Contributor

The shared code is out of sync.

The Diff
On branch master
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   src/Shared/runtime/CopyToAspNetCore.cmd
	modified:   src/Shared/runtime/CopyToRuntime.cmd

no changes added to commit (use "git add" and/or "git commit -a")

diff --git a/src/Shared/runtime/CopyToAspNetCore.cmd b/src/Shared/runtime/CopyToAspNetCore.cmd
index 43eb681..65a341b 100644
--- a/src/Shared/runtime/CopyToAspNetCore.cmd
+++ b/src/Shared/runtime/CopyToAspNetCore.cmd
@@ -10,5 +10,6 @@ IF [%remote_repo%] == [] (
 
 echo ASPNETCORE_REPO: %remote_repo%
 
-robocopy . %remote_repo%\src\Shared\runtime /MIR
-robocopy .\..\..\..\..\..\tests\Tests\System\Net\aspnetcore\ %remote_repo%\src\Shared\test\Shared.Tests\runtime /MIR
+REM https://superuser.com/questions/280425/getting-robocopy-to-return-a-proper-exit-code
+(robocopy . %remote_repo%\src\Shared\runtime /MIR) ^& IF %ERRORLEVEL% LSS 8 SET ERRORLEVEL = 0
+(robocopy .\..\..\..\..\..\tests\Tests\System\Net\aspnetcore\ %remote_repo%\src\Shared\test\Shared.Tests\runtime /MIR) ^& IF %ERRORLEVEL% LSS 8 SET ERRORLEVEL = 0
diff --git a/src/Shared/runtime/CopyToRuntime.cmd b/src/Shared/runtime/CopyToRuntime.cmd
index 7f9e5b1..f1cb32d 100644
--- a/src/Shared/runtime/CopyToRuntime.cmd
+++ b/src/Shared/runtime/CopyToRuntime.cmd
@@ -10,5 +10,6 @@ IF [%remote_repo%] == [] (
 
 echo RUNTIME_REPO: %remote_repo%
 
-robocopy . %remote_repo%\src\libraries\Common\src\System\Net\Http\aspnetcore /MIR
-robocopy .\..\test\Shared.Tests\runtime %remote_repo%\src\libraries\Common\tests\Tests\System\Net\aspnetcore /MIR
+REM https://superuser.com/questions/280425/getting-robocopy-to-return-a-proper-exit-code
+(robocopy . %remote_repo%\src\libraries\Common\src\System\Net\Http\aspnetcore /MIR) ^& IF %ERRORLEVEL% LSS 8 SET ERRORLEVEL = 0
+(robocopy .\..\test\Shared.Tests\runtime %remote_repo%\src\libraries\Common\tests\Tests\System\Net\aspnetcore /MIR) ^& IF %ERRORLEVEL% LSS 8 SET ERRORLEVEL = 0

@ghost ghost closed this as completed in #19062 Feb 14, 2020
ghost pushed a commit that referenced this issue Feb 14, 2020
* Sync script changes from the runtime repo #18943

* Additional change
@github-actions github-actions bot reopened this Feb 19, 2020
@github-actions
Copy link
Contributor

The shared code is out of sync.

The Diff
On branch master
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   src/Shared/runtime/Http3/QPack/QPackDecoder.cs

no changes added to commit (use "git add" and/or "git commit -a")

diff --git a/src/Shared/runtime/Http3/QPack/QPackDecoder.cs b/src/Shared/runtime/Http3/QPack/QPackDecoder.cs
index 6f63d66..958dfac 100644
--- a/src/Shared/runtime/Http3/QPack/QPackDecoder.cs
+++ b/src/Shared/runtime/Http3/QPack/QPackDecoder.cs
@@ -269,6 +269,10 @@ namespace System.Net.Http.QPack
 
                             if (_integerDecoder.BeginTryDecode((byte)prefixInt, LiteralHeaderFieldWithoutNameReferencePrefix, out intResult))
                             {
+                                if (intResult == 0)
+                                {
+                                    throw new QPackDecodingException(SR.Format(SR.net_http_invalid_header_name, ""));
+                                }
                                 OnStringLength(intResult, State.HeaderName);
                             }
                             else
@@ -303,6 +307,10 @@ namespace System.Net.Http.QPack
                 case State.HeaderNameLength:
                     if (_integerDecoder.TryDecode(b, out intResult))
                     {
+                        if (intResult == 0)
+                        {
+                            throw new QPackDecodingException(SR.Format(SR.net_http_invalid_header_name, ""));
+                        }
                         OnStringLength(intResult, nextState: State.HeaderName);
                     }
                     break;

@github-actions
Copy link
Contributor

The shared code is out of sync.

The Diff
On branch master
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   src/Shared/runtime/Http3/QPack/QPackDecoder.cs

no changes added to commit (use "git add" and/or "git commit -a")

diff --git a/src/Shared/runtime/Http3/QPack/QPackDecoder.cs b/src/Shared/runtime/Http3/QPack/QPackDecoder.cs
index 6f63d66..958dfac 100644
--- a/src/Shared/runtime/Http3/QPack/QPackDecoder.cs
+++ b/src/Shared/runtime/Http3/QPack/QPackDecoder.cs
@@ -269,6 +269,10 @@ namespace System.Net.Http.QPack
 
                             if (_integerDecoder.BeginTryDecode((byte)prefixInt, LiteralHeaderFieldWithoutNameReferencePrefix, out intResult))
                             {
+                                if (intResult == 0)
+                                {
+                                    throw new QPackDecodingException(SR.Format(SR.net_http_invalid_header_name, ""));
+                                }
                                 OnStringLength(intResult, State.HeaderName);
                             }
                             else
@@ -303,6 +307,10 @@ namespace System.Net.Http.QPack
                 case State.HeaderNameLength:
                     if (_integerDecoder.TryDecode(b, out intResult))
                     {
+                        if (intResult == 0)
+                        {
+                            throw new QPackDecodingException(SR.Format(SR.net_http_invalid_header_name, ""));
+                        }
                         OnStringLength(intResult, nextState: State.HeaderName);
                     }
                     break;

@ghost ghost closed this as completed in #19206 Feb 21, 2020
@github-actions github-actions bot reopened this Feb 22, 2020
@github-actions
Copy link
Contributor

The shared code is out of sync.

The Diff
On branch master
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicStream.cs

no changes added to commit (use "git add" and/or "git commit -a")

diff --git a/src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicStream.cs b/src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicStream.cs
index 536968b..00ca779 100644
--- a/src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicStream.cs
+++ b/src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicStream.cs
@@ -402,7 +402,7 @@ namespace System.Net.Quic.Implementations.MsQuic
         {
             ThrowIfDisposed();
 
-            return ReadAsync(buffer.ToArray()).GetAwaiter().GetResult();
+            return ReadAsync(buffer.ToArray()).AsTask().GetAwaiter().GetResult();
         }
 
         internal override void Write(ReadOnlySpan<byte> buffer)
@@ -410,7 +410,7 @@ namespace System.Net.Quic.Implementations.MsQuic
             ThrowIfDisposed();
 
             // TODO: optimize this.
-            WriteAsync(buffer.ToArray()).GetAwaiter().GetResult();
+            WriteAsync(buffer.ToArray()).AsTask().GetAwaiter().GetResult();
         }
 
         // MsQuic doesn't support explicit flushing

2 similar comments
@github-actions
Copy link
Contributor

The shared code is out of sync.

The Diff
On branch master
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicStream.cs

no changes added to commit (use "git add" and/or "git commit -a")

diff --git a/src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicStream.cs b/src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicStream.cs
index 536968b..00ca779 100644
--- a/src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicStream.cs
+++ b/src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicStream.cs
@@ -402,7 +402,7 @@ namespace System.Net.Quic.Implementations.MsQuic
         {
             ThrowIfDisposed();
 
-            return ReadAsync(buffer.ToArray()).GetAwaiter().GetResult();
+            return ReadAsync(buffer.ToArray()).AsTask().GetAwaiter().GetResult();
         }
 
         internal override void Write(ReadOnlySpan<byte> buffer)
@@ -410,7 +410,7 @@ namespace System.Net.Quic.Implementations.MsQuic
             ThrowIfDisposed();
 
             // TODO: optimize this.
-            WriteAsync(buffer.ToArray()).GetAwaiter().GetResult();
+            WriteAsync(buffer.ToArray()).AsTask().GetAwaiter().GetResult();
         }
 
         // MsQuic doesn't support explicit flushing

@github-actions
Copy link
Contributor

The shared code is out of sync.

The Diff
On branch master
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicStream.cs

no changes added to commit (use "git add" and/or "git commit -a")

diff --git a/src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicStream.cs b/src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicStream.cs
index 536968b..00ca779 100644
--- a/src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicStream.cs
+++ b/src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicStream.cs
@@ -402,7 +402,7 @@ namespace System.Net.Quic.Implementations.MsQuic
         {
             ThrowIfDisposed();
 
-            return ReadAsync(buffer.ToArray()).GetAwaiter().GetResult();
+            return ReadAsync(buffer.ToArray()).AsTask().GetAwaiter().GetResult();
         }
 
         internal override void Write(ReadOnlySpan<byte> buffer)
@@ -410,7 +410,7 @@ namespace System.Net.Quic.Implementations.MsQuic
             ThrowIfDisposed();
 
             // TODO: optimize this.
-            WriteAsync(buffer.ToArray()).GetAwaiter().GetResult();
+            WriteAsync(buffer.ToArray()).AsTask().GetAwaiter().GetResult();
         }
 
         // MsQuic doesn't support explicit flushing

@github-actions
Copy link
Contributor

The shared code is out of sync.

The Diff
On branch master
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   src/Shared/runtime/Http3/QPack/QPackEncoder.cs
	modified:   src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs
	modified:   src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicStream.cs

no changes added to commit (use "git add" and/or "git commit -a")

diff --git a/src/Shared/runtime/Http3/QPack/QPackEncoder.cs b/src/Shared/runtime/Http3/QPack/QPackEncoder.cs
index ecf0f1e..348106a 100644
--- a/src/Shared/runtime/Http3/QPack/QPackEncoder.cs
+++ b/src/Shared/runtime/Http3/QPack/QPackEncoder.cs
@@ -340,6 +340,7 @@ namespace System.Net.Http.QPack
             return true;
         }
 
+        // TODO these are fairly hard coded for the first two bytes to be zero.
         public bool BeginEncode(IEnumerable<KeyValuePair<string, string>> headers, Span<byte> buffer, out int length)
         {
             _enumerator = headers.GetEnumerator();
@@ -350,11 +351,7 @@ namespace System.Net.Http.QPack
             buffer[0] = 0;
             buffer[1] = 0;
 
-            bool doneEncode = Encode(buffer.Slice(2), out length);
-
-            // Add two for the first two bytes.
-            length += 2;
-            return doneEncode;
+            return Encode(buffer.Slice(2), out length);
         }
 
         public bool BeginEncode(int statusCode, IEnumerable<KeyValuePair<string, string>> headers, Span<byte> buffer, out int length)
diff --git a/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs b/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs
index fb6330c..30e5cba 100644
--- a/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs
+++ b/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs
@@ -1,4 +1,4 @@
-// Licensed to the .NET Foundation under one or more agreements.
+?// Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
@@ -143,6 +143,12 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal
 
             OperatingSystem ver = Environment.OSVersion;
 
+            if (ver.Platform == PlatformID.Win32NT && ver.Version < new Version(10, 0, 19041, 0))
+            {
+                IsQuicSupported = false;
+                return;
+            }
+
             // TODO: try to initialize TLS 1.3 in SslStream.
 
             try
diff --git a/src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicStream.cs b/src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicStream.cs
index 536968b..00ca779 100644
--- a/src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicStream.cs
+++ b/src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicStream.cs
@@ -402,7 +402,7 @@ namespace System.Net.Quic.Implementations.MsQuic
         {
             ThrowIfDisposed();
 
-            return ReadAsync(buffer.ToArray()).GetAwaiter().GetResult();
+            return ReadAsync(buffer.ToArray()).AsTask().GetAwaiter().GetResult();
         }
 
         internal override void Write(ReadOnlySpan<byte> buffer)
@@ -410,7 +410,7 @@ namespace System.Net.Quic.Implementations.MsQuic
             ThrowIfDisposed();
 
             // TODO: optimize this.
-            WriteAsync(buffer.ToArray()).GetAwaiter().GetResult();
+            WriteAsync(buffer.ToArray()).AsTask().GetAwaiter().GetResult();
         }
 
         // MsQuic doesn't support explicit flushing

@github-actions
Copy link
Contributor

The shared code is out of sync.

The Diff
On branch master
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   src/Shared/runtime/Http3/QPack/QPackEncoder.cs
	modified:   src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs

no changes added to commit (use "git add" and/or "git commit -a")

diff --git a/src/Shared/runtime/Http3/QPack/QPackEncoder.cs b/src/Shared/runtime/Http3/QPack/QPackEncoder.cs
index ecf0f1e..348106a 100644
--- a/src/Shared/runtime/Http3/QPack/QPackEncoder.cs
+++ b/src/Shared/runtime/Http3/QPack/QPackEncoder.cs
@@ -340,6 +340,7 @@ namespace System.Net.Http.QPack
             return true;
         }
 
+        // TODO these are fairly hard coded for the first two bytes to be zero.
         public bool BeginEncode(IEnumerable<KeyValuePair<string, string>> headers, Span<byte> buffer, out int length)
         {
             _enumerator = headers.GetEnumerator();
@@ -350,11 +351,7 @@ namespace System.Net.Http.QPack
             buffer[0] = 0;
             buffer[1] = 0;
 
-            bool doneEncode = Encode(buffer.Slice(2), out length);
-
-            // Add two for the first two bytes.
-            length += 2;
-            return doneEncode;
+            return Encode(buffer.Slice(2), out length);
         }
 
         public bool BeginEncode(int statusCode, IEnumerable<KeyValuePair<string, string>> headers, Span<byte> buffer, out int length)
diff --git a/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs b/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs
index fb6330c..30e5cba 100644
--- a/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs
+++ b/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs
@@ -1,4 +1,4 @@
-// Licensed to the .NET Foundation under one or more agreements.
+?// Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
@@ -143,6 +143,12 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal
 
             OperatingSystem ver = Environment.OSVersion;
 
+            if (ver.Platform == PlatformID.Win32NT && ver.Version < new Version(10, 0, 19041, 0))
+            {
+                IsQuicSupported = false;
+                return;
+            }
+
             // TODO: try to initialize TLS 1.3 in SslStream.
 
             try

Copy link
Contributor

The shared code is out of sync.

The Diff
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   src/Shared/test/Shared.Tests/runtime/Http2/HPackDecoderTest.cs

no changes added to commit (use "git add" and/or "git commit -a")

diff --git a/src/Shared/test/Shared.Tests/runtime/Http2/HPackDecoderTest.cs b/src/Shared/test/Shared.Tests/runtime/Http2/HPackDecoderTest.cs
index ff141b84..28e7a971 100644
--- a/src/Shared/test/Shared.Tests/runtime/Http2/HPackDecoderTest.cs
+++ b/src/Shared/test/Shared.Tests/runtime/Http2/HPackDecoderTest.cs
@@ -87,10 +87,6 @@ namespace System.Net.Http.Unit.Tests.HPack
             .Concat(_headerValueHuffmanBytes)
             .ToArray();
 
-        private static readonly byte[] _literalEmptyString = new byte[] { 0x00 };
-
-        private static readonly byte[] _literalEmptyStringHuffman = new byte[] { 0x80 };
-
         // &        *
         // 11111000 11111111
         private static readonly byte[] _huffmanLongPadding = new byte[] { 0x82, 0xf8, 0xff };
@@ -247,43 +243,6 @@ namespace System.Net.Http.Unit.Tests.HPack
             TestDecodeWithoutIndexing(encoded, _headerNameString, _headerValueString);
         }
 
-        [Fact]
-        public void DecodesLiteralHeaderFieldWithoutIndexing_NewName_EmptyName()
-        {
-            byte[] encoded = _literalHeaderFieldWithoutIndexingNewName
-                .Concat(_literalEmptyString)
-                .Concat(_headerValue)
-                .ToArray();
-
-            HPackDecodingException exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(encoded, endHeaders: true, handler: _handler));
-            Assert.Equal(SR.Format(SR.net_http_invalid_header_name, string.Empty), exception.Message);
-            Assert.Empty(_handler.DecodedHeaders);
-        }
-
-        [Fact]
-        public void DecodesLiteralHeaderFieldWithoutIndexing_NewName_EmptyValue()
-        {
-            byte[] encoded = _literalHeaderFieldWithoutIndexingNewName
-                .Concat(_headerName)
-                .Concat(_literalEmptyString)
-                .ToArray();
-
-            TestDecodeWithoutIndexing(encoded, _headerNameString, string.Empty);
-        }
-
-        [Fact]
-        public void DecodesLiteralHeaderFieldWithoutIndexing_NewName_EmptyNameAndValue()
-        {
-            byte[] encoded = _literalHeaderFieldWithoutIndexingNewName
-                .Concat(_literalEmptyString)
-                .Concat(_literalEmptyString)
-                .ToArray();
-
-            HPackDecodingException exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(encoded, endHeaders: true, handler: _handler));
-            Assert.Equal(SR.Format(SR.net_http_invalid_header_name, string.Empty), exception.Message);
-            Assert.Empty(_handler.DecodedHeaders);
-        }
-
         [Fact]
         public void DecodesLiteralHeaderFieldWithoutIndexing_NewName_HuffmanEncodedName()
         {
@@ -295,19 +254,6 @@ namespace System.Net.Http.Unit.Tests.HPack
             TestDecodeWithoutIndexing(encoded, _headerNameString, _headerValueString);
         }
 
-        [Fact]
-        public void DecodesLiteralHeaderFieldWithoutIndexing_NewName_HuffmanEncodedName_Empty()
-        {
-            byte[] encoded = _literalHeaderFieldWithoutIndexingNewName
-                .Concat(_literalEmptyStringHuffman)
-                .Concat(_headerValue)
-                .ToArray();
-
-            HPackDecodingException exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(encoded, endHeaders: true, handler: _handler));
-            Assert.Equal(SR.Format(SR.net_http_invalid_header_name, string.Empty), exception.Message);
-            Assert.Empty(_handler.DecodedHeaders);
-        }
-
         [Fact]
         public void DecodesLiteralHeaderFieldWithoutIndexing_NewName_HuffmanEncodedValue()
         {
@@ -319,17 +265,6 @@ namespace System.Net.Http.Unit.Tests.HPack
             TestDecodeWithoutIndexing(encoded, _headerNameString, _headerValueString);
         }
 
-        [Fact]
-        public void DecodesLiteralHeaderFieldWithoutIndexing_NewName_HuffmanEncodedValue_Empty()
-        {
-            byte[] encoded = _literalHeaderFieldWithoutIndexingNewName
-                .Concat(_headerName)
-                .Concat(_literalEmptyStringHuffman)
-                .ToArray();
-
-            TestDecodeWithoutIndexing(encoded, _headerNameString, string.Empty);
-        }
-
         [Fact]
         public void DecodesLiteralHeaderFieldWithoutIndexing_NewName_HuffmanEncodedNameAndValue()
         {
@@ -341,19 +276,6 @@ namespace System.Net.Http.Unit.Tests.HPack
             TestDecodeWithoutIndexing(encoded, _headerNameString, _headerValueString);
         }
 
-        [Fact]
-        public void DecodesLiteralHeaderFieldWithoutIndexing_NewName_HuffmanEncodedNameAndValue_Empty()
-        {
-            byte[] encoded = _literalHeaderFieldWithoutIndexingNewName
-                .Concat(_literalEmptyStringHuffman)
-                .Concat(_literalEmptyStringHuffman)
-                .ToArray();
-
-            HPackDecodingException exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(encoded, endHeaders: true, handler: _handler));
-            Assert.Equal(SR.Format(SR.net_http_invalid_header_name, string.Empty), exception.Message);
-            Assert.Empty(_handler.DecodedHeaders);
-        }
-
         [Fact]
         public void DecodesLiteralHeaderFieldWithoutIndexing_IndexedName()
         {

Copy link
Contributor

github-actions bot commented Aug 1, 2024

The shared code is out of sync.

The Diff
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   src/Shared/test/Shared.Tests/runtime/Http2/HPackDecoderTest.cs

no changes added to commit (use "git add" and/or "git commit -a")

diff --git a/src/Shared/test/Shared.Tests/runtime/Http2/HPackDecoderTest.cs b/src/Shared/test/Shared.Tests/runtime/Http2/HPackDecoderTest.cs
index ff141b84..28e7a971 100644
--- a/src/Shared/test/Shared.Tests/runtime/Http2/HPackDecoderTest.cs
+++ b/src/Shared/test/Shared.Tests/runtime/Http2/HPackDecoderTest.cs
@@ -87,10 +87,6 @@ namespace System.Net.Http.Unit.Tests.HPack
             .Concat(_headerValueHuffmanBytes)
             .ToArray();
 
-        private static readonly byte[] _literalEmptyString = new byte[] { 0x00 };
-
-        private static readonly byte[] _literalEmptyStringHuffman = new byte[] { 0x80 };
-
         // &        *
         // 11111000 11111111
         private static readonly byte[] _huffmanLongPadding = new byte[] { 0x82, 0xf8, 0xff };
@@ -247,43 +243,6 @@ namespace System.Net.Http.Unit.Tests.HPack
             TestDecodeWithoutIndexing(encoded, _headerNameString, _headerValueString);
         }
 
-        [Fact]
-        public void DecodesLiteralHeaderFieldWithoutIndexing_NewName_EmptyName()
-        {
-            byte[] encoded = _literalHeaderFieldWithoutIndexingNewName
-                .Concat(_literalEmptyString)
-                .Concat(_headerValue)
-                .ToArray();
-
-            HPackDecodingException exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(encoded, endHeaders: true, handler: _handler));
-            Assert.Equal(SR.Format(SR.net_http_invalid_header_name, string.Empty), exception.Message);
-            Assert.Empty(_handler.DecodedHeaders);
-        }
-
-        [Fact]
-        public void DecodesLiteralHeaderFieldWithoutIndexing_NewName_EmptyValue()
-        {
-            byte[] encoded = _literalHeaderFieldWithoutIndexingNewName
-                .Concat(_headerName)
-                .Concat(_literalEmptyString)
-                .ToArray();
-
-            TestDecodeWithoutIndexing(encoded, _headerNameString, string.Empty);
-        }
-
-        [Fact]
-        public void DecodesLiteralHeaderFieldWithoutIndexing_NewName_EmptyNameAndValue()
-        {
-            byte[] encoded = _literalHeaderFieldWithoutIndexingNewName
-                .Concat(_literalEmptyString)
-                .Concat(_literalEmptyString)
-                .ToArray();
-
-            HPackDecodingException exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(encoded, endHeaders: true, handler: _handler));
-            Assert.Equal(SR.Format(SR.net_http_invalid_header_name, string.Empty), exception.Message);
-            Assert.Empty(_handler.DecodedHeaders);
-        }
-
         [Fact]
         public void DecodesLiteralHeaderFieldWithoutIndexing_NewName_HuffmanEncodedName()
         {
@@ -295,19 +254,6 @@ namespace System.Net.Http.Unit.Tests.HPack
             TestDecodeWithoutIndexing(encoded, _headerNameString, _headerValueString);
         }
 
-        [Fact]
-        public void DecodesLiteralHeaderFieldWithoutIndexing_NewName_HuffmanEncodedName_Empty()
-        {
-            byte[] encoded = _literalHeaderFieldWithoutIndexingNewName
-                .Concat(_literalEmptyStringHuffman)
-                .Concat(_headerValue)
-                .ToArray();
-
-            HPackDecodingException exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(encoded, endHeaders: true, handler: _handler));
-            Assert.Equal(SR.Format(SR.net_http_invalid_header_name, string.Empty), exception.Message);
-            Assert.Empty(_handler.DecodedHeaders);
-        }
-
         [Fact]
         public void DecodesLiteralHeaderFieldWithoutIndexing_NewName_HuffmanEncodedValue()
         {
@@ -319,17 +265,6 @@ namespace System.Net.Http.Unit.Tests.HPack
             TestDecodeWithoutIndexing(encoded, _headerNameString, _headerValueString);
         }
 
-        [Fact]
-        public void DecodesLiteralHeaderFieldWithoutIndexing_NewName_HuffmanEncodedValue_Empty()
-        {
-            byte[] encoded = _literalHeaderFieldWithoutIndexingNewName
-                .Concat(_headerName)
-                .Concat(_literalEmptyStringHuffman)
-                .ToArray();
-
-            TestDecodeWithoutIndexing(encoded, _headerNameString, string.Empty);
-        }
-
         [Fact]
         public void DecodesLiteralHeaderFieldWithoutIndexing_NewName_HuffmanEncodedNameAndValue()
         {
@@ -341,19 +276,6 @@ namespace System.Net.Http.Unit.Tests.HPack
             TestDecodeWithoutIndexing(encoded, _headerNameString, _headerValueString);
         }
 
-        [Fact]
-        public void DecodesLiteralHeaderFieldWithoutIndexing_NewName_HuffmanEncodedNameAndValue_Empty()
-        {
-            byte[] encoded = _literalHeaderFieldWithoutIndexingNewName
-                .Concat(_literalEmptyStringHuffman)
-                .Concat(_literalEmptyStringHuffman)
-                .ToArray();
-
-            HPackDecodingException exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(encoded, endHeaders: true, handler: _handler));
-            Assert.Equal(SR.Format(SR.net_http_invalid_header_name, string.Empty), exception.Message);
-            Assert.Empty(_handler.DecodedHeaders);
-        }
-
         [Fact]
         public void DecodesLiteralHeaderFieldWithoutIndexing_IndexedName()
         {

@amcasey
Copy link
Member

amcasey commented Aug 1, 2024

dotnet/runtime#105765

Copy link
Contributor

github-actions bot commented Aug 2, 2024

The shared code is out of sync.

The Diff
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   src/Shared/test/Shared.Tests/runtime/Http2/HPackDecoderTest.cs

no changes added to commit (use "git add" and/or "git commit -a")

diff --git a/src/Shared/test/Shared.Tests/runtime/Http2/HPackDecoderTest.cs b/src/Shared/test/Shared.Tests/runtime/Http2/HPackDecoderTest.cs
index ff141b84..28e7a971 100644
--- a/src/Shared/test/Shared.Tests/runtime/Http2/HPackDecoderTest.cs
+++ b/src/Shared/test/Shared.Tests/runtime/Http2/HPackDecoderTest.cs
@@ -87,10 +87,6 @@ namespace System.Net.Http.Unit.Tests.HPack
             .Concat(_headerValueHuffmanBytes)
             .ToArray();
 
-        private static readonly byte[] _literalEmptyString = new byte[] { 0x00 };
-
-        private static readonly byte[] _literalEmptyStringHuffman = new byte[] { 0x80 };
-
         // &        *
         // 11111000 11111111
         private static readonly byte[] _huffmanLongPadding = new byte[] { 0x82, 0xf8, 0xff };
@@ -247,43 +243,6 @@ namespace System.Net.Http.Unit.Tests.HPack
             TestDecodeWithoutIndexing(encoded, _headerNameString, _headerValueString);
         }
 
-        [Fact]
-        public void DecodesLiteralHeaderFieldWithoutIndexing_NewName_EmptyName()
-        {
-            byte[] encoded = _literalHeaderFieldWithoutIndexingNewName
-                .Concat(_literalEmptyString)
-                .Concat(_headerValue)
-                .ToArray();
-
-            HPackDecodingException exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(encoded, endHeaders: true, handler: _handler));
-            Assert.Equal(SR.Format(SR.net_http_invalid_header_name, string.Empty), exception.Message);
-            Assert.Empty(_handler.DecodedHeaders);
-        }
-
-        [Fact]
-        public void DecodesLiteralHeaderFieldWithoutIndexing_NewName_EmptyValue()
-        {
-            byte[] encoded = _literalHeaderFieldWithoutIndexingNewName
-                .Concat(_headerName)
-                .Concat(_literalEmptyString)
-                .ToArray();
-
-            TestDecodeWithoutIndexing(encoded, _headerNameString, string.Empty);
-        }
-
-        [Fact]
-        public void DecodesLiteralHeaderFieldWithoutIndexing_NewName_EmptyNameAndValue()
-        {
-            byte[] encoded = _literalHeaderFieldWithoutIndexingNewName
-                .Concat(_literalEmptyString)
-                .Concat(_literalEmptyString)
-                .ToArray();
-
-            HPackDecodingException exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(encoded, endHeaders: true, handler: _handler));
-            Assert.Equal(SR.Format(SR.net_http_invalid_header_name, string.Empty), exception.Message);
-            Assert.Empty(_handler.DecodedHeaders);
-        }
-
         [Fact]
         public void DecodesLiteralHeaderFieldWithoutIndexing_NewName_HuffmanEncodedName()
         {
@@ -295,19 +254,6 @@ namespace System.Net.Http.Unit.Tests.HPack
             TestDecodeWithoutIndexing(encoded, _headerNameString, _headerValueString);
         }
 
-        [Fact]
-        public void DecodesLiteralHeaderFieldWithoutIndexing_NewName_HuffmanEncodedName_Empty()
-        {
-            byte[] encoded = _literalHeaderFieldWithoutIndexingNewName
-                .Concat(_literalEmptyStringHuffman)
-                .Concat(_headerValue)
-                .ToArray();
-
-            HPackDecodingException exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(encoded, endHeaders: true, handler: _handler));
-            Assert.Equal(SR.Format(SR.net_http_invalid_header_name, string.Empty), exception.Message);
-            Assert.Empty(_handler.DecodedHeaders);
-        }
-
         [Fact]
         public void DecodesLiteralHeaderFieldWithoutIndexing_NewName_HuffmanEncodedValue()
         {
@@ -319,17 +265,6 @@ namespace System.Net.Http.Unit.Tests.HPack
             TestDecodeWithoutIndexing(encoded, _headerNameString, _headerValueString);
         }
 
-        [Fact]
-        public void DecodesLiteralHeaderFieldWithoutIndexing_NewName_HuffmanEncodedValue_Empty()
-        {
-            byte[] encoded = _literalHeaderFieldWithoutIndexingNewName
-                .Concat(_headerName)
-                .Concat(_literalEmptyStringHuffman)
-                .ToArray();
-
-            TestDecodeWithoutIndexing(encoded, _headerNameString, string.Empty);
-        }
-
         [Fact]
         public void DecodesLiteralHeaderFieldWithoutIndexing_NewName_HuffmanEncodedNameAndValue()
         {
@@ -341,19 +276,6 @@ namespace System.Net.Http.Unit.Tests.HPack
             TestDecodeWithoutIndexing(encoded, _headerNameString, _headerValueString);
         }
 
-        [Fact]
-        public void DecodesLiteralHeaderFieldWithoutIndexing_NewName_HuffmanEncodedNameAndValue_Empty()
-        {
-            byte[] encoded = _literalHeaderFieldWithoutIndexingNewName
-                .Concat(_literalEmptyStringHuffman)
-                .Concat(_literalEmptyStringHuffman)
-                .ToArray();
-
-            HPackDecodingException exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(encoded, endHeaders: true, handler: _handler));
-            Assert.Equal(SR.Format(SR.net_http_invalid_header_name, string.Empty), exception.Message);
-            Assert.Empty(_handler.DecodedHeaders);
-        }
-
         [Fact]
         public void DecodesLiteralHeaderFieldWithoutIndexing_IndexedName()
         {

Copy link
Contributor

The shared code is out of sync.

The Diff
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   src/Shared/test/Shared.Tests/runtime/Http2/HPackDecoderTest.cs

no changes added to commit (use "git add" and/or "git commit -a")

diff --git a/src/Shared/test/Shared.Tests/runtime/Http2/HPackDecoderTest.cs b/src/Shared/test/Shared.Tests/runtime/Http2/HPackDecoderTest.cs
index 2dde5440..ff141b84 100644
--- a/src/Shared/test/Shared.Tests/runtime/Http2/HPackDecoderTest.cs
+++ b/src/Shared/test/Shared.Tests/runtime/Http2/HPackDecoderTest.cs
@@ -151,25 +151,6 @@ namespace System.Net.Http.Unit.Tests.HPack
             Assert.Equal(_headerValueString, _handler.DecodedHeaders[_headerNameString]);
         }
 
-        [Fact]
-        public void DecodesIndexedHeaderField_DynamicTable_ReferencedEntryRemovedOnInsertion()
-        {
-            // Pre-populate the dynamic table so we'll have something to reference.
-            // This entry will have index 62 (0x3E).
-            _dynamicTable.Insert(_headerNameBytes, _headerValueBytes);
-            Assert.Equal(1, _dynamicTable.Count);
-
-            Assert.InRange(_dynamicTable.MaxSize, 1, _literalHeaderNameBytes.Length); // Assert that our string will be too big
-
-            byte[] encoded = (new byte[] { 0x40 | 0x3E }) // Indexing enabled (0x40) | dynamic table (62 = 0x3E) as a 6-integer, 
-                .Concat(_literalHeaderName) // A header value that's too large to fit in the dynamic table
-                .ToArray();
-
-            _decoder.Decode(encoded, endHeaders: true, handler: _handler);
-            Assert.Equal(0, _dynamicTable.Count); // The large entry caused the table to be wiped
-            Assert.Equal(_literalHeaderNameString, _handler.DecodedHeaders[_headerNameString]); // but we got the header anyway
-        }
-
         [Fact]
         public void DecodesIndexedHeaderField_OutOfRange_Error()
         {

Copy link
Contributor

The shared code is out of sync.

The Diff
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   src/Shared/test/Shared.Tests/runtime/Http2/HPackDecoderTest.cs

no changes added to commit (use "git add" and/or "git commit -a")

diff --git a/src/Shared/test/Shared.Tests/runtime/Http2/HPackDecoderTest.cs b/src/Shared/test/Shared.Tests/runtime/Http2/HPackDecoderTest.cs
index 2dde5440..ff141b84 100644
--- a/src/Shared/test/Shared.Tests/runtime/Http2/HPackDecoderTest.cs
+++ b/src/Shared/test/Shared.Tests/runtime/Http2/HPackDecoderTest.cs
@@ -151,25 +151,6 @@ namespace System.Net.Http.Unit.Tests.HPack
             Assert.Equal(_headerValueString, _handler.DecodedHeaders[_headerNameString]);
         }
 
-        [Fact]
-        public void DecodesIndexedHeaderField_DynamicTable_ReferencedEntryRemovedOnInsertion()
-        {
-            // Pre-populate the dynamic table so we'll have something to reference.
-            // This entry will have index 62 (0x3E).
-            _dynamicTable.Insert(_headerNameBytes, _headerValueBytes);
-            Assert.Equal(1, _dynamicTable.Count);
-
-            Assert.InRange(_dynamicTable.MaxSize, 1, _literalHeaderNameBytes.Length); // Assert that our string will be too big
-
-            byte[] encoded = (new byte[] { 0x40 | 0x3E }) // Indexing enabled (0x40) | dynamic table (62 = 0x3E) as a 6-integer, 
-                .Concat(_literalHeaderName) // A header value that's too large to fit in the dynamic table
-                .ToArray();
-
-            _decoder.Decode(encoded, endHeaders: true, handler: _handler);
-            Assert.Equal(0, _dynamicTable.Count); // The large entry caused the table to be wiped
-            Assert.Equal(_literalHeaderNameString, _handler.DecodedHeaders[_headerNameString]); // but we got the header anyway
-        }
-
         [Fact]
         public void DecodesIndexedHeaderField_OutOfRange_Error()
         {

Copy link
Contributor

github-actions bot commented Mar 6, 2025

The shared code is out of sync.

The Diff
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   src/Shared/runtime/Http2/Hpack/DynamicTable.cs
	modified:   src/Shared/runtime/Http2/Hpack/HPackDecoder.cs
	modified:   src/Shared/test/Shared.Tests/runtime/Http2/DynamicTableTest.cs

no changes added to commit (use "git add" and/or "git commit -a")

diff --git a/src/Shared/runtime/Http2/Hpack/DynamicTable.cs b/src/Shared/runtime/Http2/Hpack/DynamicTable.cs
index 821ca75e..bf4ef061 100644
--- a/src/Shared/runtime/Http2/Hpack/DynamicTable.cs
+++ b/src/Shared/runtime/Http2/Hpack/DynamicTable.cs
@@ -1,6 +1,8 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+using System.Diagnostics;
+
 namespace System.Net.Http.HPack
 {
     internal sealed class DynamicTable
@@ -14,7 +16,7 @@ namespace System.Net.Http.HPack
 
         public DynamicTable(int maxSize)
         {
-            _buffer = new HeaderField[maxSize / HeaderField.RfcOverhead];
+            _buffer = [];
             _maxSize = maxSize;
         }
 
@@ -67,18 +69,17 @@ namespace System.Net.Http.HPack
                 return;
             }
 
-            var entry = new HeaderField(staticTableIndex, name, value);
-            _buffer[_insertIndex] = entry;
-            _insertIndex = (_insertIndex + 1) % _buffer.Length;
-            _size += entry.Length;
-            _count++;
-        }
-
-        public void Resize(int maxSize)
-        {
-            if (maxSize > _maxSize)
+            // Ensure that we have at least one slot available.
+            if (_count == _buffer.Length)
             {
-                var newBuffer = new HeaderField[maxSize / HeaderField.RfcOverhead];
+                int maxCapacity = _maxSize / HeaderField.RfcOverhead;
+                Debug.Assert(_count + 1 <= maxCapacity);
+
+                // Double the size of the current buffer, starting with at least 16 entries.
+                int newBufferSize = Math.Min(Math.Max(16, _buffer.Length * 2), maxCapacity);
+                Debug.Assert(newBufferSize > _count);
+
+                var newBuffer = new HeaderField[newBufferSize];
 
                 int headCount = Math.Min(_buffer.Length - _removeIndex, _count);
                 int tailCount = _count - headCount;
@@ -89,11 +90,27 @@ namespace System.Net.Http.HPack
                 _buffer = newBuffer;
                 _removeIndex = 0;
                 _insertIndex = _count;
-                _maxSize = maxSize;
             }
-            else
+
+            var entry = new HeaderField(staticTableIndex, name, value);
+            _buffer[_insertIndex] = entry;
+
+            if (++_insertIndex == _buffer.Length)
+            {
+                _insertIndex = 0;
+            }
+
+            _size += entry.Length;
+            _count++;
+        }
+
+        public void UpdateMaxSize(int maxSize)
+        {
+            int previousMax = _maxSize;
+            _maxSize = maxSize;
+
+            if (maxSize < previousMax)
             {
-                _maxSize = maxSize;
                 EnsureAvailable(0);
             }
         }
@@ -107,7 +124,11 @@ namespace System.Net.Http.HPack
                 field = default;
 
                 _count--;
-                _removeIndex = (_removeIndex + 1) % _buffer.Length;
+
+                if (++_removeIndex == _buffer.Length)
+                {
+                    _removeIndex = 0;
+                }
             }
         }
     }
diff --git a/src/Shared/runtime/Http2/Hpack/HPackDecoder.cs b/src/Shared/runtime/Http2/Hpack/HPackDecoder.cs
index 1f9083e2..6b4fcb3a 100644
--- a/src/Shared/runtime/Http2/Hpack/HPackDecoder.cs
+++ b/src/Shared/runtime/Http2/Hpack/HPackDecoder.cs
@@ -27,8 +27,13 @@ namespace System.Net.Http.HPack
             DynamicTableSizeUpdate
         }
 
+        // https://datatracker.ietf.org/doc/html/rfc9113#name-defined-settings
+        // Initial value for SETTINGS_HEADER_TABLE_SIZE is 4,096 octets.
         public const int DefaultHeaderTableSize = 4096;
-        public const int DefaultStringOctetsSize = 4096;
+
+        // This is the initial size. Buffers will be dynamically resized as needed.
+        public const int DefaultStringOctetsSize = 32;
+
         public const int DefaultMaxHeadersLength = 64 * 1024;
 
         // http://httpwg.org/specs/rfc7541.html#rfc.section.6.1
@@ -670,7 +675,7 @@ namespace System.Net.Http.HPack
                 throw new HPackDecodingException(SR.Format(SR.net_http_hpack_large_table_size_update, size, _maxDynamicTableSize));
             }
 
-            _dynamicTable.Resize(size);
+            _dynamicTable.UpdateMaxSize(size);
         }
     }
 }
diff --git a/src/Shared/test/Shared.Tests/runtime/Http2/DynamicTableTest.cs b/src/Shared/test/Shared.Tests/runtime/Http2/DynamicTableTest.cs
index 9576a9e6..bd7f854a 100644
--- a/src/Shared/test/Shared.Tests/runtime/Http2/DynamicTableTest.cs
+++ b/src/Shared/test/Shared.Tests/runtime/Http2/DynamicTableTest.cs
@@ -94,15 +94,33 @@ namespace System.Net.Http.Unit.Tests.HPack
             Assert.Equal(0, dynamicTable.Size);
         }
 
+        public static IEnumerable<object[]> DynamicTable_WrapsRingBuffer_Success_MemberData()
+        {
+            foreach (int maxSize in new[] { 100, 256, 1000 })
+            {
+                int maxCount = maxSize / (2 * "header-0".Length + HeaderField.RfcOverhead);
+
+                for (int i = 0; i < maxCount; i++)
+                {
+                    yield return new object[] { maxSize, i };
+                }
+            }
+        }
+
         [Theory]
-        [InlineData(0)]
-        [InlineData(1)]
-        [InlineData(2)]
-        [InlineData(3)]
-        public void DynamicTable_WrapsRingBuffer_Success(int targetInsertIndex)
+        [MemberData(nameof(DynamicTable_WrapsRingBuffer_Success_MemberData))]
+        public void DynamicTable_WrapsRingBuffer_Success(int maxSize, int targetInsertIndex)
         {
+            DynamicTable table = new DynamicTable(maxSize);
+
+            // The table grows its internal buffer dynamically.
+            // Fill it with enough entries to force it to grow to max size.
+            for (int i = 0; i < table.MaxSize / HeaderField.RfcOverhead; i++)
+            {
+                table.Insert("a"u8, "b"u8);
+            }
+
             FieldInfo insertIndexField = typeof(DynamicTable).GetField("_insertIndex", BindingFlags.NonPublic | BindingFlags.Instance);
-            DynamicTable table = new DynamicTable(maxSize: 256);
             Stack<byte[]> insertedHeaders = new Stack<byte[]>();
 
             // Insert into dynamic table until its insert index into its ring buffer loops back to 0.
@@ -167,7 +185,7 @@ namespace System.Net.Http.Unit.Tests.HPack
                 headers.Add(dynamicTable[i]);
             }
 
-            dynamicTable.Resize(finalMaxSize);
+            dynamicTable.UpdateMaxSize(finalMaxSize);
 
             int expectedCount = Math.Min(finalMaxSize / 64, headers.Count);
             Assert.Equal(expectedCount, dynamicTable.Count);
@@ -188,7 +206,7 @@ namespace System.Net.Http.Unit.Tests.HPack
 
             VerifyTableEntries(dynamicTable, _header2, _header1);
 
-            dynamicTable.Resize(_header2.Length);
+            dynamicTable.UpdateMaxSize(_header2.Length);
 
             VerifyTableEntries(dynamicTable, _header2);
         }
@@ -200,7 +218,7 @@ namespace System.Net.Http.Unit.Tests.HPack
             dynamicTable.Insert(_header1.Name, _header1.Value);
             dynamicTable.Insert(_header2.Name, _header2.Value);
 
-            dynamicTable.Resize(0);
+            dynamicTable.UpdateMaxSize(0);
 
             Assert.Equal(0, dynamicTable.Count);
             Assert.Equal(0, dynamicTable.Size);
@@ -219,7 +237,7 @@ namespace System.Net.Http.Unit.Tests.HPack
             Assert.Equal(0, dynamicTable.Count);
             Assert.Equal(0, dynamicTable.Size);
 
-            dynamicTable.Resize(dynamicTable.MaxSize + _header2.Length);
+            dynamicTable.UpdateMaxSize(dynamicTable.MaxSize + _header2.Length);
             dynamicTable.Insert(_header2.Name, _header2.Value);
 
             VerifyTableEntries(dynamicTable, _header2);

Copy link
Contributor

github-actions bot commented Mar 7, 2025

The shared code is out of sync.

The Diff
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   src/Shared/runtime/Http2/Hpack/DynamicTable.cs
	modified:   src/Shared/runtime/Http2/Hpack/HPackDecoder.cs
	modified:   src/Shared/test/Shared.Tests/runtime/Http2/DynamicTableTest.cs

no changes added to commit (use "git add" and/or "git commit -a")

diff --git a/src/Shared/runtime/Http2/Hpack/DynamicTable.cs b/src/Shared/runtime/Http2/Hpack/DynamicTable.cs
index 821ca75e..bf4ef061 100644
--- a/src/Shared/runtime/Http2/Hpack/DynamicTable.cs
+++ b/src/Shared/runtime/Http2/Hpack/DynamicTable.cs
@@ -1,6 +1,8 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+using System.Diagnostics;
+
 namespace System.Net.Http.HPack
 {
     internal sealed class DynamicTable
@@ -14,7 +16,7 @@ namespace System.Net.Http.HPack
 
         public DynamicTable(int maxSize)
         {
-            _buffer = new HeaderField[maxSize / HeaderField.RfcOverhead];
+            _buffer = [];
             _maxSize = maxSize;
         }
 
@@ -67,18 +69,17 @@ namespace System.Net.Http.HPack
                 return;
             }
 
-            var entry = new HeaderField(staticTableIndex, name, value);
-            _buffer[_insertIndex] = entry;
-            _insertIndex = (_insertIndex + 1) % _buffer.Length;
-            _size += entry.Length;
-            _count++;
-        }
-
-        public void Resize(int maxSize)
-        {
-            if (maxSize > _maxSize)
+            // Ensure that we have at least one slot available.
+            if (_count == _buffer.Length)
             {
-                var newBuffer = new HeaderField[maxSize / HeaderField.RfcOverhead];
+                int maxCapacity = _maxSize / HeaderField.RfcOverhead;
+                Debug.Assert(_count + 1 <= maxCapacity);
+
+                // Double the size of the current buffer, starting with at least 16 entries.
+                int newBufferSize = Math.Min(Math.Max(16, _buffer.Length * 2), maxCapacity);
+                Debug.Assert(newBufferSize > _count);
+
+                var newBuffer = new HeaderField[newBufferSize];
 
                 int headCount = Math.Min(_buffer.Length - _removeIndex, _count);
                 int tailCount = _count - headCount;
@@ -89,11 +90,27 @@ namespace System.Net.Http.HPack
                 _buffer = newBuffer;
                 _removeIndex = 0;
                 _insertIndex = _count;
-                _maxSize = maxSize;
             }
-            else
+
+            var entry = new HeaderField(staticTableIndex, name, value);
+            _buffer[_insertIndex] = entry;
+
+            if (++_insertIndex == _buffer.Length)
+            {
+                _insertIndex = 0;
+            }
+
+            _size += entry.Length;
+            _count++;
+        }
+
+        public void UpdateMaxSize(int maxSize)
+        {
+            int previousMax = _maxSize;
+            _maxSize = maxSize;
+
+            if (maxSize < previousMax)
             {
-                _maxSize = maxSize;
                 EnsureAvailable(0);
             }
         }
@@ -107,7 +124,11 @@ namespace System.Net.Http.HPack
                 field = default;
 
                 _count--;
-                _removeIndex = (_removeIndex + 1) % _buffer.Length;
+
+                if (++_removeIndex == _buffer.Length)
+                {
+                    _removeIndex = 0;
+                }
             }
         }
     }
diff --git a/src/Shared/runtime/Http2/Hpack/HPackDecoder.cs b/src/Shared/runtime/Http2/Hpack/HPackDecoder.cs
index 1f9083e2..6b4fcb3a 100644
--- a/src/Shared/runtime/Http2/Hpack/HPackDecoder.cs
+++ b/src/Shared/runtime/Http2/Hpack/HPackDecoder.cs
@@ -27,8 +27,13 @@ namespace System.Net.Http.HPack
             DynamicTableSizeUpdate
         }
 
+        // https://datatracker.ietf.org/doc/html/rfc9113#name-defined-settings
+        // Initial value for SETTINGS_HEADER_TABLE_SIZE is 4,096 octets.
         public const int DefaultHeaderTableSize = 4096;
-        public const int DefaultStringOctetsSize = 4096;
+
+        // This is the initial size. Buffers will be dynamically resized as needed.
+        public const int DefaultStringOctetsSize = 32;
+
         public const int DefaultMaxHeadersLength = 64 * 1024;
 
         // http://httpwg.org/specs/rfc7541.html#rfc.section.6.1
@@ -670,7 +675,7 @@ namespace System.Net.Http.HPack
                 throw new HPackDecodingException(SR.Format(SR.net_http_hpack_large_table_size_update, size, _maxDynamicTableSize));
             }
 
-            _dynamicTable.Resize(size);
+            _dynamicTable.UpdateMaxSize(size);
         }
     }
 }
diff --git a/src/Shared/test/Shared.Tests/runtime/Http2/DynamicTableTest.cs b/src/Shared/test/Shared.Tests/runtime/Http2/DynamicTableTest.cs
index 9576a9e6..bd7f854a 100644
--- a/src/Shared/test/Shared.Tests/runtime/Http2/DynamicTableTest.cs
+++ b/src/Shared/test/Shared.Tests/runtime/Http2/DynamicTableTest.cs
@@ -94,15 +94,33 @@ namespace System.Net.Http.Unit.Tests.HPack
             Assert.Equal(0, dynamicTable.Size);
         }
 
+        public static IEnumerable<object[]> DynamicTable_WrapsRingBuffer_Success_MemberData()
+        {
+            foreach (int maxSize in new[] { 100, 256, 1000 })
+            {
+                int maxCount = maxSize / (2 * "header-0".Length + HeaderField.RfcOverhead);
+
+                for (int i = 0; i < maxCount; i++)
+                {
+                    yield return new object[] { maxSize, i };
+                }
+            }
+        }
+
         [Theory]
-        [InlineData(0)]
-        [InlineData(1)]
-        [InlineData(2)]
-        [InlineData(3)]
-        public void DynamicTable_WrapsRingBuffer_Success(int targetInsertIndex)
+        [MemberData(nameof(DynamicTable_WrapsRingBuffer_Success_MemberData))]
+        public void DynamicTable_WrapsRingBuffer_Success(int maxSize, int targetInsertIndex)
         {
+            DynamicTable table = new DynamicTable(maxSize);
+
+            // The table grows its internal buffer dynamically.
+            // Fill it with enough entries to force it to grow to max size.
+            for (int i = 0; i < table.MaxSize / HeaderField.RfcOverhead; i++)
+            {
+                table.Insert("a"u8, "b"u8);
+            }
+
             FieldInfo insertIndexField = typeof(DynamicTable).GetField("_insertIndex", BindingFlags.NonPublic | BindingFlags.Instance);
-            DynamicTable table = new DynamicTable(maxSize: 256);
             Stack<byte[]> insertedHeaders = new Stack<byte[]>();
 
             // Insert into dynamic table until its insert index into its ring buffer loops back to 0.
@@ -167,7 +185,7 @@ namespace System.Net.Http.Unit.Tests.HPack
                 headers.Add(dynamicTable[i]);
             }
 
-            dynamicTable.Resize(finalMaxSize);
+            dynamicTable.UpdateMaxSize(finalMaxSize);
 
             int expectedCount = Math.Min(finalMaxSize / 64, headers.Count);
             Assert.Equal(expectedCount, dynamicTable.Count);
@@ -188,7 +206,7 @@ namespace System.Net.Http.Unit.Tests.HPack
 
             VerifyTableEntries(dynamicTable, _header2, _header1);
 
-            dynamicTable.Resize(_header2.Length);
+            dynamicTable.UpdateMaxSize(_header2.Length);
 
             VerifyTableEntries(dynamicTable, _header2);
         }
@@ -200,7 +218,7 @@ namespace System.Net.Http.Unit.Tests.HPack
             dynamicTable.Insert(_header1.Name, _header1.Value);
             dynamicTable.Insert(_header2.Name, _header2.Value);
 
-            dynamicTable.Resize(0);
+            dynamicTable.UpdateMaxSize(0);
 
             Assert.Equal(0, dynamicTable.Count);
             Assert.Equal(0, dynamicTable.Size);
@@ -219,7 +237,7 @@ namespace System.Net.Http.Unit.Tests.HPack
             Assert.Equal(0, dynamicTable.Count);
             Assert.Equal(0, dynamicTable.Size);
 
-            dynamicTable.Resize(dynamicTable.MaxSize + _header2.Length);
+            dynamicTable.UpdateMaxSize(dynamicTable.MaxSize + _header2.Length);
             dynamicTable.Insert(_header2.Name, _header2.Value);
 
             VerifyTableEntries(dynamicTable, _header2);

Copy link
Contributor

The shared code is out of sync.

The Diff
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   src/Shared/runtime/Http3/Helpers/VariableLengthIntegerHelper.cs
	modified:   src/Shared/test/Shared.Tests/runtime/Http3/VariableLengthIntegerHelperTests.cs

no changes added to commit (use "git add" and/or "git commit -a")

diff --git a/src/Shared/runtime/Http3/Helpers/VariableLengthIntegerHelper.cs b/src/Shared/runtime/Http3/Helpers/VariableLengthIntegerHelper.cs
index c7f1ec90..3a343a62 100644
--- a/src/Shared/runtime/Http3/Helpers/VariableLengthIntegerHelper.cs
+++ b/src/Shared/runtime/Http3/Helpers/VariableLengthIntegerHelper.cs
@@ -128,19 +128,19 @@ namespace System.Net.Http
             }
         }
 
-        // If callsite has 'examined', set it to buffer.End if the integer wasn't successfully read, otherwise set examined = consumed.
-        public static bool TryGetInteger(in ReadOnlySequence<byte> buffer, out SequencePosition consumed, out long integer)
+        public static long GetInteger(in ReadOnlySequence<byte> buffer, out SequencePosition consumed, out SequencePosition examined)
         {
             var reader = new SequenceReader<byte>(buffer);
-            if (TryRead(ref reader, out integer))
+            if (TryRead(ref reader, out long value))
             {
-                consumed = buffer.GetPosition(reader.Consumed);
-                return true;
+                consumed = examined = buffer.GetPosition(reader.Consumed);
+                return value;
             }
             else
             {
-                consumed = buffer.Start;
-                return false;
+                consumed = default;
+                examined = buffer.End;
+                return -1;
             }
         }
 
diff --git a/src/Shared/test/Shared.Tests/runtime/Http3/VariableLengthIntegerHelperTests.cs b/src/Shared/test/Shared.Tests/runtime/Http3/VariableLengthIntegerHelperTests.cs
index e461bfd4..d67d24c0 100644
--- a/src/Shared/test/Shared.Tests/runtime/Http3/VariableLengthIntegerHelperTests.cs
+++ b/src/Shared/test/Shared.Tests/runtime/Http3/VariableLengthIntegerHelperTests.cs
@@ -1,4 +1,4 @@
-// Licensed to the .NET Foundation under one or more agreements.
+// Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System;
@@ -223,12 +223,12 @@ namespace Common.Tests.Tests.System.Net.aspnetcore.Http3
             MemorySegment<byte> memorySegment2 = memorySegment1.Append(new byte[] { 0, 0, 0, 0, 0, 0, 2 });
             ReadOnlySequence<byte> readOnlySequence = new ReadOnlySequence<byte>(
                 memorySegment1, 0, memorySegment2, memorySegment2.Memory.Length);
-            bool result = VariableLengthIntegerHelper.TryGetInteger(readOnlySequence,
-                out SequencePosition consumed, out long integer);
+            long result = VariableLengthIntegerHelper.GetInteger(readOnlySequence,
+                out SequencePosition consumed, out SequencePosition examined);
 
-            Assert.True(result);
-            Assert.Equal(2, integer);
+            Assert.Equal(2, result);
             Assert.Equal(7, consumed.GetInteger());
+            Assert.Equal(7, examined.GetInteger());
         }
 
         [Fact]
@@ -238,11 +238,12 @@ namespace Common.Tests.Tests.System.Net.aspnetcore.Http3
             MemorySegment<byte> memorySegment2 = memorySegment1.Append(new byte[] { 0, 0, 0, 0, 0, 2 });
             ReadOnlySequence<byte> readOnlySequence = new ReadOnlySequence<byte>(
                 memorySegment1, 0, memorySegment2, memorySegment2.Memory.Length);
-            bool result = VariableLengthIntegerHelper.TryGetInteger(readOnlySequence,
-                out SequencePosition consumed, out long integer);
+            long result = VariableLengthIntegerHelper.GetInteger(readOnlySequence,
+                out SequencePosition consumed, out SequencePosition examined);
 
-            Assert.False(result);
+            Assert.Equal(-1, result);
             Assert.Equal(0, consumed.GetInteger());
+            Assert.Equal(6, examined.GetInteger());
         }
 
         [Fact]

Copy link
Contributor

The shared code is out of sync.

The Diff
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   src/Shared/runtime/Http3/Helpers/VariableLengthIntegerHelper.cs
	modified:   src/Shared/test/Shared.Tests/runtime/Http3/VariableLengthIntegerHelperTests.cs

no changes added to commit (use "git add" and/or "git commit -a")

diff --git a/src/Shared/runtime/Http3/Helpers/VariableLengthIntegerHelper.cs b/src/Shared/runtime/Http3/Helpers/VariableLengthIntegerHelper.cs
index c7f1ec90..3a343a62 100644
--- a/src/Shared/runtime/Http3/Helpers/VariableLengthIntegerHelper.cs
+++ b/src/Shared/runtime/Http3/Helpers/VariableLengthIntegerHelper.cs
@@ -128,19 +128,19 @@ namespace System.Net.Http
             }
         }
 
-        // If callsite has 'examined', set it to buffer.End if the integer wasn't successfully read, otherwise set examined = consumed.
-        public static bool TryGetInteger(in ReadOnlySequence<byte> buffer, out SequencePosition consumed, out long integer)
+        public static long GetInteger(in ReadOnlySequence<byte> buffer, out SequencePosition consumed, out SequencePosition examined)
         {
             var reader = new SequenceReader<byte>(buffer);
-            if (TryRead(ref reader, out integer))
+            if (TryRead(ref reader, out long value))
             {
-                consumed = buffer.GetPosition(reader.Consumed);
-                return true;
+                consumed = examined = buffer.GetPosition(reader.Consumed);
+                return value;
             }
             else
             {
-                consumed = buffer.Start;
-                return false;
+                consumed = default;
+                examined = buffer.End;
+                return -1;
             }
         }
 
diff --git a/src/Shared/test/Shared.Tests/runtime/Http3/VariableLengthIntegerHelperTests.cs b/src/Shared/test/Shared.Tests/runtime/Http3/VariableLengthIntegerHelperTests.cs
index e461bfd4..d67d24c0 100644
--- a/src/Shared/test/Shared.Tests/runtime/Http3/VariableLengthIntegerHelperTests.cs
+++ b/src/Shared/test/Shared.Tests/runtime/Http3/VariableLengthIntegerHelperTests.cs
@@ -1,4 +1,4 @@
-// Licensed to the .NET Foundation under one or more agreements.
+// Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System;
@@ -223,12 +223,12 @@ namespace Common.Tests.Tests.System.Net.aspnetcore.Http3
             MemorySegment<byte> memorySegment2 = memorySegment1.Append(new byte[] { 0, 0, 0, 0, 0, 0, 2 });
             ReadOnlySequence<byte> readOnlySequence = new ReadOnlySequence<byte>(
                 memorySegment1, 0, memorySegment2, memorySegment2.Memory.Length);
-            bool result = VariableLengthIntegerHelper.TryGetInteger(readOnlySequence,
-                out SequencePosition consumed, out long integer);
+            long result = VariableLengthIntegerHelper.GetInteger(readOnlySequence,
+                out SequencePosition consumed, out SequencePosition examined);
 
-            Assert.True(result);
-            Assert.Equal(2, integer);
+            Assert.Equal(2, result);
             Assert.Equal(7, consumed.GetInteger());
+            Assert.Equal(7, examined.GetInteger());
         }
 
         [Fact]
@@ -238,11 +238,12 @@ namespace Common.Tests.Tests.System.Net.aspnetcore.Http3
             MemorySegment<byte> memorySegment2 = memorySegment1.Append(new byte[] { 0, 0, 0, 0, 0, 2 });
             ReadOnlySequence<byte> readOnlySequence = new ReadOnlySequence<byte>(
                 memorySegment1, 0, memorySegment2, memorySegment2.Memory.Length);
-            bool result = VariableLengthIntegerHelper.TryGetInteger(readOnlySequence,
-                out SequencePosition consumed, out long integer);
+            long result = VariableLengthIntegerHelper.GetInteger(readOnlySequence,
+                out SequencePosition consumed, out SequencePosition examined);
 
-            Assert.False(result);
+            Assert.Equal(-1, result);
             Assert.Equal(0, consumed.GetInteger());
+            Assert.Equal(6, examined.GetInteger());
         }
 
         [Fact]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment