Browse Source

Sync shared code from runtime (#60782)

Co-authored-by: MihaZupan <[email protected]>
github-actions[bot] 1 year ago
parent
commit
a5b2b7e2d9

+ 37 - 16
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;
+                }
             }
         }
     }

+ 7 - 2
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);
         }
     }
 }

+ 28 - 10
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);