diff --git a/src/alpacki.gleam b/src/alpacki.gleam index ad52764..87d231d 100644 --- a/src/alpacki.gleam +++ b/src/alpacki.gleam @@ -112,7 +112,6 @@ import gleam/int import gleam/list import gleam/option.{type Option, None, Some} import gleam/result -import gleam/string /// Errors that can occur when decoding a header block or its components. pub type DecodeError { @@ -124,10 +123,6 @@ pub type DecodeError { InvalidEncoding /// A header field referenced a table index that does not exist. InvalidTableIndex - /// A literal header name contained bytes outside the allowed range. - InvalidHeaderName - /// A header value contained bytes that are not valid UTF-8. - InvalidHeaderValue /// Huffman-encoded data was malformed or had invalid padding. InvalidHuffmanEncoding } @@ -421,69 +416,69 @@ pub fn encode_huffman(data: BitArray) -> BitArray { /// the name-value pair or an error if the index is out of range. /// /// See: [RFC 7541 Appendix A](https://datatracker.ietf.org/doc/html/rfc7541#appendix-A) -pub fn lookup_static(index: Int) -> Result(#(String, String), Nil) { +pub fn lookup_static(index: Int) -> Result(#(BitArray, BitArray), Nil) { case index { - 1 -> Ok(#(":authority", "")) - 2 -> Ok(#(":method", "GET")) - 3 -> Ok(#(":method", "POST")) - 4 -> Ok(#(":path", "/")) - 5 -> Ok(#(":path", "/index.html")) - 6 -> Ok(#(":scheme", "http")) - 7 -> Ok(#(":scheme", "https")) - 8 -> Ok(#(":status", "200")) - 9 -> Ok(#(":status", "204")) - 10 -> Ok(#(":status", "206")) - 11 -> Ok(#(":status", "304")) - 12 -> Ok(#(":status", "400")) - 13 -> Ok(#(":status", "404")) - 14 -> Ok(#(":status", "500")) - 15 -> Ok(#("accept-charset", "")) - 16 -> Ok(#("accept-encoding", "gzip, deflate")) - 17 -> Ok(#("accept-language", "")) - 18 -> Ok(#("accept-ranges", "")) - 19 -> Ok(#("accept", "")) - 20 -> Ok(#("access-control-allow-origin", "")) - 21 -> Ok(#("age", "")) - 22 -> Ok(#("allow", "")) - 23 -> Ok(#("authorization", "")) - 24 -> Ok(#("cache-control", "")) - 25 -> Ok(#("content-disposition", "")) - 26 -> Ok(#("content-encoding", "")) - 27 -> Ok(#("content-language", "")) - 28 -> Ok(#("content-length", "")) - 29 -> Ok(#("content-location", "")) - 30 -> Ok(#("content-range", "")) - 31 -> Ok(#("content-type", "")) - 32 -> Ok(#("cookie", "")) - 33 -> Ok(#("date", "")) - 34 -> Ok(#("etag", "")) - 35 -> Ok(#("expect", "")) - 36 -> Ok(#("expires", "")) - 37 -> Ok(#("from", "")) - 38 -> Ok(#("host", "")) - 39 -> Ok(#("if-match", "")) - 40 -> Ok(#("if-modified-since", "")) - 41 -> Ok(#("if-none-match", "")) - 42 -> Ok(#("if-range", "")) - 43 -> Ok(#("if-unmodified-since", "")) - 44 -> Ok(#("last-modified", "")) - 45 -> Ok(#("link", "")) - 46 -> Ok(#("location", "")) - 47 -> Ok(#("max-forwards", "")) - 48 -> Ok(#("proxy-authenticate", "")) - 49 -> Ok(#("proxy-authorization", "")) - 50 -> Ok(#("range", "")) - 51 -> Ok(#("referer", "")) - 52 -> Ok(#("refresh", "")) - 53 -> Ok(#("retry-after", "")) - 54 -> Ok(#("server", "")) - 55 -> Ok(#("set-cookie", "")) - 56 -> Ok(#("strict-transport-security", "")) - 57 -> Ok(#("transfer-encoding", "")) - 58 -> Ok(#("user-agent", "")) - 59 -> Ok(#("vary", "")) - 60 -> Ok(#("via", "")) - 61 -> Ok(#("www-authenticate", "")) + 1 -> Ok(#(<<":authority":utf8>>, <<>>)) + 2 -> Ok(#(<<":method":utf8>>, <<"GET":utf8>>)) + 3 -> Ok(#(<<":method":utf8>>, <<"POST":utf8>>)) + 4 -> Ok(#(<<":path":utf8>>, <<"/":utf8>>)) + 5 -> Ok(#(<<":path":utf8>>, <<"/index.html":utf8>>)) + 6 -> Ok(#(<<":scheme":utf8>>, <<"http":utf8>>)) + 7 -> Ok(#(<<":scheme":utf8>>, <<"https":utf8>>)) + 8 -> Ok(#(<<":status":utf8>>, <<"200":utf8>>)) + 9 -> Ok(#(<<":status":utf8>>, <<"204":utf8>>)) + 10 -> Ok(#(<<":status":utf8>>, <<"206":utf8>>)) + 11 -> Ok(#(<<":status":utf8>>, <<"304":utf8>>)) + 12 -> Ok(#(<<":status":utf8>>, <<"400":utf8>>)) + 13 -> Ok(#(<<":status":utf8>>, <<"404":utf8>>)) + 14 -> Ok(#(<<":status":utf8>>, <<"500":utf8>>)) + 15 -> Ok(#(<<"accept-charset":utf8>>, <<>>)) + 16 -> Ok(#(<<"accept-encoding":utf8>>, <<"gzip, deflate":utf8>>)) + 17 -> Ok(#(<<"accept-language":utf8>>, <<>>)) + 18 -> Ok(#(<<"accept-ranges":utf8>>, <<>>)) + 19 -> Ok(#(<<"accept":utf8>>, <<>>)) + 20 -> Ok(#(<<"access-control-allow-origin":utf8>>, <<>>)) + 21 -> Ok(#(<<"age":utf8>>, <<>>)) + 22 -> Ok(#(<<"allow":utf8>>, <<>>)) + 23 -> Ok(#(<<"authorization":utf8>>, <<>>)) + 24 -> Ok(#(<<"cache-control":utf8>>, <<>>)) + 25 -> Ok(#(<<"content-disposition":utf8>>, <<>>)) + 26 -> Ok(#(<<"content-encoding":utf8>>, <<>>)) + 27 -> Ok(#(<<"content-language":utf8>>, <<>>)) + 28 -> Ok(#(<<"content-length":utf8>>, <<>>)) + 29 -> Ok(#(<<"content-location":utf8>>, <<>>)) + 30 -> Ok(#(<<"content-range":utf8>>, <<>>)) + 31 -> Ok(#(<<"content-type":utf8>>, <<>>)) + 32 -> Ok(#(<<"cookie":utf8>>, <<>>)) + 33 -> Ok(#(<<"date":utf8>>, <<>>)) + 34 -> Ok(#(<<"etag":utf8>>, <<>>)) + 35 -> Ok(#(<<"expect":utf8>>, <<>>)) + 36 -> Ok(#(<<"expires":utf8>>, <<>>)) + 37 -> Ok(#(<<"from":utf8>>, <<>>)) + 38 -> Ok(#(<<"host":utf8>>, <<>>)) + 39 -> Ok(#(<<"if-match":utf8>>, <<>>)) + 40 -> Ok(#(<<"if-modified-since":utf8>>, <<>>)) + 41 -> Ok(#(<<"if-none-match":utf8>>, <<>>)) + 42 -> Ok(#(<<"if-range":utf8>>, <<>>)) + 43 -> Ok(#(<<"if-unmodified-since":utf8>>, <<>>)) + 44 -> Ok(#(<<"last-modified":utf8>>, <<>>)) + 45 -> Ok(#(<<"link":utf8>>, <<>>)) + 46 -> Ok(#(<<"location":utf8>>, <<>>)) + 47 -> Ok(#(<<"max-forwards":utf8>>, <<>>)) + 48 -> Ok(#(<<"proxy-authenticate":utf8>>, <<>>)) + 49 -> Ok(#(<<"proxy-authorization":utf8>>, <<>>)) + 50 -> Ok(#(<<"range":utf8>>, <<>>)) + 51 -> Ok(#(<<"referer":utf8>>, <<>>)) + 52 -> Ok(#(<<"refresh":utf8>>, <<>>)) + 53 -> Ok(#(<<"retry-after":utf8>>, <<>>)) + 54 -> Ok(#(<<"server":utf8>>, <<>>)) + 55 -> Ok(#(<<"set-cookie":utf8>>, <<>>)) + 56 -> Ok(#(<<"strict-transport-security":utf8>>, <<>>)) + 57 -> Ok(#(<<"transfer-encoding":utf8>>, <<>>)) + 58 -> Ok(#(<<"user-agent":utf8>>, <<>>)) + 59 -> Ok(#(<<"vary":utf8>>, <<>>)) + 60 -> Ok(#(<<"via":utf8>>, <<>>)) + 61 -> Ok(#(<<"www-authenticate":utf8>>, <<>>)) _ -> Error(Nil) } } @@ -493,121 +488,121 @@ pub fn lookup_static(index: Int) -> Result(#(String, String), Nil) { /// only the name matches, or `NoMatch`. /// /// See: [RFC 7541 Appendix A](https://datatracker.ietf.org/doc/html/rfc7541#appendix-A) -pub fn match_static(name: String, value: String) -> TableMatch { +pub fn match_static(name: BitArray, value: BitArray) -> TableMatch { case name, value { - ":authority", "" -> FullMatch(1) - ":method", "GET" -> FullMatch(2) - ":method", "POST" -> FullMatch(3) - ":path", "/" -> FullMatch(4) - ":path", "/index.html" -> FullMatch(5) - ":scheme", "http" -> FullMatch(6) - ":scheme", "https" -> FullMatch(7) - ":status", "200" -> FullMatch(8) - ":status", "204" -> FullMatch(9) - ":status", "206" -> FullMatch(10) - ":status", "304" -> FullMatch(11) - ":status", "400" -> FullMatch(12) - ":status", "404" -> FullMatch(13) - ":status", "500" -> FullMatch(14) - "accept-charset", "" -> FullMatch(15) - "accept-encoding", "gzip, deflate" -> FullMatch(16) - "accept-language", "" -> FullMatch(17) - "accept-ranges", "" -> FullMatch(18) - "accept", "" -> FullMatch(19) - "access-control-allow-origin", "" -> FullMatch(20) - "age", "" -> FullMatch(21) - "allow", "" -> FullMatch(22) - "authorization", "" -> FullMatch(23) - "cache-control", "" -> FullMatch(24) - "content-disposition", "" -> FullMatch(25) - "content-encoding", "" -> FullMatch(26) - "content-language", "" -> FullMatch(27) - "content-length", "" -> FullMatch(28) - "content-location", "" -> FullMatch(29) - "content-range", "" -> FullMatch(30) - "content-type", "" -> FullMatch(31) - "cookie", "" -> FullMatch(32) - "date", "" -> FullMatch(33) - "etag", "" -> FullMatch(34) - "expect", "" -> FullMatch(35) - "expires", "" -> FullMatch(36) - "from", "" -> FullMatch(37) - "host", "" -> FullMatch(38) - "if-match", "" -> FullMatch(39) - "if-modified-since", "" -> FullMatch(40) - "if-none-match", "" -> FullMatch(41) - "if-range", "" -> FullMatch(42) - "if-unmodified-since", "" -> FullMatch(43) - "last-modified", "" -> FullMatch(44) - "link", "" -> FullMatch(45) - "location", "" -> FullMatch(46) - "max-forwards", "" -> FullMatch(47) - "proxy-authenticate", "" -> FullMatch(48) - "proxy-authorization", "" -> FullMatch(49) - "range", "" -> FullMatch(50) - "referer", "" -> FullMatch(51) - "refresh", "" -> FullMatch(52) - "retry-after", "" -> FullMatch(53) - "server", "" -> FullMatch(54) - "set-cookie", "" -> FullMatch(55) - "strict-transport-security", "" -> FullMatch(56) - "transfer-encoding", "" -> FullMatch(57) - "user-agent", "" -> FullMatch(58) - "vary", "" -> FullMatch(59) - "via", "" -> FullMatch(60) - "www-authenticate", "" -> FullMatch(61) - ":authority", _ -> NameMatch(1) - ":method", _ -> NameMatch(2) - ":path", _ -> NameMatch(4) - ":scheme", _ -> NameMatch(6) - ":status", _ -> NameMatch(8) - "accept-charset", _ -> NameMatch(15) - "accept-encoding", _ -> NameMatch(16) - "accept-language", _ -> NameMatch(17) - "accept-ranges", _ -> NameMatch(18) - "accept", _ -> NameMatch(19) - "access-control-allow-origin", _ -> NameMatch(20) - "age", _ -> NameMatch(21) - "allow", _ -> NameMatch(22) - "authorization", _ -> NameMatch(23) - "cache-control", _ -> NameMatch(24) - "content-disposition", _ -> NameMatch(25) - "content-encoding", _ -> NameMatch(26) - "content-language", _ -> NameMatch(27) - "content-length", _ -> NameMatch(28) - "content-location", _ -> NameMatch(29) - "content-range", _ -> NameMatch(30) - "content-type", _ -> NameMatch(31) - "cookie", _ -> NameMatch(32) - "date", _ -> NameMatch(33) - "etag", _ -> NameMatch(34) - "expect", _ -> NameMatch(35) - "expires", _ -> NameMatch(36) - "from", _ -> NameMatch(37) - "host", _ -> NameMatch(38) - "if-match", _ -> NameMatch(39) - "if-modified-since", _ -> NameMatch(40) - "if-none-match", _ -> NameMatch(41) - "if-range", _ -> NameMatch(42) - "if-unmodified-since", _ -> NameMatch(43) - "last-modified", _ -> NameMatch(44) - "link", _ -> NameMatch(45) - "location", _ -> NameMatch(46) - "max-forwards", _ -> NameMatch(47) - "proxy-authenticate", _ -> NameMatch(48) - "proxy-authorization", _ -> NameMatch(49) - "range", _ -> NameMatch(50) - "referer", _ -> NameMatch(51) - "refresh", _ -> NameMatch(52) - "retry-after", _ -> NameMatch(53) - "server", _ -> NameMatch(54) - "set-cookie", _ -> NameMatch(55) - "strict-transport-security", _ -> NameMatch(56) - "transfer-encoding", _ -> NameMatch(57) - "user-agent", _ -> NameMatch(58) - "vary", _ -> NameMatch(59) - "via", _ -> NameMatch(60) - "www-authenticate", _ -> NameMatch(61) + <<":authority":utf8>>, <<>> -> FullMatch(1) + <<":method":utf8>>, <<"GET":utf8>> -> FullMatch(2) + <<":method":utf8>>, <<"POST":utf8>> -> FullMatch(3) + <<":path":utf8>>, <<"/":utf8>> -> FullMatch(4) + <<":path":utf8>>, <<"/index.html":utf8>> -> FullMatch(5) + <<":scheme":utf8>>, <<"http":utf8>> -> FullMatch(6) + <<":scheme":utf8>>, <<"https":utf8>> -> FullMatch(7) + <<":status":utf8>>, <<"200":utf8>> -> FullMatch(8) + <<":status":utf8>>, <<"204":utf8>> -> FullMatch(9) + <<":status":utf8>>, <<"206":utf8>> -> FullMatch(10) + <<":status":utf8>>, <<"304":utf8>> -> FullMatch(11) + <<":status":utf8>>, <<"400":utf8>> -> FullMatch(12) + <<":status":utf8>>, <<"404":utf8>> -> FullMatch(13) + <<":status":utf8>>, <<"500":utf8>> -> FullMatch(14) + <<"accept-charset":utf8>>, <<>> -> FullMatch(15) + <<"accept-encoding":utf8>>, <<"gzip, deflate":utf8>> -> FullMatch(16) + <<"accept-language":utf8>>, <<>> -> FullMatch(17) + <<"accept-ranges":utf8>>, <<>> -> FullMatch(18) + <<"accept":utf8>>, <<>> -> FullMatch(19) + <<"access-control-allow-origin":utf8>>, <<>> -> FullMatch(20) + <<"age":utf8>>, <<>> -> FullMatch(21) + <<"allow":utf8>>, <<>> -> FullMatch(22) + <<"authorization":utf8>>, <<>> -> FullMatch(23) + <<"cache-control":utf8>>, <<>> -> FullMatch(24) + <<"content-disposition":utf8>>, <<>> -> FullMatch(25) + <<"content-encoding":utf8>>, <<>> -> FullMatch(26) + <<"content-language":utf8>>, <<>> -> FullMatch(27) + <<"content-length":utf8>>, <<>> -> FullMatch(28) + <<"content-location":utf8>>, <<>> -> FullMatch(29) + <<"content-range":utf8>>, <<>> -> FullMatch(30) + <<"content-type":utf8>>, <<>> -> FullMatch(31) + <<"cookie":utf8>>, <<>> -> FullMatch(32) + <<"date":utf8>>, <<>> -> FullMatch(33) + <<"etag":utf8>>, <<>> -> FullMatch(34) + <<"expect":utf8>>, <<>> -> FullMatch(35) + <<"expires":utf8>>, <<>> -> FullMatch(36) + <<"from":utf8>>, <<>> -> FullMatch(37) + <<"host":utf8>>, <<>> -> FullMatch(38) + <<"if-match":utf8>>, <<>> -> FullMatch(39) + <<"if-modified-since":utf8>>, <<>> -> FullMatch(40) + <<"if-none-match":utf8>>, <<>> -> FullMatch(41) + <<"if-range":utf8>>, <<>> -> FullMatch(42) + <<"if-unmodified-since":utf8>>, <<>> -> FullMatch(43) + <<"last-modified":utf8>>, <<>> -> FullMatch(44) + <<"link":utf8>>, <<>> -> FullMatch(45) + <<"location":utf8>>, <<>> -> FullMatch(46) + <<"max-forwards":utf8>>, <<>> -> FullMatch(47) + <<"proxy-authenticate":utf8>>, <<>> -> FullMatch(48) + <<"proxy-authorization":utf8>>, <<>> -> FullMatch(49) + <<"range":utf8>>, <<>> -> FullMatch(50) + <<"referer":utf8>>, <<>> -> FullMatch(51) + <<"refresh":utf8>>, <<>> -> FullMatch(52) + <<"retry-after":utf8>>, <<>> -> FullMatch(53) + <<"server":utf8>>, <<>> -> FullMatch(54) + <<"set-cookie":utf8>>, <<>> -> FullMatch(55) + <<"strict-transport-security":utf8>>, <<>> -> FullMatch(56) + <<"transfer-encoding":utf8>>, <<>> -> FullMatch(57) + <<"user-agent":utf8>>, <<>> -> FullMatch(58) + <<"vary":utf8>>, <<>> -> FullMatch(59) + <<"via":utf8>>, <<>> -> FullMatch(60) + <<"www-authenticate":utf8>>, <<>> -> FullMatch(61) + <<":authority":utf8>>, _ -> NameMatch(1) + <<":method":utf8>>, _ -> NameMatch(2) + <<":path":utf8>>, _ -> NameMatch(4) + <<":scheme":utf8>>, _ -> NameMatch(6) + <<":status":utf8>>, _ -> NameMatch(8) + <<"accept-charset":utf8>>, _ -> NameMatch(15) + <<"accept-encoding":utf8>>, _ -> NameMatch(16) + <<"accept-language":utf8>>, _ -> NameMatch(17) + <<"accept-ranges":utf8>>, _ -> NameMatch(18) + <<"accept":utf8>>, _ -> NameMatch(19) + <<"access-control-allow-origin":utf8>>, _ -> NameMatch(20) + <<"age":utf8>>, _ -> NameMatch(21) + <<"allow":utf8>>, _ -> NameMatch(22) + <<"authorization":utf8>>, _ -> NameMatch(23) + <<"cache-control":utf8>>, _ -> NameMatch(24) + <<"content-disposition":utf8>>, _ -> NameMatch(25) + <<"content-encoding":utf8>>, _ -> NameMatch(26) + <<"content-language":utf8>>, _ -> NameMatch(27) + <<"content-length":utf8>>, _ -> NameMatch(28) + <<"content-location":utf8>>, _ -> NameMatch(29) + <<"content-range":utf8>>, _ -> NameMatch(30) + <<"content-type":utf8>>, _ -> NameMatch(31) + <<"cookie":utf8>>, _ -> NameMatch(32) + <<"date":utf8>>, _ -> NameMatch(33) + <<"etag":utf8>>, _ -> NameMatch(34) + <<"expect":utf8>>, _ -> NameMatch(35) + <<"expires":utf8>>, _ -> NameMatch(36) + <<"from":utf8>>, _ -> NameMatch(37) + <<"host":utf8>>, _ -> NameMatch(38) + <<"if-match":utf8>>, _ -> NameMatch(39) + <<"if-modified-since":utf8>>, _ -> NameMatch(40) + <<"if-none-match":utf8>>, _ -> NameMatch(41) + <<"if-range":utf8>>, _ -> NameMatch(42) + <<"if-unmodified-since":utf8>>, _ -> NameMatch(43) + <<"last-modified":utf8>>, _ -> NameMatch(44) + <<"link":utf8>>, _ -> NameMatch(45) + <<"location":utf8>>, _ -> NameMatch(46) + <<"max-forwards":utf8>>, _ -> NameMatch(47) + <<"proxy-authenticate":utf8>>, _ -> NameMatch(48) + <<"proxy-authorization":utf8>>, _ -> NameMatch(49) + <<"range":utf8>>, _ -> NameMatch(50) + <<"referer":utf8>>, _ -> NameMatch(51) + <<"refresh":utf8>>, _ -> NameMatch(52) + <<"retry-after":utf8>>, _ -> NameMatch(53) + <<"server":utf8>>, _ -> NameMatch(54) + <<"set-cookie":utf8>>, _ -> NameMatch(55) + <<"strict-transport-security":utf8>>, _ -> NameMatch(56) + <<"transfer-encoding":utf8>>, _ -> NameMatch(57) + <<"user-agent":utf8>>, _ -> NameMatch(58) + <<"vary":utf8>>, _ -> NameMatch(59) + <<"via":utf8>>, _ -> NameMatch(60) + <<"www-authenticate":utf8>>, _ -> NameMatch(61) _, _ -> NoMatch } } @@ -622,7 +617,7 @@ pub fn match_static(name: String, value: String) -> TableMatch { /// See: [RFC 7541 Section 2.3.2](https://datatracker.ietf.org/doc/html/rfc7541#section-2.3.2) pub opaque type DynamicTable { DynamicTable( - entries: List(#(String, String)), + entries: List(#(BitArray, BitArray)), size: Int, max_size: Int, length: Int, @@ -671,8 +666,8 @@ pub fn new_dynamic(max_size: Int) -> DynamicTable { /// See: [RFC 7541 Section 4.4](https://datatracker.ietf.org/doc/html/rfc7541#section-4.4) pub fn add_dynamic( table: DynamicTable, - name: String, - value: String, + name: BitArray, + value: BitArray, ) -> DynamicTable { let entry_size = calculate_entry_size(name, value) @@ -698,7 +693,7 @@ pub fn add_dynamic( pub fn lookup_dynamic( table: DynamicTable, index: Int, -) -> Result(#(String, String), Nil) { +) -> Result(#(BitArray, BitArray), Nil) { case index < dynamic_table_start { True -> Error(Nil) False -> { @@ -718,8 +713,8 @@ pub fn lookup_dynamic( /// See: [RFC 7541 Section 2.3.2](https://datatracker.ietf.org/doc/html/rfc7541#section-2.3.2) pub fn match_dynamic( table: DynamicTable, - name: String, - value: String, + name: BitArray, + value: BitArray, ) -> TableMatch { case table.length { 0 -> NoMatch @@ -728,9 +723,9 @@ pub fn match_dynamic( } fn do_match_dynamic( - entries: List(#(String, String)), - name: String, - value: String, + entries: List(#(BitArray, BitArray)), + name: BitArray, + value: BitArray, position: Int, match_accumulator: TableMatch, ) -> TableMatch { @@ -806,12 +801,12 @@ fn evict_until_fits(table: DynamicTable, needed_space: Int) -> DynamicTable { } fn do_evict_until_fits( - reversed_entries: List(#(String, String)), + reversed_entries: List(#(BitArray, BitArray)), size: Int, length: Int, needed_space: Int, max_size: Int, -) -> #(List(#(String, String)), Int, Int) { +) -> #(List(#(BitArray, BitArray)), Int, Int) { case size + needed_space <= max_size { True -> #(reversed_entries, size, length) False -> @@ -856,11 +851,11 @@ fn evict_to_size(table: DynamicTable, target_size: Int) -> DynamicTable { } fn do_evict_to_size( - reversed_entries: List(#(String, String)), + reversed_entries: List(#(BitArray, BitArray)), size: Int, length: Int, target_size: Int, -) -> #(List(#(String, String)), Int, Int) { +) -> #(List(#(BitArray, BitArray)), Int, Int) { case size <= target_size { True -> #(reversed_entries, size, length) False -> @@ -880,8 +875,8 @@ fn do_evict_to_size( } // Calculates entry size per RFC 7541 Section 4.1: name + value + 32 bytes. -fn calculate_entry_size(name: String, value: String) -> Int { - string.byte_size(name) + string.byte_size(value) + entry_overhead +fn calculate_entry_size(name: BitArray, value: BitArray) -> Int { + bit_array.byte_size(name) + bit_array.byte_size(value) + entry_overhead } // Index Address Space (Section 2.3.3) @@ -913,8 +908,8 @@ pub type TableMatch { /// See: [RFC 7541 Section 2.3.3](https://datatracker.ietf.org/doc/html/rfc7541#section-2.3.3) pub fn match( dynamic_table: DynamicTable, - name: String, - value: String, + name: BitArray, + value: BitArray, ) -> TableMatch { case match_static(name, value) { NameMatch(static_index) -> { @@ -937,7 +932,7 @@ pub fn match( pub fn lookup( dynamic_table: DynamicTable, index: Int, -) -> Result(#(String, String), Nil) { +) -> Result(#(BitArray, BitArray), Nil) { case index < dynamic_table_start { True -> lookup_static(index) False -> lookup_dynamic(dynamic_table, index) @@ -967,7 +962,7 @@ pub type Indexing { /// the `indexing` mode controls the wire representation. When decoding, it /// preserves the representation chosen by the sender. pub type HeaderField { - HeaderField(name: String, value: String, indexing: Indexing) + HeaderField(name: BitArray, value: BitArray, indexing: Indexing) } /// Decodes a complete header block fragment into a list of header fields, @@ -1094,18 +1089,13 @@ fn decode_literal( data: BitArray, table: DynamicTable, prefix: Int, -) -> Result(#(String, String, BitArray), DecodeError) { +) -> Result(#(BitArray, BitArray, BitArray), DecodeError) { use #(index, remaining) <- result.try(decode_integer(data, prefix)) use #(name, remaining) <- result.try(case index { // That is a new string literal. 0 -> { use #(name, remaining) <- result.try(decode_string_literal(remaining)) - use name <- result.try( - validate_header_name(name) - |> result.replace_error(InvalidHeaderName), - ) - Ok(#(name, remaining)) } // That is a name from the table. @@ -1118,16 +1108,10 @@ fn decode_literal( }) use #(value, remaining) <- result.try(decode_string_literal(remaining)) - use value <- result.try( - bit_array.to_string(value) |> result.replace_error(InvalidHeaderValue), - ) Ok(#(name, value, remaining)) } -@external(erlang, "alpacki_ffi", "validate_header_name") -fn validate_header_name(data: BitArray) -> Result(String, Nil) - /// Encodes a list of header fields into a header block fragment, updating the /// dynamic table as entries are added. Returns the encoded block as a /// `BytesTree` and the updated dynamic table. When `huffman` is `True`, all @@ -1273,26 +1257,26 @@ fn encode_indexed(index: Int) -> BitArray { // 6.2.x Literal Header Field with name referenced by index. fn encode_literal( index: Int, - value: String, + value: BitArray, prefix: Int, type_bits: Int, huffman: Bool, ) -> BitArray { let index = encode_prefixed_integer(index, prefix, type_bits) - let value = encode_string_literal(<>, huffman:) + let value = encode_string_literal(value, huffman:) <> } // 6.2.x Literal Header Field with new name. fn encode_literal_new_name( - name: String, - value: String, + name: BitArray, + value: BitArray, prefix: Int, type_bits: Int, huffman: Bool, ) -> BitArray { let index = encode_prefixed_integer(0, prefix, type_bits) - let name = encode_string_literal(<>, huffman:) - let value = encode_string_literal(<>, huffman:) + let name = encode_string_literal(name, huffman:) + let value = encode_string_literal(value, huffman:) <> } diff --git a/src/alpacki_ffi.erl b/src/alpacki_ffi.erl deleted file mode 100644 index 06a0707..0000000 --- a/src/alpacki_ffi.erl +++ /dev/null @@ -1,53 +0,0 @@ --module(alpacki_ffi). - --export([validate_header_name/1]). - -validate_header_name(<<>>) -> - {error, nil}; -% Allowed Pseudo-Headers. -validate_header_name(<<":method">>) -> - {ok, <<":method">>}; -validate_header_name(<<":path">>) -> - {ok, <<":path">>}; -validate_header_name(<<":scheme">>) -> - {ok, <<":scheme">>}; -validate_header_name(<<":status">>) -> - {ok, <<":status">>}; -validate_header_name(<<":authority">>) -> - {ok, <<":authority">>}; -validate_header_name(<<$:, _/binary>>) -> - {error, nil}; -% Per RFC (9110 Section 5.6.2 tchar + 9113 Section 8.2.1 mentioning lowercase -% ASCII) the valid bytes are: -% - a-z -% - 0-9 -% - ! # $ % & ' * + - . ^ _ | ~ ` -% See: -% - https://datatracker.ietf.org/doc/html/rfc9110#section-5.6.2 -% - https://datatracker.ietf.org/doc/html/rfc9113#section-8.2.1 -validate_header_name(Name) -> - validate_tchar(Name, Name). - -validate_tchar(<<>>, Name) -> - {ok, Name}; -validate_tchar(<>, Name) - when C >= $a andalso C =< $z; - C >= $0 andalso C =< $9; - C =:= $!; - C =:= $#; - C =:= $$; - C =:= $%; - C =:= $&; - C =:= $'; - C =:= $*; - C =:= $+; - C =:= $-; - C =:= $.; - C =:= $^; - C =:= $_; - C =:= $`; - C =:= $|; - C =:= $~ -> - validate_tchar(Remaining, Name); -validate_tchar(_, _) -> - {error, nil}. diff --git a/test/headers_test.gleam b/test/headers_test.gleam index 0f47ede..72343e9 100644 --- a/test/headers_test.gleam +++ b/test/headers_test.gleam @@ -16,7 +16,11 @@ pub fn decode_literal_with_indexing_new_name_test() { ) assert headers == [ - alpacki.HeaderField("custom-key", "custom-header", alpacki.WithIndexing), + alpacki.HeaderField( + <<"custom-key":utf8>>, + <<"custom-header":utf8>>, + alpacki.WithIndexing, + ), ] assert alpacki.dynamic_length(table) == 1 assert alpacki.dynamic_size(table) == 55 @@ -28,7 +32,11 @@ pub fn decode_literal_without_indexing_indexed_name_test() { alpacki.decode_header_block(<<0x04, 0x0c, "/sample/path":utf8>>, table) assert headers == [ - alpacki.HeaderField(":path", "/sample/path", alpacki.WithoutIndexing), + alpacki.HeaderField( + <<":path":utf8>>, + <<"/sample/path":utf8>>, + alpacki.WithoutIndexing, + ), ] assert alpacki.dynamic_length(table) == 0 } @@ -41,7 +49,13 @@ pub fn decode_literal_never_indexed_new_name_test() { table, ) assert headers - == [alpacki.HeaderField("password", "secret", alpacki.NeverIndexed)] + == [ + alpacki.HeaderField( + <<"password":utf8>>, + <<"secret":utf8>>, + alpacki.NeverIndexed, + ), + ] } pub fn decode_indexed_test() { @@ -49,7 +63,13 @@ pub fn decode_indexed_test() { let assert Ok(#(headers, _table)) = alpacki.decode_header_block(<<0x82>>, table) assert headers - == [alpacki.HeaderField(":method", "GET", alpacki.WithIndexing)] + == [ + alpacki.HeaderField( + <<":method":utf8>>, + <<"GET":utf8>>, + alpacki.WithIndexing, + ), + ] } // Sequential Requests (RFC 7541 C.3) @@ -65,10 +85,22 @@ pub fn decode_c3_sequential_requests_test() { ) assert headers == [ - alpacki.HeaderField(":method", "GET", alpacki.WithIndexing), - alpacki.HeaderField(":scheme", "http", alpacki.WithIndexing), - alpacki.HeaderField(":path", "/", alpacki.WithIndexing), - alpacki.HeaderField(":authority", "www.example.com", alpacki.WithIndexing), + alpacki.HeaderField( + <<":method":utf8>>, + <<"GET":utf8>>, + alpacki.WithIndexing, + ), + alpacki.HeaderField( + <<":scheme":utf8>>, + <<"http":utf8>>, + alpacki.WithIndexing, + ), + alpacki.HeaderField(<<":path":utf8>>, <<"/":utf8>>, alpacki.WithIndexing), + alpacki.HeaderField( + <<":authority":utf8>>, + <<"www.example.com":utf8>>, + alpacki.WithIndexing, + ), ] assert alpacki.dynamic_length(table) == 1 assert alpacki.dynamic_size(table) == 57 @@ -81,11 +113,27 @@ pub fn decode_c3_sequential_requests_test() { ) assert headers == [ - alpacki.HeaderField(":method", "GET", alpacki.WithIndexing), - alpacki.HeaderField(":scheme", "http", alpacki.WithIndexing), - alpacki.HeaderField(":path", "/", alpacki.WithIndexing), - alpacki.HeaderField(":authority", "www.example.com", alpacki.WithIndexing), - alpacki.HeaderField("cache-control", "no-cache", alpacki.WithIndexing), + alpacki.HeaderField( + <<":method":utf8>>, + <<"GET":utf8>>, + alpacki.WithIndexing, + ), + alpacki.HeaderField( + <<":scheme":utf8>>, + <<"http":utf8>>, + alpacki.WithIndexing, + ), + alpacki.HeaderField(<<":path":utf8>>, <<"/":utf8>>, alpacki.WithIndexing), + alpacki.HeaderField( + <<":authority":utf8>>, + <<"www.example.com":utf8>>, + alpacki.WithIndexing, + ), + alpacki.HeaderField( + <<"cache-control":utf8>>, + <<"no-cache":utf8>>, + alpacki.WithIndexing, + ), ] assert alpacki.dynamic_length(table) == 2 assert alpacki.dynamic_size(table) == 110 @@ -101,11 +149,31 @@ pub fn decode_c3_sequential_requests_test() { ) assert headers == [ - alpacki.HeaderField(":method", "GET", alpacki.WithIndexing), - alpacki.HeaderField(":scheme", "https", alpacki.WithIndexing), - alpacki.HeaderField(":path", "/index.html", alpacki.WithIndexing), - alpacki.HeaderField(":authority", "www.example.com", alpacki.WithIndexing), - alpacki.HeaderField("custom-key", "custom-value", alpacki.WithIndexing), + alpacki.HeaderField( + <<":method":utf8>>, + <<"GET":utf8>>, + alpacki.WithIndexing, + ), + alpacki.HeaderField( + <<":scheme":utf8>>, + <<"https":utf8>>, + alpacki.WithIndexing, + ), + alpacki.HeaderField( + <<":path":utf8>>, + <<"/index.html":utf8>>, + alpacki.WithIndexing, + ), + alpacki.HeaderField( + <<":authority":utf8>>, + <<"www.example.com":utf8>>, + alpacki.WithIndexing, + ), + alpacki.HeaderField( + <<"custom-key":utf8>>, + <<"custom-value":utf8>>, + alpacki.WithIndexing, + ), ] assert alpacki.dynamic_length(table) == 3 assert alpacki.dynamic_size(table) == 164 @@ -126,7 +194,11 @@ pub fn decode_huffman_encoded_value_test() { ) assert headers == [ - alpacki.HeaderField(":authority", "www.example.com", alpacki.WithIndexing), + alpacki.HeaderField( + <<":authority":utf8>>, + <<"www.example.com":utf8>>, + alpacki.WithIndexing, + ), ] } @@ -138,7 +210,13 @@ pub fn decode_size_update_before_headers_test() { let assert Ok(#(headers, table)) = alpacki.decode_header_block(<<0x3f, 0x61, 0x82>>, table) assert headers - == [alpacki.HeaderField(":method", "GET", alpacki.WithIndexing)] + == [ + alpacki.HeaderField( + <<":method":utf8>>, + <<"GET":utf8>>, + alpacki.WithIndexing, + ), + ] assert alpacki.dynamic_max_size(table) == 128 } @@ -156,14 +234,20 @@ pub fn decode_size_update_to_zero_clears_table_test() { pub fn decode_consecutive_size_updates_test() { let table = alpacki.new_dynamic(4096) - |> alpacki.add_dynamic("server", "ewe") + |> alpacki.add_dynamic(<<"server":utf8>>, <<"ewe":utf8>>) assert alpacki.dynamic_length(table) == 1 // Two size updates (0 then 128) followed by indexed :method GET. // These are the same bytes encode_pending_double_resize_test produces. let assert Ok(#(headers, table)) = alpacki.decode_header_block(<<0x20, 0x3f, 0x61, 0x82>>, table) assert headers - == [alpacki.HeaderField(":method", "GET", alpacki.WithIndexing)] + == [ + alpacki.HeaderField( + <<":method":utf8>>, + <<"GET":utf8>>, + alpacki.WithIndexing, + ), + ] assert alpacki.dynamic_length(table) == 0 assert alpacki.dynamic_max_size(table) == 128 } @@ -195,14 +279,18 @@ pub fn decode_indexed_zero_test() { == Error(alpacki.InvalidTableIndex) } -// Uppercase header name rejected by validate_header_name FFI -pub fn decode_invalid_header_name_test() { +// HPACK treats names as opaque octets (RFC 7541 Section 1.3) +pub fn decode_opaque_header_name_test() { let table = alpacki.new_dynamic(4096) - assert alpacki.decode_header_block( + let assert Ok(#(headers, _table)) = + alpacki.decode_header_block( <<0x40, 0x03, "FOO":utf8, 0x03, "bar":utf8>>, table, ) - == Error(alpacki.InvalidHeaderName) + assert headers + == [ + alpacki.HeaderField(<<"FOO":utf8>>, <<"bar":utf8>>, alpacki.WithIndexing), + ] } // Encode @@ -220,7 +308,13 @@ pub fn encode_indexed_test() { let table = alpacki.new_dynamic(4096) let #(encoded, _table) = encode( - [alpacki.HeaderField(":method", "GET", alpacki.WithIndexing)], + [ + alpacki.HeaderField( + <<":method":utf8>>, + <<"GET":utf8>>, + alpacki.WithIndexing, + ), + ], table, False, ) @@ -232,7 +326,13 @@ pub fn encode_full_match_ignores_indexing_test() { let table = alpacki.new_dynamic(4096) let #(encoded, _table) = encode( - [alpacki.HeaderField(":method", "GET", alpacki.NeverIndexed)], + [ + alpacki.HeaderField( + <<":method":utf8>>, + <<"GET":utf8>>, + alpacki.NeverIndexed, + ), + ], table, False, ) @@ -243,7 +343,13 @@ pub fn encode_literal_with_indexing_indexed_name_test() { let table = alpacki.new_dynamic(4096) let #(encoded, table) = encode( - [alpacki.HeaderField(":status", "418", alpacki.WithIndexing)], + [ + alpacki.HeaderField( + <<":status":utf8>>, + <<"418":utf8>>, + alpacki.WithIndexing, + ), + ], table, False, ) @@ -255,7 +361,13 @@ pub fn encode_literal_without_indexing_indexed_name_test() { let table = alpacki.new_dynamic(4096) let #(encoded, table) = encode( - [alpacki.HeaderField(":path", "/sample/path", alpacki.WithoutIndexing)], + [ + alpacki.HeaderField( + <<":path":utf8>>, + <<"/sample/path":utf8>>, + alpacki.WithoutIndexing, + ), + ], table, False, ) @@ -267,7 +379,13 @@ pub fn encode_literal_never_indexed_indexed_name_test() { let table = alpacki.new_dynamic(4096) let #(encoded, table) = encode( - [alpacki.HeaderField(":status", "418", alpacki.NeverIndexed)], + [ + alpacki.HeaderField( + <<":status":utf8>>, + <<"418":utf8>>, + alpacki.NeverIndexed, + ), + ], table, False, ) @@ -280,7 +398,11 @@ pub fn encode_literal_with_indexing_new_name_test() { let #(encoded, table) = encode( [ - alpacki.HeaderField("custom-key", "custom-header", alpacki.WithIndexing), + alpacki.HeaderField( + <<"custom-key":utf8>>, + <<"custom-header":utf8>>, + alpacki.WithIndexing, + ), ], table, False, @@ -295,7 +417,13 @@ pub fn encode_literal_without_indexing_new_name_test() { let table = alpacki.new_dynamic(4096) let #(encoded, table) = encode( - [alpacki.HeaderField("x-custom", "value", alpacki.WithoutIndexing)], + [ + alpacki.HeaderField( + <<"x-custom":utf8>>, + <<"value":utf8>>, + alpacki.WithoutIndexing, + ), + ], table, False, ) @@ -307,13 +435,37 @@ pub fn encode_literal_never_indexed_new_name_test() { let table = alpacki.new_dynamic(4096) let #(encoded, _table) = encode( - [alpacki.HeaderField("password", "secret", alpacki.NeverIndexed)], + [ + alpacki.HeaderField( + <<"password":utf8>>, + <<"secret":utf8>>, + alpacki.NeverIndexed, + ), + ], table, False, ) assert encoded == <<0x10, 0x08, "password":utf8, 0x06, "secret":utf8>> } +// HPACK treats names as opaque octets (RFC 7541 Section 1.3) +pub fn encode_uppercase_header_name_test() { + let table = alpacki.new_dynamic(4096) + let #(encoded, _table) = + encode( + [ + alpacki.HeaderField( + <<"FOO":utf8>>, + <<"bar":utf8>>, + alpacki.WithIndexing, + ), + ], + table, + False, + ) + assert encoded == <<0x40, 0x03, "FOO":utf8, 0x03, "bar":utf8>> +} + // Sequential Requests (RFC 7541 C.3) // ----------------------------------------------------------------------------- @@ -323,12 +475,24 @@ pub fn encode_c3_sequential_requests_test() { let #(encoded, table) = encode( [ - alpacki.HeaderField(":method", "GET", alpacki.WithIndexing), - alpacki.HeaderField(":scheme", "http", alpacki.WithIndexing), - alpacki.HeaderField(":path", "/", alpacki.WithIndexing), alpacki.HeaderField( - ":authority", - "www.example.com", + <<":method":utf8>>, + <<"GET":utf8>>, + alpacki.WithIndexing, + ), + alpacki.HeaderField( + <<":scheme":utf8>>, + <<"http":utf8>>, + alpacki.WithIndexing, + ), + alpacki.HeaderField( + <<":path":utf8>>, + <<"/":utf8>>, + alpacki.WithIndexing, + ), + alpacki.HeaderField( + <<":authority":utf8>>, + <<"www.example.com":utf8>>, alpacki.WithIndexing, ), ], @@ -341,15 +505,31 @@ pub fn encode_c3_sequential_requests_test() { let #(encoded, table) = encode( [ - alpacki.HeaderField(":method", "GET", alpacki.WithIndexing), - alpacki.HeaderField(":scheme", "http", alpacki.WithIndexing), - alpacki.HeaderField(":path", "/", alpacki.WithIndexing), alpacki.HeaderField( - ":authority", - "www.example.com", + <<":method":utf8>>, + <<"GET":utf8>>, + alpacki.WithIndexing, + ), + alpacki.HeaderField( + <<":scheme":utf8>>, + <<"http":utf8>>, + alpacki.WithIndexing, + ), + alpacki.HeaderField( + <<":path":utf8>>, + <<"/":utf8>>, + alpacki.WithIndexing, + ), + alpacki.HeaderField( + <<":authority":utf8>>, + <<"www.example.com":utf8>>, + alpacki.WithIndexing, + ), + alpacki.HeaderField( + <<"cache-control":utf8>>, + <<"no-cache":utf8>>, alpacki.WithIndexing, ), - alpacki.HeaderField("cache-control", "no-cache", alpacki.WithIndexing), ], table, False, @@ -360,15 +540,31 @@ pub fn encode_c3_sequential_requests_test() { let #(encoded, _table) = encode( [ - alpacki.HeaderField(":method", "GET", alpacki.WithIndexing), - alpacki.HeaderField(":scheme", "https", alpacki.WithIndexing), - alpacki.HeaderField(":path", "/index.html", alpacki.WithIndexing), alpacki.HeaderField( - ":authority", - "www.example.com", + <<":method":utf8>>, + <<"GET":utf8>>, + alpacki.WithIndexing, + ), + alpacki.HeaderField( + <<":scheme":utf8>>, + <<"https":utf8>>, + alpacki.WithIndexing, + ), + alpacki.HeaderField( + <<":path":utf8>>, + <<"/index.html":utf8>>, + alpacki.WithIndexing, + ), + alpacki.HeaderField( + <<":authority":utf8>>, + <<"www.example.com":utf8>>, + alpacki.WithIndexing, + ), + alpacki.HeaderField( + <<"custom-key":utf8>>, + <<"custom-value":utf8>>, alpacki.WithIndexing, ), - alpacki.HeaderField("custom-key", "custom-value", alpacki.WithIndexing), ], table, False, @@ -389,8 +585,8 @@ pub fn encode_huffman_value_test() { encode( [ alpacki.HeaderField( - ":authority", - "www.example.com", + <<":authority":utf8>>, + <<"www.example.com":utf8>>, alpacki.WithIndexing, ), ], @@ -413,7 +609,13 @@ pub fn encode_pending_resize_test() { |> alpacki.resize_dynamic(128) let #(encoded, table) = encode( - [alpacki.HeaderField(":method", "GET", alpacki.WithIndexing)], + [ + alpacki.HeaderField( + <<":method":utf8>>, + <<"GET":utf8>>, + alpacki.WithIndexing, + ), + ], table, False, ) @@ -429,7 +631,13 @@ pub fn encode_pending_double_resize_test() { |> alpacki.resize_dynamic(128) let #(encoded, table) = encode( - [alpacki.HeaderField(":method", "GET", alpacki.WithIndexing)], + [ + alpacki.HeaderField( + <<":method":utf8>>, + <<"GET":utf8>>, + alpacki.WithIndexing, + ), + ], table, False, ) @@ -441,7 +649,13 @@ pub fn encode_no_pending_resize_test() { let table = alpacki.new_dynamic(4096) let #(encoded, _table) = encode( - [alpacki.HeaderField(":method", "GET", alpacki.WithIndexing)], + [ + alpacki.HeaderField( + <<":method":utf8>>, + <<"GET":utf8>>, + alpacki.WithIndexing, + ), + ], table, False, ) diff --git a/test/table_test.gleam b/test/table_test.gleam index 0a256b4..aaeb84f 100644 --- a/test/table_test.gleam +++ b/test/table_test.gleam @@ -4,11 +4,11 @@ import alpacki // ----------------------------------------------------------------------------- pub fn static_lookup_first_entry_test() { - assert alpacki.lookup_static(1) == Ok(#(":authority", "")) + assert alpacki.lookup_static(1) == Ok(#(<<":authority":utf8>>, <<>>)) } pub fn static_lookup_last_entry_test() { - assert alpacki.lookup_static(61) == Ok(#("www-authenticate", "")) + assert alpacki.lookup_static(61) == Ok(#(<<"www-authenticate":utf8>>, <<>>)) } pub fn static_lookup_invalid_index_test() { @@ -17,24 +17,29 @@ pub fn static_lookup_invalid_index_test() { } pub fn static_match_full_test() { - assert alpacki.match_static(":method", "GET") == alpacki.FullMatch(2) + assert alpacki.match_static(<<":method":utf8>>, <<"GET":utf8>>) + == alpacki.FullMatch(2) } pub fn static_match_name_only_test() { - assert alpacki.match_static(":status", "418") == alpacki.NameMatch(8) - assert alpacki.match_static(":status", "500") == alpacki.FullMatch(14) + assert alpacki.match_static(<<":status":utf8>>, <<"418":utf8>>) + == alpacki.NameMatch(8) + assert alpacki.match_static(<<":status":utf8>>, <<"500":utf8>>) + == alpacki.FullMatch(14) } pub fn static_match_not_found_test() { - assert alpacki.match_static("x-custom-header", "value") == alpacki.NoMatch + assert alpacki.match_static(<<"x-custom-header":utf8>>, <<"value":utf8>>) + == alpacki.NoMatch } pub fn static_match_case_sensitive_test() { - assert alpacki.match_static(":METHOD", "GET") == alpacki.NoMatch + assert alpacki.match_static(<<":METHOD":utf8>>, <<"GET":utf8>>) + == alpacki.NoMatch } pub fn static_match_empty_name_test() { - assert alpacki.match_static("", "wibble") == alpacki.NoMatch + assert alpacki.match_static(<<"":utf8>>, <<"wibble":utf8>>) == alpacki.NoMatch } // Dynamic Table @@ -51,24 +56,29 @@ pub fn dynamic_new_test() { pub fn dynamic_add_and_lookup_test() { let table = alpacki.new_dynamic(4096) - |> alpacki.add_dynamic("content-type", "text/html") + |> alpacki.add_dynamic(<<"content-type":utf8>>, <<"text/html":utf8>>) assert alpacki.dynamic_length(table) == 1 assert alpacki.dynamic_size(table) == 53 - assert alpacki.lookup_dynamic(table, 62) == Ok(#("content-type", "text/html")) + assert alpacki.lookup_dynamic(table, 62) + == Ok(#(<<"content-type":utf8>>, <<"text/html":utf8>>)) } pub fn dynamic_add_two_entries_ordering_test() { let table = alpacki.new_dynamic(4096) - |> alpacki.add_dynamic("content-type", "text/html") - |> alpacki.add_dynamic("server", "ewe") + |> alpacki.add_dynamic(<<"content-type":utf8>>, <<"text/html":utf8>>) + |> alpacki.add_dynamic(<<"server":utf8>>, <<"ewe":utf8>>) assert alpacki.dynamic_length(table) == 2 - assert alpacki.lookup_dynamic(table, 62) == Ok(#("server", "ewe")) - assert alpacki.lookup_dynamic(table, 63) == Ok(#("content-type", "text/html")) + assert alpacki.lookup_dynamic(table, 62) + == Ok(#(<<"server":utf8>>, <<"ewe":utf8>>)) + assert alpacki.lookup_dynamic(table, 63) + == Ok(#(<<"content-type":utf8>>, <<"text/html":utf8>>)) } pub fn dynamic_lookup_out_of_range_test() { - let table = alpacki.new_dynamic(4096) |> alpacki.add_dynamic("server", "ewe") + let table = + alpacki.new_dynamic(4096) + |> alpacki.add_dynamic(<<"server":utf8>>, <<"ewe":utf8>>) assert alpacki.lookup_dynamic(table, 61) == Error(Nil) assert alpacki.lookup_dynamic(table, 63) == Error(Nil) } @@ -77,18 +87,19 @@ pub fn dynamic_lookup_out_of_range_test() { pub fn dynamic_evict_oldest_test() { let table = alpacki.new_dynamic(90) - |> alpacki.add_dynamic("server", "ewe") - |> alpacki.add_dynamic("content-type", "text/html") + |> alpacki.add_dynamic(<<"server":utf8>>, <<"ewe":utf8>>) + |> alpacki.add_dynamic(<<"content-type":utf8>>, <<"text/html":utf8>>) assert alpacki.dynamic_length(table) == 1 assert alpacki.dynamic_size(table) == 53 - assert alpacki.lookup_dynamic(table, 62) == Ok(#("content-type", "text/html")) + assert alpacki.lookup_dynamic(table, 62) + == Ok(#(<<"content-type":utf8>>, <<"text/html":utf8>>)) } pub fn dynamic_add_oversized_clears_test() { let table = alpacki.new_dynamic(40) - |> alpacki.add_dynamic("via", "1.1") - |> alpacki.add_dynamic("content-type", "application/json") + |> alpacki.add_dynamic(<<"via":utf8>>, <<"1.1":utf8>>) + |> alpacki.add_dynamic(<<"content-type":utf8>>, <<"application/json":utf8>>) assert alpacki.dynamic_length(table) == 0 assert alpacki.dynamic_size(table) == 0 } @@ -98,71 +109,84 @@ pub fn dynamic_add_oversized_clears_test() { pub fn dynamic_evict_multiple_test() { let table = alpacki.new_dynamic(130) - |> alpacki.add_dynamic("via", "1.1") - |> alpacki.add_dynamic("age", "300") - |> alpacki.add_dynamic("server", "ewe") - |> alpacki.add_dynamic("content-type", "text/html") + |> alpacki.add_dynamic(<<"via":utf8>>, <<"1.1":utf8>>) + |> alpacki.add_dynamic(<<"age":utf8>>, <<"300":utf8>>) + |> alpacki.add_dynamic(<<"server":utf8>>, <<"ewe":utf8>>) + |> alpacki.add_dynamic(<<"content-type":utf8>>, <<"text/html":utf8>>) assert alpacki.dynamic_length(table) == 2 assert alpacki.dynamic_size(table) == 94 - assert alpacki.lookup_dynamic(table, 62) == Ok(#("content-type", "text/html")) - assert alpacki.lookup_dynamic(table, 63) == Ok(#("server", "ewe")) + assert alpacki.lookup_dynamic(table, 62) + == Ok(#(<<"content-type":utf8>>, <<"text/html":utf8>>)) + assert alpacki.lookup_dynamic(table, 63) + == Ok(#(<<"server":utf8>>, <<"ewe":utf8>>)) } pub fn dynamic_match_full_test() { let table = alpacki.new_dynamic(4096) - |> alpacki.add_dynamic("x-request-id", "7f3a9b2e") - assert alpacki.match_dynamic(table, "x-request-id", "7f3a9b2e") + |> alpacki.add_dynamic(<<"x-request-id":utf8>>, <<"7f3a9b2e":utf8>>) + assert alpacki.match_dynamic(table, <<"x-request-id":utf8>>, << + "7f3a9b2e":utf8, + >>) == alpacki.FullMatch(62) } pub fn dynamic_match_name_only_test() { let table = alpacki.new_dynamic(4096) - |> alpacki.add_dynamic("x-request-id", "7f3a9b2e") - assert alpacki.match_dynamic(table, "x-request-id", "c4d8e1f0") + |> alpacki.add_dynamic(<<"x-request-id":utf8>>, <<"7f3a9b2e":utf8>>) + assert alpacki.match_dynamic(table, <<"x-request-id":utf8>>, << + "c4d8e1f0":utf8, + >>) == alpacki.NameMatch(62) } pub fn dynamic_match_empty_table_test() { let table = alpacki.new_dynamic(4096) - assert alpacki.match_dynamic(table, "server", "ewe") == alpacki.NoMatch + assert alpacki.match_dynamic(table, <<"server":utf8>>, <<"ewe":utf8>>) + == alpacki.NoMatch } pub fn dynamic_match_after_eviction_test() { let table = alpacki.new_dynamic(90) - |> alpacki.add_dynamic("server", "ewe") - |> alpacki.add_dynamic("content-type", "text/html") - assert alpacki.match_dynamic(table, "server", "ewe") == alpacki.NoMatch + |> alpacki.add_dynamic(<<"server":utf8>>, <<"ewe":utf8>>) + |> alpacki.add_dynamic(<<"content-type":utf8>>, <<"text/html":utf8>>) + assert alpacki.match_dynamic(table, <<"server":utf8>>, <<"ewe":utf8>>) + == alpacki.NoMatch } pub fn dynamic_match_prefers_newest_test() { let table = alpacki.new_dynamic(4096) - |> alpacki.add_dynamic("x-request-id", "7f3a9b2e") - |> alpacki.add_dynamic("x-request-id", "7f3a9b2e") - assert alpacki.match_dynamic(table, "x-request-id", "7f3a9b2e") + |> alpacki.add_dynamic(<<"x-request-id":utf8>>, <<"7f3a9b2e":utf8>>) + |> alpacki.add_dynamic(<<"x-request-id":utf8>>, <<"7f3a9b2e":utf8>>) + assert alpacki.match_dynamic(table, <<"x-request-id":utf8>>, << + "7f3a9b2e":utf8, + >>) == alpacki.FullMatch(62) - assert alpacki.match_dynamic(table, "x-request-id", "c4d8e1f0") + assert alpacki.match_dynamic(table, <<"x-request-id":utf8>>, << + "c4d8e1f0":utf8, + >>) == alpacki.NameMatch(62) } pub fn dynamic_resize_down_evicts_test() { let table = alpacki.new_dynamic(4096) - |> alpacki.add_dynamic("server", "ewe") - |> alpacki.add_dynamic("content-type", "text/html") + |> alpacki.add_dynamic(<<"server":utf8>>, <<"ewe":utf8>>) + |> alpacki.add_dynamic(<<"content-type":utf8>>, <<"text/html":utf8>>) |> alpacki.resize_dynamic(55) assert alpacki.dynamic_length(table) == 1 assert alpacki.dynamic_max_size(table) == 55 - assert alpacki.lookup_dynamic(table, 62) == Ok(#("content-type", "text/html")) + assert alpacki.lookup_dynamic(table, 62) + == Ok(#(<<"content-type":utf8>>, <<"text/html":utf8>>)) } pub fn dynamic_resize_to_zero_test() { let table = alpacki.new_dynamic(4096) - |> alpacki.add_dynamic("server", "ewe") + |> alpacki.add_dynamic(<<"server":utf8>>, <<"ewe":utf8>>) |> alpacki.resize_dynamic(0) assert alpacki.dynamic_length(table) == 0 assert alpacki.dynamic_size(table) == 0 @@ -172,18 +196,19 @@ pub fn dynamic_resize_to_zero_test() { pub fn dynamic_resize_up_preserves_test() { let table = alpacki.new_dynamic(100) - |> alpacki.add_dynamic("server", "ewe") + |> alpacki.add_dynamic(<<"server":utf8>>, <<"ewe":utf8>>) |> alpacki.resize_dynamic(8192) assert alpacki.dynamic_length(table) == 1 assert alpacki.dynamic_size(table) == 41 assert alpacki.dynamic_max_size(table) == 8192 - assert alpacki.lookup_dynamic(table, 62) == Ok(#("server", "ewe")) + assert alpacki.lookup_dynamic(table, 62) + == Ok(#(<<"server":utf8>>, <<"ewe":utf8>>)) } pub fn dynamic_clear_preserves_max_size_test() { let table = alpacki.new_dynamic(4096) - |> alpacki.add_dynamic("server", "ewe") + |> alpacki.add_dynamic(<<"server":utf8>>, <<"ewe":utf8>>) |> alpacki.clear_dynamic assert alpacki.dynamic_length(table) == 0 assert alpacki.dynamic_size(table) == 0 @@ -196,36 +221,48 @@ pub fn dynamic_clear_preserves_max_size_test() { pub fn lookup_dispatches_test() { let table = alpacki.new_dynamic(4096) - |> alpacki.add_dynamic("x-request-id", "7f3a9b2e") - assert alpacki.lookup(table, 1) == Ok(#(":authority", "")) - assert alpacki.lookup(table, 62) == Ok(#("x-request-id", "7f3a9b2e")) + |> alpacki.add_dynamic(<<"x-request-id":utf8>>, <<"7f3a9b2e":utf8>>) + assert alpacki.lookup(table, 1) == Ok(#(<<":authority":utf8>>, <<>>)) + assert alpacki.lookup(table, 62) + == Ok(#(<<"x-request-id":utf8>>, <<"7f3a9b2e":utf8>>)) assert alpacki.lookup(table, 63) == Error(Nil) } pub fn match_prefers_static_full_match_test() { - let table = alpacki.new_dynamic(4096) |> alpacki.add_dynamic(":method", "GET") - assert alpacki.match(table, ":method", "GET") == alpacki.FullMatch(2) + let table = + alpacki.new_dynamic(4096) + |> alpacki.add_dynamic(<<":method":utf8>>, <<"GET":utf8>>) + assert alpacki.match(table, <<":method":utf8>>, <<"GET":utf8>>) + == alpacki.FullMatch(2) } // Static has :status NameMatch(8) for unknown values. Dynamic has :status 418 // FullMatch(62). Dynamic FullMatch should win. pub fn match_prefers_dynamic_full_over_static_name_test() { - let table = alpacki.new_dynamic(4096) |> alpacki.add_dynamic(":status", "418") - assert alpacki.match(table, ":status", "418") == alpacki.FullMatch(62) + let table = + alpacki.new_dynamic(4096) + |> alpacki.add_dynamic(<<":status":utf8>>, <<"418":utf8>>) + assert alpacki.match(table, <<":status":utf8>>, <<"418":utf8>>) + == alpacki.FullMatch(62) } // Both tables have name-only match for :status. Static NameMatch(8) should win. pub fn match_prefers_static_name_over_dynamic_name_test() { - let table = alpacki.new_dynamic(4096) |> alpacki.add_dynamic(":status", "418") - assert alpacki.match(table, ":status", "501") == alpacki.NameMatch(8) + let table = + alpacki.new_dynamic(4096) + |> alpacki.add_dynamic(<<":status":utf8>>, <<"418":utf8>>) + assert alpacki.match(table, <<":status":utf8>>, <<"501":utf8>>) + == alpacki.NameMatch(8) } pub fn match_falls_through_to_dynamic_test() { let table = alpacki.new_dynamic(4096) - |> alpacki.add_dynamic("x-request-id", "7f3a9b2e") - assert alpacki.match(table, "x-request-id", "7f3a9b2e") + |> alpacki.add_dynamic(<<"x-request-id":utf8>>, <<"7f3a9b2e":utf8>>) + assert alpacki.match(table, <<"x-request-id":utf8>>, <<"7f3a9b2e":utf8>>) == alpacki.FullMatch(62) - assert alpacki.match(table, "x-request-id", "other") == alpacki.NameMatch(62) - assert alpacki.match(table, "x-unknown", "val") == alpacki.NoMatch + assert alpacki.match(table, <<"x-request-id":utf8>>, <<"other":utf8>>) + == alpacki.NameMatch(62) + assert alpacki.match(table, <<"x-unknown":utf8>>, <<"val":utf8>>) + == alpacki.NoMatch }