update
This commit is contained in:
360
phoenix/deps/hpax/lib/hpax.ex
Normal file
360
phoenix/deps/hpax/lib/hpax.ex
Normal file
@@ -0,0 +1,360 @@
|
||||
defmodule HPAX do
|
||||
@moduledoc """
|
||||
Support for the HPACK header compression algorithm.
|
||||
|
||||
This module provides support for the HPACK header compression algorithm used mainly in HTTP/2.
|
||||
|
||||
## Encoding and decoding contexts
|
||||
|
||||
The HPACK algorithm requires both
|
||||
|
||||
* an encoding context on the encoder side
|
||||
* a decoding context on the decoder side
|
||||
|
||||
These contexts are semantically different but structurally the same. In HPACK they are
|
||||
implemented as **HPACK tables**. This library uses the name "tables" everywhere internally
|
||||
|
||||
HPACK tables can be created through the `new/1` function.
|
||||
"""
|
||||
|
||||
alias HPAX.{Table, Types}
|
||||
|
||||
@typedoc """
|
||||
An HPACK table.
|
||||
|
||||
This can be used for encoding or decoding.
|
||||
"""
|
||||
@typedoc since: "0.2.0"
|
||||
@opaque table() :: Table.t()
|
||||
|
||||
@typedoc """
|
||||
An HPACK header name.
|
||||
"""
|
||||
@type header_name() :: binary()
|
||||
|
||||
@typedoc """
|
||||
An HPACK header value.
|
||||
"""
|
||||
@type header_value() :: binary()
|
||||
|
||||
@valid_header_actions [:store, :store_name, :no_store, :never_store]
|
||||
|
||||
@doc """
|
||||
Creates a new HPACK table.
|
||||
|
||||
Same as `new/2` with default options.
|
||||
"""
|
||||
@spec new(non_neg_integer()) :: table()
|
||||
def new(max_table_size), do: new(max_table_size, [])
|
||||
|
||||
@doc """
|
||||
Create a new HPACK table that can be used as encoding or decoding context.
|
||||
|
||||
See the "Encoding and decoding contexts" section in the module documentation.
|
||||
|
||||
`max_table_size` is the maximum table size (in bytes) for the newly created table.
|
||||
|
||||
## Options
|
||||
|
||||
This function accepts the following `options`:
|
||||
|
||||
* `:huffman_encoding` - (since 0.2.0) `:always` or `:never`. If `:always`,
|
||||
then HPAX will always encode headers using Huffman encoding. If `:never`,
|
||||
HPAX will not use any Huffman encoding. Defaults to `:never`.
|
||||
|
||||
## Examples
|
||||
|
||||
encoding_context = HPAX.new(4096)
|
||||
|
||||
"""
|
||||
@doc since: "0.2.0"
|
||||
@spec new(non_neg_integer(), [keyword()]) :: table()
|
||||
def new(max_table_size, options)
|
||||
when is_integer(max_table_size) and max_table_size >= 0 and is_list(options) do
|
||||
options = Keyword.put_new(options, :huffman_encoding, :never)
|
||||
|
||||
Enum.each(options, fn
|
||||
{:huffman_encoding, _huffman_encoding} -> :ok
|
||||
{key, _value} -> raise ArgumentError, "unknown option: #{inspect(key)}"
|
||||
end)
|
||||
|
||||
Table.new(max_table_size, Keyword.fetch!(options, :huffman_encoding))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Resizes the given table to the given maximum size.
|
||||
|
||||
This is intended for use where the overlying protocol has signaled a change to the table's
|
||||
maximum size, such as when an HTTP/2 `SETTINGS` frame is received.
|
||||
|
||||
If the indicated size is less than the table's current size, entries
|
||||
will be evicted as needed to fit within the specified size, and the table's
|
||||
maximum size will be decreased to the specified value. A flag will also be
|
||||
set which will enqueue a "dynamic table size update" command to be prefixed
|
||||
to the next block encoded with this table, per
|
||||
[RFC9113§4.3.1](https://www.rfc-editor.org/rfc/rfc9113.html#section-4.3.1).
|
||||
|
||||
If the indicated size is greater than or equal to the table's current max size, no entries are evicted
|
||||
and the table's maximum size changes to the specified value.
|
||||
|
||||
## Examples
|
||||
|
||||
decoding_context = HPAX.new(4096)
|
||||
HPAX.resize(decoding_context, 8192)
|
||||
|
||||
"""
|
||||
@spec resize(table(), non_neg_integer()) :: table()
|
||||
defdelegate resize(table, new_max_size), to: Table
|
||||
|
||||
@doc """
|
||||
Decodes a header block fragment (HBF) through a given table.
|
||||
|
||||
If decoding is successful, this function returns a `{:ok, headers, updated_table}` tuple where
|
||||
`headers` is a list of decoded headers, and `updated_table` is the updated table. If there's
|
||||
an error in decoding, this function returns `{:error, reason}`.
|
||||
|
||||
## Examples
|
||||
|
||||
decoding_context = HPAX.new(1000)
|
||||
hbf = get_hbf_from_somewhere()
|
||||
HPAX.decode(hbf, decoding_context)
|
||||
#=> {:ok, [{":method", "GET"}], decoding_context}
|
||||
|
||||
"""
|
||||
@spec decode(binary(), table()) ::
|
||||
{:ok, [{header_name(), header_value()}], table()} | {:error, term()}
|
||||
|
||||
# Dynamic resizes must occur only at the start of a block
|
||||
# https://datatracker.ietf.org/doc/html/rfc7541#section-4.2
|
||||
def decode(<<0b001::3, rest::bitstring>>, %Table{} = table) do
|
||||
{new_max_size, rest} = decode_integer(rest, 5)
|
||||
|
||||
# Dynamic resizes must be less than protocol max table size
|
||||
# https://datatracker.ietf.org/doc/html/rfc7541#section-6.3
|
||||
if new_max_size <= table.protocol_max_table_size do
|
||||
decode(rest, Table.dynamic_resize(table, new_max_size))
|
||||
else
|
||||
{:error, :protocol_error}
|
||||
end
|
||||
end
|
||||
|
||||
def decode(block, %Table{} = table) when is_binary(block) do
|
||||
decode_headers(block, table, _acc = [])
|
||||
catch
|
||||
:throw, {:hpax, error} -> {:error, error}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Encodes a list of headers through the given table.
|
||||
|
||||
Returns a two-element tuple where the first element is a binary representing the encoded headers
|
||||
and the second element is an updated table.
|
||||
|
||||
## Examples
|
||||
|
||||
headers = [{:store, ":authority", "https://example.com"}]
|
||||
encoding_context = HPAX.new(1000)
|
||||
HPAX.encode(headers, encoding_context)
|
||||
#=> {iodata, updated_encoding_context}
|
||||
|
||||
"""
|
||||
@spec encode([header], table()) :: {iodata(), table()}
|
||||
when header: {action, header_name(), header_value()},
|
||||
action: :store | :store_name | :no_store | :never_store
|
||||
def encode(headers, %Table{} = table) when is_list(headers) do
|
||||
{table, pending_resizes} = Table.pop_pending_resizes(table)
|
||||
acc = Enum.map(pending_resizes, &[<<0b001::3, Types.encode_integer(&1, 5)::bitstring>>])
|
||||
encode_headers(headers, table, acc)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Encodes a list of headers through the given table, applying the same `action` to all of them.
|
||||
|
||||
This function is the similar to `encode/2`, but `headers` are `{name, value}` tuples instead,
|
||||
and the same `action` is applied to all headers.
|
||||
|
||||
## Examples
|
||||
|
||||
headers = [{":authority", "https://example.com"}]
|
||||
encoding_context = HPAX.new(1000)
|
||||
HPAX.encode(:store, headers, encoding_context)
|
||||
#=> {iodata, updated_encoding_context}
|
||||
|
||||
"""
|
||||
@doc since: "0.2.0"
|
||||
@spec encode(action, [header], table()) :: {iodata(), table()}
|
||||
when action: :store | :store_name | :no_store | :never_store,
|
||||
header: {header_name(), header_value()}
|
||||
def encode(action, headers, %Table{} = table)
|
||||
when is_list(headers) and action in [:store, :store_name, :no_store, :never_store] do
|
||||
headers
|
||||
|> Enum.map(fn {name, value} -> {action, name, value} end)
|
||||
|> encode(table)
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp decode_headers(<<>>, table, acc) do
|
||||
{:ok, Enum.reverse(acc), table}
|
||||
end
|
||||
|
||||
# Indexed header field
|
||||
# http://httpwg.org/specs/rfc7541.html#rfc.section.6.1
|
||||
defp decode_headers(<<0b1::1, rest::bitstring>>, table, acc) do
|
||||
{index, rest} = decode_integer(rest, 7)
|
||||
decode_headers(rest, table, [lookup_by_index!(table, index) | acc])
|
||||
end
|
||||
|
||||
# Literal header field with incremental indexing
|
||||
# http://httpwg.org/specs/rfc7541.html#rfc.section.6.2.1
|
||||
defp decode_headers(<<0b01::2, rest::bitstring>>, table, acc) do
|
||||
{name, value, rest} =
|
||||
case rest do
|
||||
# The header name is a string.
|
||||
<<0::6, rest::binary>> ->
|
||||
{name, rest} = decode_binary(rest)
|
||||
{value, rest} = decode_binary(rest)
|
||||
{name, value, rest}
|
||||
|
||||
# The header name is an index to be looked up in the table.
|
||||
_other ->
|
||||
{index, rest} = decode_integer(rest, 6)
|
||||
{value, rest} = decode_binary(rest)
|
||||
{name, _value} = lookup_by_index!(table, index)
|
||||
{name, value, rest}
|
||||
end
|
||||
|
||||
decode_headers(rest, Table.add(table, name, value), [{name, value} | acc])
|
||||
end
|
||||
|
||||
# Literal header field without indexing
|
||||
# http://httpwg.org/specs/rfc7541.html#rfc.section.6.2.2
|
||||
defp decode_headers(<<0b0000::4, rest::bitstring>>, table, acc) do
|
||||
{name, value, rest} =
|
||||
case rest do
|
||||
<<0::4, rest::binary>> ->
|
||||
{name, rest} = decode_binary(rest)
|
||||
{value, rest} = decode_binary(rest)
|
||||
{name, value, rest}
|
||||
|
||||
_other ->
|
||||
{index, rest} = decode_integer(rest, 4)
|
||||
{value, rest} = decode_binary(rest)
|
||||
{name, _value} = lookup_by_index!(table, index)
|
||||
{name, value, rest}
|
||||
end
|
||||
|
||||
decode_headers(rest, table, [{name, value} | acc])
|
||||
end
|
||||
|
||||
# Literal header field never indexed
|
||||
# http://httpwg.org/specs/rfc7541.html#rfc.section.6.2.3
|
||||
defp decode_headers(<<0b0001::4, rest::bitstring>>, table, acc) do
|
||||
{name, value, rest} =
|
||||
case rest do
|
||||
<<0::4, rest::binary>> ->
|
||||
{name, rest} = decode_binary(rest)
|
||||
{value, rest} = decode_binary(rest)
|
||||
{name, value, rest}
|
||||
|
||||
_other ->
|
||||
{index, rest} = decode_integer(rest, 4)
|
||||
{value, rest} = decode_binary(rest)
|
||||
{name, _value} = lookup_by_index!(table, index)
|
||||
{name, value, rest}
|
||||
end
|
||||
|
||||
# TODO: enforce the "never indexed" part somehow.
|
||||
decode_headers(rest, table, [{name, value} | acc])
|
||||
end
|
||||
|
||||
defp decode_headers(_other, _table, _acc) do
|
||||
throw({:hpax, :protocol_error})
|
||||
end
|
||||
|
||||
defp lookup_by_index!(table, index) do
|
||||
case Table.lookup_by_index(table, index) do
|
||||
{:ok, header} -> header
|
||||
:error -> throw({:hpax, {:index_not_found, index}})
|
||||
end
|
||||
end
|
||||
|
||||
defp decode_integer(bitstring, prefix) do
|
||||
case Types.decode_integer(bitstring, prefix) do
|
||||
{:ok, int, rest} -> {int, rest}
|
||||
:error -> throw({:hpax, :bad_integer_encoding})
|
||||
end
|
||||
end
|
||||
|
||||
defp decode_binary(binary) do
|
||||
case Types.decode_binary(binary) do
|
||||
{:ok, binary, rest} -> {binary, rest}
|
||||
:error -> throw({:hpax, :bad_binary_encoding})
|
||||
end
|
||||
end
|
||||
|
||||
defp encode_headers([], table, acc) do
|
||||
{acc, table}
|
||||
end
|
||||
|
||||
defp encode_headers([{action, name, value} | rest], table, acc)
|
||||
when action in @valid_header_actions and is_binary(name) and is_binary(value) do
|
||||
huffman? = table.huffman_encoding == :always
|
||||
|
||||
{encoded, table} =
|
||||
case Table.lookup_by_header(table, name, value) do
|
||||
{:full, index} ->
|
||||
{encode_indexed_header(index), table}
|
||||
|
||||
{:name, index} when action == :store ->
|
||||
{encode_literal_header_with_indexing(index, value, huffman?),
|
||||
Table.add(table, name, value)}
|
||||
|
||||
{:name, index} when action in [:store_name, :no_store] ->
|
||||
{encode_literal_header_without_indexing(index, value, huffman?), table}
|
||||
|
||||
{:name, index} when action == :never_store ->
|
||||
{encode_literal_header_never_indexed(index, value, huffman?), table}
|
||||
|
||||
:not_found when action in [:store, :store_name] ->
|
||||
{encode_literal_header_with_indexing(name, value, huffman?),
|
||||
Table.add(table, name, value)}
|
||||
|
||||
:not_found when action == :no_store ->
|
||||
{encode_literal_header_without_indexing(name, value, huffman?), table}
|
||||
|
||||
:not_found when action == :never_store ->
|
||||
{encode_literal_header_never_indexed(name, value, huffman?), table}
|
||||
end
|
||||
|
||||
encode_headers(rest, table, [acc, encoded])
|
||||
end
|
||||
|
||||
defp encode_indexed_header(index) do
|
||||
<<1::1, Types.encode_integer(index, 7)::bitstring>>
|
||||
end
|
||||
|
||||
defp encode_literal_header_with_indexing(index, value, huffman?) when is_integer(index) do
|
||||
[<<1::2, Types.encode_integer(index, 6)::bitstring>>, Types.encode_binary(value, huffman?)]
|
||||
end
|
||||
|
||||
defp encode_literal_header_with_indexing(name, value, huffman?) when is_binary(name) do
|
||||
[<<1::2, 0::6>>, Types.encode_binary(name, huffman?), Types.encode_binary(value, huffman?)]
|
||||
end
|
||||
|
||||
defp encode_literal_header_without_indexing(index, value, huffman?) when is_integer(index) do
|
||||
[<<0::4, Types.encode_integer(index, 4)::bitstring>>, Types.encode_binary(value, huffman?)]
|
||||
end
|
||||
|
||||
defp encode_literal_header_without_indexing(name, value, huffman?) when is_binary(name) do
|
||||
[<<0::4, 0::4>>, Types.encode_binary(name, huffman?), Types.encode_binary(value, huffman?)]
|
||||
end
|
||||
|
||||
defp encode_literal_header_never_indexed(index, value, huffman?) when is_integer(index) do
|
||||
[<<1::4, Types.encode_integer(index, 4)::bitstring>>, Types.encode_binary(value, huffman?)]
|
||||
end
|
||||
|
||||
defp encode_literal_header_never_indexed(name, value, huffman?) when is_binary(name) do
|
||||
[<<1::4, 0::4>>, Types.encode_binary(name, huffman?), Types.encode_binary(value, huffman?)]
|
||||
end
|
||||
end
|
||||
94
phoenix/deps/hpax/lib/hpax/huffman.ex
Normal file
94
phoenix/deps/hpax/lib/hpax/huffman.ex
Normal file
@@ -0,0 +1,94 @@
|
||||
defmodule HPAX.Huffman do
|
||||
@moduledoc false
|
||||
|
||||
import Bitwise, only: [>>>: 2]
|
||||
|
||||
# This file is downloaded from the spec directly.
|
||||
# http://httpwg.org/specs/rfc7541.html#huffman.code
|
||||
table_file = Path.absname("huffman_table", __DIR__)
|
||||
@external_resource table_file
|
||||
|
||||
entries =
|
||||
Enum.map(File.stream!(table_file), fn line ->
|
||||
[byte_value, bits, _hex, bit_count] =
|
||||
line
|
||||
|> case do
|
||||
<<?', _, ?', ?\s, rest::binary>> -> rest
|
||||
"EOS " <> rest -> rest
|
||||
_other -> line
|
||||
end
|
||||
|> String.replace(["|", "(", ")", "[", "]"], "")
|
||||
|> String.split()
|
||||
|
||||
byte_value = String.to_integer(byte_value)
|
||||
bits = String.to_integer(bits, 2)
|
||||
bit_count = String.to_integer(bit_count)
|
||||
|
||||
{byte_value, bits, bit_count}
|
||||
end)
|
||||
|
||||
{regular_entries, [eos_entry]} = Enum.split(entries, -1)
|
||||
{_eos_byte_value, eos_bits, eos_bit_count} = eos_entry
|
||||
|
||||
## Encoding
|
||||
|
||||
@spec encode(binary()) :: binary()
|
||||
def encode(binary) do
|
||||
encode(binary, _acc = <<>>)
|
||||
end
|
||||
|
||||
for {byte_value, bits, bit_count} <- regular_entries do
|
||||
defp encode(<<unquote(byte_value), rest::binary>>, acc) do
|
||||
encode(rest, <<acc::bitstring, unquote(bits)::size(unquote(bit_count))>>)
|
||||
end
|
||||
end
|
||||
|
||||
defp encode(<<>>, acc) do
|
||||
overflowing_bits = rem(bit_size(acc), 8)
|
||||
|
||||
if overflowing_bits == 0 do
|
||||
acc
|
||||
else
|
||||
bits_to_add = 8 - overflowing_bits
|
||||
|
||||
value_of_bits_to_add =
|
||||
take_significant_bits(unquote(eos_bits), unquote(eos_bit_count), bits_to_add)
|
||||
|
||||
<<acc::bitstring, value_of_bits_to_add::size(bits_to_add)>>
|
||||
end
|
||||
end
|
||||
|
||||
## Decoding
|
||||
|
||||
@spec decode(binary()) :: binary()
|
||||
def decode(binary)
|
||||
|
||||
for {byte_value, bits, bit_count} <- regular_entries do
|
||||
def decode(<<unquote(bits)::size(unquote(bit_count)), rest::bitstring>>) do
|
||||
<<unquote(byte_value), decode(rest)::binary>>
|
||||
end
|
||||
end
|
||||
|
||||
def decode(<<>>) do
|
||||
<<>>
|
||||
end
|
||||
|
||||
# Use binary syntax for single match context optimization.
|
||||
def decode(<<padding::bitstring>>) when bit_size(padding) in 1..7 do
|
||||
padding_size = bit_size(padding)
|
||||
<<padding::size(padding_size)>> = padding
|
||||
|
||||
if take_significant_bits(unquote(eos_bits), unquote(eos_bit_count), padding_size) == padding do
|
||||
<<>>
|
||||
else
|
||||
throw({:hpax, {:protocol_error, :invalid_huffman_encoding}})
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
@compile {:inline, take_significant_bits: 3}
|
||||
defp take_significant_bits(value, bit_count, bits_to_take) do
|
||||
value >>> (bit_count - bits_to_take)
|
||||
end
|
||||
end
|
||||
257
phoenix/deps/hpax/lib/hpax/huffman_table
Normal file
257
phoenix/deps/hpax/lib/hpax/huffman_table
Normal file
@@ -0,0 +1,257 @@
|
||||
( 0) |11111111|11000 1ff8 [13]
|
||||
( 1) |11111111|11111111|1011000 7fffd8 [23]
|
||||
( 2) |11111111|11111111|11111110|0010 fffffe2 [28]
|
||||
( 3) |11111111|11111111|11111110|0011 fffffe3 [28]
|
||||
( 4) |11111111|11111111|11111110|0100 fffffe4 [28]
|
||||
( 5) |11111111|11111111|11111110|0101 fffffe5 [28]
|
||||
( 6) |11111111|11111111|11111110|0110 fffffe6 [28]
|
||||
( 7) |11111111|11111111|11111110|0111 fffffe7 [28]
|
||||
( 8) |11111111|11111111|11111110|1000 fffffe8 [28]
|
||||
( 9) |11111111|11111111|11101010 ffffea [24]
|
||||
( 10) |11111111|11111111|11111111|111100 3ffffffc [30]
|
||||
( 11) |11111111|11111111|11111110|1001 fffffe9 [28]
|
||||
( 12) |11111111|11111111|11111110|1010 fffffea [28]
|
||||
( 13) |11111111|11111111|11111111|111101 3ffffffd [30]
|
||||
( 14) |11111111|11111111|11111110|1011 fffffeb [28]
|
||||
( 15) |11111111|11111111|11111110|1100 fffffec [28]
|
||||
( 16) |11111111|11111111|11111110|1101 fffffed [28]
|
||||
( 17) |11111111|11111111|11111110|1110 fffffee [28]
|
||||
( 18) |11111111|11111111|11111110|1111 fffffef [28]
|
||||
( 19) |11111111|11111111|11111111|0000 ffffff0 [28]
|
||||
( 20) |11111111|11111111|11111111|0001 ffffff1 [28]
|
||||
( 21) |11111111|11111111|11111111|0010 ffffff2 [28]
|
||||
( 22) |11111111|11111111|11111111|111110 3ffffffe [30]
|
||||
( 23) |11111111|11111111|11111111|0011 ffffff3 [28]
|
||||
( 24) |11111111|11111111|11111111|0100 ffffff4 [28]
|
||||
( 25) |11111111|11111111|11111111|0101 ffffff5 [28]
|
||||
( 26) |11111111|11111111|11111111|0110 ffffff6 [28]
|
||||
( 27) |11111111|11111111|11111111|0111 ffffff7 [28]
|
||||
( 28) |11111111|11111111|11111111|1000 ffffff8 [28]
|
||||
( 29) |11111111|11111111|11111111|1001 ffffff9 [28]
|
||||
( 30) |11111111|11111111|11111111|1010 ffffffa [28]
|
||||
( 31) |11111111|11111111|11111111|1011 ffffffb [28]
|
||||
' ' ( 32) |010100 14 [ 6]
|
||||
'!' ( 33) |11111110|00 3f8 [10]
|
||||
'"' ( 34) |11111110|01 3f9 [10]
|
||||
'#' ( 35) |11111111|1010 ffa [12]
|
||||
'$' ( 36) |11111111|11001 1ff9 [13]
|
||||
'%' ( 37) |010101 15 [ 6]
|
||||
'&' ( 38) |11111000 f8 [ 8]
|
||||
''' ( 39) |11111111|010 7fa [11]
|
||||
'(' ( 40) |11111110|10 3fa [10]
|
||||
')' ( 41) |11111110|11 3fb [10]
|
||||
'*' ( 42) |11111001 f9 [ 8]
|
||||
'+' ( 43) |11111111|011 7fb [11]
|
||||
',' ( 44) |11111010 fa [ 8]
|
||||
'-' ( 45) |010110 16 [ 6]
|
||||
'.' ( 46) |010111 17 [ 6]
|
||||
'/' ( 47) |011000 18 [ 6]
|
||||
'0' ( 48) |00000 0 [ 5]
|
||||
'1' ( 49) |00001 1 [ 5]
|
||||
'2' ( 50) |00010 2 [ 5]
|
||||
'3' ( 51) |011001 19 [ 6]
|
||||
'4' ( 52) |011010 1a [ 6]
|
||||
'5' ( 53) |011011 1b [ 6]
|
||||
'6' ( 54) |011100 1c [ 6]
|
||||
'7' ( 55) |011101 1d [ 6]
|
||||
'8' ( 56) |011110 1e [ 6]
|
||||
'9' ( 57) |011111 1f [ 6]
|
||||
':' ( 58) |1011100 5c [ 7]
|
||||
';' ( 59) |11111011 fb [ 8]
|
||||
'<' ( 60) |11111111|1111100 7ffc [15]
|
||||
'=' ( 61) |100000 20 [ 6]
|
||||
'>' ( 62) |11111111|1011 ffb [12]
|
||||
'?' ( 63) |11111111|00 3fc [10]
|
||||
'@' ( 64) |11111111|11010 1ffa [13]
|
||||
'A' ( 65) |100001 21 [ 6]
|
||||
'B' ( 66) |1011101 5d [ 7]
|
||||
'C' ( 67) |1011110 5e [ 7]
|
||||
'D' ( 68) |1011111 5f [ 7]
|
||||
'E' ( 69) |1100000 60 [ 7]
|
||||
'F' ( 70) |1100001 61 [ 7]
|
||||
'G' ( 71) |1100010 62 [ 7]
|
||||
'H' ( 72) |1100011 63 [ 7]
|
||||
'I' ( 73) |1100100 64 [ 7]
|
||||
'J' ( 74) |1100101 65 [ 7]
|
||||
'K' ( 75) |1100110 66 [ 7]
|
||||
'L' ( 76) |1100111 67 [ 7]
|
||||
'M' ( 77) |1101000 68 [ 7]
|
||||
'N' ( 78) |1101001 69 [ 7]
|
||||
'O' ( 79) |1101010 6a [ 7]
|
||||
'P' ( 80) |1101011 6b [ 7]
|
||||
'Q' ( 81) |1101100 6c [ 7]
|
||||
'R' ( 82) |1101101 6d [ 7]
|
||||
'S' ( 83) |1101110 6e [ 7]
|
||||
'T' ( 84) |1101111 6f [ 7]
|
||||
'U' ( 85) |1110000 70 [ 7]
|
||||
'V' ( 86) |1110001 71 [ 7]
|
||||
'W' ( 87) |1110010 72 [ 7]
|
||||
'X' ( 88) |11111100 fc [ 8]
|
||||
'Y' ( 89) |1110011 73 [ 7]
|
||||
'Z' ( 90) |11111101 fd [ 8]
|
||||
'[' ( 91) |11111111|11011 1ffb [13]
|
||||
'\' ( 92) |11111111|11111110|000 7fff0 [19]
|
||||
']' ( 93) |11111111|11100 1ffc [13]
|
||||
'^' ( 94) |11111111|111100 3ffc [14]
|
||||
'_' ( 95) |100010 22 [ 6]
|
||||
'`' ( 96) |11111111|1111101 7ffd [15]
|
||||
'a' ( 97) |00011 3 [ 5]
|
||||
'b' ( 98) |100011 23 [ 6]
|
||||
'c' ( 99) |00100 4 [ 5]
|
||||
'd' (100) |100100 24 [ 6]
|
||||
'e' (101) |00101 5 [ 5]
|
||||
'f' (102) |100101 25 [ 6]
|
||||
'g' (103) |100110 26 [ 6]
|
||||
'h' (104) |100111 27 [ 6]
|
||||
'i' (105) |00110 6 [ 5]
|
||||
'j' (106) |1110100 74 [ 7]
|
||||
'k' (107) |1110101 75 [ 7]
|
||||
'l' (108) |101000 28 [ 6]
|
||||
'm' (109) |101001 29 [ 6]
|
||||
'n' (110) |101010 2a [ 6]
|
||||
'o' (111) |00111 7 [ 5]
|
||||
'p' (112) |101011 2b [ 6]
|
||||
'q' (113) |1110110 76 [ 7]
|
||||
'r' (114) |101100 2c [ 6]
|
||||
's' (115) |01000 8 [ 5]
|
||||
't' (116) |01001 9 [ 5]
|
||||
'u' (117) |101101 2d [ 6]
|
||||
'v' (118) |1110111 77 [ 7]
|
||||
'w' (119) |1111000 78 [ 7]
|
||||
'x' (120) |1111001 79 [ 7]
|
||||
'y' (121) |1111010 7a [ 7]
|
||||
'z' (122) |1111011 7b [ 7]
|
||||
'{' (123) |11111111|1111110 7ffe [15]
|
||||
'|' (124) |11111111|100 7fc [11]
|
||||
'}' (125) |11111111|111101 3ffd [14]
|
||||
'~' (126) |11111111|11101 1ffd [13]
|
||||
(127) |11111111|11111111|11111111|1100 ffffffc [28]
|
||||
(128) |11111111|11111110|0110 fffe6 [20]
|
||||
(129) |11111111|11111111|010010 3fffd2 [22]
|
||||
(130) |11111111|11111110|0111 fffe7 [20]
|
||||
(131) |11111111|11111110|1000 fffe8 [20]
|
||||
(132) |11111111|11111111|010011 3fffd3 [22]
|
||||
(133) |11111111|11111111|010100 3fffd4 [22]
|
||||
(134) |11111111|11111111|010101 3fffd5 [22]
|
||||
(135) |11111111|11111111|1011001 7fffd9 [23]
|
||||
(136) |11111111|11111111|010110 3fffd6 [22]
|
||||
(137) |11111111|11111111|1011010 7fffda [23]
|
||||
(138) |11111111|11111111|1011011 7fffdb [23]
|
||||
(139) |11111111|11111111|1011100 7fffdc [23]
|
||||
(140) |11111111|11111111|1011101 7fffdd [23]
|
||||
(141) |11111111|11111111|1011110 7fffde [23]
|
||||
(142) |11111111|11111111|11101011 ffffeb [24]
|
||||
(143) |11111111|11111111|1011111 7fffdf [23]
|
||||
(144) |11111111|11111111|11101100 ffffec [24]
|
||||
(145) |11111111|11111111|11101101 ffffed [24]
|
||||
(146) |11111111|11111111|010111 3fffd7 [22]
|
||||
(147) |11111111|11111111|1100000 7fffe0 [23]
|
||||
(148) |11111111|11111111|11101110 ffffee [24]
|
||||
(149) |11111111|11111111|1100001 7fffe1 [23]
|
||||
(150) |11111111|11111111|1100010 7fffe2 [23]
|
||||
(151) |11111111|11111111|1100011 7fffe3 [23]
|
||||
(152) |11111111|11111111|1100100 7fffe4 [23]
|
||||
(153) |11111111|11111110|11100 1fffdc [21]
|
||||
(154) |11111111|11111111|011000 3fffd8 [22]
|
||||
(155) |11111111|11111111|1100101 7fffe5 [23]
|
||||
(156) |11111111|11111111|011001 3fffd9 [22]
|
||||
(157) |11111111|11111111|1100110 7fffe6 [23]
|
||||
(158) |11111111|11111111|1100111 7fffe7 [23]
|
||||
(159) |11111111|11111111|11101111 ffffef [24]
|
||||
(160) |11111111|11111111|011010 3fffda [22]
|
||||
(161) |11111111|11111110|11101 1fffdd [21]
|
||||
(162) |11111111|11111110|1001 fffe9 [20]
|
||||
(163) |11111111|11111111|011011 3fffdb [22]
|
||||
(164) |11111111|11111111|011100 3fffdc [22]
|
||||
(165) |11111111|11111111|1101000 7fffe8 [23]
|
||||
(166) |11111111|11111111|1101001 7fffe9 [23]
|
||||
(167) |11111111|11111110|11110 1fffde [21]
|
||||
(168) |11111111|11111111|1101010 7fffea [23]
|
||||
(169) |11111111|11111111|011101 3fffdd [22]
|
||||
(170) |11111111|11111111|011110 3fffde [22]
|
||||
(171) |11111111|11111111|11110000 fffff0 [24]
|
||||
(172) |11111111|11111110|11111 1fffdf [21]
|
||||
(173) |11111111|11111111|011111 3fffdf [22]
|
||||
(174) |11111111|11111111|1101011 7fffeb [23]
|
||||
(175) |11111111|11111111|1101100 7fffec [23]
|
||||
(176) |11111111|11111111|00000 1fffe0 [21]
|
||||
(177) |11111111|11111111|00001 1fffe1 [21]
|
||||
(178) |11111111|11111111|100000 3fffe0 [22]
|
||||
(179) |11111111|11111111|00010 1fffe2 [21]
|
||||
(180) |11111111|11111111|1101101 7fffed [23]
|
||||
(181) |11111111|11111111|100001 3fffe1 [22]
|
||||
(182) |11111111|11111111|1101110 7fffee [23]
|
||||
(183) |11111111|11111111|1101111 7fffef [23]
|
||||
(184) |11111111|11111110|1010 fffea [20]
|
||||
(185) |11111111|11111111|100010 3fffe2 [22]
|
||||
(186) |11111111|11111111|100011 3fffe3 [22]
|
||||
(187) |11111111|11111111|100100 3fffe4 [22]
|
||||
(188) |11111111|11111111|1110000 7ffff0 [23]
|
||||
(189) |11111111|11111111|100101 3fffe5 [22]
|
||||
(190) |11111111|11111111|100110 3fffe6 [22]
|
||||
(191) |11111111|11111111|1110001 7ffff1 [23]
|
||||
(192) |11111111|11111111|11111000|00 3ffffe0 [26]
|
||||
(193) |11111111|11111111|11111000|01 3ffffe1 [26]
|
||||
(194) |11111111|11111110|1011 fffeb [20]
|
||||
(195) |11111111|11111110|001 7fff1 [19]
|
||||
(196) |11111111|11111111|100111 3fffe7 [22]
|
||||
(197) |11111111|11111111|1110010 7ffff2 [23]
|
||||
(198) |11111111|11111111|101000 3fffe8 [22]
|
||||
(199) |11111111|11111111|11110110|0 1ffffec [25]
|
||||
(200) |11111111|11111111|11111000|10 3ffffe2 [26]
|
||||
(201) |11111111|11111111|11111000|11 3ffffe3 [26]
|
||||
(202) |11111111|11111111|11111001|00 3ffffe4 [26]
|
||||
(203) |11111111|11111111|11111011|110 7ffffde [27]
|
||||
(204) |11111111|11111111|11111011|111 7ffffdf [27]
|
||||
(205) |11111111|11111111|11111001|01 3ffffe5 [26]
|
||||
(206) |11111111|11111111|11110001 fffff1 [24]
|
||||
(207) |11111111|11111111|11110110|1 1ffffed [25]
|
||||
(208) |11111111|11111110|010 7fff2 [19]
|
||||
(209) |11111111|11111111|00011 1fffe3 [21]
|
||||
(210) |11111111|11111111|11111001|10 3ffffe6 [26]
|
||||
(211) |11111111|11111111|11111100|000 7ffffe0 [27]
|
||||
(212) |11111111|11111111|11111100|001 7ffffe1 [27]
|
||||
(213) |11111111|11111111|11111001|11 3ffffe7 [26]
|
||||
(214) |11111111|11111111|11111100|010 7ffffe2 [27]
|
||||
(215) |11111111|11111111|11110010 fffff2 [24]
|
||||
(216) |11111111|11111111|00100 1fffe4 [21]
|
||||
(217) |11111111|11111111|00101 1fffe5 [21]
|
||||
(218) |11111111|11111111|11111010|00 3ffffe8 [26]
|
||||
(219) |11111111|11111111|11111010|01 3ffffe9 [26]
|
||||
(220) |11111111|11111111|11111111|1101 ffffffd [28]
|
||||
(221) |11111111|11111111|11111100|011 7ffffe3 [27]
|
||||
(222) |11111111|11111111|11111100|100 7ffffe4 [27]
|
||||
(223) |11111111|11111111|11111100|101 7ffffe5 [27]
|
||||
(224) |11111111|11111110|1100 fffec [20]
|
||||
(225) |11111111|11111111|11110011 fffff3 [24]
|
||||
(226) |11111111|11111110|1101 fffed [20]
|
||||
(227) |11111111|11111111|00110 1fffe6 [21]
|
||||
(228) |11111111|11111111|101001 3fffe9 [22]
|
||||
(229) |11111111|11111111|00111 1fffe7 [21]
|
||||
(230) |11111111|11111111|01000 1fffe8 [21]
|
||||
(231) |11111111|11111111|1110011 7ffff3 [23]
|
||||
(232) |11111111|11111111|101010 3fffea [22]
|
||||
(233) |11111111|11111111|101011 3fffeb [22]
|
||||
(234) |11111111|11111111|11110111|0 1ffffee [25]
|
||||
(235) |11111111|11111111|11110111|1 1ffffef [25]
|
||||
(236) |11111111|11111111|11110100 fffff4 [24]
|
||||
(237) |11111111|11111111|11110101 fffff5 [24]
|
||||
(238) |11111111|11111111|11111010|10 3ffffea [26]
|
||||
(239) |11111111|11111111|1110100 7ffff4 [23]
|
||||
(240) |11111111|11111111|11111010|11 3ffffeb [26]
|
||||
(241) |11111111|11111111|11111100|110 7ffffe6 [27]
|
||||
(242) |11111111|11111111|11111011|00 3ffffec [26]
|
||||
(243) |11111111|11111111|11111011|01 3ffffed [26]
|
||||
(244) |11111111|11111111|11111100|111 7ffffe7 [27]
|
||||
(245) |11111111|11111111|11111101|000 7ffffe8 [27]
|
||||
(246) |11111111|11111111|11111101|001 7ffffe9 [27]
|
||||
(247) |11111111|11111111|11111101|010 7ffffea [27]
|
||||
(248) |11111111|11111111|11111101|011 7ffffeb [27]
|
||||
(249) |11111111|11111111|11111111|1110 ffffffe [28]
|
||||
(250) |11111111|11111111|11111101|100 7ffffec [27]
|
||||
(251) |11111111|11111111|11111101|101 7ffffed [27]
|
||||
(252) |11111111|11111111|11111101|110 7ffffee [27]
|
||||
(253) |11111111|11111111|11111101|111 7ffffef [27]
|
||||
(254) |11111111|11111111|11111110|000 7fffff0 [27]
|
||||
(255) |11111111|11111111|11111011|10 3ffffee [26]
|
||||
EOS (256) |11111111|11111111|11111111|111111 3fffffff [30]
|
||||
348
phoenix/deps/hpax/lib/hpax/table.ex
Normal file
348
phoenix/deps/hpax/lib/hpax/table.ex
Normal file
@@ -0,0 +1,348 @@
|
||||
defmodule HPAX.Table do
|
||||
@moduledoc false
|
||||
|
||||
@enforce_keys [:max_table_size, :huffman_encoding]
|
||||
defstruct [
|
||||
:protocol_max_table_size,
|
||||
:max_table_size,
|
||||
:huffman_encoding,
|
||||
entries: [],
|
||||
size: 0,
|
||||
length: 0,
|
||||
pending_minimum_resize: nil
|
||||
]
|
||||
|
||||
@type huffman_encoding() :: :always | :never
|
||||
|
||||
@type t() :: %__MODULE__{
|
||||
protocol_max_table_size: non_neg_integer(),
|
||||
max_table_size: non_neg_integer(),
|
||||
huffman_encoding: huffman_encoding(),
|
||||
entries: [{binary(), binary()}],
|
||||
size: non_neg_integer(),
|
||||
length: non_neg_integer(),
|
||||
pending_minimum_resize: non_neg_integer() | nil
|
||||
}
|
||||
|
||||
@static_table [
|
||||
{":authority", nil},
|
||||
{":method", "GET"},
|
||||
{":method", "POST"},
|
||||
{":path", "/"},
|
||||
{":path", "/index.html"},
|
||||
{":scheme", "http"},
|
||||
{":scheme", "https"},
|
||||
{":status", "200"},
|
||||
{":status", "204"},
|
||||
{":status", "206"},
|
||||
{":status", "304"},
|
||||
{":status", "400"},
|
||||
{":status", "404"},
|
||||
{":status", "500"},
|
||||
{"accept-charset", nil},
|
||||
{"accept-encoding", "gzip, deflate"},
|
||||
{"accept-language", nil},
|
||||
{"accept-ranges", nil},
|
||||
{"accept", nil},
|
||||
{"access-control-allow-origin", nil},
|
||||
{"age", nil},
|
||||
{"allow", nil},
|
||||
{"authorization", nil},
|
||||
{"cache-control", nil},
|
||||
{"content-disposition", nil},
|
||||
{"content-encoding", nil},
|
||||
{"content-language", nil},
|
||||
{"content-length", nil},
|
||||
{"content-location", nil},
|
||||
{"content-range", nil},
|
||||
{"content-type", nil},
|
||||
{"cookie", nil},
|
||||
{"date", nil},
|
||||
{"etag", nil},
|
||||
{"expect", nil},
|
||||
{"expires", nil},
|
||||
{"from", nil},
|
||||
{"host", nil},
|
||||
{"if-match", nil},
|
||||
{"if-modified-since", nil},
|
||||
{"if-none-match", nil},
|
||||
{"if-range", nil},
|
||||
{"if-unmodified-since", nil},
|
||||
{"last-modified", nil},
|
||||
{"link", nil},
|
||||
{"location", nil},
|
||||
{"max-forwards", nil},
|
||||
{"proxy-authenticate", nil},
|
||||
{"proxy-authorization", nil},
|
||||
{"range", nil},
|
||||
{"referer", nil},
|
||||
{"refresh", nil},
|
||||
{"retry-after", nil},
|
||||
{"server", nil},
|
||||
{"set-cookie", nil},
|
||||
{"strict-transport-security", nil},
|
||||
{"transfer-encoding", nil},
|
||||
{"user-agent", nil},
|
||||
{"vary", nil},
|
||||
{"via", nil},
|
||||
{"www-authenticate", nil}
|
||||
]
|
||||
|
||||
@static_table_size length(@static_table)
|
||||
@dynamic_table_start @static_table_size + 1
|
||||
|
||||
@doc """
|
||||
Creates a new HPACK table with the given maximum size.
|
||||
|
||||
The maximum size is not the maximum number of entries but rather the maximum size as defined in
|
||||
http://httpwg.org/specs/rfc7541.html#maximum.table.size.
|
||||
"""
|
||||
@spec new(non_neg_integer(), huffman_encoding()) :: t()
|
||||
def new(protocol_max_table_size, huffman_encoding)
|
||||
when is_integer(protocol_max_table_size) and protocol_max_table_size >= 0 and
|
||||
huffman_encoding in [:always, :never] do
|
||||
%__MODULE__{
|
||||
protocol_max_table_size: protocol_max_table_size,
|
||||
max_table_size: protocol_max_table_size,
|
||||
huffman_encoding: huffman_encoding
|
||||
}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds the given header to the given table.
|
||||
|
||||
If the new entry does not fit within the max table size then the oldest entries will be evicted.
|
||||
|
||||
Header names should be lowercase when added to the HPACK table
|
||||
as per the [HTTP/2 spec](https://http2.github.io/http2-spec/#rfc.section.8.1.2):
|
||||
|
||||
> header field names MUST be converted to lowercase prior to their encoding in HTTP/2
|
||||
|
||||
"""
|
||||
@spec add(t(), binary(), binary()) :: t()
|
||||
def add(%__MODULE__{} = table, name, value) do
|
||||
%{max_table_size: max_table_size, size: size} = table
|
||||
entry_size = entry_size(name, value)
|
||||
|
||||
cond do
|
||||
# An attempt to add an entry larger than the maximum size causes the table to be emptied of
|
||||
# all existing entries and results in an empty table.
|
||||
entry_size > max_table_size ->
|
||||
%{table | entries: [], size: 0, length: 0}
|
||||
|
||||
size + entry_size > max_table_size ->
|
||||
table
|
||||
|> evict_to_size(max_table_size - entry_size)
|
||||
|> add_header(name, value, entry_size)
|
||||
|
||||
true ->
|
||||
add_header(table, name, value, entry_size)
|
||||
end
|
||||
end
|
||||
|
||||
defp add_header(%__MODULE__{} = table, name, value, entry_size) do
|
||||
%{entries: entries, size: size, length: length} = table
|
||||
%{table | entries: [{name, value} | entries], size: size + entry_size, length: length + 1}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Looks up a header by index `index` in the given `table`.
|
||||
|
||||
Returns `{:ok, {name, value}}` if a header is found at the given `index`, otherwise returns
|
||||
`:error`. `value` can be a binary in case both the header name and value are present in the
|
||||
table, or `nil` if only the name is present (this can only happen in the static table).
|
||||
"""
|
||||
@spec lookup_by_index(t(), pos_integer()) :: {:ok, {binary(), binary() | nil}} | :error
|
||||
def lookup_by_index(table, index)
|
||||
|
||||
# Static table
|
||||
for {header, index} <- Enum.with_index(@static_table, 1) do
|
||||
def lookup_by_index(%__MODULE__{}, unquote(index)), do: {:ok, unquote(header)}
|
||||
end
|
||||
|
||||
def lookup_by_index(%__MODULE__{length: 0}, _index) do
|
||||
:error
|
||||
end
|
||||
|
||||
def lookup_by_index(%__MODULE__{entries: entries, length: length}, index)
|
||||
when index >= @dynamic_table_start and index <= @dynamic_table_start + length - 1 do
|
||||
{:ok, Enum.at(entries, index - @dynamic_table_start)}
|
||||
end
|
||||
|
||||
def lookup_by_index(%__MODULE__{}, _index) do
|
||||
:error
|
||||
end
|
||||
|
||||
@doc """
|
||||
Looks up the index of a header by its name and value.
|
||||
|
||||
It returns:
|
||||
|
||||
* `{:full, index}` if the full header (name and value) are present in the table at `index`
|
||||
|
||||
* `{:name, index}` if `name` is present in the table but with a different value than `value`
|
||||
|
||||
* `:not_found` if the header name is not in the table at all
|
||||
|
||||
Header names should be lowercase when looked up in the HPACK table
|
||||
as per the [HTTP/2 spec](https://http2.github.io/http2-spec/#rfc.section.8.1.2):
|
||||
|
||||
> header field names MUST be converted to lowercase prior to their encoding in HTTP/2
|
||||
|
||||
"""
|
||||
@spec lookup_by_header(t(), binary(), binary() | nil) ::
|
||||
{:full, pos_integer()} | {:name, pos_integer()} | :not_found
|
||||
def lookup_by_header(table, name, value)
|
||||
|
||||
def lookup_by_header(%__MODULE__{entries: entries}, name, value) do
|
||||
case static_lookup_by_header(name, value) do
|
||||
{:full, _index} = result ->
|
||||
result
|
||||
|
||||
{:name, index} ->
|
||||
# Check if we get full match in the dynamic tabble
|
||||
case dynamic_lookup_by_header(entries, name, value, @dynamic_table_start, nil) do
|
||||
{:full, _index} = result -> result
|
||||
_other -> {:name, index}
|
||||
end
|
||||
|
||||
:not_found ->
|
||||
dynamic_lookup_by_header(entries, name, value, @dynamic_table_start, nil)
|
||||
end
|
||||
end
|
||||
|
||||
for {{name, value}, index} when is_binary(value) <- Enum.with_index(@static_table, 1) do
|
||||
defp static_lookup_by_header(unquote(name), unquote(value)) do
|
||||
{:full, unquote(index)}
|
||||
end
|
||||
end
|
||||
|
||||
static_table_names =
|
||||
@static_table
|
||||
|> Enum.map(&elem(&1, 0))
|
||||
|> Enum.with_index(1)
|
||||
|> Enum.uniq_by(&elem(&1, 0))
|
||||
|
||||
for {name, index} <- static_table_names do
|
||||
defp static_lookup_by_header(unquote(name), _value) do
|
||||
{:name, unquote(index)}
|
||||
end
|
||||
end
|
||||
|
||||
defp static_lookup_by_header(_name, _value) do
|
||||
:not_found
|
||||
end
|
||||
|
||||
defp dynamic_lookup_by_header([{name, value} | _rest], name, value, index, _name_index) do
|
||||
{:full, index}
|
||||
end
|
||||
|
||||
defp dynamic_lookup_by_header([{name, _} | rest], name, value, index, _name_index) do
|
||||
dynamic_lookup_by_header(rest, name, value, index + 1, index)
|
||||
end
|
||||
|
||||
defp dynamic_lookup_by_header([_other | rest], name, value, index, name_index) do
|
||||
dynamic_lookup_by_header(rest, name, value, index + 1, name_index)
|
||||
end
|
||||
|
||||
defp dynamic_lookup_by_header([], _name, _value, _index, name_index) do
|
||||
if name_index, do: {:name, name_index}, else: :not_found
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changes the table's protocol negotiated maximum size, possibly evicting entries as needed to satisfy.
|
||||
|
||||
If the indicated size is less than the table's current max size, entries
|
||||
will be evicted as needed to fit within the specified size, and the table's
|
||||
maximum size will be decreased to the specified value. An will also be
|
||||
set which will enqueue a 'dynamic table size update' command to be prefixed
|
||||
to the next block encoded with this table, per RFC9113§4.3.1.
|
||||
|
||||
If the indicated size is greater than or equal to the table's current max size, no entries are evicted
|
||||
and the table's maximum size changes to the specified value.
|
||||
|
||||
In all cases, the table's `:protocol_max_table_size` is updated accordingly
|
||||
"""
|
||||
@spec resize(t(), non_neg_integer()) :: t()
|
||||
def resize(%__MODULE__{} = table, new_protocol_max_table_size) do
|
||||
pending_minimum_resize =
|
||||
case table.pending_minimum_resize do
|
||||
nil -> new_protocol_max_table_size
|
||||
current -> min(current, new_protocol_max_table_size)
|
||||
end
|
||||
|
||||
%{
|
||||
evict_to_size(table, new_protocol_max_table_size)
|
||||
| protocol_max_table_size: new_protocol_max_table_size,
|
||||
max_table_size: new_protocol_max_table_size,
|
||||
pending_minimum_resize: pending_minimum_resize
|
||||
}
|
||||
end
|
||||
|
||||
def dynamic_resize(%__MODULE__{} = table, new_max_table_size) do
|
||||
%{
|
||||
evict_to_size(table, new_max_table_size)
|
||||
| max_table_size: new_max_table_size
|
||||
}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns (and clears) any pending resize events on the table which will need to be signalled to
|
||||
the decoder via dynamic table size update messages. Intended to be called at the start of any
|
||||
block encode to prepend such dynamic table size update(s) as needed. The value of
|
||||
`pending_minimum_resize` indicates the smallest maximum size of this table which has not yet
|
||||
been signalled to the decoder, and is always included in the list returned if it is set.
|
||||
Additionally, if the current max table size is larger than this value, it is also included int
|
||||
the list, per https://www.rfc-editor.org/rfc/rfc7541#section-4.2
|
||||
"""
|
||||
def pop_pending_resizes(%__MODULE__{pending_minimum_resize: nil} = table), do: {table, []}
|
||||
|
||||
def pop_pending_resizes(%__MODULE__{} = table) do
|
||||
pending_resizes =
|
||||
if table.max_table_size > table.pending_minimum_resize,
|
||||
do: [table.pending_minimum_resize, table.max_table_size],
|
||||
else: [table.pending_minimum_resize]
|
||||
|
||||
{%{table | pending_minimum_resize: nil}, pending_resizes}
|
||||
end
|
||||
|
||||
# Removes records as necessary to have the total size of entries within the table be less than
|
||||
# or equal to the specified value. Does not change the table's max size.
|
||||
defp evict_to_size(%__MODULE__{size: size} = table, new_size) when size <= new_size, do: table
|
||||
|
||||
defp evict_to_size(%__MODULE__{entries: entries, size: size} = table, new_size) do
|
||||
{new_entries_reversed, new_size} =
|
||||
evict_towards_size(Enum.reverse(entries), size, new_size)
|
||||
|
||||
%{
|
||||
table
|
||||
| entries: Enum.reverse(new_entries_reversed),
|
||||
size: new_size,
|
||||
length: length(new_entries_reversed)
|
||||
}
|
||||
end
|
||||
|
||||
defp evict_towards_size([{name, value} | rest], size, max_target_size) do
|
||||
new_size = size - entry_size(name, value)
|
||||
|
||||
if new_size <= max_target_size do
|
||||
{rest, new_size}
|
||||
else
|
||||
evict_towards_size(rest, new_size, max_target_size)
|
||||
end
|
||||
end
|
||||
|
||||
defp evict_towards_size([], 0, _max_target_size) do
|
||||
{[], 0}
|
||||
end
|
||||
|
||||
defp entry_size(name, value) do
|
||||
byte_size(name) + byte_size(value) + 32
|
||||
end
|
||||
|
||||
# Made public to be used in tests.
|
||||
@doc false
|
||||
def __static_table__() do
|
||||
@static_table
|
||||
end
|
||||
end
|
||||
89
phoenix/deps/hpax/lib/hpax/types.ex
Normal file
89
phoenix/deps/hpax/lib/hpax/types.ex
Normal file
@@ -0,0 +1,89 @@
|
||||
defmodule HPAX.Types do
|
||||
@moduledoc false
|
||||
|
||||
import Bitwise, only: [<<<: 2]
|
||||
|
||||
alias HPAX.Huffman
|
||||
|
||||
# This is used as a macro and not an inlined function because we want to be able to use it in
|
||||
# guards.
|
||||
defmacrop power_of_two(n) do
|
||||
quote do: 1 <<< unquote(n)
|
||||
end
|
||||
|
||||
## Encoding
|
||||
|
||||
@spec encode_integer(non_neg_integer(), 1..8) :: bitstring()
|
||||
def encode_integer(integer, prefix)
|
||||
|
||||
def encode_integer(integer, prefix) when integer < power_of_two(prefix) - 1 do
|
||||
<<integer::size(prefix)>>
|
||||
end
|
||||
|
||||
def encode_integer(integer, prefix) do
|
||||
initial = power_of_two(prefix) - 1
|
||||
remaining = integer - initial
|
||||
<<initial::size(prefix), encode_remaining_integer(remaining)::binary>>
|
||||
end
|
||||
|
||||
defp encode_remaining_integer(remaining) when remaining >= 128 do
|
||||
first = rem(remaining, 128) + 128
|
||||
<<first::8, encode_remaining_integer(div(remaining, 128))::binary>>
|
||||
end
|
||||
|
||||
defp encode_remaining_integer(remaining) do
|
||||
<<remaining::8>>
|
||||
end
|
||||
|
||||
@spec encode_binary(binary(), boolean()) :: iodata()
|
||||
def encode_binary(binary, huffman?) do
|
||||
binary = if huffman?, do: Huffman.encode(binary), else: binary
|
||||
huffman_bit = if huffman?, do: 1, else: 0
|
||||
binary_size = encode_integer(byte_size(binary), 7)
|
||||
[<<huffman_bit::1, binary_size::bitstring>>, binary]
|
||||
end
|
||||
|
||||
## Decoding
|
||||
|
||||
@spec decode_integer(bitstring, 1..8) :: {:ok, non_neg_integer(), binary()} | :error
|
||||
def decode_integer(bitstring, prefix) when is_bitstring(bitstring) and prefix in 1..8 do
|
||||
with <<value::size(prefix), rest::binary>> <- bitstring do
|
||||
if value < power_of_two(prefix) - 1 do
|
||||
{:ok, value, rest}
|
||||
else
|
||||
decode_remaining_integer(rest, value, 0)
|
||||
end
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
defp decode_remaining_integer(<<0::1, value::7, rest::binary>>, int, m) do
|
||||
{:ok, int + (value <<< m), rest}
|
||||
end
|
||||
|
||||
defp decode_remaining_integer(<<1::1, value::7, rest::binary>>, int, m) do
|
||||
decode_remaining_integer(rest, int + (value <<< m), m + 7)
|
||||
end
|
||||
|
||||
defp decode_remaining_integer(_, _, _) do
|
||||
:error
|
||||
end
|
||||
|
||||
@spec decode_binary(binary) :: {:ok, binary(), binary()} | :error
|
||||
def decode_binary(binary) when is_binary(binary) do
|
||||
with <<huffman_bit::1, rest::bitstring>> <- binary,
|
||||
{:ok, length, rest} <- decode_integer(rest, 7),
|
||||
<<contents::binary-size(length), rest::binary>> <- rest do
|
||||
contents =
|
||||
case huffman_bit do
|
||||
0 -> contents
|
||||
1 -> Huffman.decode(contents)
|
||||
end
|
||||
|
||||
{:ok, contents, rest}
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user