-module(cabal_posts).
-export([decode/1, encode/3]).
encode(KeyPair, Links, Post) ->
{PostType, PostBody} = encode_body(Post),
NumLinks = cabal_wire:encode_varint(length(Links)),
LinksBin = iolist_to_binary(Links),
Timestamp = cabal_wire:encode_varint(os:system_time(1000)),
Payload = iolist_to_binary([NumLinks, LinksBin, PostType, Timestamp, PostBody]),
#{public := Public, secret := Secret} = KeyPair,
Signature = enacl:sign_detached(Payload, Secret),
iolist_to_binary([Public, Signature, Payload]).
encode_body({text, Chan, Text}) ->
ChanBin = unicode:characters_to_binary(Chan),
ChanLen = cabal_wire:encode_varint(byte_size(ChanBin)),
TextBin = unicode:characters_to_binary(Text),
TextLen = cabal_wire:encode_varint(byte_size(TextBin)),
{0, iolist_to_binary([ChanLen, ChanBin, TextLen, TextBin])};
encode_body({delete, Hashes}) ->
NumHashes = cabal_wire:encode_varint(length(Hashes)),
lists:foreach(fun(H) -> 32 = length(H) end, Hashes),
{1, iolist_to_binary([1, NumHashes, Hashes])};
encode_body({info, {name, Name}}) ->
NameBin = unicode:characters_to_binary(Name),
NameLen = cabal_wire:encode_varint(byte_size(NameBin)),
{2, iolist_to_binary([4, <<"name">>, NameLen, NameBin, 0])};
encode_body({topic, Chan, Topic}) ->
ChanBin = unicode:characters_to_binary(Chan),
ChanLen = cabal_wire:encode_varint(byte_size(ChanBin)),
TopicData =
case length(Topic) of
0 ->
[0];
_ ->
TopicBin = unicode:characters_to_binary(Topic),
TopicLen = cabal_wire:encode_varint(byte_size(TopicBin)),
[TopicLen, TopicBin]
end,
{3, iolist_to_binary([ChanLen, ChanBin] ++ TopicData)};
encode_body({JL, Chan}) when JL =:= join; JL =:= leave ->
TypeCode =
case JL of
join -> 4;
leave -> 5
end,
ChanBin = unicode:characters_to_binary(Chan),
ChanLen = cabal_wire:encode_varint(byte_size(ChanBin)),
{TypeCode, iolist_to_binary([ChanLen, ChanBin])}.
decode(Data) ->
[Header, Body] = decode_post_header(Data),
Decoded =
case proplists:get_value(type, Header) of
0 -> decode_post_text(Body);
1 -> decode_post_delete(Body);
2 -> decode_post_info(Body);
3 -> decode_post_topic(Body);
4 -> decode_post_join(Body);
5 -> decode_post_leave(Body)
end,
[Header, Decoded].
decode_post_header(Data) ->
<<PubKey:32/binary, Signature:64/binary, SignedData/binary>> = Data,
true = enacl:sign_verify_detached(Signature, SignedData, PubKey),
{NumLinks, Rest} = cabal_wire:decode_varint(SignedData),
<<LinkData:(32 * NumLinks)/binary, Rest2/binary>> = Rest,
Links = [Link || <<Link:32/binary>> <= LinkData],
case length(Links) =:= NumLinks of
false ->
ErrMsg = io_lib:format("invalid num_links - expected ~p but got ~p", [
NumLinks, length(Links)
]),
erlang:error(lists:flatten(ErrMsg));
true ->
[PostType, Timestamp, PostBody] = cabal_wire:decode_varints(Rest2, 2),
PostHash = enacl:generichash(32, Data),
[
[
{public_key, PubKey},
{links, Links},
{type, PostType},
{timestamp, Timestamp},
{hash, PostHash}
],
PostBody
]
end.
decode_post_text(Body) ->
{ChannelLen, Rest} = cabal_wire:decode_varint(Body),
<<Channel:(ChannelLen)/binary, Rest2/binary>> = Rest,
{TextLen, Rest3} = cabal_wire:decode_varint(Rest2),
<<Text:(TextLen)/binary>> = Rest3,
[{channel, Channel}, {text, unicode:characters_to_binary(Text)}].
decode_post_delete(Body) ->
{NumHashes, Rest} = cabal_wire:decode_varint(Body),
<<HashData:(32 * NumHashes)/binary>> = Rest,
Hashes = [Hash || <<Hash:32/binary>> <= HashData],
[{hashes, Hashes}].
decode_post_info(Body) ->
KVs = cabal_wire:decode_list_of_binaries(Body),
[{infos, list_to_map(KVs)}].
decode_post_topic(Body) ->
{ChannelLen, Rest} = cabal_wire:decode_varint(Body),
<<Channel:(ChannelLen)/binary, Rest2/binary>> = Rest,
{TopicLen, Rest3} = cabal_wire:decode_varint(Rest2),
<<Topic:(TopicLen)/binary>> = Rest3,
[{channel, Channel}, {topic, Topic}].
decode_post_join(Body) ->
{ChannelLen, Rest} = cabal_wire:decode_varint(Body),
<<Channel:(ChannelLen)/binary>> = Rest,
[{channel, Channel}].
decode_post_leave(Body) ->
{ChannelLen, Rest} = cabal_wire:decode_varint(Body),
<<Channel:(ChannelLen)/binary>> = Rest,
[{channel, Channel}].
list_to_map(Lst) -> list_to_map(Lst, #{}).
list_to_map([K, V | Rest], Acc) ->
list_to_map(Rest, Acc#{K => V});
list_to_map([], Acc) ->
Acc.