%%%------------------------------------------------------------------- %%% @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. | #{items := [{field_name(), type()}]} %% Record with named fields represented as a map. Encoded as a list in the given order. | tuple(). %% Any arity, containing type(). This means a static size array. -type encodable_term() :: non_neg_integer() | binary() | boolean() | [encodable_term()] %% Of any length | #{atom() => encodable_term()} | 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(#{items := Items}, Map) -> lists:map( fun({K, Type}) -> V = maps:get(K, Map), encode_field(Type, V) end, Items); 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(#{items := Items}, List) when length(List) =:= length(Items) -> Zipped = lists:zip(Items, List), lists:foldl( fun({{K, Type}, V}, Map) -> maps:is_key(K, Map) andalso error(badarg, duplicate_field), Map#{ K => decode_field(Type, V) } end, #{}, Zipped); 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}).