Compare commits
1 Commits
master
...
uw-dyn-opt
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a250828afa |
141
README.md
141
README.md
@ -49,25 +49,8 @@ range from 10 to 200, and also to stay within 1 byte.)
|
|||||||
When encoding `map` types, the map elements are first sorted.
|
When encoding `map` types, the map elements are first sorted.
|
||||||
|
|
||||||
When specifying a map type for template-driven encoding, use
|
When specifying a map type for template-driven encoding, use
|
||||||
the `#{items => [{Key, ValueType} | {opt, Key, ValueType}]}` construct.
|
the `#{items => [{Key, Value}]}` construct.
|
||||||
The key names are included in the encoding, and are match against the item
|
|
||||||
specs during decoding. If the key names don't match, the decoding fails, unless
|
|
||||||
for an `{opt, K, V}` item, in which case that item spec is skipped.
|
|
||||||
|
|
||||||
```erlang
|
|
||||||
T = #{items => [{a,int},{opt,b,int},{c,int}]}
|
|
||||||
E1 = gmser_dyn:encode_typed(T, #{a => 1, b => 2, c => 3}) ->
|
|
||||||
[<<0>>,<<1>>,[<<252>>,
|
|
||||||
[[[<<255>>,<<97>>],[<<248>>,<<1>>]],
|
|
||||||
[[<<255>>,<<98>>],[<<248>>,<<2>>]],
|
|
||||||
[[<<255>>,<<99>>],[<<248>>,<<3>>]]]]]
|
|
||||||
E2 = gmser_dyn:encode_typed(T, #{a => 1, c => 3}) ->
|
|
||||||
[<<0>>,<<1>>,[<<252>>,
|
|
||||||
[[[<<255>>,<<97>>],[<<248>>,<<1>>]],
|
|
||||||
[[<<255>>,<<99>>],[<<248>>,<<3>>]]]]]
|
|
||||||
gmser_dyn:decode_typed(T,E2) ->
|
|
||||||
#{c => 3,a => 1}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Labels
|
## Labels
|
||||||
|
|
||||||
@ -81,12 +64,12 @@ converted to binaries, and `create` means that the atom is created dynamically.
|
|||||||
|
|
||||||
The option can be passed e.g.:
|
The option can be passed e.g.:
|
||||||
```erlang
|
```erlang
|
||||||
gmser_dyn:deserialize(Binary, gmser_dyn:set_opts(#{missing_labels => convert}))
|
gmser_dyn:deserialize(Binary, set_opts(#{missing_labels => convert}))
|
||||||
```
|
```
|
||||||
|
|
||||||
or
|
or
|
||||||
```erlang
|
```erlang
|
||||||
gmser_dyn:deserialize(Binary, gmser_dyn:set_opts(#{missing_labels => convert}, Types))
|
gmser_dyn:deserialize(Binary, set_opts(#{missing_labels => convert}, Types))
|
||||||
```
|
```
|
||||||
|
|
||||||
By calling `gmser_dyn:register_types/1`, after having added options to the type map,
|
By calling `gmser_dyn:register_types/1`, after having added options to the type map,
|
||||||
@ -131,70 +114,31 @@ Templates can be provided to the encoder by either naming an already registered
|
|||||||
type, or by passing a template directly. In both cases, the encoder will enforce
|
type, or by passing a template directly. In both cases, the encoder will enforce
|
||||||
the type information in the template.
|
the type information in the template.
|
||||||
|
|
||||||
If the template has been registered, the encoder uses the registered type specification
|
If the template has been registered, the encoder omits inner type tags (still
|
||||||
to drive the encoding. The code of the registered template is embedded in the encoded
|
inserting the top-level tag), leading to some compression of the output.
|
||||||
output:
|
This also means that the serialized term cannot be decoded without the same
|
||||||
|
schema information on the decoder side.
|
||||||
|
|
||||||
|
In the case of a directly provided template, all type information is inserted,
|
||||||
|
such that the serialized term can be decoded without any added type information.
|
||||||
|
The template types are still enforced during encoding.
|
||||||
|
|
||||||
```erlang
|
```erlang
|
||||||
gmser_dyn:encode_typed({int,int,int}, {1,2,3}) ->
|
ET = fun(Type,Term) -> io:fwrite("~w~n", [gmser_dyn:encode_typed(Type,Term)]) end.
|
||||||
[<<0>>,<<1>>,[<<253>>,
|
|
||||||
[[<<248>>,<<1>>],[<<248>>,<<2>>],[<<248>>,<<3>>]]]]
|
|
||||||
|
|
||||||
Types = gmser_dyn_types:add_type(t3,1013,{int,int,int}).
|
ET([{int,int}], [{1,2}]) -> [<<0>>,<<1>>,[<<251>>,[[[<<248>>,<<1>>],[<<248>>,<<2>>]]]]]
|
||||||
gmser_dyn:encode_typed(t3, {1,2,3}, Types) ->
|
|
||||||
[<<0>>,<<1>>,[[<<3,245>>,<<253>>],
|
gmser_dyn:register_type(1000,lt2i,[{int,int}]).
|
||||||
[[<<248>>,<<1>>],[<<248>>,<<2>>],[<<248>>,<<3>>]]]]
|
ET(lt2i, [{1,2}]) -> [<<0>>,<<1>>,[<<3,232>>,[[<<1>>,<<2>>]]]]
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that the original `<<253>>` type code is wrapped as `[<<3,245>>,<<253>>]`,
|
|
||||||
where `<<3,245>>` corresponds to the custom code `1013`.
|
|
||||||
|
|
||||||
Using the default option `#{strict => true}`, the decoder will extract the custom
|
|
||||||
type spec, and validate the encoded data against it. If the custom code is missing,
|
|
||||||
the decoder aborts. Using `#{strict => false}`, the custom code is used if it exists,
|
|
||||||
but otherwise, it's ignored, and the encoded data is decoded using the dynamic type
|
|
||||||
info.
|
|
||||||
|
|
||||||
### Alternative types
|
### Alternative types
|
||||||
|
|
||||||
The dynamic encoder supports a few additions to the `gmserialization` template
|
The dynamic encoder supports two additions to the `gmserialization` template
|
||||||
language: `any`, `#{list => Type}`, `#{alt => [AltTypes]}` and `#{switch => [AltTypes]}`.
|
language: `any` and `#{alt => [AltTypes]}`.
|
||||||
|
|
||||||
#### `any`
|
|
||||||
|
|
||||||
The `any` type doesn't have an associated code, but enforces dynamic encoding.
|
The `any` type doesn't have an associated code, but enforces dynamic encoding.
|
||||||
|
|
||||||
#### `list`
|
|
||||||
|
|
||||||
The original list type notation expects a key-value list, e.g.
|
|
||||||
|
|
||||||
`[{name, binary}, {age, int}]`
|
|
||||||
|
|
||||||
```erlang
|
|
||||||
EL = gmser_dyn:encode_typed([{name,binary},{age,int}], [{name,<<"Ulf">>},{age,29}]) ->
|
|
||||||
[<<0>>,<<1>>,[<<251>>,
|
|
||||||
[[<<253>>,[[<<255>>,<<110,97,109,101>>],[<<249>>,<<85,108,102>>]]],
|
|
||||||
[<<253>>,[[<<255>>,<<97,103,101>>],[<<248>>,<<29>>]]]]]]
|
|
||||||
```
|
|
||||||
Note that the encoding explicitly lays out a `[{Key, Value}]` structure, all
|
|
||||||
dynamically typed. This means it can be dynamically decoded without templates.
|
|
||||||
|
|
||||||
```erlang
|
|
||||||
gmser_dyn:decode(EL).
|
|
||||||
[{name,<<"Ulf">>},{age,29}]
|
|
||||||
```
|
|
||||||
|
|
||||||
In order to specify something like Erlang's `[integer()]` type, we can use
|
|
||||||
the following:
|
|
||||||
|
|
||||||
```erlang
|
|
||||||
gmser_dyn:encode_typed(#{list => int}, [1,2,3,4]) ->
|
|
||||||
[<<0>>,<<1>>,[<<251>>,
|
|
||||||
[[<<248>>,<<1>>],[<<248>>,<<2>>],[<<248>>,<<3>>],[<<248>>,<<4>>]]]]
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `alt`
|
|
||||||
|
|
||||||
The `#{alt => [Type]}` construct also enforces dynamic encoding, and will try
|
The `#{alt => [Type]}` construct also enforces dynamic encoding, and will try
|
||||||
to encode as each type in the list, in the specified order, until one matches.
|
to encode as each type in the list, in the specified order, until one matches.
|
||||||
|
|
||||||
@ -206,55 +150,6 @@ gmser_dyn:encode_typed(anyint,-5) -> [<<0>>,<<1>>,[<<246>>,[<<247>>,<<5>>]]]
|
|||||||
gmser_dyn:encode_typed(anyint,5) -> [<<0>>,<<1>>,[<<246>>,[<<248>>,<<5>>]]]
|
gmser_dyn:encode_typed(anyint,5) -> [<<0>>,<<1>>,[<<246>>,[<<248>>,<<5>>]]]
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `switch`
|
|
||||||
|
|
||||||
The `switch` type allows for encoding a 'tagged' object, where the tag determines
|
|
||||||
the type.
|
|
||||||
|
|
||||||
```erlang
|
|
||||||
E1 = gmser_dyn:encode_typed(#{switch => #{name => binary, age => int}}, #{age => 29}) ->
|
|
||||||
[<<0>>,<<1>>,[<<252>>,[[[<<255>>,<<97,103,101>>],[<<248>>,<<29>>]]]]]
|
|
||||||
gmser_dyn:decode_typed(#{switch => #{name => binary, age => int}}, E1) ->
|
|
||||||
#{age => 29}
|
|
||||||
E2 = gmser_dyn:encode_typed(#{switch => #{name => binary, age => int}}, #{name => <<"Ulf">>}) ->
|
|
||||||
[<<0>>,<<1>>,[<<252>>,[[[<<255>>,<<110,97,109,101>>],[<<249>>,<<85,108,102>>]]]]]
|
|
||||||
gmser_dyn:decode_typed(#{switch => #{name => binary, age => int}}, E1) ->
|
|
||||||
#{name => <<"Ulf">>}
|
|
||||||
```
|
|
||||||
|
|
||||||
A practical use of `switch` would be in a protocol schema:
|
|
||||||
|
|
||||||
```erlang
|
|
||||||
t_msg(_) ->
|
|
||||||
#{switch => #{ call => t_call
|
|
||||||
, reply => t_reply
|
|
||||||
, notification => t_notification }}.
|
|
||||||
|
|
||||||
t_call(_) ->
|
|
||||||
#{items => [ {id, anyint}
|
|
||||||
, {req, t_req} ]}.
|
|
||||||
|
|
||||||
t_reply(_) ->
|
|
||||||
#{alt => [#{items => [ {id, anyint}
|
|
||||||
, {result, t_result} ]},
|
|
||||||
#{items => [ {id, anyint}
|
|
||||||
, {code, anyint}
|
|
||||||
, {message, binary} ]}
|
|
||||||
]}.
|
|
||||||
```
|
|
||||||
|
|
||||||
In this scenario, messages are 'taggged' as 1-element maps, e.g.:
|
|
||||||
|
|
||||||
```erlang
|
|
||||||
async_request(Msg) ->
|
|
||||||
Id = erlang:unique_integer(),
|
|
||||||
gmmp_cp:to_server(
|
|
||||||
whereis(gmmp_core_connector),
|
|
||||||
#{call => #{ id => Id
|
|
||||||
, req => Msg }}),
|
|
||||||
Id.
|
|
||||||
```
|
|
||||||
|
|
||||||
### Notes
|
### Notes
|
||||||
|
|
||||||
Note that `anyint` is a standard type. The static serializer supports only
|
Note that `anyint` is a standard type. The static serializer supports only
|
||||||
|
|||||||
@ -7,11 +7,4 @@
|
|||||||
{enacl,
|
{enacl,
|
||||||
{git,
|
{git,
|
||||||
"https://git.qpq.swiss/QPQ-AG/enacl.git",
|
"https://git.qpq.swiss/QPQ-AG/enacl.git",
|
||||||
{ref, "4eb7ec70084ba7c87b1af8797c4c4e90c84f95a2"}}},
|
{ref, "4eb7ec70084ba7c87b1af8797c4c4e90c84f95a2"}}}]}.
|
||||||
{eblake2, "1.0.0"}
|
|
||||||
]}.
|
|
||||||
|
|
||||||
{dialyzer,
|
|
||||||
[ {plt_apps, all_deps},
|
|
||||||
{base_plt_apps, [erts, kernel, stdlib, enacl, base58, eblake2]}
|
|
||||||
]}.
|
|
||||||
|
|||||||
10
rebar.lock
10
rebar.lock
@ -1,16 +1,8 @@
|
|||||||
{"1.2.0",
|
|
||||||
[{<<"base58">>,
|
[{<<"base58">>,
|
||||||
{git,"https://git.qpq.swiss/QPQ-AG/erl-base58.git",
|
{git,"https://git.qpq.swiss/QPQ-AG/erl-base58.git",
|
||||||
{ref,"e6aa62eeae3d4388311401f06e4b939bf4e94b9c"}},
|
{ref,"e6aa62eeae3d4388311401f06e4b939bf4e94b9c"}},
|
||||||
0},
|
0},
|
||||||
{<<"eblake2">>,{pkg,<<"eblake2">>,<<"1.0.0">>},0},
|
|
||||||
{<<"enacl">>,
|
{<<"enacl">>,
|
||||||
{git,"https://git.qpq.swiss/QPQ-AG/enacl.git",
|
{git,"https://git.qpq.swiss/QPQ-AG/enacl.git",
|
||||||
{ref,"4eb7ec70084ba7c87b1af8797c4c4e90c84f95a2"}},
|
{ref,"4eb7ec70084ba7c87b1af8797c4c4e90c84f95a2"}},
|
||||||
0}]}.
|
0}].
|
||||||
[
|
|
||||||
{pkg_hash,[
|
|
||||||
{<<"eblake2">>, <<"EC8AD20E438AAB3F2E8D5D118C366A0754219195F8A0F536587440F8F9BCF2EF">>}]},
|
|
||||||
{pkg_hash_ext,[
|
|
||||||
{<<"eblake2">>, <<"3C4D300A91845B25D501929A26AC2E6F7157480846FAB2347A4C11AE52E08A99">>}]}
|
|
||||||
].
|
|
||||||
|
|||||||
@ -13,13 +13,7 @@
|
|||||||
safe_decode/2,
|
safe_decode/2,
|
||||||
byte_size_for_type/1]).
|
byte_size_for_type/1]).
|
||||||
|
|
||||||
-export([encode_keypair/1,
|
-export_type([encoded/0]).
|
||||||
safe_decode_keypair/1]).
|
|
||||||
|
|
||||||
-export([unsafe_encode/2]). %% Encode without size checks
|
|
||||||
|
|
||||||
-export_type([encoded/0,
|
|
||||||
known_type/0]).
|
|
||||||
|
|
||||||
-type known_type() :: key_block_hash
|
-type known_type() :: key_block_hash
|
||||||
| micro_block_hash
|
| micro_block_hash
|
||||||
@ -44,7 +38,6 @@
|
|||||||
| native_token
|
| native_token
|
||||||
| commitment
|
| commitment
|
||||||
| peer_pubkey
|
| peer_pubkey
|
||||||
| hash
|
|
||||||
| state
|
| state
|
||||||
| poi
|
| poi
|
||||||
| state_trees
|
| state_trees
|
||||||
@ -58,66 +51,14 @@
|
|||||||
-type payload() :: binary().
|
-type payload() :: binary().
|
||||||
-type encoded() :: binary().
|
-type encoded() :: binary().
|
||||||
|
|
||||||
-type keypair() :: #{public := <<_:(32*8)>>, secret := <<_:(64*8)>>}.
|
|
||||||
-type encoded_keypair() :: #{binary() => binary()}.
|
|
||||||
|
|
||||||
-export_type([ keypair/0
|
|
||||||
, encoded_keypair/0 ]).
|
|
||||||
|
|
||||||
-define(BASE58, 1).
|
-define(BASE58, 1).
|
||||||
-define(BASE64, 2).
|
-define(BASE64, 2).
|
||||||
|
|
||||||
-spec encode_keypair(keypair()) -> encoded_keypair().
|
|
||||||
encode_keypair(#{public := Pub, secret := Sec}) ->
|
|
||||||
case Sec of
|
|
||||||
<<Seed:32/binary, Pub1:32/binary>> when Pub1 =:= Pub ->
|
|
||||||
#{ <<"pub">> => encode(account_pubkey, Pub)
|
|
||||||
, <<"priv">> => encode(account_seckey, Seed) };
|
|
||||||
_ ->
|
|
||||||
erlang:error(invalid_keypair)
|
|
||||||
end.
|
|
||||||
|
|
||||||
-spec safe_decode_keypair(encoded_keypair()) -> {'ok', keypair()} | {'error', atom()}.
|
|
||||||
safe_decode_keypair(#{<<"pub">> := EncPub, <<"priv">> := EncPriv}) ->
|
|
||||||
case safe_decode(account_pubkey, EncPub) of
|
|
||||||
{ok, Pub} ->
|
|
||||||
case safe_decode(account_seckey, EncPriv) of
|
|
||||||
{ok, Seed} when byte_size(Seed) =:= 32 ->
|
|
||||||
case enacl:sign_seed_keypair(Seed) of
|
|
||||||
#{public := Pub, secret := _} = KP ->
|
|
||||||
{ok, KP};
|
|
||||||
_ ->
|
|
||||||
{error, illegal_encoding}
|
|
||||||
end;
|
|
||||||
{ok, <<Seed:32/binary, Pub:32/binary>>} ->
|
|
||||||
case enacl:sign_seed_keypair(Seed) of
|
|
||||||
#{public := Pub} = KP ->
|
|
||||||
{ok, KP};
|
|
||||||
_ ->
|
|
||||||
{error, illegal_encoding}
|
|
||||||
end;
|
|
||||||
{ok, _} ->
|
|
||||||
{error, illegal_encoding};
|
|
||||||
{error, _} = Error1 ->
|
|
||||||
Error1
|
|
||||||
end;
|
|
||||||
Error ->
|
|
||||||
Error
|
|
||||||
end.
|
|
||||||
|
|
||||||
-spec encode(known_type(), payload() | gmser_id:id()) -> encoded().
|
-spec encode(known_type(), payload() | gmser_id:id()) -> encoded().
|
||||||
encode(id_hash, Payload) ->
|
encode(id_hash, Payload) ->
|
||||||
{IdType, Val} = gmser_id:specialize(Payload),
|
{IdType, Val} = gmser_id:specialize(Payload),
|
||||||
encode(id2type(IdType), Val);
|
encode(id2type(IdType), Val);
|
||||||
encode(Type, Payload) ->
|
encode(Type, Payload) ->
|
||||||
case type_size_check(Type, Payload) of
|
|
||||||
ok ->
|
|
||||||
unsafe_encode(Type, Payload);
|
|
||||||
{error, Reason} ->
|
|
||||||
erlang:error(Reason)
|
|
||||||
end.
|
|
||||||
|
|
||||||
unsafe_encode(Type, Payload) ->
|
|
||||||
Pfx = type2pfx(Type),
|
Pfx = type2pfx(Type),
|
||||||
Enc = case type2enc(Type) of
|
Enc = case type2enc(Type) of
|
||||||
?BASE58 -> base58_check(Payload);
|
?BASE58 -> base58_check(Payload);
|
||||||
@ -125,7 +66,6 @@ unsafe_encode(Type, Payload) ->
|
|||||||
end,
|
end,
|
||||||
<<Pfx/binary, "_", Enc/binary>>.
|
<<Pfx/binary, "_", Enc/binary>>.
|
||||||
|
|
||||||
|
|
||||||
-spec decode(binary()) -> {known_type(), payload()}.
|
-spec decode(binary()) -> {known_type(), payload()}.
|
||||||
decode(Bin0) ->
|
decode(Bin0) ->
|
||||||
case split(Bin0) of
|
case split(Bin0) of
|
||||||
@ -141,13 +81,6 @@ decode(Bin0) ->
|
|||||||
erlang:error(missing_prefix)
|
erlang:error(missing_prefix)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
type_size_check(account_seckey, Bin) ->
|
|
||||||
case byte_size(Bin) of
|
|
||||||
Sz when Sz =:= 32; Sz =:= 64 ->
|
|
||||||
ok;
|
|
||||||
_ ->
|
|
||||||
{error, incorrect_size}
|
|
||||||
end;
|
|
||||||
type_size_check(Type, Bin) ->
|
type_size_check(Type, Bin) ->
|
||||||
case byte_size_for_type(Type) of
|
case byte_size_for_type(Type) of
|
||||||
not_applicable -> ok;
|
not_applicable -> ok;
|
||||||
|
|||||||
1037
src/gmser_dyn.erl
1037
src/gmser_dyn.erl
File diff suppressed because it is too large
Load Diff
@ -1,71 +0,0 @@
|
|||||||
-module(gmser_dyn_types).
|
|
||||||
|
|
||||||
-export([ add_type/3 %% (Tag, Code, Template) -> Types1
|
|
||||||
, add_type/4 %% (Tag, Code, Template, Types) -> Types1
|
|
||||||
, from_list/2
|
|
||||||
, expand/1 ]).
|
|
||||||
-export([ next_code/1 ]).
|
|
||||||
|
|
||||||
next_code(#{codes := Codes}) ->
|
|
||||||
lists:max(maps:keys(Codes)) + 1.
|
|
||||||
|
|
||||||
-spec add_type(Tag, Code, Template) -> Types
|
|
||||||
when Tag :: gmser_dyn:tag()
|
|
||||||
, Code :: gmser_dyn:code()
|
|
||||||
, Template :: gmser_dyn:template()
|
|
||||||
, Types :: gmser_dyn:types().
|
|
||||||
add_type(Tag, Code, Template) ->
|
|
||||||
add_type(Tag, Code, Template, gmser_dyn:registered_types()).
|
|
||||||
|
|
||||||
add_type(Tag, Code, Template, Types) ->
|
|
||||||
elem_to_type({Tag, Code, Template}, Types).
|
|
||||||
|
|
||||||
from_list(L, Types) ->
|
|
||||||
lists:foldl(fun elem_to_type/2, Types, L).
|
|
||||||
|
|
||||||
expand(#{vsn := V, templates := Templates0} = Types) ->
|
|
||||||
Templates =
|
|
||||||
maps:map(
|
|
||||||
fun(_, F) when is_function(F, 0) ->
|
|
||||||
F();
|
|
||||||
(_, F) when is_function(F, 1) ->
|
|
||||||
F(V);
|
|
||||||
(_, T) ->
|
|
||||||
T
|
|
||||||
end, Templates0),
|
|
||||||
Types#{templates := Templates}.
|
|
||||||
|
|
||||||
elem_to_type({Tag, Code, Template}, Acc) when is_atom(Tag), is_integer(Code) ->
|
|
||||||
#{codes := Codes, rev := Rev, templates := Temps} = Acc,
|
|
||||||
case {is_map_key(Tag, Rev), is_map_key(Code, Codes)} of
|
|
||||||
{false, false} ->
|
|
||||||
Acc#{ codes := Codes#{Code => Tag}
|
|
||||||
, rev := Rev#{Tag => Code}
|
|
||||||
, templates => Temps#{Tag => Template}
|
|
||||||
};
|
|
||||||
{true, _} -> error({duplicate_tag, Tag});
|
|
||||||
{_, true} -> error({duplicate_code, Code})
|
|
||||||
end;
|
|
||||||
elem_to_type({modify, {Tag, Template}}, Acc) ->
|
|
||||||
#{codes := _, rev := Rev, templates := Templates} = Acc,
|
|
||||||
_ = maps:get(Tag, Rev),
|
|
||||||
Templates1 = Templates#{Tag := Template},
|
|
||||||
Acc#{templates := Templates1};
|
|
||||||
elem_to_type({labels, Lbls}, Acc) ->
|
|
||||||
lists:foldl(fun add_label/2, Acc, Lbls);
|
|
||||||
elem_to_type({vsn, V}, Acc) ->
|
|
||||||
Acc#{vsn => V};
|
|
||||||
elem_to_type(Elem, _) ->
|
|
||||||
error({invalid_type, Elem}).
|
|
||||||
|
|
||||||
add_label({L, Code}, #{labels := Lbls, rev_labels := RevLbls} = Acc)
|
|
||||||
when is_atom(L), is_integer(Code), Code > 0 ->
|
|
||||||
case {is_map_key(L, Lbls), is_map_key(Code, RevLbls)} of
|
|
||||||
{false, false} ->
|
|
||||||
Acc#{labels := Lbls#{L => Code},
|
|
||||||
rev_labels := RevLbls#{Code => L}};
|
|
||||||
{true, _} -> error({duplicate_label, L});
|
|
||||||
{_, true} -> error({duplicate_label_code, Code})
|
|
||||||
end;
|
|
||||||
add_label(Elem, _) ->
|
|
||||||
error({invalid_label, Elem}).
|
|
||||||
@ -17,9 +17,6 @@
|
|||||||
, is_id/1
|
, is_id/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export([ t_id/1
|
|
||||||
]).
|
|
||||||
|
|
||||||
%% For aec_serialization
|
%% For aec_serialization
|
||||||
-export([ encode/1
|
-export([ encode/1
|
||||||
, decode/1
|
, decode/1
|
||||||
@ -29,18 +26,11 @@
|
|||||||
, val
|
, val
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-type tag() :: 'account'
|
-type tag() :: 'account' | 'name'
|
||||||
| 'associate_chain'
|
| 'commitment' | 'contract' | 'channel'
|
||||||
| 'channel'
|
| 'associate_chain' | 'entry' .
|
||||||
| 'commitment'
|
|
||||||
| 'contract'
|
|
||||||
| 'contract_source'
|
|
||||||
| 'name'
|
|
||||||
| 'native_token'
|
|
||||||
| 'entry'.
|
|
||||||
|
|
||||||
-type val() :: <<_:256>>.
|
-type val() :: <<_:256>>.
|
||||||
-type id() :: #id{}.
|
-opaque(id() :: #id{}).
|
||||||
|
|
||||||
-export_type([ id/0
|
-export_type([ id/0
|
||||||
, tag/0
|
, tag/0
|
||||||
@ -104,9 +94,6 @@ decode(<<Tag:?TAG_SIZE/unit:8, Val:?PUB_SIZE/binary>>) ->
|
|||||||
#id{ tag = decode_tag(Tag)
|
#id{ tag = decode_tag(Tag)
|
||||||
, val = Val}.
|
, val = Val}.
|
||||||
|
|
||||||
-spec t_id(any()) -> id().
|
|
||||||
t_id(#id{} = Id) -> Id.
|
|
||||||
|
|
||||||
%%%===================================================================
|
%%%===================================================================
|
||||||
%%% Internal functions
|
%%% Internal functions
|
||||||
%%%===================================================================
|
%%%===================================================================
|
||||||
|
|||||||
@ -22,39 +22,10 @@
|
|||||||
, {native_token , 32}
|
, {native_token , 32}
|
||||||
, {commitment , 32}
|
, {commitment , 32}
|
||||||
, {peer_pubkey , 32}
|
, {peer_pubkey , 32}
|
||||||
, {hash , 32}
|
|
||||||
, {state , 32}
|
, {state , 32}
|
||||||
, {poi , not_applicable}]).
|
, {poi , not_applicable}]).
|
||||||
|
|
||||||
encode_decode_test_() ->
|
encode_decode_test_() ->
|
||||||
encode_decode_test_(?TYPES).
|
|
||||||
|
|
||||||
encode_decode_known_types_test_() ->
|
|
||||||
KnownTypes = known_types(),
|
|
||||||
SizedTypes = [{T, ?TEST_MODULE:byte_size_for_type(T)} || T <- KnownTypes],
|
|
||||||
encode_decode_test_(SizedTypes).
|
|
||||||
|
|
||||||
prefixes_are_known_types_test() ->
|
|
||||||
MappedPfxs = mapped_prefixes(),
|
|
||||||
KnownTypes = known_types(),
|
|
||||||
lists:foreach(
|
|
||||||
fun({Pfx, Type}) ->
|
|
||||||
case lists:member(Type, KnownTypes) of
|
|
||||||
true -> ok;
|
|
||||||
false ->
|
|
||||||
error({not_a_known_type, Pfx, Type})
|
|
||||||
end
|
|
||||||
end, MappedPfxs),
|
|
||||||
lists:foreach(
|
|
||||||
fun(Type) ->
|
|
||||||
case lists:keyfind(Type, 2, MappedPfxs) of
|
|
||||||
{_, _} -> ok;
|
|
||||||
false ->
|
|
||||||
error({has_no_mapped_prefix, Type})
|
|
||||||
end
|
|
||||||
end, KnownTypes).
|
|
||||||
|
|
||||||
encode_decode_test_(Types) ->
|
|
||||||
[{"Byte sizes are correct",
|
[{"Byte sizes are correct",
|
||||||
fun() ->
|
fun() ->
|
||||||
lists:foreach(
|
lists:foreach(
|
||||||
@ -62,7 +33,7 @@ encode_decode_test_(Types) ->
|
|||||||
{_Type, _, ByteSize} = {Type, ByteSize,
|
{_Type, _, ByteSize} = {Type, ByteSize,
|
||||||
?TEST_MODULE:byte_size_for_type(Type)}
|
?TEST_MODULE:byte_size_for_type(Type)}
|
||||||
end,
|
end,
|
||||||
Types)
|
?TYPES)
|
||||||
end
|
end
|
||||||
},
|
},
|
||||||
{"Serialize/deserialize known types",
|
{"Serialize/deserialize known types",
|
||||||
@ -79,7 +50,7 @@ encode_decode_test_(Types) ->
|
|||||||
{Type, Key} = ?TEST_MODULE:decode(EncodedKey),
|
{Type, Key} = ?TEST_MODULE:decode(EncodedKey),
|
||||||
{ok, Key} = ?TEST_MODULE:safe_decode(Type, EncodedKey)
|
{ok, Key} = ?TEST_MODULE:safe_decode(Type, EncodedKey)
|
||||||
end,
|
end,
|
||||||
Types)
|
?TYPES)
|
||||||
end
|
end
|
||||||
},
|
},
|
||||||
{"Key size check works",
|
{"Key size check works",
|
||||||
@ -87,18 +58,17 @@ encode_decode_test_(Types) ->
|
|||||||
lists:foreach(
|
lists:foreach(
|
||||||
fun({_Type, not_applicable}) -> ok;
|
fun({_Type, not_applicable}) -> ok;
|
||||||
({Type, ByteSize}) ->
|
({Type, ByteSize}) ->
|
||||||
CheckIllegalSize =
|
CheckIlligalSize =
|
||||||
fun(S) ->
|
fun(S) ->
|
||||||
Key = <<42:S/unit:8>>,
|
Key = <<42:S/unit:8>>,
|
||||||
?assertError(incorrect_size, ?TEST_MODULE:encode(Type, Key)),
|
EncodedKey = ?TEST_MODULE:encode(Type, Key),
|
||||||
EncodedKey = ?TEST_MODULE:unsafe_encode(Type, Key), %% no size check
|
|
||||||
{error, invalid_encoding} = ?TEST_MODULE:safe_decode(Type, EncodedKey)
|
{error, invalid_encoding} = ?TEST_MODULE:safe_decode(Type, EncodedKey)
|
||||||
end,
|
end,
|
||||||
CheckIllegalSize(0),
|
CheckIlligalSize(0),
|
||||||
CheckIllegalSize(ByteSize - 1),
|
CheckIlligalSize(ByteSize - 1),
|
||||||
CheckIllegalSize(ByteSize + 1)
|
CheckIlligalSize(ByteSize + 1)
|
||||||
end,
|
end,
|
||||||
Types)
|
?TYPES)
|
||||||
end
|
end
|
||||||
},
|
},
|
||||||
{"Missing prefix",
|
{"Missing prefix",
|
||||||
@ -121,7 +91,7 @@ encode_decode_test_(Types) ->
|
|||||||
<<_WholePrefix:3/unit:8, RestOfKey2/binary>> = EncodedKey,
|
<<_WholePrefix:3/unit:8, RestOfKey2/binary>> = EncodedKey,
|
||||||
{error, invalid_encoding} = ?TEST_MODULE:safe_decode(Type, RestOfKey2)
|
{error, invalid_encoding} = ?TEST_MODULE:safe_decode(Type, RestOfKey2)
|
||||||
end,
|
end,
|
||||||
Types)
|
?TYPES)
|
||||||
end
|
end
|
||||||
},
|
},
|
||||||
{"Piece of encoded key",
|
{"Piece of encoded key",
|
||||||
@ -140,7 +110,7 @@ encode_decode_test_(Types) ->
|
|||||||
{error, invalid_encoding} = ?TEST_MODULE:safe_decode(Type, HalfKey),
|
{error, invalid_encoding} = ?TEST_MODULE:safe_decode(Type, HalfKey),
|
||||||
{error, invalid_encoding} = ?TEST_MODULE:safe_decode(Type, RestOfKey)
|
{error, invalid_encoding} = ?TEST_MODULE:safe_decode(Type, RestOfKey)
|
||||||
end,
|
end,
|
||||||
Types)
|
?TYPES)
|
||||||
end
|
end
|
||||||
},
|
},
|
||||||
{"Encode/decode binary with only zeros",
|
{"Encode/decode binary with only zeros",
|
||||||
@ -161,58 +131,8 @@ encode_decode_test_(Types) ->
|
|||||||
Encoded1 = base58:binary_to_base58(Bin),
|
Encoded1 = base58:binary_to_base58(Bin),
|
||||||
Decoded1 = base58:base58_to_binary(Encoded1),
|
Decoded1 = base58:base58_to_binary(Encoded1),
|
||||||
?assertEqual(Bin, Decoded1)
|
?assertEqual(Bin, Decoded1)
|
||||||
end, Types)
|
end, ?TYPES)
|
||||||
end,
|
end,
|
||||||
Bins)
|
Bins)
|
||||||
end},
|
end}
|
||||||
{"Encode/decode keypairs",
|
|
||||||
fun() ->
|
|
||||||
KP1 = enacl:sign_keypair(),
|
|
||||||
Enc1 = ?TEST_MODULE:encode_keypair(KP1),
|
|
||||||
{ok, KP1} = ?TEST_MODULE:safe_decode_keypair(Enc1),
|
|
||||||
KP2 = enacl:sign_keypair(),
|
|
||||||
Enc2 = ?TEST_MODULE:encode_keypair(KP2),
|
|
||||||
{ok, KP2} = ?TEST_MODULE:safe_decode_keypair(Enc2),
|
|
||||||
BadEnc = Enc1#{~"priv" => maps:get(~"priv", Enc2)},
|
|
||||||
{error, illegal_encoding} = ?TEST_MODULE:safe_decode_keypair(BadEnc)
|
|
||||||
end
|
|
||||||
},
|
|
||||||
{"Encode AND decode both 32-byte and 64-byte account_seckey",
|
|
||||||
fun() ->
|
|
||||||
%% Originally, we could encode a 64-byte seckey, but decode would fail.
|
|
||||||
#{public := Pub, secret := Sec} = enacl:sign_keypair(),
|
|
||||||
<<Seed:32/binary, Pub:32/binary>> = Sec,
|
|
||||||
EncSeed = ?TEST_MODULE:encode(account_seckey, Seed),
|
|
||||||
EncSec = ?TEST_MODULE:encode(account_seckey, Sec),
|
|
||||||
{ok, Seed} = ?TEST_MODULE:safe_decode(account_seckey, EncSeed),
|
|
||||||
{ok, Sec} = ?TEST_MODULE:safe_decode(account_seckey, EncSec)
|
|
||||||
end
|
|
||||||
}
|
|
||||||
].
|
].
|
||||||
|
|
||||||
known_types() ->
|
|
||||||
Forms = get_forms(),
|
|
||||||
[{type, _, union, Types}] =
|
|
||||||
[Def || {attribute, _, type, {known_type, Def, []}} <- Forms],
|
|
||||||
[Name || {atom,_, Name} <- Types].
|
|
||||||
|
|
||||||
mapped_prefixes() ->
|
|
||||||
Forms = get_forms(),
|
|
||||||
[Clauses] = [Cs || {function,_,pfx2type,1,Cs} <- Forms],
|
|
||||||
Abst = [{B, A} || {clause,_,[B],[],[A]} <- Clauses],
|
|
||||||
lists:map(
|
|
||||||
fun({B, A}) ->
|
|
||||||
{eval_expr(B), eval_expr(A)}
|
|
||||||
end, Abst).
|
|
||||||
|
|
||||||
get_forms() ->
|
|
||||||
get_forms(code:which(?TEST_MODULE)).
|
|
||||||
|
|
||||||
get_forms(Beam) ->
|
|
||||||
{ok, {_, [{abstract_code, {raw_abstract_v1, Forms}}]}} =
|
|
||||||
beam_lib:chunks(Beam, [abstract_code]),
|
|
||||||
Forms.
|
|
||||||
|
|
||||||
eval_expr(Expr) ->
|
|
||||||
{value, Val, []} = erl_eval:expr(Expr, []),
|
|
||||||
Val.
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user