this repo has no description
at master 221 lines 7.0 kB view raw
1defmodule Hobbes.Encoding.Keyset 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_neg_8 0x0C 12 @int_neg_7 0x0D 13 @int_neg_6 0x0E 14 @int_neg_5 0x0F 15 @int_neg_4 0x10 16 @int_neg_3 0x11 17 @int_neg_2 0x12 18 @int_neg_1 0x13 19 @int_0 0x14 20 @int_pos_1 0x15 21 @int_pos_2 0x16 22 @int_pos_3 0x17 23 @int_pos_4 0x18 24 @int_pos_5 0x1A 25 @int_pos_6 0x1B 26 @int_pos_7 0x1C 27 @int_pos_8 0x1D 28 29 @bool_false 0x26 30 @bool_true 0x27 31 32 @spec pack(list) :: binary 33 def pack(list) when is_list(list) do 34 Enum.map(list, &encode(&1, 0)) 35 |> IO.iodata_to_binary() 36 end 37 38 def pack(term) do 39 raise """ 40 Keyset.pack/1 expects a top-level list, got: #{inspect(term)} 41 If you want to encode a single term, wrap it in a list, like: Keyset.pack([123]) 42 """ 43 end 44 45 @spec unpack(binary) :: list 46 def unpack(binary) when is_binary(binary) do 47 case decode(binary, 0) do 48 {values, ""} -> 49 values 50 end 51 end 52 53 defp encode(nil, 0), do: <<@nil_value>> 54 defp encode(nil, depth) when depth > 0, do: <<@nil_value, @escape>> 55 56 defp encode(false, _depth), do: <<@bool_false>> 57 defp encode(true, _depth), do: <<@bool_true>> 58 59 defp encode(list, depth) when is_list(list) do 60 values = Enum.map(list, &encode(&1, depth + 1)) 61 [@nested_list, values, @nil_value] 62 end 63 64 defp encode(binary, _depth) when is_binary(binary) do 65 [<<@binary>>, encode_binary(binary, 0)] 66 end 67 68 defp encode(int, _depth) when is_integer(int) and int < 0 do 69 binary = :binary.encode_unsigned(-int) 70 binary = for <<b <- binary>>, into: "", do: <<Bitwise.bxor(b, 0xFF)>> 71 case byte_size(binary) do 72 8 -> [<<@int_neg_8>>, binary] 73 7 -> [<<@int_neg_7>>, binary] 74 6 -> [<<@int_neg_6>>, binary] 75 5 -> [<<@int_neg_5>>, binary] 76 4 -> [<<@int_neg_4>>, binary] 77 3 -> [<<@int_neg_3>>, binary] 78 2 -> [<<@int_neg_2>>, binary] 79 1 -> [<<@int_neg_1>>, binary] 80 end 81 end 82 83 defp encode(0, _depth) do 84 <<@int_0>> 85 end 86 87 defp encode(int, _depth) when is_integer(int) and int > 0 do 88 binary = :binary.encode_unsigned(int) 89 case byte_size(binary) do 90 1 -> [<<@int_pos_1>>, binary] 91 2 -> [<<@int_pos_2>>, binary] 92 3 -> [<<@int_pos_3>>, binary] 93 4 -> [<<@int_pos_4>>, binary] 94 5 -> [<<@int_pos_5>>, binary] 95 6 -> [<<@int_pos_6>>, binary] 96 7 -> [<<@int_pos_7>>, binary] 97 8 -> [<<@int_pos_8>>, binary] 98 end 99 end 100 101 # Encodes a null-terminated binary, nulls are escaped as <<0x00, 0xFF>> 102 defp encode_binary(binary, offset) do 103 case binary do 104 <<head::binary-size(offset)>> -> 105 # Terminate encoded binary (null-terminated) 106 [head, <<@nil_value>>] 107 108 <<head::binary-size(offset), 0x00, tail::binary>> -> 109 # Escape null value in binary and keep going 110 [head, <<@nil_value, @escape>> | encode_binary(tail, 0)] 111 112 <<_head::binary-size(offset), _other, _tail::binary>> -> 113 # Non-null character, keep scanning 114 encode_binary(binary, offset + 1) 115 end 116 end 117 118 defp decode("", 0) do 119 {[], ""} 120 end 121 122 defp decode(<<@nil_value, rest::binary>>, 0 = depth) do 123 {values, tail} = decode(rest, depth) 124 {[nil | values], tail} 125 end 126 127 # Escaped nil within a nested list 128 defp decode(<<@nil_value, @escape, rest::binary>>, depth) when depth > 0 do 129 {values, tail} = decode(rest, depth) 130 {[nil | values], tail} 131 end 132 133 # Closing a nested list 134 defp decode(<<@nil_value, rest::binary>>, depth) when depth > 0 do 135 {[], rest} 136 end 137 138 defp decode(<<@bool_false, rest::binary>>, depth) do 139 {values, tail} = decode(rest, depth) 140 {[false | values], tail} 141 end 142 143 defp decode(<<@bool_true, rest::binary>>, depth) do 144 {values, tail} = decode(rest, depth) 145 {[true | values], tail} 146 end 147 148 defp decode(<<@binary, rest::binary>>, depth) do 149 {decoded_binary, rest} = decode_binary(rest) 150 {values, tail} = decode(rest, depth) 151 {[decoded_binary | values], tail} 152 end 153 154 defp decode(<<@nested_list, rest::binary>>, depth) do 155 {nested_values, rest} = decode(rest, depth + 1) 156 {values, tail} = decode(rest, depth) 157 {[nested_values | values], tail} 158 end 159 160 defp decode(<<@int_neg_8, rest::binary>>, depth), do: dec_neg_int(8, rest, depth) 161 defp decode(<<@int_neg_7, rest::binary>>, depth), do: dec_neg_int(7, rest, depth) 162 defp decode(<<@int_neg_6, rest::binary>>, depth), do: dec_neg_int(6, rest, depth) 163 defp decode(<<@int_neg_5, rest::binary>>, depth), do: dec_neg_int(5, rest, depth) 164 defp decode(<<@int_neg_4, rest::binary>>, depth), do: dec_neg_int(4, rest, depth) 165 defp decode(<<@int_neg_3, rest::binary>>, depth), do: dec_neg_int(3, rest, depth) 166 defp decode(<<@int_neg_2, rest::binary>>, depth), do: dec_neg_int(2, rest, depth) 167 defp decode(<<@int_neg_1, rest::binary>>, depth), do: dec_neg_int(1, rest, depth) 168 169 defp decode(<<@int_0, rest::binary>>, depth) do 170 {values, tail} = decode(rest, depth) 171 {[0 | values], tail} 172 end 173 174 defp decode(<<@int_pos_1, rest::binary>>, depth), do: dec_pos_int(1, rest, depth) 175 defp decode(<<@int_pos_2, rest::binary>>, depth), do: dec_pos_int(2, rest, depth) 176 defp decode(<<@int_pos_3, rest::binary>>, depth), do: dec_pos_int(3, rest, depth) 177 defp decode(<<@int_pos_4, rest::binary>>, depth), do: dec_pos_int(4, rest, depth) 178 defp decode(<<@int_pos_5, rest::binary>>, depth), do: dec_pos_int(5, rest, depth) 179 defp decode(<<@int_pos_6, rest::binary>>, depth), do: dec_pos_int(6, rest, depth) 180 defp decode(<<@int_pos_7, rest::binary>>, depth), do: dec_pos_int(7, rest, depth) 181 defp decode(<<@int_pos_8, rest::binary>>, depth), do: dec_pos_int(8, rest, depth) 182 183 defp decode_binary(binary) do 184 {parts, tail} = decode_binary(binary, 0) 185 {IO.iodata_to_binary(parts), tail} 186 end 187 188 defp decode_binary(binary, offset) do 189 case binary do 190 <<head::binary-size(offset), @nil_value, @escape, bin_tail::binary>> -> 191 # Un-escape null byte and keep going 192 {parts, tail} = decode_binary(bin_tail, 0) 193 {[head, "\x00" | parts], tail} 194 195 <<head::binary-size(offset), @nil_value, tail::binary>> -> 196 # Found null byte without escape, terminate binary (null-terminated) 197 {[head], tail} 198 199 <<_head::binary-size(offset), _other, _tail::binary>> -> 200 # Found any other byte, keep scanning 201 decode_binary(binary, offset + 1) 202 203 <<_head::binary-size(offset)>> -> raise "Encoded binary was not terminated: #{inspect(binary)}" 204 end 205 end 206 207 defp dec_neg_int(bytes, rest, depth) do 208 <<int::unsigned-big-integer-unit(8)-size(bytes), rest::binary>> = rest 209 int = int - Bitwise.bsl(1, bytes * 8) + 1 210 211 {values, tail} = decode(rest, depth) 212 {[int | values], tail} 213 end 214 215 defp dec_pos_int(bytes, rest, depth) do 216 <<int::unsigned-big-integer-size(bytes)-unit(8), rest::binary>> = rest 217 218 {values, tail} = decode(rest, depth) 219 {[int | values], tail} 220 end 221end