cbor-core
A Rust implementation of CBOR::Core, the deterministic subset of CBOR (RFC 8949).
The central type is Value. It can be constructed, inspected,
modified in place, encoded to bytes, and decoded back. The API
follows CBOR's own shape, so tagged values, simple values, and
arbitrary map keys stay directly reachable.
Decoding from a byte slice is zero-copy: text and byte strings
in the result borrow from the input.
To map custom Rust types to CBOR instead, enable the serde feature
for Serialize/Deserialize support.
The API is not stable yet and may change in future releases.
Status
The implementation targets draft-rundgren-cbor-core-25 and passes
all test vectors from Appendix A of that specification, including
rejection of non-deterministic encodings.
Supported types: integers and big integers, IEEE 754 floats (half, single, double), byte strings, text strings, arrays, maps, tagged values, and simple values (null, booleans). Arrays and maps are heterogeneous.
Accessor methods look through tags, including custom tags wrapping big integers (tags 2/3).
Multi-item streams (RFC 8742) decode through SequenceDecoder for
byte slices and SequenceReader for io::Read sources.
Decoding binary CBOR from a byte slice is zero-copy: text and byte strings in the result borrow directly from the input and the returned value's lifetime is tied to the slice.
Encoding is deterministic: integers and floats use their shortest form, and map keys are sorted in canonical order. The decoder rejects input that deviates. NaN payloads, including signaling NaNs, survive round-trips bit-for-bit.
Diagnostic notation
Value implements both directions of CBOR::Core diagnostic notation
(Section 2.3.6 of the draft):
Debugprints diagnostic text.{:#?}indents nested arrays and maps.FromStrparses diagnostic text back into aValue.
Parsing is useful on its own, beyond the round trip. For tests, fixtures, or examples it is often the shortest way to write a literal value, nested structures included:
use Value;
let diag = r#"{
"iss": "https://issuer.example",
"sub": "user-42",
"iat": 1700000000,
"cnf": {
"kty": "OKP",
"crv": "Ed25519",
"x": h'd75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a'
},
"scope": ["read", "write"]
}"#;
let cert: Value = diag.parse.unwrap;
assert_eq!;
The grammar covers integers in any base (with _ separators),
arbitrary-precision integers, floats (decimal, scientific, NaN,
Infinity, or float'<hex>' for explicit bit patterns), text strings
with JSON-style escapes, byte strings (h'...', b64'...', '...', or
<<...>> for embedded CBOR), arrays, maps, tagged values, simple
values, and comments. Input may be non-canonical; the parser sorts map
keys and rejects duplicates, producing a canonical Value.
Security
The decoder rejects malicious input:
- Nesting depth for arrays, maps, and tags is limited to 200 levels.
- Declared lengths for arrays, maps, byte strings, and text strings are capped at 1 billion.
- Pre-allocated capacity is bounded to 100 MB per decode call.
- Declared lengths that exceed the available data produce an error.
These are the defaults; DecodeOptions overrides them per-decode.
Optional features
Optional integration with external crates. To enable an integration
add the relevant feature flag to Cargo.toml.
| Feature name | Enables |
|---|---|
serde |
Serialize/Deserialize for Value, Value::serialized, Value::deserialized |
chrono |
Conversions between chrono::DateTime and DateTime/EpochTime/Value |
time |
Conversions between time::UtcDateTime/time::OffsetDateTime and DateTime/EpochTime/Value |
jiff |
Conversions between jiff::Timestamp/jiff::Zoned and DateTime/EpochTime/Value |
half |
From/TryFrom conversions between Float/Value and half::f16 |
num-bigint |
From/TryFrom conversions between Value and num_bigint::BigInt/BigUint |
crypto-bigint |
From/TryFrom conversions between Value and crypto_bigint::Uint/Int/NonZero |
rug |
From/TryFrom conversions between Value and rug::Integer |
Usage
use ;
let value = map! ;
let bytes = value.encode;
let decoded = decode.unwrap;
assert_eq!;
// Diagnostic notation round-trips through Debug / FromStr
let text = format!;
let parsed: Value = text.parse.unwrap;
assert_eq!;
Arrays and maps can also be built from standard Rust collections
(Vec, BTreeMap, HashMap, slices of pairs), and values can be
modified in place through the as_*_mut() accessors. See the
documentation on Value for the full API.
For detailed notes on design decisions and trade-offs, see DESIGN-NOTES.md.
Changelog
See CHANGELOG.md for a summary of changes per release.
License
MIT