macula_record_cbor (macula v11.4.0)

View Source

Deterministic CBOR encoder/decoder.

Implements the subset of RFC 8949 needed by Macula records: unsigned ints, negative ints, floats, byte strings, text strings, arrays, maps, and null.

Encoding follows RFC 8949 §4.2.1 (deterministic):

  • Smallest length encoding.
  • Definite lengths only (no indefinite items).
  • Map keys sorted by bytewise lexicographic order of their deterministic encoding.

Internal value representation:

  • non_neg_integer() — uint (major 0)
  • neg_integer() — negative integer (major 1)
  • binary() — byte string (major 2)
  • {text, binary()} — UTF-8 text string (major 3)
  • atom() — UTF-8 text string (major 3) via atom_to_binary/1. Accepted for round-trip robustness: the frame decoder atomizes binary keys via binary_to_existing_atom/1, so a record decoded from the wire and re-encoded for sig verify carries atom keys inside the payload sub-map. Encoding atoms as text reproduces the original wire bytes byte-for-byte (atom names round-trip exactly through utf8). null has a dedicated clause and is NOT routed here.
  • [value()] — array (major 4)
  • #{value() => value()} — map (major 5)
  • null — simple null (major 7, value 22)
  • float() — IEEE 754 binary64 (major 7, value 27)

Summary

Functions

Decode one item under the post-quantum decoding rule of DESIGN_PQ_SIGNED_FRAMES_AND_RECORDS.md, without raising. It refuses bytes after the top-level item, a map key that is not text or an integer, a duplicate key (equal after decoding, so a text key in two length widths is one key), text that is not valid UTF-8, nesting deeper than 64 levels, a negative integer below -2^63, and malformed input such as an indefinite length, a tag or a simple value other than null. It also refuses more CBOR items than the element budget of macula_cbor_nif:element_budget/0, 131,072, as too_many_elements: every item counts once, map keys and array elements included. decode/1 keeps its behaviour: a duplicate key there still keeps the last value.

decode_strict/1 within Budget CBOR items, what a caller has left of an element budget, returning what is left after the item as {ok, Value, Left}.

Can this integer be rendered as major 0 / major 1?

Types

strict_refusal/0

-type strict_refusal() ::
          trailing_bytes | bad_key | duplicate_key | invalid_text | too_deep | integer_out_of_range |
          too_many_elements | malformed.

value/0

-type value() ::
          integer() |
          float() |
          binary() |
          {text, binary()} |
          [value()] |
          #{value() => value()} |
          null |
          atom().

Functions

decode(Bin)

-spec decode(binary()) -> value().

decode_strict(Bin)

-spec decode_strict(binary()) -> {ok, value()} | {error, strict_refusal()}.

Decode one item under the post-quantum decoding rule of DESIGN_PQ_SIGNED_FRAMES_AND_RECORDS.md, without raising. It refuses bytes after the top-level item, a map key that is not text or an integer, a duplicate key (equal after decoding, so a text key in two length widths is one key), text that is not valid UTF-8, nesting deeper than 64 levels, a negative integer below -2^63, and malformed input such as an indefinite length, a tag or a simple value other than null. It also refuses more CBOR items than the element budget of macula_cbor_nif:element_budget/0, 131,072, as too_many_elements: every item counts once, map keys and array elements included. decode/1 keeps its behaviour: a duplicate key there still keeps the last value.

decode_strict(Bin, Budget)

-spec decode_strict(binary(), non_neg_integer()) ->
                       {ok, value(), non_neg_integer()} | {error, strict_refusal()}.

decode_strict/1 within Budget CBOR items, what a caller has left of an element budget, returning what is left after the item as {ok, Value, Left}.

encode(N)

-spec encode(value()) -> binary().

is_encodable_int(N)

-spec is_encodable_int(integer()) -> boolean().

Can this integer be rendered as major 0 / major 1?

Exported so callers that must decide admissibility BEFORE encoding (see macula_frame:check_payload/1) can ask rather than restate the bound. A bignum past 64 bits matches no encode/1 clause and would otherwise crash whichever process happens to be encoding.