this repo has no description
1
fork

Configure Feed

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

Add structure encoding

garrison b1db8b04 9f3c57e4

+176 -1
+162
lib/encoding/structure.ex
··· 1 + defmodule Hobbes.Encoding.Structure do 2 + import ExUnit.Assertions, only: [assert: 1] 3 + 4 + @escape 0xFF 5 + 6 + @int_neg_8 0x0C 7 + @int_neg_7 0x0D 8 + @int_neg_6 0x0E 9 + @int_neg_5 0x0F 10 + @int_neg_4 0x10 11 + @int_neg_3 0x11 12 + @int_neg_2 0x12 13 + @int_neg_1 0x13 14 + @int_0 0x14 15 + @int_pos_1 0x15 16 + @int_pos_2 0x16 17 + @int_pos_3 0x17 18 + @int_pos_4 0x18 19 + @int_pos_5 0x1A 20 + @int_pos_6 0x1B 21 + @int_pos_7 0x1C 22 + @int_pos_8 0x1D 23 + 24 + def pack(fields) do 25 + encode(fields, "", "", 0) 26 + end 27 + 28 + def unpack(bin) do 29 + {header_fields, body_data} = decode_header(bin, [], 0, 0) 30 + Enum.map(header_fields, fn {id, type, pos, size} -> 31 + value = decode_value(type, pos, size, body_data) 32 + {id, value} 33 + end) 34 + end 35 + 36 + defp encode([], header_acc, body_acc, _last_id) do 37 + << 38 + header_acc::binary, 39 + <<@escape>>, 40 + body_acc::binary, 41 + >> 42 + end 43 + 44 + defp encode([field | fields_rest], header_acc, body_acc, last_id) do 45 + {field_id, field_value} = field 46 + assert field_id > last_id 47 + 48 + id_diff_enc = encode_varint(field_id - last_id) 49 + {field_head, field_body} = encode_field(field_value) 50 + 51 + header_acc = << 52 + header_acc::binary, 53 + field_head::binary, 54 + id_diff_enc::binary, 55 + >> 56 + body_acc = << 57 + body_acc::binary, 58 + field_body::binary, 59 + >> 60 + 61 + encode(fields_rest, header_acc, body_acc, field_id) 62 + end 63 + 64 + defp encode_field(int) when is_integer(int) and int < 0 do 65 + binary = :binary.encode_unsigned(-int) 66 + case byte_size(binary) do 67 + 8 -> {<<@int_neg_8>>, binary} 68 + 7 -> {<<@int_neg_7>>, binary} 69 + 6 -> {<<@int_neg_6>>, binary} 70 + 5 -> {<<@int_neg_5>>, binary} 71 + 4 -> {<<@int_neg_4>>, binary} 72 + 3 -> {<<@int_neg_3>>, binary} 73 + 2 -> {<<@int_neg_2>>, binary} 74 + 1 -> {<<@int_neg_1>>, binary} 75 + end 76 + end 77 + 78 + defp encode_field(0) do 79 + {<<@int_0>>, ""} 80 + end 81 + 82 + defp encode_field(int) when is_integer(int) and int > 0 do 83 + binary = :binary.encode_unsigned(int) 84 + case byte_size(binary) do 85 + 1 -> {<<@int_pos_1>>, binary} 86 + 2 -> {<<@int_pos_2>>, binary} 87 + 3 -> {<<@int_pos_3>>, binary} 88 + 4 -> {<<@int_pos_4>>, binary} 89 + 5 -> {<<@int_pos_5>>, binary} 90 + 6 -> {<<@int_pos_6>>, binary} 91 + 7 -> {<<@int_pos_7>>, binary} 92 + 8 -> {<<@int_pos_8>>, binary} 93 + end 94 + end 95 + 96 + defp decode_header(<<@escape, rest::binary>>, acc, _pos, _id_last) do 97 + {Enum.reverse(acc), rest} 98 + end 99 + 100 + defp decode_header(<<type::integer-8, rest::binary>>, acc, pos, id_last) do 101 + {size, rest} = decode_size(type, rest) 102 + {id_diff, rest} = decode_varint(rest) 103 + id = id_last + id_diff 104 + 105 + acc = [{id, type, pos, size} | acc] 106 + pos = pos + size 107 + decode_header(rest, acc, pos, id) 108 + end 109 + 110 + defp decode_size(@int_neg_8, bin), do: {8, bin} 111 + defp decode_size(@int_neg_7, bin), do: {7, bin} 112 + defp decode_size(@int_neg_6, bin), do: {6, bin} 113 + defp decode_size(@int_neg_5, bin), do: {5, bin} 114 + defp decode_size(@int_neg_4, bin), do: {4, bin} 115 + defp decode_size(@int_neg_3, bin), do: {3, bin} 116 + defp decode_size(@int_neg_2, bin), do: {2, bin} 117 + defp decode_size(@int_neg_1, bin), do: {1, bin} 118 + 119 + defp decode_size(@int_0, bin), do: {0, bin} 120 + 121 + defp decode_size(@int_pos_1, bin), do: {1, bin} 122 + defp decode_size(@int_pos_2, bin), do: {2, bin} 123 + defp decode_size(@int_pos_3, bin), do: {3, bin} 124 + defp decode_size(@int_pos_4, bin), do: {4, bin} 125 + defp decode_size(@int_pos_5, bin), do: {5, bin} 126 + defp decode_size(@int_pos_6, bin), do: {6, bin} 127 + defp decode_size(@int_pos_7, bin), do: {7, bin} 128 + defp decode_size(@int_pos_8, bin), do: {8, bin} 129 + 130 + @neg_int_types [ 131 + @int_neg_1, @int_neg_2, @int_neg_3, @int_neg_4, 132 + @int_neg_5, @int_neg_6, @int_neg_7, @int_neg_8, 133 + ] 134 + @pos_int_types [ 135 + @int_pos_1, @int_pos_2, @int_pos_3, @int_pos_4, 136 + @int_pos_5, @int_pos_6, @int_pos_7, @int_pos_8, 137 + ] 138 + 139 + defp decode_value(int_type, pos, size, data) when int_type in @neg_int_types do 140 + <<_prefix::binary-size(pos), int::integer-unit(8)-size(size), _rest::binary>> = data 141 + -int 142 + end 143 + 144 + defp decode_value(@int_0, _pos, _size, _data) do 145 + 0 146 + end 147 + 148 + defp decode_value(int_type, pos, size, data) when int_type in @pos_int_types do 149 + <<_prefix::binary-size(pos), int::integer-unit(8)-size(size), _rest::binary>> = data 150 + int 151 + end 152 + 153 + defp encode_varint(int) do 154 + # TODO 155 + assert int < 128 156 + <<0::1, int::unsigned-integer-7>> 157 + end 158 + 159 + defp decode_varint(<<0::1, int::unsigned-integer-7, rest::binary>>) do 160 + {int, rest} 161 + end 162 + end
+14 -1
test/encoding_test.exs
··· 1 1 defmodule Hobbes.EncTest do 2 2 use ExUnit.Case, async: true 3 3 4 - alias Hobbes.Encoding.Keyset 4 + alias Hobbes.Encoding.{Keyset, Structure} 5 5 6 6 @moduletag :encoding 7 7 ··· 65 65 66 66 assert "\x1D" <> rest = Keyset.pack([(2 ** 64) - 1]) 67 67 assert rest == String.duplicate("\xFF", 8) 68 + end 69 + end 70 + 71 + describe "structure" do 72 + test "encode" do 73 + fields = [ 74 + {2, 123}, 75 + {3, -1_000_000}, 76 + {6, 0}, 77 + ] 78 + 79 + bin = Structure.pack(fields) 80 + assert Structure.unpack(bin) == fields 68 81 end 69 82 end 70 83 end