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 <> -> 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(<>, acc) do encode(rest, <>) 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) <> end end ## Decoding @spec decode(binary()) :: binary() def decode(binary) for {byte_value, bits, bit_count} <- regular_entries do def decode(<>) do <> end end def decode(<<>>) do <<>> end # Use binary syntax for single match context optimization. def decode(<>) when bit_size(padding) in 1..7 do padding_size = bit_size(padding) <> = 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