cbor2
A serde implementation of RFC 8949 — the Concise Binary Object Representation (CBOR) — for Rust.
CBOR adopts and modestly builds on the data model used by JSON, except the encoding is in binary form. Its primary goals include a balance of implementation size, message size and extensibility.
Dual-licensed under MIT or the UNLICENSE.
Status
This project descends from the cbor crate created by
Andrew Gallant in 2015, which was built on
the pre-serde rustc-serialize framework and went unmaintained for many
years. Version 0.5 is a from-scratch rewrite on top of
serde, maintained by LDC Labs
and published as cbor2 — the cbor name on crates.io stays with the
legacy 0.4 release. None of the 0.4 API survives.
The rewrite follows the design of (and is wire-compatible with)
ciborium — many thanks to its authors.
If you need no_std support today, use ciborium; this crate currently
requires std.
Features
- Full serde integration —
#[derive(Serialize, Deserialize)]types encode and decode directly. - RFC 8949 preferred serialization — integers and floats are always encoded in their smallest lossless form, including half-precision floats.
- A dynamic [
Value] type — the CBOR analogue ofserde_json::Value, with acbor!macro for building values in JSON-like syntax. - Tag support — capture and emit semantic tags (RFC 8949 §3.4) through
the wrapper types in the
tagmodule;u128/i128map to bignum tags automatically. - Deterministic encoding —
to_canonical_vec/to_canonical_writerandValue::canonicalizeimplement the core deterministic encoding requirements (RFC 8949 §4.2.1): bytewise lexicographic map key order, definite lengths, preferred serializations, normalized bignums and NaN. For protocols built on the older RFC 7049 §3.9 "Canonical CBOR" rule (kept as RFC 8949 §4.2.3, and used by ciborium's canonical module), the*_withvariants takeKeyOrder::LengthFirst. - Integer map keys (COSE) — with the
derivefeature, the#[cbor2::int_keys]attribute macro maps struct fields to integer keys (#[cbor(key = 1)]), as RFC 9052 requires, with no ambiguity against textual keys;aliasand the other serde field attributes work as usual. - Robust decoding — indefinite-length items, segmented strings, duplicate map keys, unknown tags and CBOR sequences (RFC 8742) are all handled; recursion is depth-limited and forged lengths cannot trigger huge allocations.
- Diagnostic notation —
diagnosticrenders raw CBOR as the human-readable text of RFC 8949 §8 (matching the Appendix A examples exactly, indefinite-length markers and all);ValueimplementsDisplaywith the same notation andDebugas its indented, multi-line form. - Allocation-free helpers —
validatechecks that an input is exactly one well-formed CBOR item (RFC 8949 §5.3.1, including text UTF-8) andserialized_sizecomputes the exact encoded size of any serializable value; neither allocates heap memory. - A low-level header codec — the
coremodule exposes the pull/pushHeaderinterface for applications that need precise wire control.
Usage
[]
= "0.5"
Type-based encoding and decoding
use ;
let photo = Photo ;
let bytes = to_vec.unwrap;
let back: Photo = from_slice.unwrap;
assert_eq!;
to_writer and from_reader work with any std::io::Write/Read, and
Deserializer::into_iter decodes a stream of concatenated items.
Dynamic values
use ;
let value = cbor!.unwrap;
let bytes = to_vec.unwrap;
let back: Value = from_slice.unwrap;
assert_eq!;
Tags
use RequireExact;
// Tag 0: standard date/time string.
let datetime = ;
let bytes = to_vec.unwrap;
assert_eq!;
Design decisions
This implementation deliberately matches ciborium's wire behavior, so the two crates interoperate byte for byte:
- Numbers always encode in their smallest lossless form, as deterministic encoding (RFC 8949 §4.2.1) requires. Integer width in Rust is treated as an in-memory detail, not a wire property.
- Enums encode as a bare string (unit variants) or a single-entry map
{variant: payload}(everything else). Valuemaps areVec<(Value, Value)>, preserving wire order and arbitrary keys.- Decoding follows the robustness principle: indefinite lengths, segmented strings, half-width floats and unknown tags are accepted even though encoding never produces them.
Command line tools
The workspace ships two small converters in cbor_conv:
| |
{
}
Roadmap
no_std+allocsupport- Benchmarks against other CBOR implementations
Testing
cargo test runs the unit tests, a single integration-test binary and the
doc tests — including the RFC 8949 Appendix A vectors and fault-injection
tests for I/O failures and malformed input. Line coverage of the library is
100% (measured with cargo llvm-cov); the only never-executed regions are
five error branches that are unreachable on 64-bit targets or guard
conditions that cannot occur.
Minimum supported Rust version
Rust 1.85.
License
Dual-licensed under MIT or the UNLICENSE, like the original crate.