Current section
Files
Jump to
Current section
Files
lib/protobuf_lite.ex
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 = <<num::64>>
<<signed::64-signed>> = bin
{signed, r}
end
# 1 64-bit fixed64, sfixed64, double
def dbit64(<<int64::little-signed-size(64), r::binary>>), do: {int64, r}
# 2 Length-delimited string, bytes, embedded messages, packed repeated fields
def dlen_delimited(bin) do
{len, r} = dvarint(bin)
<<content::binary-size(len), r::binary>> = 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(<<int32::little-signed-size(32), r::binary>>), do: {int32, r}
def dtag_type(<<tag::size(5), type::size(3), r::binary>>) 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
<<r::integer-little-size(1), n::integer-little-size(7)>> = <<num::integer-size(8)-little>>
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 ->
<<tag::size(5), type::size(3)>>
true ->
btag = (tag &&& 0xF) ||| 0x10
<<btag::size(5), type::size(3), evarint(tag >>> 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) <> <<n::little-size(64)>>
{: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) <> <<n::little-size(32)>>
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