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 bykey_bytesusing plainOrd. 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,
0xFBprefix), 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§
Enums§
- Decode
Error - Why
decoderefused 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
decodemay 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
bytesas 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
valueas 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.