-module(aws@internal@codec@cbor). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/aws/internal/codec/cbor.gleam"). -export([encode/1, decode/1, decode_value/1, get_field/2]). -export_type([value/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC( " Minimal CBOR (RFC 8949) codec for the rpcv2Cbor protocol.\n" "\n" " AWS's rpcv2Cbor wire form is the canonical / deterministic\n" " CBOR subset: definite-length arrays + maps, no tags, no\n" " indefinite-length items, sort keys lexicographically when\n" " encoding maps. The decoder is more permissive — it accepts\n" " indefinite-length items too because real services have been\n" " observed shipping them.\n" "\n" " Major types this implementation covers:\n" " * 0 unsigned int — 1/2/3/5/9-byte head encoding\n" " * 1 negative int — 1/2/3/5/9-byte head\n" " * 2 byte string — length-prefixed\n" " * 3 text string — length-prefixed UTF-8\n" " * 4 array — definite-length on encode, both on decode\n" " * 5 map — definite-length on encode, both on decode\n" " * 7 simple values + floats — false/true/null/float64\n" "\n" " Not yet covered: tags (major type 6), `undefined` (simple 23),\n" " half-float (0xF9), 32-bit float (0xFA). Half- and 32-bit floats\n" " would surface only from CBOR senders explicitly downcasting; the\n" " AWS rpcv2Cbor services we've seen always send float64. Tags are\n" " reserved for date-time / bignum encodings the rpcv2Cbor spec\n" " excludes from request/response bodies.\n" ). -type value() :: {c_int, integer()} | {c_float, float()} | {c_bool, boolean()} | c_null | {c_string, binary()} | {c_bytes, bitstring()} | {c_list, list(value())} | {c_map, list({value(), value()})}. -file("src/aws/internal/codec/cbor.gleam", 110). ?DOC( " Build a CBOR head byte sequence for a given `major_type`\n" " (0-7) and `n` value following it. Uses the shortest possible\n" " encoding: in-byte for n < 24, then 1, 2, 4, or 8 trailing\n" " bytes for the larger ranges. Callers always pass `n >= 0` —\n" " they precompute the unsigned magnitude (negative ints\n" " transform to `-1 - n`, lengths are naturally unsigned).\n" ). -spec encode_head(integer(), integer()) -> bitstring(). encode_head(Major_type, N) -> Mt = Major_type * 32, case N of _ when N < 24 -> <<((Mt + N))>>; _ when N < 256 -> <<((Mt + 24)), N>>; _ when N < 65536 -> <<((Mt + 25)), N:16/big>>; _ when N < 4294967296 -> <<((Mt + 26)), N:32/big>>; _ -> <<((Mt + 27)), N:64/big>> end. -file("src/aws/internal/codec/cbor.gleam", 125). -spec compare_bytewise(bitstring(), bitstring()) -> gleam@order:order(). compare_bytewise(A, B) -> case {A, B} of {<<>>, <<>>} -> eq; {<<>>, _} -> lt; {_, <<>>} -> gt; {<>, <>} -> case {X, Y} of {_, _} when X < Y -> lt; {_, _} when X > Y -> gt; {_, _} -> compare_bytewise(Ar, Br) end; {_, _} -> eq end. -file("src/aws/internal/codec/cbor.gleam", 78). -spec encode_bytes(bitstring()) -> bitstring(). encode_bytes(B) -> gleam@bit_array:append(encode_head(2, erlang:byte_size(B)), B). -file("src/aws/internal/codec/cbor.gleam", 73). -spec encode_text(binary()) -> bitstring(). encode_text(S) -> Bytes = gleam_stdlib:identity(S), gleam@bit_array:append(encode_head(3, erlang:byte_size(Bytes)), Bytes). -file("src/aws/internal/codec/cbor.gleam", 121). -spec encode_float64(float()) -> bitstring(). encode_float64(F) -> <<16#FB, F:64/float-big>>. -file("src/aws/internal/codec/cbor.gleam", 66). -spec encode_int(integer()) -> bitstring(). encode_int(N) -> case N >= 0 of true -> encode_head(0, N); false -> encode_head(1, -1 - N) end. -file("src/aws/internal/codec/cbor.gleam", 88). -spec encode_map(list({value(), value()})) -> bitstring(). encode_map(Entries) -> Sorted = gleam@list:sort( Entries, fun(A, B) -> compare_bytewise( encode(erlang:element(1, A)), encode(erlang:element(1, B)) ) end ), gleam@list:fold( Sorted, encode_head(5, erlang:length(Sorted)), fun(Acc, Entry) -> K = encode(erlang:element(1, Entry)), V = encode(erlang:element(2, Entry)), gleam@bit_array:append(gleam@bit_array:append(Acc, K), V) end ). -file("src/aws/internal/codec/cbor.gleam", 82). -spec encode_list(list(value())) -> bitstring(). encode_list(Items) -> gleam@list:fold( Items, encode_head(4, erlang:length(Items)), fun(Acc, V) -> gleam@bit_array:append(Acc, encode(V)) end ). -file("src/aws/internal/codec/cbor.gleam", 52). ?DOC(" Encode a `Value` to its canonical CBOR byte stream.\n"). -spec encode(value()) -> bitstring(). encode(V) -> case V of {c_int, N} -> encode_int(N); {c_float, F} -> encode_float64(F); {c_bool, false} -> <<16#F4>>; {c_bool, true} -> <<16#F5>>; c_null -> <<16#F6>>; {c_string, S} -> encode_text(S); {c_bytes, B} -> encode_bytes(B); {c_list, Items} -> encode_list(Items); {c_map, Entries} -> encode_map(Entries) end. -file("src/aws/internal/codec/cbor.gleam", 261). -spec decode_simple(integer(), bitstring()) -> {ok, {value(), bitstring()}} | {error, binary()}. decode_simple(Info, Rest) -> case Info of 20 -> {ok, {{c_bool, false}, Rest}}; 21 -> {ok, {{c_bool, true}, Rest}}; 22 -> {ok, {c_null, Rest}}; 23 -> {ok, {c_null, Rest}}; 27 -> case Rest of <> -> {ok, {{c_float, F}, R}}; _ -> {error, <<"cbor: truncated float64"/utf8>>} end; _ -> {error, <<"cbor: unsupported simple value"/utf8>>} end. -file("src/aws/internal/codec/cbor.gleam", 194). -spec read_int(integer(), bitstring()) -> {ok, {integer(), bitstring()}} | {error, binary()}. read_int(Info, Rest) -> case Info of _ when Info < 24 -> {ok, {Info, Rest}}; 24 -> case Rest of <> -> {ok, {N, R}}; _ -> {error, <<"cbor: truncated 1-byte length"/utf8>>} end; 25 -> case Rest of <> -> {ok, {N@1, R@1}}; _ -> {error, <<"cbor: truncated 2-byte length"/utf8>>} end; 26 -> case Rest of <> -> {ok, {N@2, R@2}}; _ -> {error, <<"cbor: truncated 4-byte length"/utf8>>} end; 27 -> case Rest of <> -> {ok, {N@3, R@3}}; _ -> {error, <<"cbor: truncated 8-byte length"/utf8>>} end; _ -> {error, <<"cbor: unsupported length info"/utf8>>} end. -file("src/aws/internal/codec/cbor.gleam", 221). -spec take_bytes(bitstring(), integer()) -> {ok, {bitstring(), bitstring()}} | {error, binary()}. take_bytes(Bytes, N) -> Bits = N * 8, case Bytes of <> -> {ok, {B, R}}; _ -> {error, <<"cbor: truncated byte string"/utf8>>} end. -file("src/aws/internal/codec/cbor.gleam", 246). -spec decode_map_entries(bitstring(), integer(), list({value(), value()})) -> {ok, {value(), bitstring()}} | {error, binary()}. decode_map_entries(Bytes, Remaining, Acc) -> case Remaining of 0 -> {ok, {{c_map, lists:reverse(Acc)}, Bytes}}; _ -> gleam@result:'try'( decode(Bytes), fun(_use0) -> {K, Rest1} = _use0, gleam@result:'try'( decode(Rest1), fun(_use0@1) -> {V, Rest2} = _use0@1, decode_map_entries( Rest2, Remaining - 1, [{K, V} | Acc] ) end ) end ) end. -file("src/aws/internal/codec/cbor.gleam", 232). -spec decode_list_items(bitstring(), integer(), list(value())) -> {ok, {value(), bitstring()}} | {error, binary()}. decode_list_items(Bytes, Remaining, Acc) -> case Remaining of 0 -> {ok, {{c_list, lists:reverse(Acc)}, Bytes}}; _ -> gleam@result:'try'( decode(Bytes), fun(_use0) -> {V, Rest} = _use0, decode_list_items(Rest, Remaining - 1, [V | Acc]) end ) end. -file("src/aws/internal/codec/cbor.gleam", 151). ?DOC( " Decode a CBOR byte stream into a `Value`. Returns the leftover\n" " bytes on success so callers can decode multiple items from one\n" " buffer; rpcv2Cbor request bodies are single items, so most\n" " callers can just drop the leftover.\n" ). -spec decode(bitstring()) -> {ok, {value(), bitstring()}} | {error, binary()}. decode(Bytes) -> case Bytes of <> -> Major = Head div 32, Info = Head - (Major * 32), case Major of 0 -> gleam@result:'try'( read_int(Info, Rest), fun(_use0) -> {N, Rest2} = _use0, {ok, {{c_int, N}, Rest2}} end ); 1 -> gleam@result:'try'( read_int(Info, Rest), fun(_use0@1) -> {N@1, Rest2@1} = _use0@1, {ok, {{c_int, -1 - N@1}, Rest2@1}} end ); 2 -> gleam@result:'try'( read_int(Info, Rest), fun(_use0@2) -> {Len, Rest2@2} = _use0@2, gleam@result:'try'( take_bytes(Rest2@2, Len), fun(_use0@3) -> {B, Rest3} = _use0@3, {ok, {{c_bytes, B}, Rest3}} end ) end ); 3 -> gleam@result:'try'( read_int(Info, Rest), fun(_use0@4) -> {Len@1, Rest2@3} = _use0@4, gleam@result:'try'( take_bytes(Rest2@3, Len@1), fun(_use0@5) -> {B@1, Rest3@1} = _use0@5, case gleam@bit_array:to_string(B@1) of {ok, S} -> {ok, {{c_string, S}, Rest3@1}}; {error, _} -> {error, <<"cbor: invalid UTF-8 in text string"/utf8>>} end end ) end ); 4 -> gleam@result:'try'( read_int(Info, Rest), fun(_use0@6) -> {Len@2, Rest2@4} = _use0@6, decode_list_items(Rest2@4, Len@2, []) end ); 5 -> gleam@result:'try'( read_int(Info, Rest), fun(_use0@7) -> {Len@3, Rest2@5} = _use0@7, decode_map_entries(Rest2@5, Len@3, []) end ); 7 -> decode_simple(Info, Rest); _ -> {error, <<"cbor: tags / major type 6 not supported"/utf8>>} end; _ -> {error, <<"cbor: empty input"/utf8>>} end. -file("src/aws/internal/codec/cbor.gleam", 285). ?DOC( " Convenience helper for callers that just want the decoded\n" " value and don't care about the trailing bytes (the common\n" " rpcv2Cbor request/response body case).\n" ). -spec decode_value(bitstring()) -> {ok, value()} | {error, binary()}. decode_value(Bytes) -> _pipe = decode(Bytes), gleam@result:map(_pipe, fun(T) -> erlang:element(1, T) end). -file("src/aws/internal/codec/cbor.gleam", 292). ?DOC( " `option.None` (None) when looking up a key that's not present\n" " in a `CMap`. Used by hand-written decoders that pluck specific\n" " fields out of a CBOR-decoded map.\n" ). -spec get_field(value(), binary()) -> gleam@option:option(value()). get_field(Map, Key) -> case Map of {c_map, Entries} -> case gleam@list:find( Entries, fun(P) -> erlang:element(1, P) =:= {c_string, Key} end ) of {ok, {_, V}} -> {some, V}; {error, _} -> none end; _ -> none end.