this repo has no description
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add key encodings

garrison a7c09005 8d7da004

+227
+162
lib/enc.ex
··· 1 + defmodule Hobbes.Enc do 2 + @moduledoc """ 3 + Encodings for Hobbes keys and values. 4 + """ 5 + 6 + @escape 0xFF 7 + @nil_value 0x00 8 + @binary 0x01 9 + @nested_list 0x05 10 + 11 + @int_0 0x14 12 + @int_pos_1 0x15 13 + @int_pos_2 0x16 14 + @int_pos_3 0x17 15 + @int_pos_4 0x18 16 + @int_pos_5 0x1A 17 + @int_pos_6 0x1B 18 + @int_pos_7 0x1C 19 + @int_pos_8 0x1D 20 + 21 + @spec encode_key(list) :: binary 22 + def encode_key(list) when is_list(list) do 23 + enc_key(list, 0) 24 + |> IO.iodata_to_binary() 25 + end 26 + 27 + @spec decode_key(binary) :: list 28 + def decode_key(binary) when is_binary(binary) do 29 + case dec_key(binary, 0) do 30 + {values, ""} -> 31 + values 32 + end 33 + end 34 + 35 + defp enc_key(nil, 0), do: <<@nil_value>> 36 + defp enc_key(nil, depth) when depth > 0, do: <<@nil_value, @escape>> 37 + 38 + defp enc_key(list, 0) when is_list(list) do 39 + Enum.map(list, &enc_key(&1, 1)) 40 + end 41 + 42 + defp enc_key(list, depth) when is_list(list) and depth > 0 do 43 + values = Enum.map(list, &enc_key(&1, depth + 1)) 44 + [@nested_list, values, @nil_value] 45 + end 46 + 47 + defp enc_key(binary, _depth) when is_binary(binary) do 48 + [<<@binary>>, enc_binary(binary, 0)] 49 + end 50 + 51 + defp enc_key(0, _depth) do 52 + <<@int_0>> 53 + end 54 + 55 + defp enc_key(int, _depth) when is_integer(int) and int > 0 do 56 + binary = :binary.encode_unsigned(int) 57 + case byte_size(binary) do 58 + 1 -> [<<@int_pos_1>>, binary] 59 + 2 -> [<<@int_pos_2>>, binary] 60 + 3 -> [<<@int_pos_3>>, binary] 61 + 4 -> [<<@int_pos_4>>, binary] 62 + 5 -> [<<@int_pos_5>>, binary] 63 + 6 -> [<<@int_pos_6>>, binary] 64 + 7 -> [<<@int_pos_7>>, binary] 65 + 8 -> [<<@int_pos_8>>, binary] 66 + end 67 + end 68 + 69 + # Encodes a null-terminated binary, nulls are escaped as <<0x00, 0xFF>> 70 + defp enc_binary(binary, offset) do 71 + case binary do 72 + <<head::binary-size(offset)>> -> 73 + # Terminate encoded binary (null-terminated) 74 + [head, <<@nil_value>>] 75 + 76 + <<head::binary-size(offset), 0x00, tail::binary>> -> 77 + # Escape null value in binary and keep going 78 + [head, <<@nil_value, @escape>> | enc_binary(tail, 0)] 79 + 80 + <<_head::binary-size(offset), _other, _tail::binary>> -> 81 + # Non-null character, keep scanning 82 + enc_binary(binary, offset + 1) 83 + end 84 + end 85 + 86 + defp dec_key("", 0) do 87 + {[], ""} 88 + end 89 + 90 + defp dec_key(<<@nil_value, rest::binary>>, 0 = depth) do 91 + {values, tail} = dec_key(rest, depth) 92 + {[nil | values], tail} 93 + end 94 + 95 + # Escaped nil within a nested list 96 + defp dec_key(<<@nil_value, @escape, rest::binary>>, depth) when depth > 0 do 97 + {values, tail} = dec_key(rest, depth) 98 + {[nil | values], tail} 99 + end 100 + 101 + # Closing a nested list 102 + defp dec_key(<<@nil_value, rest::binary>>, depth) when depth > 0 do 103 + {[], rest} 104 + end 105 + 106 + defp dec_key(<<@binary, rest::binary>>, depth) do 107 + {decoded_binary, rest} = dec_binary(rest) 108 + {values, tail} = dec_key(rest, depth) 109 + {[decoded_binary | values], tail} 110 + end 111 + 112 + defp dec_key(<<@nested_list, rest::binary>>, depth) do 113 + {nested_values, rest} = dec_key(rest, depth + 1) 114 + {values, tail} = dec_key(rest, depth) 115 + {[nested_values | values], tail} 116 + end 117 + 118 + defp dec_key(<<@int_0, rest::binary>>, depth) do 119 + {values, tail} = dec_key(rest, depth) 120 + {[0 | values], tail} 121 + end 122 + 123 + defp dec_key(<<@int_pos_1, rest::binary>>, depth), do: dec_pos_integer(1, rest, depth) 124 + defp dec_key(<<@int_pos_2, rest::binary>>, depth), do: dec_pos_integer(2, rest, depth) 125 + defp dec_key(<<@int_pos_3, rest::binary>>, depth), do: dec_pos_integer(3, rest, depth) 126 + defp dec_key(<<@int_pos_4, rest::binary>>, depth), do: dec_pos_integer(4, rest, depth) 127 + defp dec_key(<<@int_pos_5, rest::binary>>, depth), do: dec_pos_integer(5, rest, depth) 128 + defp dec_key(<<@int_pos_6, rest::binary>>, depth), do: dec_pos_integer(6, rest, depth) 129 + defp dec_key(<<@int_pos_7, rest::binary>>, depth), do: dec_pos_integer(7, rest, depth) 130 + defp dec_key(<<@int_pos_8, rest::binary>>, depth), do: dec_pos_integer(8, rest, depth) 131 + 132 + defp dec_binary(binary) do 133 + {parts, tail} = dec_binary(binary, 0) 134 + {IO.iodata_to_binary(parts), tail} 135 + end 136 + 137 + defp dec_binary(binary, offset) do 138 + case binary do 139 + <<head::binary-size(offset), @nil_value, @escape, bin_tail::binary>> -> 140 + # Un-escape null byte and keep going 141 + {parts, tail} = dec_binary(bin_tail, 0) 142 + {[head, "\x00" | parts], tail} 143 + 144 + <<head::binary-size(offset), @nil_value, tail::binary>> -> 145 + # Found null byte without escape, terminate binary (null-terminated) 146 + {[head], tail} 147 + 148 + <<_head::binary-size(offset), _other, _tail::binary>> -> 149 + # Found any other byte, keep scanning 150 + dec_binary(binary, offset + 1) 151 + 152 + <<_head::binary-size(offset)>> -> raise "Encoded binary was not terminated: #{inspect(binary)}" 153 + end 154 + end 155 + 156 + defp dec_pos_integer(bytes, rest, depth) do 157 + <<int::unsigned-big-integer-size(bytes)-unit(8), rest::binary>> = rest 158 + 159 + {values, tail} = dec_key(rest, depth) 160 + {[int | values], tail} 161 + end 162 + end
+65
test/enc_test.exs
··· 1 + defmodule Hobbes.EncTest do 2 + use ExUnit.Case, async: true 3 + 4 + alias Hobbes.Enc 5 + 6 + @moduletag :enc 7 + 8 + # TODO: fuzz 9 + describe "tuples" do 10 + test "encodes and decodes integers" do 11 + keys = [ 12 + [0], 13 + [1], 14 + [0, 100], 15 + [1, 2, 3], 16 + [1_000_000, 2_000_000, 3], 17 + [(2 ** 64) - 1, 0], 18 + ] 19 + 20 + for k <- keys do 21 + result = Enc.encode_key(k) |> Enc.decode_key() 22 + assert result == k 23 + end 24 + end 25 + 26 + test "encodes and decodes binaries" do 27 + keys = [ 28 + ["foo"], 29 + ["foo", "bar"], 30 + ["foo\x00bar", "baz"], 31 + ["foo\x00bar", ["bar\x00baz", "qux"]], 32 + ] 33 + 34 + for k <- keys do 35 + result = Enc.encode_key(k) |> Enc.decode_key() 36 + assert result == k 37 + end 38 + end 39 + 40 + test "encodes and decodes nested" do 41 + keys = [ 42 + [0, [1, 2, [3, 4]]], 43 + [0, "foo", [1, "bar", 2, [3]]], 44 + [0, "fo\x00o", [1, "ba\x00\x00r\x00", 2, [3]]], 45 + ] 46 + 47 + for k <- keys do 48 + result = Enc.encode_key(k) |> Enc.decode_key() 49 + assert result == k 50 + end 51 + end 52 + end 53 + 54 + describe "encode_key/1" do 55 + test "encodes integers" do 56 + assert "\x14" = Enc.encode_key([0]) 57 + 58 + assert "\x17" <> rest = Enc.encode_key([1_000_000]) 59 + assert :binary.decode_unsigned(rest) == 1_000_000 60 + 61 + assert "\x1D" <> rest = Enc.encode_key([(2 ** 64) - 1]) 62 + assert rest == String.duplicate("\xFF", 8) 63 + end 64 + end 65 + end