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 frame’s
Ed25519 signature is computed 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 (rebar3 shell against macula-io/macula at v10.10.0), 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 is deliberately narrow to match the reference: major type 6
(tags) is rejected outright, and major 7 only supports null and the
three float widths (binary16/32/64, all promoted to f64) — no
booleans, no “undefined” simple value. 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 - 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_
NESTING_ DEPTH - Recursive-descent nesting limit — see
DecodeError::NestingTooDeepfor why this exists. No real macula wire value nests remotely this deep; this only ever rejects an adversarial input.
Functions§
- decode
- Decode a single deterministic-CBOR value from
bytes. The whole buffer must be consumed by exactly one top-level value — trailing bytes are an error, matching the reference decoder’s own contract. - 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.