Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 37 additions & 16 deletions src/Shared/runtime/Http2/Hpack/DynamicTable.cs
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -14,7 +16,7 @@ internal sealed class DynamicTable

public DynamicTable(int maxSize)
{
_buffer = new HeaderField[maxSize / HeaderField.RfcOverhead];
_buffer = [];
_maxSize = maxSize;
}

Expand Down Expand Up @@ -67,18 +69,17 @@ public void Insert(int? staticTableIndex, ReadOnlySpan<byte> name, ReadOnlySpan<
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;
Expand All @@ -89,11 +90,27 @@ public void Resize(int maxSize)
_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);
}
}
Expand All @@ -107,7 +124,11 @@ private void EnsureAvailable(int available)
field = default;

_count--;
_removeIndex = (_removeIndex + 1) % _buffer.Length;

if (++_removeIndex == _buffer.Length)
{
_removeIndex = 0;
}
}
}
}
Expand Down
9 changes: 7 additions & 2 deletions src/Shared/runtime/Http2/Hpack/HPackDecoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,13 @@ private enum State : byte
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
Expand Down Expand Up @@ -670,7 +675,7 @@ private void SetDynamicHeaderTableSize(int size)
throw new HPackDecodingException(SR.Format(SR.net_http_hpack_large_table_size_update, size, _maxDynamicTableSize));
}

_dynamicTable.Resize(size);
_dynamicTable.UpdateMaxSize(size);
}
}
}
38 changes: 28 additions & 10 deletions src/Shared/test/Shared.Tests/runtime/Http2/DynamicTableTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,15 +94,33 @@ public void DynamicTable_InsertEntryLargerThanRemainingSpace_NoOp()
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.
Expand Down Expand Up @@ -167,7 +185,7 @@ public void DynamicTable_Resize_Success(int initialMaxSize, int finalMaxSize, in
headers.Add(dynamicTable[i]);
}

dynamicTable.Resize(finalMaxSize);
dynamicTable.UpdateMaxSize(finalMaxSize);

int expectedCount = Math.Min(finalMaxSize / 64, headers.Count);
Assert.Equal(expectedCount, dynamicTable.Count);
Expand All @@ -188,7 +206,7 @@ public void DynamicTable_ResizingEvictsOldestEntries()

VerifyTableEntries(dynamicTable, _header2, _header1);

dynamicTable.Resize(_header2.Length);
dynamicTable.UpdateMaxSize(_header2.Length);

VerifyTableEntries(dynamicTable, _header2);
}
Expand All @@ -200,7 +218,7 @@ public void DynamicTable_ResizingToZeroEvictsAllEntries()
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);
Expand All @@ -219,7 +237,7 @@ public void DynamicTable_CanBeResizedToLargerMaxSize()
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);
Expand Down
Loading