%%============================================================================== %% Copyright 2021-2026 Jan Henry Nystrom %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %%============================================================================== %%%------------------------------------------------------------------- %%% @doc %%% A CBOR library based on: %%% %%% Concise Binary Object Representation (CBOR) (rfc8949) %%% %%% IANA Considerations and IETF Protocol and %%% Documentation Usage for IEEE 802 Parameters (rfc9542) %%% %%% Supported tags: %%% %%% rfc8949: 0, 1, 2, 3, 4, 5, 24, 32, 33, 34, 55799 %%% %%% rfc9542: 48 %%% %%% Tags unlikely to ever be supported: 21, 22, 23 %%% %%% CBOR cbor() values are represented as follows: %%% %%% Simple values: %%% %%% False(20) : false %%% %%% True(21) : true %%% %%% Null(22) : null %%% %%% Undefined(23) : undefined %%% %%% (Integer) : {simple, integer()} %%% %%% Numbers: %%% %%% Integer : integer() %%% %%% Float : float() | {float16, float()} | %%% {float32, float()} | {float64, float()} | %%% inf | neg_inf | nan. %%% %%% Bistrings: %%% %%% String : {string, binary()} %%% %%% indefinite length String : {strings, [{string, binary()}]} %%% %%% Text : binary() %%% %%% indefinite length Texts : {texts, [binary()]} %%% %%% Collections: %%% %%% Map : #{cbor() => cbor()} %%% %%% indefinite length Map : {map, [#{cbor() => cbor()}]} %%% %%% Array : [cbor()] %%% %%% Arrays : {arrays, [cbor()]} %%% %%% Tags: %%% %%% timestamp(0) : {tag, timestamp, integer() | binary()} %%% %%% posix(1) : {tag, posix, integer() | float() | null | undefined} %%% %%% bignum(2) : {tag, bignum, non_neg_integer()} %%% %%% bignum(3) : {tag, bignum, neg_integer()} %%% %%% decimal_fraction(4) : {tag, decimal_fraction, {integer(), integer()}} %%% %%% bigfloat(5) : {tag, bigfloat, {integer(), integer()} %%% %%% embedded(24) : {tag, embedded, binary()} %%% %%% uri(32) : {tag, uri, #uri{} | binary()} %%% %%% base64url(33) : {tag, base64url, binary()} %%% %%% base64(34) : {tag, based, binary()} %%% %%% mac(48) : {tag, mac, binary()} %%% %%% cbor(55799) : {tag, cbor, cbor()} %%% %%% (Integer) : {tag, integer(), _} %%% %%% @end %%% %% @author Jan Henry Nystrom %% @copyright (C) 2021-2026, Jan Henry Nystrom %%%------------------------------------------------------------------- -module(jhn_cbor). -copyright('Jan Henry Nystrom '). %% Library functions -export([encode/1, encode/2, decode/1, decode/2 ]). %% Include -include_lib("jhn_stdlib/include/uri.hrl"). %% Exported types -export_type([cbor/0, simple/0, cfloat/0, cstring/0, ctext/0, array/0, cmap/0, tag/0]). %% Types -type cbor() :: simple() | integer() | float() | cfloat() | cstring() | ctext() | cmap() | array() | tag(). -type simple() :: true | false | null | undefined | {simple, integer()}. -type cfloat() :: {float16, float()} | {float32, float()} | {float64, float()}| inf | neg_inf | nan. -type cstring() :: {string, binary()} | {strings, [{string, binary()}]}. -type ctext() :: binary() | {texts, [binary()]}. -type cmap() :: #{cbor() => cbor()} | {maps, [#{cbor() => cbor()}]}. -type array() :: [cbor()] | {arrays, [[cbor()]]}. -type tag() :: {tag, integer(), tag_content()} | {tag, tag_alias(), tag_content()}. -type tag_content() :: _. -type tag_alias() :: timestamp | %% 0 value integer (posix timestamp) or binary posix | %% 1 value integer, float, binary, null, or undefined bignum | %% 2, 3 value integer or negative integer decimal_fraction | %% 4 value {mantissa, scaling factor} bigfloat | %% 5 value {mantissa, scaling factor} embedded | %% 24 value CBOR uri | %% 32 value jhn_uri:#uri{} or binary base64url | %% 33 value binary base64 | %% 34 value binary mac | %% 48 value binary, example <<"01-23-45-67-89-AB">> or %% <<"01-23-45-67-89-AB-FE-03">> cbor. %% 55799 -type opts() :: [opt()]. -type opt() :: binary | iolist | continue | {tag_callbacks, [{integer(), atom()}]}. %% Defines -define(MAJOR0, 0). -define(MAJOR1, 1). -define(MAJOR2, 2). -define(MAJOR3, 3). -define(MAJOR4, 4). -define(MAJOR5, 5). -define(MAJOR6, 6). -define(MAJOR7, 7). -define(BREAK, ?MAJOR7:3, 31:5). -define(MAX_16BYTE,16#FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF). -define(MAX_8BYTE, 16#FFFFFFFFFFFFFFFF). -define(MAX_4BYTE, 16#FFFFFFFF). -define(MAX_2BYTE, 16#FFFF). -define(MAX_1BYTE, 16#FF). %% Records -record(opts, {tag_callbacks = [] :: [{integer(), atom()}], return_type = iolist :: iolist | binary, continue = false :: boolean() }). %% =================================================================== %% Library functions. %% =================================================================== %%-------------------------------------------------------------------- %% Function: encode(Term) -> CBOR. %% @doc %% Encodes the structured Erlang term as an iolist. %% Equivalent of encode(Term, []) -> CBOR. %% @end %%-------------------------------------------------------------------- -spec encode(cbor()) -> iodata(). %%-------------------------------------------------------------------- encode(CBOR) -> encode(CBOR, []). %%-------------------------------------------------------------------- %% Function: encode(Term, [Option]) -> CBOR. %% @doc %% Encodes the structured Erlang term as an iolist or binary. %% Encode will give an exception if the erlang term is not well formed. %% Options are: %% binary -> a binary is returned %% iolist -> an iolist is returned (default) %% {tag_callbacks, [{integer(), nodule()}]} specifies %% a module with a cbor_encode_tag callback function to decode tags %% not supporrted in this module. %% @end %%-------------------------------------------------------------------- -spec encode(cbor(), opts()) -> binary(). %%-------------------------------------------------------------------- encode(T, Opts) -> case parse_opts(Opts) of OptsRec = #opts{return_type = iolist} -> do_encode(T, OptsRec); OptsRec = #opts{return_type = binary} -> iolist_to_binary(do_encode(T, OptsRec)) end. %%-------------------------------------------------------------------- %% Function: decode(CBOR) -> {Term, Binary} %% @doc %% Decodes the binary into a tuple of structured Erlang term. %% @end %%-------------------------------------------------------------------- -spec decode(binary()) -> {cbor(), binary()}. %%-------------------------------------------------------------------- decode(Binary) -> decode(Binary, []). %%-------------------------------------------------------------------- %% Function: decode(CBOR) -> {Term, Binary} %% @doc %% Decodes the binary into a structured Erlang term or a tuple of a %% structured Erlang term and the rest of the binary if the continue %% option is given. %% A {tag_callbacks, [{integer(), nodule()}]} can supplied to specify %% a module with a cbor_decode_tag callback function to decode tags %% not supporrted in this module. %% @end %%-------------------------------------------------------------------- -spec decode(binary(), opts()) -> {cbor(), binary()}. %%-------------------------------------------------------------------- decode(Binary, Opts) -> case parse_opts(Opts) of OptsRec = #opts{continue = true} -> do_decode(Binary, OptsRec); OptsRec -> {CBOR, _} = do_decode(Binary, OptsRec), CBOR end. %% =================================================================== %% Internal functions. %% =================================================================== %% =================================================================== %% Encoding %% =================================================================== %% Defined Simple values do_encode(false, _) -> <>; do_encode(true, _) -> <>; do_encode(null, _) -> <>; do_encode(undefined, _) -> <>; do_encode({simple, I}, _) when is_integer(I), I =< 20 -> <>; do_encode({simple, I}, _) when is_integer(I), I >= 32, I =< 255 -> <>; %% Integers do_encode(I, Opts) when is_integer(I), I > ?MAX_8BYTE -> do_encode({tag, bignum, I}, Opts); do_encode(I, _) when is_integer(I), I > ?MAX_4BYTE -> <>; do_encode(I, _) when is_integer(I), I > ?MAX_2BYTE -> <>; do_encode(I, _) when is_integer(I), I > ?MAX_1BYTE -> <>; do_encode(I, _) when is_integer(I), I > 23 -> <>; do_encode(I, _) when is_integer(I), I >= 0 -> <>; do_encode(I, _) when is_integer(I), I > -25 -> <>; do_encode(I, _) when is_integer(I), I > -?MAX_1BYTE -> <>; do_encode(I, _) when is_integer(I), I > -?MAX_2BYTE -> <>; do_encode(I, _) when is_integer(I), I > -?MAX_4BYTE -> <>; do_encode(I, _) when is_integer(I), I >= -?MAX_8BYTE -> <>; do_encode(I, Opts) when is_integer(I) -> do_encode({tag, bignum, I}, Opts); %% Floats do_encode(inf, _) -> <>; do_encode(neg_inf, _) -> <>; do_encode(nan, _) -> <>; do_encode({float16, F}, _) when is_float(F) -> <>; do_encode({float16, inf}, _) -> <>; do_encode({float16, neg_inf}, _) -> <>; do_encode({float16, nan}, _) -> <>; do_encode({float32, F}, _) when is_float(F) -> <>; do_encode({float32, inf}, _) -> <>; do_encode({float32, neg_inf}, _) -> <>; do_encode({float32, nan}, _) -> <>; do_encode({float64, F}, _) when is_float(F) -> <>; do_encode({float64, inf}, _) -> <>; do_encode({float64, neg_inf}, _) -> <>; do_encode({float64, nan}, _) -> <>; do_encode(F, _) when is_float(F) -> <>; %% Byte String do_encode({string, S}, _) when is_binary(S) -> case byte_size(S) of Size when Size < 24 -> <>; Size when Size =< ?MAX_1BYTE -> <>; Size when Size =< ?MAX_2BYTE -> <>; Size when Size =< ?MAX_4BYTE -> <>; Size when Size =< ?MAX_8BYTE -> <> end; %% Byte Strings indefinite length do_encode({strings, Strings}, Opts) -> [<>, [do_encode(String, Opts) || String <- Strings], <>]; %% Text String do_encode(T, _) when is_binary(T) -> case byte_size(T) of Size when Size < 24 -> <>; Size when Size =< ?MAX_1BYTE -> <>; Size when Size =< ?MAX_2BYTE -> <>; Size when Size =< ?MAX_4BYTE -> <>; Size when Size =< ?MAX_8BYTE -> <> end; %% Text Strings indefinite length do_encode({texts, Texts}, Opts) -> [<>, [do_encode(Text, Opts) || Text <- Texts], <>]; %% Array do_encode(Array, Opts) when is_list(Array) -> Head = case length(Array) of Size when Size < 24 -> <>; Size when Size =< ?MAX_1BYTE -> <>; Size when Size =< ?MAX_2BYTE -> <>; Size when Size =< ?MAX_4BYTE -> <>; Size when Size =< ?MAX_8BYTE -> <> end, [Head | [do_encode(E, Opts) || E <- Array]]; %% Array indefinite length do_encode({arrays, Arrays}, Opts) -> [<>, [do_encode(Array, Opts) || Array <- Arrays], <>]; %% Map do_encode(Map, Opts) when is_map(Map) -> Head = case maps:size(Map) of Size when Size < 24 -> <>; Size when Size =< ?MAX_1BYTE -> <>; Size when Size =< ?MAX_2BYTE -> <>; Size when Size =< ?MAX_4BYTE -> <>; Size when Size =< ?MAX_8BYTE -> <> end, [Head | maps:fold(fun(K, V, A) -> [[do_encode(K, Opts), do_encode(V, Opts)] | A] end, [], Map)]; %% Map indefinite length do_encode({maps, Maps}, Opts) -> [<>, [do_encode(Map, Opts) || Map <- Maps], <>]; %% Tags do_encode(Tag = {tag, _, _}, Opts) -> encode_tag(Tag, Opts). %% Tags Named encode_tag({tag, bignum, BN}, Opts) when BN >= 0 -> encode_tag({tag,2,BN},Opts); encode_tag({tag, bignum, BN}, Opts) -> encode_tag({tag, 3, BN}, Opts); encode_tag({tag, Name, V}, Opts) when is_atom(Name) -> encode_tag({tag, tag_no(Name), V}, Opts); %% Tags encode_tag({tag, 0, TS}, Opts) when is_binary(TS) -> [<>, do_encode(TS, Opts)]; encode_tag({tag, 0, TS}, Opts) when is_integer(TS) -> [<>, do_encode(jhn_timestamp:encode(TS, [binary]), Opts)]; encode_tag({tag, 1, null}, Opts) -> [<>, do_encode(null, Opts)]; encode_tag({tag, 1, undefined}, Opts) -> [<>, do_encode(undefined, Opts)]; encode_tag({tag, 1, Posix}, Opts) when is_integer(Posix) -> [<>, do_encode(Posix, Opts)]; encode_tag({tag, 1, Posix}, Opts) when is_float(Posix) -> [<>, do_encode(Posix, Opts)]; encode_tag({tag, 1, TS}, Opts) when is_binary(TS) -> Posix = jhn_timestamp:encode(jhn_timestamp:decode(TS), [posix]), [<>, do_encode(Posix, Opts)]; encode_tag({tag, 2, Bignum}, Opts) -> [<>, do_encode({string, encode_bignum(Bignum)}, Opts)]; encode_tag({tag, 3, Bignum}, Opts) -> [<>, do_encode({string, encode_bignum(-1 -Bignum)}, Opts)]; encode_tag({tag, 4, {Mantissa, ScalingFactor}}, Opts) -> [<>, do_encode([Mantissa, ScalingFactor], Opts)]; encode_tag({tag, 5, {Mantissa, ScalingFactor}}, Opts) -> [<>, do_encode([Mantissa, ScalingFactor], Opts)]; encode_tag({tag, 24, CBOR}, Opts) -> [<>, do_encode({string, encode(CBOR, [binary])}, Opts)]; encode_tag({tag, 32, URI = #uri{}}, Opts) -> [<>, do_encode(jhn_uri:encode(URI, [binary]), Opts)]; encode_tag({tag, 32, URI}, Opts) when is_binary(URI) -> [<>, do_encode(URI, Opts)]; encode_tag({tag, 33, Text}, Opts) when is_binary(Text) -> [<>, do_encode(base64:encode(Text, #{mode => urlsafe}), Opts)]; encode_tag({tag, 34, Text}, Opts) when is_binary(Text) -> [<>, do_encode(base64:encode(Text), Opts)]; encode_tag({tag, 48, Binary = <<_:17/binary>>}, Opts) -> <> = Binary, String = <>, [<>, do_encode({string, String}, Opts)]; encode_tag({tag, 48, Binary = <<_:23/binary>>}, Opts) -> <> = Binary, String = <>, [<>, do_encode({string, String}, Opts)]; encode_tag({tag, 55799, CBOR}, Opts) -> [<>, do_encode(CBOR, Opts)]; %% Tag general case encode_tag({tag, I, Val}, Opts) when I > ?MAX_4BYTE -> [<>, encode_content(I, Val, Opts)]; encode_tag({tag, I, Val}, Opts) when I > ?MAX_2BYTE -> [<>, encode_content(I, Val, Opts)]; encode_tag({tag, I, Val}, Opts) when I > ?MAX_1BYTE -> [<>, encode_content(I, Val, Opts)]; encode_tag({tag, I, Val}, Opts) when I > 23 -> [<>, encode_content(I, Val, Opts)]; encode_tag({tag, I, Val}, Opts) when I >= 0 -> [<>, encode_content(I, Val, Opts)]. encode_content(N, Val, Opts = #opts{tag_callbacks = TCBMs}) -> case jhn_plist:find(N, TCBMs) of undefined -> do_encode(Val, Opts); CBM -> CBM:cbor_encode_tag(N, Val, Opts) end. encode_bignum(0) -> <<0>>; encode_bignum(N) -> <>. bytes(0, N) -> N; bytes(X, N) -> bytes(X bsr 8, N + 1). tag_no(timestamp) -> 0; tag_no(posix) -> 1; tag_no(decimal_fraction) -> 4; tag_no(bigfloat) -> 5; tag_no(embedded) -> 24; tag_no(uri) -> 32; tag_no(base64url) -> 33; tag_no(base64) -> 34; tag_no(mac) -> 48; tag_no(cbor) -> 55799. %% =================================================================== %% Decoding %% =================================================================== %% Positive number do_decode(<>, _) when N < 24 -> {N, T}; do_decode(<>, _) -> {I, T}; do_decode(<>, _) -> {I, T}; do_decode(<>, _) -> {I, T}; do_decode(<>, _) -> {I, T}; %% Negative number do_decode(<>, _) when I < 24 -> {-1 - I, T}; do_decode(<>, _) -> {-1 - I, T}; do_decode(<>, _) -> {-1 - I, T}; do_decode(<>, _) -> {-1 - I, T}; do_decode(<>, _) -> {-1 - I, T}; %% byte string do_decode(<>, _) when I < 24 -> {{string, S},T}; do_decode(<>, _) -> {{string, S}, T}; do_decode(<>, _) -> {{string, S}, T}; do_decode(<>, _) -> {{string, S}, T}; do_decode(<>, _) -> {{string, S}, T}; do_decode(<>, Opts) -> decode_string(T, Opts, []); %% text string do_decode(<>, _) when I < 24 -> {S, T}; do_decode(<>, _) -> {S, T}; do_decode(<>, _) -> {S, T}; do_decode(<>, _) -> {S, T}; do_decode(<>, _) -> {S, T}; do_decode(<>, Opts) -> decode_text(T, Opts, []); %% array do_decode(<>, Opts) when I < 24 -> decode_array(I, T, Opts, []); do_decode(<>, Opts) -> decode_array(I, T, Opts, []); do_decode(<>, Opts) -> decode_array(I, T, Opts, []); do_decode(<>, Opts) -> decode_array(I, T, Opts, []); do_decode(<>, Opts) -> decode_array(I, T, Opts, []); do_decode(<>, Opts) -> decode_array(T, Opts, []); %% map do_decode(<>, Opts) when I < 24 -> decode_map(I, T, Opts, #{}); do_decode(<>, Opts) -> decode_map(I, T, Opts, #{}); do_decode(<>, Opts) -> decode_map(I, T, Opts, #{}); do_decode(<>, Opts) -> decode_map(I, T, Opts, #{}); do_decode(<>, Opts) -> decode_map(I, T, Opts, #{}); do_decode(<>, Opts) -> decode_map(T, Opts, []); %% tagged item do_decode(<>,Opts) when I < 24 -> decode_tag(I,T,Opts); do_decode(<>, Opts) -> decode_tag(I, T, Opts); do_decode(<>, Opts) -> decode_tag(I, T, Opts); do_decode(<>, Opts) -> decode_tag(I, T, Opts); do_decode(<>, Opts) -> decode_tag(I, T, Opts); %% Simple Value do_decode(<>, _) when I < 20 -> {{simple, I}, T}; do_decode(<>, _) -> {false, T}; do_decode(<>, _) -> {true, T}; do_decode(<>, _) -> {null, T}; do_decode(<>, _) -> {undefined, T}; do_decode(<>, _) when I >= 32 -> {{simple,I},T}; %% Float %% 16 bit do_decode(<>, _) -> {inf, T}; do_decode(<>, _) -> {neg_inf, T}; do_decode(<>, _) -> {nan, T}; do_decode(<>, _) -> {0.0, T}; do_decode(<>, _) -> {decode_f16(E, M), T}; do_decode(<>, _) -> {-decode_f16(E, M), T}; %% 32 bit do_decode(<>, _) -> {inf, T}; do_decode(<>, _) -> {neg_inf,T}; do_decode(<>, _) -> {nan, T}; do_decode(<>, _) -> {F, T}; %% 64 bit do_decode(<>, _) -> {inf, T}; do_decode(<>, _) -> {neg_inf, T}; do_decode(<>, _) -> {nan, T}; do_decode(<>, _) -> {F, T}. decode_string(<>, _, Acc) -> {{string, iolist_to_binary(lists:reverse(Acc))}, T}; decode_string(B = <>, Opts, Acc) when N < 28 -> {{string, H}, T} = do_decode(B, Opts), decode_string(T, Opts, [H | Acc]). decode_text(<>, _, Acc) -> {{text, iolist_to_binary(lists:reverse(Acc))}, T}; decode_text(B = <>, Opts, Acc) when N < 28 -> {H, T} = do_decode(B, Opts), decode_text(T, Opts, [H | Acc]). decode_array(0, T, _, Acc) -> {lists:reverse(Acc), T}; decode_array(N, T, Opts, Acc) -> {H, T1} = do_decode(T, Opts), decode_array(N - 1, T1, Opts, [H | Acc]). decode_array(<>, _, Acc) -> {{arrays, lists:reverse(Acc)}, T}; decode_array(T, Opts, Acc) -> {H, T1} = do_decode(T, Opts), decode_array(T1, Opts, [H | Acc]). decode_map(0, T, _, Acc) -> {Acc, T}; decode_map(N, T, Opts, Acc) -> {Key, T1} = do_decode(T, Opts), {Val, T2} = do_decode(T1, Opts), decode_map(N - 1, T2, Opts, Acc#{Key => Val}). decode_map(<>, _, Acc) -> {{maps, lists:reverse(Acc)}, T}; decode_map(T, Opts, Acc) -> {Map, T1} = do_decode(T, Opts), decode_map(T1, Opts, [Map | Acc]). %% Timestamp decode_tag(0, T, Opts) -> {Text, T1} = do_decode(T, Opts), case jhn_timestamp:decode(Text) of _ -> {{tag, timestamp, Text}, T1} end; %% Posix decode_tag(1, T, Opts) -> case do_decode(T, Opts) of {null, T1} -> {{tag, posix, null}, T1}; {undefined, T1} -> {{tag, posix, undefined}, T1}; {I, T1} when is_integer(I) -> {{tag, posix, jhn_timestamp:encode(I, [binary])}, T1}; {F, T1} when is_float(F) -> {{tag, posix, decode_float_ts(F)}, T1} end; %% Pos bignum decode_tag(2, T, Opts) -> {{string, String}, T1} = do_decode(T, Opts), <> = String, {{tag, bignum, N}, T1}; %% Neg Bignum decode_tag(3, T, Opts) -> {{string, String}, T1} = do_decode(T, Opts), <> = String, {{tag, bignum, - 1 - N}, T1}; %% Decimal fraction decode_tag(4, T, Opts) -> {[Mantissa, ScalingFactor], T1} = do_decode(T, Opts), {{tag, decimal_fraction, {Mantissa, ScalingFactor}}, T1}; %% Bigfloat decode_tag(5, T, Opts) -> {[Mantissa, ScalingFactor], T1} = do_decode(T, Opts), {{tag, bigfloat, {Mantissa, ScalingFactor}}, T1}; %% Embedded CBOR decode_tag(24, T, Opts) -> {{string, String}, T1} = do_decode(T, Opts), {{tag, embedded, String}, T1}; %% URI decode_tag(32, T, Opts) -> {URI, T1} = do_decode(T, Opts), {{tag, uri, jhn_uri:decode(URI)}, T1}; %% base64url decode_tag(33, T, Opts) -> {Base, T1} = do_decode(T, Opts), {{tag, base64url, base64:decode(Base, #{mode => urlsafe})}, T1}; %% base64 decode_tag(34, T, Opts) -> {Base, T1} = do_decode(T, Opts), {{tag, base64, base64:decode(Base)}, T1}; %% MAC decode_tag(48, T, Opts) -> {{string, MAC}, T1} = do_decode(T, Opts), {{tag, mac, jhn_bstring:join([<> || <> <= MAC], <<$->>)}, T1}; %% Marked as CBOR decode_tag(55799, T, Opts) -> {CBOR, T1} = do_decode(T, Opts), {{tag, cbor, CBOR}, T1}; %% decode_tag(N, T, Opts = #opts{tag_callbacks = TCBMs}) -> {Val, T1} = do_decode(T, Opts), case jhn_plist:find(N, TCBMs) of undefined -> {{tag, N, Val}, T1}; CBM -> {{tag, N, CBM:cbor_decode_tag(N, Val, Opts)}, T1} end. decode_f16(0, Mant) -> Mant / (1 bsl 24); decode_f16(Exp, Mant) when Exp < 25 -> (1024 + Mant)/(1 bsl (25 - Exp)); decode_f16(25, Mant) -> 1024 + Mant; decode_f16(Exp, Mant) -> (1024 + Mant) * (1 bsl (Exp - 25)). decode_float_ts(F) -> I = trunc(F), TS = jhn_timestamp:encode(I, [binary]), case string:tokens(float_to_list(F, [short]), [$.]) of [_, "0"] -> TS; [_, Frac] -> TSM = jhn_timestamp:decode(TS), TSM1 = TSM#{fraction => list_to_integer(Frac)}, Precision = case length(Frac) of L when L < 4 -> milli; L when L < 7 -> micro; _ -> nano end, jhn_timestamp:encode(TSM1, [binary, Precision]) end. %% =================================================================== %% Common %% =================================================================== parse_opts(Opts) -> lists:foldl(fun parse_opt/2, #opts{}, Opts). parse_opt(iolist, Opts) -> Opts#opts{return_type = iolist}; parse_opt(binary, Opts) -> Opts#opts{return_type = binary}; parse_opt(continue, Opts) -> Opts#opts{continue = true}; parse_opt({tag_callbacks, CBs}, Opts) -> Opts#opts{tag_callbacks = CBs}.