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 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 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 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§

IntOutOfRange

Enums§

DecodeError
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::NestingTooDeep for 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 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.