%%%------------------------------------------------------------------- %%% @copyright (C) 2025, QPQ AG %%% @copyright (C) 2017, Aeternity Anstalt %%% @doc %%% Implementation of the Recursive Length Prefix. %%% %%% https://zxq9.com/archives/2749 %%% https://github.com/ethereum/wiki/wiki/RLP %%% %%% @end %%%------------------------------------------------------------------- -module(gmser_rlp). -vsn("0.1.2"). -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(<> = 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 <>. -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(<>) when X =< ?UNTAGGED_LIMIT -> %% Untagged value {<>, B}; decode_one(<> = B) when L < ?LIST_OFFSET -> %% Byte array {Size, Rest} = decode_size(B, ?BYTE_ARRAY_OFFSET), <> = Rest, {X, Tail}; decode_one(<<_/binary>> = B) -> %% List {Size, Rest} = decode_size(B, ?LIST_OFFSET), <> = Rest, {decode_list(X), Tail}. decode_size(<>, 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(<>, Offset) -> %% Actual size is in a byte array. BinSize = L - Offset - ?UNTAGGED_SIZE_LIMIT, <> = B, {Size, Rest}. decode_list(<<>>) -> []; decode_list(B) -> {Element, Rest} = decode_one(B), [Element|decode_list(Rest)].