New application aeserialization for shared deps with compiler

This commit is contained in:
Tobias Lindahl
2019-02-25 16:46:33 +01:00
commit b55c3726f4
13 changed files with 1177 additions and 0 deletions
+280
View File
@@ -0,0 +1,280 @@
%%%-------------------------------------------------------------------
%%% @copyright (C) 2017, Aeternity Anstalt
%%% @doc
%%% API encoding for the Aeternity node.
%%% @end
%%%-------------------------------------------------------------------
-module(aeser_api_encoder).
-export([encode/2,
decode/1,
safe_decode/2,
byte_size_for_type/1]).
-export_type([encoded/0]).
-type known_type() :: key_block_hash
| micro_block_hash
| block_pof_hash
| block_tx_hash
| block_state_hash
| channel
| contract_pubkey
| transaction
| tx_hash
| oracle_pubkey
| oracle_query
| oracle_query_id
| oracle_response
| account_pubkey
| signature
| name
| commitment
| peer_pubkey
| state
| poi
| state_trees.
-type extended_type() :: known_type() | block_hash | {id_hash, [known_type()]}.
-type payload() :: binary().
-type encoded() :: binary().
-define(BASE58, 1).
-define(BASE64, 2).
-spec encode(known_type(), payload() | aeser_id:id()) -> encoded().
encode(id_hash, Payload) ->
{IdType, Val} = aeser_id:specialize(Payload),
encode(id2type(IdType), Val);
encode(Type, Payload) ->
Pfx = type2pfx(Type),
Enc = case type2enc(Type) of
?BASE58 -> base58_check(Payload);
?BASE64 -> base64_check(Payload)
end,
<<Pfx/binary, "_", Enc/binary>>.
-spec decode(binary()) -> {known_type(), payload()}.
decode(Bin0) ->
case split(Bin0) of
[Pfx, Payload] ->
Type = pfx2type(Pfx),
Bin = decode_check(Type, Payload),
case type_size_check(Type, Bin) of
ok -> {Type, Bin};
{error, Reason} -> erlang:error(Reason)
end;
_ ->
%% {<<>>, decode_check(Bin)}
erlang:error(missing_prefix)
end.
type_size_check(Type, Bin) ->
case byte_size_for_type(Type) of
not_applicable -> ok;
CorrectSize ->
Size = byte_size(Bin),
case Size =:= CorrectSize of
true -> ok;
false -> {error, incorrect_size}
end
end.
-spec safe_decode(extended_type(), encoded()) -> {'ok', payload() | aeser_id:id()}
| {'error', atom()}.
safe_decode({id_hash, AllowedTypes}, Enc) ->
try decode(Enc) of
{ActualType, Dec} ->
case lists:member(ActualType, AllowedTypes) of
true ->
try {ok, aeser_id:create(type2id(ActualType), Dec)}
catch error:_ -> {error, invalid_prefix}
end;
false ->
{error, invalid_prefix}
end
catch
error:_ ->
{error, invalid_encoding}
end;
safe_decode(block_hash, Enc) ->
try decode(Enc) of
{key_block_hash, Dec} ->
{ok, Dec};
{micro_block_hash, Dec} ->
{ok, Dec};
{_, _} ->
{error, invalid_prefix}
catch
error:_ ->
{error, invalid_encoding}
end;
safe_decode(Type, Enc) ->
try decode(Enc) of
{Type, Dec} ->
{ok, Dec};
{_, _} ->
{error, invalid_prefix}
catch
error:_ ->
{error, invalid_encoding}
end.
decode_check(Type, Bin) ->
Dec =
case type2enc(Type) of
?BASE58 -> base58_to_binary(Bin);
?BASE64 -> base64_to_binary(Bin)
end,
Sz = byte_size(Dec),
BSz = Sz - 4,
<<Body:BSz/binary, C:4/binary>> = Dec,
C = check_str(Body),
Body.
base64_check(Bin) ->
C = check_str(Bin),
binary_to_base64(iolist_to_binary([Bin, C])).
%% modified from github.com/mbrix/lib_hd
base58_check(Bin) ->
C = check_str(Bin),
binary_to_base58(iolist_to_binary([Bin, C])).
split(Bin) ->
binary:split(Bin, [<<"_">>], []).
check_str(Bin) ->
<<C:32/bitstring,_/binary>> =
sha256_hash(sha256_hash(Bin)),
C.
sha256_hash(Bin) ->
crypto:hash(sha256, Bin).
id2type(account) -> account_pubkey;
id2type(channel) -> channel;
id2type(commitment) -> commitment;
id2type(contract) -> contract_pubkey;
id2type(name) -> name;
id2type(oracle) -> oracle_pubkey.
type2id(account_pubkey) -> account;
type2id(channel) -> channel;
type2id(commitment) -> commitment;
type2id(contract_pubkey) -> contract;
type2id(name) -> name;
type2id(oracle_pubkey) -> oracle.
type2enc(key_block_hash) -> ?BASE58;
type2enc(micro_block_hash) -> ?BASE58;
type2enc(block_pof_hash) -> ?BASE58;
type2enc(block_tx_hash) -> ?BASE58;
type2enc(block_state_hash) -> ?BASE58;
type2enc(channel) -> ?BASE58;
type2enc(contract_pubkey) -> ?BASE58;
type2enc(contract_bytearray)-> ?BASE64;
type2enc(transaction) -> ?BASE64;
type2enc(tx_hash) -> ?BASE58;
type2enc(oracle_pubkey) -> ?BASE58;
type2enc(oracle_query) -> ?BASE64;
type2enc(oracle_query_id) -> ?BASE58;
type2enc(oracle_response) -> ?BASE64;
type2enc(account_pubkey) -> ?BASE58;
type2enc(signature) -> ?BASE58;
type2enc(commitment) -> ?BASE58;
type2enc(peer_pubkey) -> ?BASE58;
type2enc(name) -> ?BASE58;
type2enc(state) -> ?BASE64;
type2enc(poi) -> ?BASE64;
type2enc(state_trees) -> ?BASE64.
type2pfx(key_block_hash) -> <<"kh">>;
type2pfx(micro_block_hash) -> <<"mh">>;
type2pfx(block_pof_hash) -> <<"bf">>;
type2pfx(block_tx_hash) -> <<"bx">>;
type2pfx(block_state_hash) -> <<"bs">>;
type2pfx(channel) -> <<"ch">>;
type2pfx(contract_pubkey) -> <<"ct">>;
type2pfx(contract_bytearray)-> <<"cb">>;
type2pfx(transaction) -> <<"tx">>;
type2pfx(tx_hash) -> <<"th">>;
type2pfx(oracle_pubkey) -> <<"ok">>;
type2pfx(oracle_query) -> <<"ov">>;
type2pfx(oracle_query_id) -> <<"oq">>;
type2pfx(oracle_response) -> <<"or">>;
type2pfx(account_pubkey) -> <<"ak">>;
type2pfx(signature) -> <<"sg">>;
type2pfx(commitment) -> <<"cm">>;
type2pfx(peer_pubkey) -> <<"pp">>;
type2pfx(name) -> <<"nm">>;
type2pfx(state) -> <<"st">>;
type2pfx(poi) -> <<"pi">>;
type2pfx(state_trees) -> <<"ss">>.
pfx2type(<<"kh">>) -> key_block_hash;
pfx2type(<<"mh">>) -> micro_block_hash;
pfx2type(<<"bf">>) -> block_pof_hash;
pfx2type(<<"bx">>) -> block_tx_hash;
pfx2type(<<"bs">>) -> block_state_hash;
pfx2type(<<"ch">>) -> channel;
pfx2type(<<"cb">>) -> contract_bytearray;
pfx2type(<<"ct">>) -> contract_pubkey;
pfx2type(<<"tx">>) -> transaction;
pfx2type(<<"th">>) -> tx_hash;
pfx2type(<<"ok">>) -> oracle_pubkey;
pfx2type(<<"ov">>) -> oracle_query;
pfx2type(<<"oq">>) -> oracle_query_id;
pfx2type(<<"or">>) -> oracle_response;
pfx2type(<<"ak">>) -> account_pubkey;
pfx2type(<<"sg">>) -> signature;
pfx2type(<<"cm">>) -> commitment;
pfx2type(<<"pp">>) -> peer_pubkey;
pfx2type(<<"nm">>) -> name;
pfx2type(<<"st">>) -> state;
pfx2type(<<"pi">>) -> poi;
pfx2type(<<"ss">>) -> state_trees.
-spec byte_size_for_type(known_type()) -> non_neg_integer() | not_applicable.
byte_size_for_type(key_block_hash) -> 32;
byte_size_for_type(micro_block_hash) -> 32;
byte_size_for_type(block_pof_hash) -> 32;
byte_size_for_type(block_tx_hash) -> 32;
byte_size_for_type(block_state_hash) -> 32;
byte_size_for_type(channel) -> 32;
byte_size_for_type(contract_pubkey) -> 32;
byte_size_for_type(contract_bytearray)-> not_applicable;
byte_size_for_type(transaction) -> not_applicable;
byte_size_for_type(tx_hash) -> 32;
byte_size_for_type(oracle_pubkey) -> 32;
byte_size_for_type(oracle_query) -> not_applicable;
byte_size_for_type(oracle_query_id) -> 32;
byte_size_for_type(oracle_response) -> not_applicable;
byte_size_for_type(account_pubkey) -> 32;
byte_size_for_type(signature) -> 64;
byte_size_for_type(name) -> not_applicable;
byte_size_for_type(commitment) -> 32;
byte_size_for_type(peer_pubkey) -> 32;
byte_size_for_type(state) -> 32;
byte_size_for_type(poi) -> not_applicable;
byte_size_for_type(state_trees) -> not_applicable.
%% TODO: Fix the base58 module so that it consistently uses binaries instead
%%
binary_to_base58(Bin) ->
iolist_to_binary(base58:binary_to_base58(Bin)).
base58_to_binary(Bin) when is_binary(Bin) ->
base58:base58_to_binary(binary_to_list(Bin)).
binary_to_base64(Bin) ->
base64:encode(Bin).
base64_to_binary(Bin) when is_binary(Bin) ->
base64:decode(Bin).
+143
View File
@@ -0,0 +1,143 @@
%%% -*- erlang-indent-level:4; indent-tabs-mode: nil -*-
%%%-------------------------------------------------------------------
%%% @copyright (C) 2018, Aeternity Anstalt
%%% @doc
%%% Functions for serializing chain objects to binary format.
%%% @end
%%%-------------------------------------------------------------------
-module(aeser_chain_objects).
-export([ serialize/4
, deserialize/4
, deserialize_type_and_vsn/1
]).
-type template() :: aeserialization:template().
-type fields() :: aeserialization:fields().
%%%===================================================================
%%% API
%%%===================================================================
-spec serialize(atom(), non_neg_integer(), template(), fields()) -> binary().
serialize(Type, Vsn, Template, Fields) ->
aeserialization:serialize(tag(Type), Vsn, Template, Fields).
deserialize_type_and_vsn(Binary) ->
{Tag, Vsn, Fields} = aeserialization:deserialize_tag_and_vsn(Binary),
{rev_tag(Tag), Vsn, Fields}.
-spec deserialize(atom(), non_neg_integer(), template(), binary()) -> fields().
deserialize(Type, Vsn, Template, Binary) ->
aeserialization:deserialize(Type, tag(Type), Vsn, Template, Binary).
%%%===================================================================
%%% Internal functions
%%%===================================================================
tag(account) -> 10;
tag(signed_tx) -> 11;
tag(spend_tx) -> 12;
tag(oracle) -> 20;
tag(oracle_query) -> 21;
tag(oracle_register_tx) -> 22;
tag(oracle_query_tx) -> 23;
tag(oracle_response_tx) -> 24;
tag(oracle_extend_tx) -> 25;
tag(name) -> 30;
tag(name_commitment) -> 31;
tag(name_claim_tx) -> 32;
tag(name_preclaim_tx) -> 33;
tag(name_update_tx) -> 34;
tag(name_revoke_tx) -> 35;
tag(name_transfer_tx) -> 36;
tag(contract) -> 40;
tag(contract_call) -> 41;
tag(contract_create_tx) -> 42;
tag(contract_call_tx) -> 43;
tag(channel_create_tx) -> 50;
tag(channel_deposit_tx) -> 51;
tag(channel_withdraw_tx) -> 52;
tag(channel_force_progress_tx) -> 521;
tag(channel_close_mutual_tx) -> 53;
tag(channel_close_solo_tx) -> 54;
tag(channel_slash_tx) -> 55;
tag(channel_settle_tx) -> 56;
tag(channel_offchain_tx) -> 57;
tag(channel_offchain_update_transfer) -> 570;
tag(channel_offchain_update_deposit) -> 571;
tag(channel_offchain_update_withdraw) -> 572;
tag(channel_offchain_update_create_contract) -> 573;
tag(channel_offchain_update_call_contract) -> 574;
tag(channel) -> 58;
tag(channel_snapshot_solo_tx) -> 59;
tag(trees_poi) -> 60;
tag(trees_db) -> 61;
tag(state_trees) -> 62;
tag(mtree) -> 63;
tag(mtree_value) -> 64;
tag(contracts_mtree) -> 621;
tag(calls_mtree) -> 622;
tag(channels_mtree) -> 623;
tag(nameservice_mtree) -> 624;
tag(oracles_mtree) -> 625;
tag(accounts_mtree) -> 626;
tag(compiler_sophia) -> 70;
tag(key_block) -> 100;
tag(micro_block) -> 101;
tag(light_micro_block) -> 102;
tag(pof) -> 200.
rev_tag(10) -> account;
rev_tag(11) -> signed_tx;
rev_tag(12) -> spend_tx;
rev_tag(20) -> oracle;
rev_tag(21) -> oracle_query;
rev_tag(22) -> oracle_register_tx;
rev_tag(23) -> oracle_query_tx;
rev_tag(24) -> oracle_response_tx;
rev_tag(25) -> oracle_extend_tx;
rev_tag(30) -> name;
rev_tag(31) -> name_commitment;
rev_tag(32) -> name_claim_tx;
rev_tag(33) -> name_preclaim_tx;
rev_tag(34) -> name_update_tx;
rev_tag(35) -> name_revoke_tx;
rev_tag(36) -> name_transfer_tx;
rev_tag(40) -> contract;
rev_tag(41) -> contract_call;
rev_tag(42) -> contract_create_tx;
rev_tag(43) -> contract_call_tx;
rev_tag(50) -> channel_create_tx;
rev_tag(51) -> channel_deposit_tx;
rev_tag(52) -> channel_withdraw_tx;
rev_tag(521) -> channel_force_progress_tx;
rev_tag(53) -> channel_close_mutual_tx;
rev_tag(54) -> channel_close_solo_tx;
rev_tag(55) -> channel_slash_tx;
rev_tag(56) -> channel_settle_tx;
rev_tag(57) -> channel_offchain_tx;
rev_tag(570) -> channel_offchain_update_transfer;
rev_tag(571) -> channel_offchain_update_deposit;
rev_tag(572) -> channel_offchain_update_withdraw;
rev_tag(573) -> channel_offchain_update_create_contract;
rev_tag(574) -> channel_offchain_update_call_contract;
rev_tag(58) -> channel;
rev_tag(59) -> channel_snapshot_solo_tx;
rev_tag(60) -> trees_poi;
rev_tag(61) -> trees_db;
rev_tag(62) -> state_trees;
rev_tag(63) -> mtree;
rev_tag(64) -> mtree_value;
rev_tag(621) -> contracts_mtree;
rev_tag(622) -> calls_mtree;
rev_tag(623) -> channels_mtree;
rev_tag(624) -> nameservice_mtree;
rev_tag(625) -> oracles_mtree;
rev_tag(626) -> accounts_mtree;
rev_tag(70) -> compiler_sophia;
rev_tag(100) -> key_block;
rev_tag(101) -> micro_block;
rev_tag(102) -> light_micro_block;
rev_tag(200) -> pof.
+111
View File
@@ -0,0 +1,111 @@
%%% -*- erlang-indent-level:4; indent-tabs-mode: nil -*-
%%%-------------------------------------------------------------------
%%% @copyright (C) 2018, Aeternity Anstalt
%%% @doc
%%% ADT for identifiers
%%% @end
%%%-------------------------------------------------------------------
-module(aeser_id).
-export([ create/2
, specialize/1
, specialize/2
, specialize_type/1
, is_id/1
]).
%% For aec_serialization
-export([ encode/1
, decode/1
]).
-record(id, { tag
, val
}).
-type tag() :: 'account' | 'oracle' | 'name'
| 'commitment' | 'contract' | 'channel'.
-type val() :: <<_:256>>.
-opaque(id() :: #id{}).
-export_type([ id/0
, tag/0
, val/0
]).
-define(PUB_SIZE, 32).
-define(TAG_SIZE, 1).
-define(SERIALIZED_SIZE, 33). %% ?TAG_SIZE + ?PUB_SIZE
-define(IS_TAG(___TAG___), ___TAG___ =:= account;
___TAG___ =:= oracle;
___TAG___ =:= name;
___TAG___ =:= commitment;
___TAG___ =:= contract;
___TAG___ =:= channel
).
-define(IS_VAL(___VAL___), byte_size(___VAL___) =:= 32).
%%%===================================================================
%%% API
%%%===================================================================
-spec create(tag(), val()) -> id().
create(Tag, Val) when ?IS_TAG(Tag), ?IS_VAL(Val) ->
#id{ tag = Tag
, val = Val};
create(Tag, Val) when ?IS_VAL(Val) ->
error({illegal_tag, Tag});
create(Tag, Val) when ?IS_TAG(Tag)->
error({illegal_val, Val});
create(Tag, Val) ->
error({illegal_tag_and_val, Tag, Val}).
-spec specialize(id()) -> {tag(), val()}.
specialize(#id{tag = Tag, val = Val}) ->
{Tag, Val}.
-spec specialize(id(), tag()) -> val().
specialize(#id{tag = Tag, val = Val}, Tag) when ?IS_TAG(Tag), ?IS_VAL(Val) ->
Val.
-spec specialize_type(id()) -> tag().
specialize_type(#id{tag = Tag}) when ?IS_TAG(Tag) ->
Tag.
-spec is_id(term()) -> boolean().
is_id(#id{}) -> true;
is_id(_) -> false.
-spec encode(id()) -> binary().
encode(#id{tag = Tag, val = Val}) ->
Res = <<(encode_tag(Tag)):?TAG_SIZE/unit:8, Val/binary>>,
true = ?SERIALIZED_SIZE =:= byte_size(Res),
Res.
-spec decode(binary()) -> id().
decode(<<Tag:?TAG_SIZE/unit:8, Val:?PUB_SIZE/binary>>) ->
#id{ tag = decode_tag(Tag)
, val = Val}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
encode_tag(account) -> 1;
encode_tag(name) -> 2;
encode_tag(commitment) -> 3;
encode_tag(oracle) -> 4;
encode_tag(contract) -> 5;
encode_tag(channel) -> 6;
encode_tag(Other) -> error({illegal_id_tag_name, Other}).
decode_tag(1) -> account;
decode_tag(2) -> name;
decode_tag(3) -> commitment;
decode_tag(4) -> oracle;
decode_tag(5) -> contract;
decode_tag(6) -> channel;
decode_tag(X) -> error({illegal_id_tag, X}).
+91
View File
@@ -0,0 +1,91 @@
%%%-------------------------------------------------------------------
%%% @copyright (C) 2017, Aeternity Anstalt
%%% @doc
%%% Implementation of the Recursive Length Prefix.
%%%
%%% https://github.com/ethereum/wiki/wiki/RLP
%%%
%%% @end
%%%-------------------------------------------------------------------
-module(aeser_rlp).
-export([ decode/1
, decode_one/1
, encode/1
]).
-export_type([ encodable/0
, encoded/0
]).
-type encodable() :: [encodable()] | binary().
-type encoded() :: <<_:8, _:_*8>>.
-define(UNTAGGED_SIZE_LIMIT , 55).
-define(UNTAGGED_LIMIT , 127).
-define(BYTE_ARRAY_OFFSET , 128).
-define(LIST_OFFSET , 192).
-spec encode(encodable()) -> encoded().
encode(X) ->
encode(X, []).
encode(<<B>> = X,_Opts) when B =< ?UNTAGGED_LIMIT ->
%% An untagged value
X;
encode(X,_Opts) when is_binary(X) ->
%% Byte array
add_size(?BYTE_ARRAY_OFFSET, X);
encode(L, Opts) when is_list(L) ->
%% Lists items are encoded and concatenated
ByteArray = << << (encode(X, Opts))/binary >> || X <- L >>,
add_size(?LIST_OFFSET, ByteArray).
add_size(Offset, X) when byte_size(X) =< ?UNTAGGED_SIZE_LIMIT ->
%% The size fits in one tagged byte
<<(Offset + byte_size(X)), X/binary>>;
add_size(Offset, X) when is_binary(X) ->
%% The size itself needs to be encoded as a byte array
%% Add the tagged size of the size byte array
SizeBin = binary:encode_unsigned(byte_size(X)),
TaggedSize = ?UNTAGGED_SIZE_LIMIT + Offset + byte_size(SizeBin),
true = (TaggedSize < 256 ), %% Assert
<<TaggedSize, SizeBin/binary, X/binary>>.
-spec decode(encoded()) -> encodable().
decode(Bin) when is_binary(Bin), byte_size(Bin) > 0 ->
case decode_one(Bin) of
{X, <<>>} -> X;
{X, Left} -> error({trailing, X, Bin, Left})
end.
decode_one(<<X, B/binary>>) when X =< ?UNTAGGED_LIMIT ->
%% Untagged value
{<<X>>, B};
decode_one(<<L, _/binary>> = B) when L < ?LIST_OFFSET ->
%% Byte array
{Size, Rest} = decode_size(B, ?BYTE_ARRAY_OFFSET),
<<X:Size/binary, Tail/binary>> = Rest,
{X, Tail};
decode_one(<<_/binary>> = B) ->
%% List
{Size, Rest} = decode_size(B, ?LIST_OFFSET),
<<X:Size/binary, Tail/binary>> = Rest,
{decode_list(X), Tail}.
decode_size(<<L, B/binary>>, Offset) when L =< Offset + ?UNTAGGED_SIZE_LIMIT->
%% One byte tagged size.
{L - Offset, B};
decode_size(<<_, 0, _/binary>>,_Offset) ->
error(leading_zeroes_in_size);
decode_size(<<L, B/binary>>, Offset) ->
%% Actual size is in a byte array.
BinSize = L - Offset - ?UNTAGGED_SIZE_LIMIT,
<<Size:BinSize/unit:8, Rest/binary>> = B,
{Size, Rest}.
decode_list(<<>>) -> [];
decode_list(B) ->
{Element, Rest} = decode_one(B),
[Element|decode_list(Rest)].
+15
View File
@@ -0,0 +1,15 @@
{application, aeserialization,
[{description, "Serialization of data for Aeternity"},
{vsn, "0.1.0"},
{registered, []},
{applications,
[kernel,
stdlib,
crypto,
base58
]},
{env,[]},
{modules, []},
{licenses, []},
{links, []}
]}.
+134
View File
@@ -0,0 +1,134 @@
%%%-------------------------------------------------------------------
%%% @copyright (C) 2018, Aeternity Anstalt
%%% @doc
%%% Functions for serializing generic objects to/from binary format.
%%% @end
%%%-------------------------------------------------------------------
-module(aeserialization).
-export([ decode_fields/2
, deserialize/5
, deserialize_tag_and_vsn/1
, encode_fields/2
, serialize/4 ]).
%%%===================================================================
%%% Types
%%%===================================================================
-export_type([ template/0
, fields/0
]).
-type template() :: [{field_name(), type()}].
-type field_name() :: atom().
-type type() :: 'int'
| 'bool'
| 'binary'
| 'id' %% As defined in aec_id.erl
| [type()] %% Length one in the type. This means a list of any length.
| tuple(). %% Any arity, containing type(). This means a static size array.
-type encodable_term() :: non_neg_integer()
| binary()
| boolean()
| [encodable_term()] %% Of any length
| tuple() %% Any arity, containing encodable_term().
| aeser_id:id().
-type fields() :: [{field_name(), encodable_term()}].
%%%===================================================================
%%% API
%%%===================================================================
-spec serialize(non_neg_integer(), non_neg_integer(), template(), fields()) -> binary().
serialize(Tag, Vsn, Template, Fields) ->
List = encode_fields([{tag, int}, {vsn, int}|Template],
[{tag, Tag}, {vsn, Vsn}|Fields]),
aeser_rlp:encode(List).
%% Type isn't strictly necessary, but will give a better error reason
-spec deserialize(atom(), non_neg_integer(), non_neg_integer(),
template(), binary()) -> fields().
deserialize(Type, Tag, Vsn, Template0, Binary) ->
Decoded = aeser_rlp:decode(Binary),
Template = [{tag, int}, {vsn, int}|Template0],
case decode_fields(Template, Decoded) of
[{tag, Tag}, {vsn, Vsn}|Left] ->
Left;
Other ->
error({illegal_serialization, Type, Vsn,
Other, Binary, Decoded, Template})
end.
-spec deserialize_tag_and_vsn(binary()) ->
{non_neg_integer(), non_neg_integer(), fields()}.
deserialize_tag_and_vsn(Binary) ->
[TagBin, VsnBin|Fields] = aeser_rlp:decode(Binary),
Template = [{tag, int}, {vsn, int}],
[{tag, Tag}, {vsn, Vsn}] = decode_fields(Template, [TagBin, VsnBin]),
{Tag, Vsn, Fields}.
encode_fields([{Field, Type}|TypesLeft],
[{Field, Val}|FieldsLeft]) ->
try encode_field(Type, Val) of
Encoded -> [Encoded | encode_fields(TypesLeft, FieldsLeft)]
catch error:{illegal, T, V} ->
error({illegal_field, Field, Type, Val, T, V})
end;
encode_fields([], []) ->
[];
encode_fields(Template, Values) ->
error({illegal_template_or_values, Template, Values}).
decode_fields([{Field, Type}|TypesLeft],
[Bin |FieldsLeft]) ->
try decode_field(Type, Bin) of
Decoded -> [{Field, Decoded} | decode_fields(TypesLeft, FieldsLeft)]
catch error:{illegal, T, V} ->
error({illegal_field, Field, Type, Bin, T, V})
end;
decode_fields([], []) ->
[];
decode_fields(Template, Values) ->
error({illegal_template_or_values, Template, Values}).
%%%===================================================================
%%% Internal functions
%%%===================================================================
encode_field([Type], L) when is_list(L) ->
[encode_field(Type, X) || X <- L];
encode_field(Type, T) when tuple_size(Type) =:= tuple_size(T) ->
Zipped = lists:zip(tuple_to_list(Type), tuple_to_list(T)),
[encode_field(X, Y) || {X, Y} <- Zipped];
encode_field(int, X) when is_integer(X), X >= 0 ->
binary:encode_unsigned(X);
encode_field(binary, X) when is_binary(X) -> X;
encode_field(bool, true) -> <<1:8>>;
encode_field(bool, false) -> <<0:8>>;
encode_field(id, Val) ->
try aeser_id:encode(Val)
catch _:_ -> error({illegal, id, Val})
end;
encode_field(Type, Val) -> error({illegal, Type, Val}).
decode_field([Type], List) when is_list(List) ->
[decode_field(Type, X) || X <- List];
decode_field(Type, List) when length(List) =:= tuple_size(Type) ->
Zipped = lists:zip(tuple_to_list(Type), List),
list_to_tuple([decode_field(X, Y) || {X, Y} <- Zipped]);
decode_field(int, <<0:8, X/binary>> = B) when X =/= <<>> ->
error({illegal, int, B});
decode_field(int, X) when is_binary(X) -> binary:decode_unsigned(X);
decode_field(binary, X) when is_binary(X) -> X;
decode_field(bool, <<1:8>>) -> true;
decode_field(bool, <<0:8>>) -> false;
decode_field(id, Val) ->
try aeser_id:decode(Val)
catch _:_ -> error({illegal, id, Val})
end;
decode_field(Type, X) -> error({illegal, Type, X}).