@@ -3,21 +3,25 @@ defmodule HPAX.Table do
33
44 @ enforce_keys [ :max_table_size , :huffman_encoding ]
55 defstruct [
6+ :protocol_max_table_size ,
67 :max_table_size ,
78 :huffman_encoding ,
89 entries: [ ] ,
910 size: 0 ,
10- length: 0
11+ length: 0 ,
12+ pending_minimum_resize: nil
1113 ]
1214
1315 @ type huffman_encoding ( ) :: :always | :never
1416
1517 @ type t ( ) :: % __MODULE__ {
18+ protocol_max_table_size: non_neg_integer ( ) ,
1619 max_table_size: non_neg_integer ( ) ,
1720 huffman_encoding: huffman_encoding ( ) ,
1821 entries: [ { binary ( ) , binary ( ) } ] ,
1922 size: non_neg_integer ( ) ,
20- length: non_neg_integer ( )
23+ length: non_neg_integer ( ) ,
24+ pending_minimum_resize: non_neg_integer ( ) | nil
2125 }
2226
2327 @ static_table [
@@ -94,10 +98,14 @@ defmodule HPAX.Table do
9498 http://httpwg.org/specs/rfc7541.html#maximum.table.size.
9599 """
96100 @ 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
99103 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+ }
101109 end
102110
103111 @ doc """
@@ -124,7 +132,7 @@ defmodule HPAX.Table do
124132
125133 size + entry_size > max_table_size ->
126134 table
127- |> resize ( max_table_size - entry_size )
135+ |> evict_to_size ( max_table_size - entry_size )
128136 |> add_header ( name , value , entry_size )
129137
130138 true ->
@@ -242,15 +250,78 @@ defmodule HPAX.Table do
242250 end
243251
244252 @ 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.
246263
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
248265 """
249266 @ 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 )
252323
253- % {
324+ % __MODULE__ {
254325 table
255326 | entries: Enum . reverse ( new_entries_reversed ) ,
256327 size: new_size ,
0 commit comments