@@ -12,13 +12,21 @@ namespace TurboHTTP.Protocol.Http2.Hpack;
1212/// </summary>
1313internal sealed class HpackDynamicTable
1414{
15- // Each slot stores the header, its name byte length, and total RFC 7541 §4.1 entry size.
16- private readonly List < ( HpackHeader Header , int NameByteLength , int EncodedSize ) > _entries = [ ] ;
15+ private ( HpackHeader Header , int NameByteLength , int EncodedSize ) [ ] _ring ;
16+ private int _head ;
17+ private int _count ;
1718
1819 private readonly Dictionary < string , int > _nameIndex = new ( StringComparer . OrdinalIgnoreCase ) ;
1920
2021 private int _evictedCount ;
2122
23+ public HpackDynamicTable ( ) : this ( 16 ) { }
24+
25+ private HpackDynamicTable ( int initialCapacity )
26+ {
27+ _ring = new ( HpackHeader , int , int ) [ initialCapacity ] ;
28+ }
29+
2230 /// <summary>Currently configured maximum table size in bytes.</summary>
2331 public int MaxSize { get ; private set ; } = 4096 ;
2432
@@ -56,8 +64,15 @@ public void Add(string name, string value)
5664 return ;
5765 }
5866
59- var absolutePos = _evictedCount + _entries . Count ;
60- _entries . Add ( ( new HpackHeader ( name , value ) , nameByteLength , entrySize ) ) ;
67+ if ( _count == _ring . Length )
68+ {
69+ Grow ( ) ;
70+ }
71+
72+ var absolutePos = _evictedCount + _count ;
73+ var index = ( _head + _count ) % _ring . Length ;
74+ _ring [ index ] = ( new HpackHeader ( name , value ) , nameByteLength , entrySize ) ;
75+ _count ++ ;
6176 _nameIndex [ name ] = absolutePos ;
6277 CurrentSize += entrySize ;
6378 Evict ( ) ;
@@ -69,12 +84,14 @@ public void Add(string name, string value)
6984 /// </summary>
7085 public HpackHeader ? GetEntry ( int dynamicIndex )
7186 {
72- if ( dynamicIndex <= 0 || dynamicIndex > _entries . Count )
87+ if ( dynamicIndex <= 0 || dynamicIndex > _count )
7388 {
7489 return null ;
7590 }
7691
77- return _entries [ ^ dynamicIndex ] . Header ;
92+ var listIndex = _count - dynamicIndex ;
93+ var ringIndex = ( _head + listIndex ) % _ring . Length ;
94+ return _ring [ ringIndex ] . Header ;
7895 }
7996
8097 /// <summary>
@@ -83,17 +100,19 @@ public void Add(string name, string value)
83100 /// </summary>
84101 public ( HpackHeader Header , int NameByteLength , int EncodedSize ) ? GetEntryWithSizes ( int dynamicIndex )
85102 {
86- if ( dynamicIndex <= 0 || dynamicIndex > _entries . Count )
103+ if ( dynamicIndex <= 0 || dynamicIndex > _count )
87104 {
88105 return null ;
89106 }
90107
91- var entry = _entries [ ^ dynamicIndex ] ;
108+ var listIndex = _count - dynamicIndex ;
109+ var ringIndex = ( _head + listIndex ) % _ring . Length ;
110+ var entry = _ring [ ringIndex ] ;
92111 return ( entry . Header , entry . NameByteLength , entry . EncodedSize ) ;
93112 }
94113
95114 /// <summary>Number of entries currently in the dynamic table.</summary>
96- public int Count => _entries . Count ;
115+ public int Count => _count ;
97116
98117 /// <summary>
99118 /// O(1) lookup: finds the 1-based dynamic index for a full (name+value) match.
@@ -107,24 +126,26 @@ public int FindFullMatch(string name, string value)
107126 }
108127
109128 var listIndex = absolutePos - _evictedCount ;
110- if ( listIndex < 0 || listIndex >= _entries . Count )
129+ if ( listIndex < 0 || listIndex >= _count )
111130 {
112131 return 0 ;
113132 }
114133
115- var entry = _entries [ listIndex ] ;
134+ var ringIndex = ( _head + listIndex ) % _ring . Length ;
135+ var entry = _ring [ ringIndex ] ;
116136 if ( string . Equals ( entry . Header . Value , value , StringComparison . Ordinal ) )
117137 {
118- return _entries . Count - listIndex ;
138+ return _count - listIndex ;
119139 }
120140
121- for ( var i = _entries . Count - 1 ; i >= 0 ; i -- )
141+ for ( var i = _count - 1 ; i >= 0 ; i -- )
122142 {
123- var e = _entries [ i ] ;
143+ var ri = ( _head + i ) % _ring . Length ;
144+ var e = _ring [ ri ] ;
124145 if ( string . Equals ( e . Header . Name , name , StringComparison . OrdinalIgnoreCase ) &&
125146 string . Equals ( e . Header . Value , value , StringComparison . Ordinal ) )
126147 {
127- return _entries . Count - i ;
148+ return _count - i ;
128149 }
129150 }
130151
@@ -143,35 +164,53 @@ public int FindNameMatch(string name)
143164 }
144165
145166 var listIndex = absolutePos - _evictedCount ;
146- if ( listIndex < 0 || listIndex >= _entries . Count )
167+ if ( listIndex < 0 || listIndex >= _count )
147168 {
148169 return 0 ;
149170 }
150171
151- return _entries . Count - listIndex ;
172+ return _count - listIndex ;
152173 }
153174
154175 private void Evict ( )
155176 {
156- while ( CurrentSize > MaxSize && _entries . Count > 0 )
177+ while ( CurrentSize > MaxSize && _count > 0 )
157178 {
158- var oldest = _entries [ 0 ] ;
179+ var oldest = _ring [ _head ] ;
159180 CurrentSize -= oldest . EncodedSize ;
160181
161182 if ( _nameIndex . TryGetValue ( oldest . Header . Name , out var pos ) && pos == _evictedCount )
162183 {
163184 _nameIndex . Remove ( oldest . Header . Name ) ;
164185 }
165186
166- _entries . RemoveAt ( 0 ) ;
187+ _ring [ _head ] = default ;
188+ _head = ( _head + 1 ) % _ring . Length ;
189+ _count -- ;
167190 _evictedCount ++ ;
168191 }
169192 }
170193
171194 private void Clear ( )
172195 {
173- _entries . Clear ( ) ;
196+ Array . Clear ( _ring , 0 , _ring . Length ) ;
197+ _head = 0 ;
198+ _count = 0 ;
174199 _nameIndex . Clear ( ) ;
175200 CurrentSize = 0 ;
176201 }
177- }
202+
203+ private void Grow ( )
204+ {
205+ var newCapacity = _ring . Length * 2 ;
206+ var newRing = new ( HpackHeader , int , int ) [ newCapacity ] ;
207+
208+ for ( var i = 0 ; i < _count ; i ++ )
209+ {
210+ newRing [ i ] = _ring [ ( _head + i ) % _ring . Length ] ;
211+ }
212+
213+ _ring = newRing ;
214+ _head = 0 ;
215+ }
216+ }
0 commit comments