Skip to content
Open
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
54 changes: 32 additions & 22 deletions src/decoder.ml
Original file line number Diff line number Diff line change
Expand Up @@ -64,29 +64,39 @@ let header_field table max_size prefix prefix_length =
(name, value)

let rec header ({table; max_size_limit; max_field_size} as decoder) =
let* b = any_uint8 in
if b >= 32 && b < 64 then
let* max_size = any_int b 5 <* commit in
if max_size <= max_size_limit then begin
Dynamic_table.change_max_size table max_size;
header decoder
end else fail "exceeded size limit"
else if table.max_size > max_size_limit then
fail "exceeded size limit"
else if b >= 128 then
let* index = any_int b 7 in
match get_indexed_field table index with
| name, value -> return (Header.make name value)
| exception Invalid_index -> fail "invalid index"
else if b >= 64 then
let* (name, value) = header_field table max_field_size b 6 <* commit in
Dynamic_table.add table (name, value) |> ignore;
return (Header.make name value)
else
let* (name, value) = header_field table max_field_size b 4 <* commit in
return (Header.make ~never_index:(b >= 16) name value)
at_end_of_input >>= function
| true -> return None
| false ->
let* b = any_uint8 in
if b >= 32 && b < 64 then
let* max_size = any_int b 5 <* commit in
if max_size <= max_size_limit then begin
Dynamic_table.change_max_size table max_size;
header decoder
end else fail "exceeded size limit"
else if table.max_size > max_size_limit then
fail "exceeded size limit"
else if b >= 128 then
let* index = any_int b 7 <* commit in
match get_indexed_field table index with
| name, value -> return (Some (Header.make name value))
| exception Invalid_index -> fail "invalid index"
else if b >= 64 then
let* (name, value) = header_field table max_field_size b 6 <* commit in
Dynamic_table.add table (name, value) |> ignore;
return (Some (Header.make name value))
else
let* (name, value) = header_field table max_field_size b 4 <* commit in
return (Some (Header.make ~never_index:(b >= 16) name value))

let headers t = many (header t) <* end_of_input
let headers t =
fix begin fun m ->
header t >>= function
| Some h ->
let* hs = m in
return (h :: hs)
| None -> return []
end

let change_table_size_limit ({table; _} as decoder) max_size_limit =
if max_size_limit < 0 then
Expand Down
2 changes: 1 addition & 1 deletion src/decoder.mli
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ val create : ?max_size_limit:int -> ?max_field_size:int -> unit -> t
field. The default is 4096.
*)

val header : t -> Header.t Angstrom.t
val header : t -> Header.t option Angstrom.t
(** A parser for one header *)

val headers : t -> Header.t list Angstrom.t
Expand Down
50 changes: 31 additions & 19 deletions test/test.ml
Original file line number Diff line number Diff line change
Expand Up @@ -25,25 +25,28 @@ let rec headers_of_entries = function
| Size _ :: entries -> headers_of_entries entries
| [] -> []

let test entries s = [
"Encode", `Quick, begin fun () ->
let t = Faraday.create 100 in
let encoder = Encoder.create () in
entries |> List.iter begin function
| Header header -> Encoder.encode_header encoder t header
| Size size -> Encoder.change_table_size encoder size
end;
let s' = Faraday.serialize_to_string t in
Alcotest.(check encoding) "same encoding" s s'
end;
"Decode", `Quick, begin fun () ->
let decoder = Decoder.create () in
Alcotest.(check (result (list header) reject))
"same headers"
(Angstrom.parse_string (Decoder.headers decoder) s)
(Ok (headers_of_entries entries))
end;
]
let test_encode entries s =
("Encode", `Quick, begin fun () ->
let t = Faraday.create 100 in
let encoder = Encoder.create () in
entries |> List.iter begin function
| Header header -> Encoder.encode_header encoder t header
| Size size -> Encoder.change_table_size encoder size
end;
let s' = Faraday.serialize_to_string t in
Alcotest.(check encoding) "same encoding" s s'
end)

let test_decode entries s =
("Decode", `Quick, begin fun () ->
let decoder = Decoder.create () in
Alcotest.(check (result (list header) reject))
"same headers"
(Angstrom.parse_string (Decoder.headers decoder) s)
(Ok (headers_of_entries entries))
end)

let test entries s = [test_encode entries s; test_decode entries s]

let test_static =
let entries = [
Expand Down Expand Up @@ -126,6 +129,13 @@ let test_eviction =
\x40\x04ABCD\x03XYZ" in
test entries s

let test_empty = test [] ""

let test_resize_only = [
test_encode [Size 0] "";
test_decode [Size 0] "\x20"
]

let () =
Alcotest.run "Hpack" [
"Static Indexing", test_static; (* RFC7541§2.3.1 *)
Expand All @@ -135,4 +145,6 @@ let () =
"Never Index", test_never_index; (* RFC7541§7.1.3 *)
"Table Size Update", test_size_update; (* RFC7541§4.2 *)
"Entry Eviction", test_eviction; (* RFC7541§4.4 *)
"Empty header list", test_empty;
"Empty header list with table size update", test_resize_only;
]