Skip to content

Commit 9f3ded0

Browse files
authored
Improved checking of dynamic resize updates (#2)
1 parent 6321eec commit 9f3ded0

File tree

2 files changed

+69
-6
lines changed

2 files changed

+69
-6
lines changed

lib/hpax.ex

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,21 @@ defmodule HPAX do
7777
"""
7878
@spec decode(binary(), Table.t()) ::
7979
{:ok, [{header_name(), header_value()}], Table.t()} | {:error, term()}
80+
81+
# Dynamic resizes must occur only at the start of a block
82+
# https://datatracker.ietf.org/doc/html/rfc7541#section-4.2
83+
def decode(<<0b001::3, rest::bitstring>>, %Table{} = table) do
84+
{new_size, rest} = decode_integer(rest, 5)
85+
86+
# Dynamic resizes must be less than max table size
87+
# https://datatracker.ietf.org/doc/html/rfc7541#section-6.3
88+
if new_size <= table.max_table_size do
89+
decode(rest, Table.resize(table, new_size))
90+
else
91+
{:error, :protocol_error}
92+
end
93+
end
94+
8095
def decode(block, %Table{} = table) when is_binary(block) do
8196
decode_headers(block, table, _acc = [])
8297
catch
@@ -180,12 +195,6 @@ defmodule HPAX do
180195
decode_headers(rest, table, [{name, value} | acc])
181196
end
182197

183-
# Dynamic table size update
184-
defp decode_headers(<<0b001::3, rest::bitstring>>, table, acc) do
185-
{new_size, rest} = decode_integer(rest, 5)
186-
decode_headers(rest, Table.resize(table, new_size), acc)
187-
end
188-
189198
defp decode_headers(_other, _table, _acc) do
190199
throw({:hpax, :protocol_error})
191200
end

test/hpax_test.exs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,56 @@ defmodule HPAXTest do
4747
assert dec_table.entries == [{"b", "B"}, {"a", "A"}]
4848
end
4949

50+
# https://datatracker.ietf.org/doc/html/rfc7541#section-4.2
51+
property "decode/2 accepts dynamic resizes at the start of a block" do
52+
enc_table = HPAX.new(20_000)
53+
# Start with a non-empty decode table
54+
dec_table = HPAX.new(20_000)
55+
{encoded, _enc_table} = HPAX.encode([{:store, "bogus", "BOGUS"}], dec_table)
56+
encoded = IO.iodata_to_binary(encoded)
57+
assert {:ok, _decoded, dec_table} = HPAX.decode(encoded, dec_table)
58+
assert dec_table.size > 0
59+
60+
check all headers_to_encode <- list_of(header_with_store(), min_length: 1) do
61+
assert {encoded, enc_table} = HPAX.encode(headers_to_encode, enc_table)
62+
encoded = IO.iodata_to_binary(encoded)
63+
assert {:ok, _decoded, new_dec_table} = HPAX.decode(encoded, dec_table)
64+
assert new_dec_table.size > enc_table.size
65+
66+
# Now prepend a table zeroing to the beginning and ensure that we are exactly
67+
# the same size as the encode table
68+
encoded = <<0b001::3, 0::5>> <> encoded
69+
assert {:ok, _decoded, new_dec_table} = HPAX.decode(encoded, dec_table)
70+
assert new_dec_table.size == enc_table.size
71+
end
72+
end
73+
74+
# https://datatracker.ietf.org/doc/html/rfc7541#section-4.2
75+
property "decode/2 rejects dynamic resizes anywhere but at the start of a block" do
76+
enc_table = HPAX.new(20_000)
77+
dec_table = HPAX.new(20_000)
78+
79+
check all headers_to_encode <- list_of(header_with_store(), min_length: 1) do
80+
assert {encoded, _enc_table} = HPAX.encode(headers_to_encode, enc_table)
81+
82+
encoded = IO.iodata_to_binary(encoded) <> <<0b001::3, 0::5>>
83+
assert {:error, :protocol_error} = HPAX.decode(encoded, dec_table)
84+
end
85+
end
86+
87+
# https://datatracker.ietf.org/doc/html/rfc7541#section-6.2
88+
property "decode/2 rejects dynamic resizes larger than the original table size" do
89+
enc_table = HPAX.new(29)
90+
dec_table = HPAX.new(29)
91+
92+
check all headers_to_encode <- list_of(header_with_store(), min_length: 1) do
93+
assert {encoded, _enc_table} = HPAX.encode(headers_to_encode, enc_table)
94+
95+
encoded = <<0b001::3, 0b11110::5>> <> IO.iodata_to_binary(encoded)
96+
assert {:error, :protocol_error} = HPAX.decode(encoded, dec_table)
97+
end
98+
end
99+
50100
property "encoding then decoding headers is circular" do
51101
table = HPAX.new(500)
52102

@@ -86,6 +136,10 @@ defmodule HPAXTest do
86136
end
87137
end
88138

139+
defp header_with_store() do
140+
map(header(), fn {name, value} -> {:store, name, value} end)
141+
end
142+
89143
# Header generator.
90144
defp header_with_action() do
91145
action = member_of([:store, :store_name, :no_store, :never_store])

0 commit comments

Comments
 (0)