Skip to main content

Module cbor

Module cbor 

Source
Expand description

Deterministic CBOR encode/decode, byte-for-byte compatible with macula’s own wire codec.

This is NOT generic CBOR (no ciborium involved on either side) — it is a direct Rust transcription of the hand-rolled canonical encoder macula actually ships in native/macula_cbor_nif/src/deterministic.rs (macula-io/macula), which macula_frame.erl’s wire codec calls as pack_deterministic/1 / unpack_deterministic/1. Every signed frame, record and binding is signed over these exact bytes, so a divergence here silently breaks signature verification against real stations — this module’s tests include fixtures captured directly from the real NIF, not just hand-derived expectations.

Encoding rules (all verified against the reference, see tests below):

  • Integers: minimal-length encoding (inline for 0..=23, else the smallest of 1/2/4/8 extra bytes that fits). Non-negative → major 0. Negative → major 1, encoded value is -1 - n. Range: -(2^64)..=u64::MAX — anything outside that is a hard encode error, not silent truncation.
  • Byte strings → major 2, raw bytes.
  • Text → major 3. Used both for real text payloads and for macula’s fixed field-name/enum-value vocabulary (what the Erlang side encodes as atoms) — there is no separate “atom” wire type.
  • Lists → major 4.
  • Maps → major 5, with keys sorted by the bytewise order of their own already-encoded bytes — encode each key independently, then sort the resulting (key_bytes, value_bytes) pairs by key_bytes using plain Ord. This is the single rule most likely to be gotten wrong: sorting by the original value instead of its encoded bytes silently diverges from station output for keys of different CBOR major types or different lengths.
  • Value::Null → major 7, additional info 22 (0xF6).
  • Floats → always binary64 (major 7, AI 27, 0xFB prefix), regardless of whether the value would round-trip in fewer bits. This is a deliberate divergence from RFC 8949’s own canonical-form recommendation (which prefers the shortest float width that round-trips) — macula’s own comment says it’s done so the byte derivation is independent of platform float encoding. A generic “canonical CBOR” crate that follows the RFC’s shortest-float rule would silently produce non-matching, non-verifying bytes here.

Decode applies macula 12’s decoding rule, the rule every stack applies to what a peer sends (tests/cbor_decoding_rule.rs holds it to the shared vectors and to the reason macula’s reference decoder gives for each refusal): lengths in any width, map keys in any order but only text or integers and never twice, integers within -2^63..=2^63-1, null and finite half, single and double floats, at most MAX_NESTING_DEPTH levels and MAX_ELEMENTS items. Tags, booleans and every other simple value are refused. Every read is bounds-checked; nothing in this module panics on malformed or truncated input, since decode exists specifically to parse untrusted, network-received bytes.

Structs§

IntOutOfRange

Enums§

DecodeError
Why decode refused an input: one variant for each reason macula’s reference decoder (macula_record_cbor:decode_strict/1) gives, so an input is refused for the same reason in every stack.
Value
A deterministic-CBOR value, restricted to exactly the shapes macula’s wire format supports. There is no generic “any CBOR” here on purpose.

Constants§

MAX_ELEMENTS
How many CBOR items one decode may read: every item counts once, the top-level value, array elements, map keys and map values included. It is macula’s element budget, so an input macula refuses for the items it holds is refused here too.
MAX_NESTING_DEPTH
How many arrays and maps may nest inside each other, the outermost counted: 64 levels decode, and a 65th is refused, as in macula’s decoding rule.

Functions§

decode
Decode bytes as exactly one value under macula’s post-quantum decoding rule, the rule every stack applies to what a peer sends. Lengths are accepted in any width, map keys in any order, and half, single and double floats; everything else the rule refuses is refused with the reason macula’s reference decoder gives. Every path returns an error rather than panicking, since the input is untrusted.
encode
Encode value as deterministic CBOR. See the module doc for the exact rules; every one of them is verified against the real reference in this module’s tests.