defmodule ProtobufLite do import Bitwise # 0 Varint int32, int64, uint32, uint64, sint32, sint64, bool, enum def dvarint(bin), do: dvarint(bin, 0, 0) def dvarint(<<1::size(1), i::size(7), r::binary>>, count, acc), do: dvarint(r, count + 1, acc + (i <<< (count * 7))) def dvarint(<<0::size(1), i::size(7), r::binary>>, count, acc) do num = acc + (i <<< (count * 7)) bin = <> <> = bin {signed, r} end # 1 64-bit fixed64, sfixed64, double def dbit64(<>), do: {int64, r} # 2 Length-delimited string, bytes, embedded messages, packed repeated fields def dlen_delimited(bin) do {len, r} = dvarint(bin) <> = r {content, r} end # 3 Start group groups (deprecated) # def group(r), do: {0, r} # 4 End group groups (deprecated) # def group_end(r), do: {0, r} # 5 32-bit fixed32, sfixed32, float def dbit32(<>), do: {int32, r} def dtag_type(<>) do case tag &&& 16 do 16 -> {value, r} = dvarint(r) {(value <<< 4) + (tag &&& 0xF), type, r} _ -> {tag, type, r} end end def decode(bin), do: decode(bin, []) def decode(<<>>, acc), do: acc def decode(bin, acc) do {tag, type, r} = dtag_type(bin) # IO.puts "#{type} #{tag} #{inspect r}" case type do # varint 0 -> {value, r} = dvarint(r) decode(r, acc ++ [{:varint, tag, value}]) # int64 1 -> {value, r} = dbit64(r) decode(r, acc ++ [{:int64, tag, value}]) # binary 2 -> {value, r} = dlen_delimited(r) decode(r, acc ++ [{:binary, tag, value}]) # group 3 -> {nest, r} = decode(r, []) decode(r, acc ++ [{:group, tag, nest}]) # group_end 4 -> {acc, r} # int32 5 -> {value, r} = dbit32(r) decode(r, acc ++ [{:int32, tag, value}]) end end def evarint(num), do: evarint(num, <<>>) def evarint(num, acc) when num <= 127, do: acc <> <<0::integer-size(1), num::integer-size(7)>> def evarint(num, acc) do <> = <> acc = acc <> <<1::integer-size(1), n::integer-size(7)>> evarint((num >>> 8 <<< 1) + r, acc) end def etag_type(tag, type) do cond do tag < 16 -> <> true -> btag = (tag &&& 0xF) ||| 0x10 <>> 4)::binary>> end end def encode(proplists), do: encode(proplists, <<>>) def encode([], acc), do: acc def encode([data | rest], acc) do acc = case data do {:varint, tag, n} -> acc <> etag_type(tag, 0) <> evarint(n) {:int64, tag, n} -> acc <> etag_type(tag, 1) <> <> {:binary, tag, n} -> acc <> etag_type(tag, 2) <> evarint(byte_size(n)) <> n {:group, tag, n} -> acc <> etag_type(tag, 3) <> encode(n) <> etag_type(tag, 4) {:int32, tag, n} -> # IO.inspect {acc, etag_type(tag, 5), n} acc <> etag_type(tag, 5) <> <> end encode(rest, acc) end # Dangerous! dont use often def to_map(proplist) do Enum.reduce(proplist, %{}, fn {type, tag, value}, a -> v = case type do :group -> to_map(value) _ -> value end case Map.has_key?(a, tag) do false -> Map.merge(a, %{tag => v}) true -> oldV = Map.get(a, tag) v = if is_list(oldV) do oldV ++ [v] else [oldV, v] end Map.merge(a, %{tag => v}) end end) end end