types.ex (2563B)
1 defmodule HPAX.Types do 2 @moduledoc false 3 4 import Bitwise, only: [<<<: 2] 5 6 alias HPAX.Huffman 7 8 # This is used as a macro and not an inlined function because we want to be able to use it in 9 # guards. 10 defmacrop power_of_two(n) do 11 quote do: 1 <<< unquote(n) 12 end 13 14 ## Encoding 15 16 @spec encode_integer(non_neg_integer(), 1..8) :: bitstring() 17 def encode_integer(integer, prefix) 18 19 def encode_integer(integer, prefix) when integer < power_of_two(prefix) - 1 do 20 <<integer::size(prefix)>> 21 end 22 23 def encode_integer(integer, prefix) do 24 initial = power_of_two(prefix) - 1 25 remaining = integer - initial 26 <<initial::size(prefix), encode_remaining_integer(remaining)::binary>> 27 end 28 29 defp encode_remaining_integer(remaining) when remaining >= 128 do 30 first = rem(remaining, 128) + 128 31 <<first::8, encode_remaining_integer(div(remaining, 128))::binary>> 32 end 33 34 defp encode_remaining_integer(remaining) do 35 <<remaining::8>> 36 end 37 38 @spec encode_binary(binary(), boolean()) :: iodata() 39 def encode_binary(binary, huffman?) do 40 binary = if huffman?, do: Huffman.encode(binary), else: binary 41 huffman_bit = if huffman?, do: 1, else: 0 42 binary_size = encode_integer(byte_size(binary), 7) 43 [<<huffman_bit::1, binary_size::bitstring>>, binary] 44 end 45 46 ## Decoding 47 48 @spec decode_integer(bitstring, 1..8) :: {:ok, non_neg_integer(), binary()} | :error 49 def decode_integer(bitstring, prefix) when is_bitstring(bitstring) and prefix in 1..8 do 50 with <<value::size(prefix), rest::binary>> <- bitstring do 51 if value < power_of_two(prefix) - 1 do 52 {:ok, value, rest} 53 else 54 decode_remaining_integer(rest, value, 0) 55 end 56 else 57 _ -> :error 58 end 59 end 60 61 defp decode_remaining_integer(<<0::1, value::7, rest::binary>>, int, m) do 62 {:ok, int + (value <<< m), rest} 63 end 64 65 defp decode_remaining_integer(<<1::1, value::7, rest::binary>>, int, m) do 66 decode_remaining_integer(rest, int + (value <<< m), m + 7) 67 end 68 69 defp decode_remaining_integer(_, _, _) do 70 :error 71 end 72 73 @spec decode_binary(binary) :: {:ok, binary(), binary()} | :error 74 def decode_binary(binary) when is_binary(binary) do 75 with <<huffman_bit::1, rest::bitstring>> <- binary, 76 {:ok, length, rest} <- decode_integer(rest, 7), 77 <<contents::binary-size(length), rest::binary>> <- rest do 78 contents = 79 case huffman_bit do 80 0 -> contents 81 1 -> Huffman.decode(contents) 82 end 83 84 {:ok, contents, rest} 85 else 86 _ -> :error 87 end 88 end 89 end