@@ -3,21 +3,25 @@ defmodule HPAX.Table do
3
3
4
4
@ enforce_keys [ :max_table_size , :huffman_encoding ]
5
5
defstruct [
6
+ :protocol_max_table_size ,
6
7
:max_table_size ,
7
8
:huffman_encoding ,
8
9
entries: [ ] ,
9
10
size: 0 ,
10
- length: 0
11
+ length: 0 ,
12
+ pending_minimum_resize: nil
11
13
]
12
14
13
15
@ type huffman_encoding ( ) :: :always | :never
14
16
15
17
@ type t ( ) :: % __MODULE__ {
18
+ protocol_max_table_size: non_neg_integer ( ) ,
16
19
max_table_size: non_neg_integer ( ) ,
17
20
huffman_encoding: huffman_encoding ( ) ,
18
21
entries: [ { binary ( ) , binary ( ) } ] ,
19
22
size: non_neg_integer ( ) ,
20
- length: non_neg_integer ( )
23
+ length: non_neg_integer ( ) ,
24
+ pending_minimum_resize: non_neg_integer ( ) | nil
21
25
}
22
26
23
27
@ static_table [
@@ -94,10 +98,14 @@ defmodule HPAX.Table do
94
98
http://httpwg.org/specs/rfc7541.html#maximum.table.size.
95
99
"""
96
100
@ spec new ( non_neg_integer ( ) , huffman_encoding ( ) ) :: t ( )
97
- def new ( max_table_size , huffman_encoding )
98
- when is_integer ( max_table_size ) and max_table_size >= 0 and
101
+ def new ( protocol_max_table_size , huffman_encoding )
102
+ when is_integer ( protocol_max_table_size ) and protocol_max_table_size >= 0 and
99
103
huffman_encoding in [ :always , :never ] do
100
- % __MODULE__ { max_table_size: max_table_size , huffman_encoding: huffman_encoding }
104
+ % __MODULE__ {
105
+ protocol_max_table_size: protocol_max_table_size ,
106
+ max_table_size: protocol_max_table_size ,
107
+ huffman_encoding: huffman_encoding
108
+ }
101
109
end
102
110
103
111
@ doc """
@@ -124,7 +132,7 @@ defmodule HPAX.Table do
124
132
125
133
size + entry_size > max_table_size ->
126
134
table
127
- |> resize ( max_table_size - entry_size )
135
+ |> evict_to_size ( max_table_size - entry_size )
128
136
|> add_header ( name , value , entry_size )
129
137
130
138
true ->
@@ -242,15 +250,78 @@ defmodule HPAX.Table do
242
250
end
243
251
244
252
@ doc """
245
- Resizes the table.
253
+ Changes the table's protocol negotiated maximum size, possibly evicting entries as needed to satisfy.
254
+
255
+ If the indicated size is less than the table's current max size, entries
256
+ will be evicted as needed to fit within the specified size, and the table's
257
+ maximum size will be decreased to the specified value. An will also be
258
+ set which will enqueue a 'dynamic table size update' command to be prefixed
259
+ to the next block encoded with this table, per RFC9113§4.3.1.
260
+
261
+ If the indicated size is greater than or equal to the table's current max size, no entries are evicted
262
+ and the table's maximum size changes to the specified value.
246
263
247
- If the existing entries do not fit in the new table size the oldest entries are evicted.
264
+ In all cases, the table's `:protocol_max_table_size` is updated accordingly
248
265
"""
249
266
@ spec resize ( t ( ) , non_neg_integer ( ) ) :: t ( )
250
- def resize ( % __MODULE__ { entries: entries , size: size } = table , new_size ) do
251
- { new_entries_reversed , new_size } = evict_towards_size ( Enum . reverse ( entries ) , size , new_size )
267
+ def resize ( % __MODULE__ { max_table_size: max_table_size } = table , new_protocol_max_table_size )
268
+ when new_protocol_max_table_size >= max_table_size do
269
+ % __MODULE__ {
270
+ table
271
+ | protocol_max_table_size: new_protocol_max_table_size ,
272
+ max_table_size: new_protocol_max_table_size
273
+ }
274
+ end
275
+
276
+ def resize ( % __MODULE__ { } = table , new_protocol_max_table_size ) do
277
+ pending_minimum_resize =
278
+ case table . pending_minimum_resize do
279
+ nil -> new_protocol_max_table_size
280
+ current -> min ( current , new_protocol_max_table_size )
281
+ end
282
+
283
+ % __MODULE__ {
284
+ evict_to_size ( table , new_protocol_max_table_size )
285
+ | protocol_max_table_size: new_protocol_max_table_size ,
286
+ max_table_size: new_protocol_max_table_size ,
287
+ pending_minimum_resize: pending_minimum_resize
288
+ }
289
+ end
290
+
291
+ def dynamic_resize ( % __MODULE__ { } = table , new_max_table_size ) do
292
+ % __MODULE__ {
293
+ evict_to_size ( table , new_max_table_size )
294
+ | max_table_size: new_max_table_size
295
+ }
296
+ end
297
+
298
+ @ doc """
299
+ Returns (and clears) any pending resize events on the table which will need to be signalled to
300
+ the decoder via dynamic table size update messages. Intended to be called at the start of any
301
+ block encode to prepend such dynamic table size update(s) as needed. The value of
302
+ `pending_minimum_resize` indicates the smallest maximum size of this table which has not yet
303
+ been signalled to the decoder, and is always included in the list returned if it is set.
304
+ Additionally, if the current max table size is larger than this value, it is also included int
305
+ the list, per https://www.rfc-editor.org/rfc/rfc7541#section-4.2
306
+ """
307
+ def pop_pending_resizes ( % { pending_minimum_resize: nil } = table ) , do: { table , [ ] }
308
+
309
+ def pop_pending_resizes ( table ) do
310
+ pending_resizes =
311
+ if table . max_table_size > table . pending_minimum_resize ,
312
+ do: [ table . pending_minimum_resize , table . max_table_size ] ,
313
+ else: [ table . pending_minimum_resize ]
314
+
315
+ { % __MODULE__ { table | pending_minimum_resize: nil } , pending_resizes }
316
+ end
317
+
318
+ # Removes records as necessary to have the total size of entries within the table be less than
319
+ # or equal to the specified value. Does not change the table's max size.
320
+ defp evict_to_size ( % __MODULE__ { entries: entries , size: size } = table , new_size ) do
321
+ { new_entries_reversed , new_size } =
322
+ evict_towards_size ( Enum . reverse ( entries ) , size , new_size )
252
323
253
- % {
324
+ % __MODULE__ {
254
325
table
255
326
| entries: Enum . reverse ( new_entries_reversed ) ,
256
327
size: new_size ,
0 commit comments