atomvm-cbor

Public Release GateLine and branch coverage gate 95%MIT LicenseErlang OTP 25+AtomVM 0.6.6Buy Me a Coffee

A compact RFC 8949 CBOR encoder and decoder for AtomVM and constrained Erlang runtimes. It is pure Erlang, has no runtime dependencies, NIFs, or ports, and preserves the public {text, Binary} and {map, Pairs} representations.

Version 0.2.0 is the first Hex.pm release. Public Git tag v0.1.1 is the reproducible source and benchmark baseline and was never published to Hex.

Installation

Rebar3 projects use the OTP application name avm_cbor and Hex package name atomvm_cbor:

{deps, [
{avm_cbor, "~> 0.2.0", {pkg, atomvm_cbor}}
]}.

Mix projects use:

{:avm_cbor, "~> 0.2.0", hex: :atomvm_cbor}

Capabilities

Attached-device benchmarks

Lower timings are better. The common encode/decode results compare the exact public v0.1.1 baseline with 0.2.0 using the same CBOR payload and AtomVM 0.6.6 harness on each device. See the benchmark methodology and the full 0.2.0 validation report for complete provenance, hardware details, soak results, and reproduction guidance.

BoardFunctionv0.1.1 µs0.2.0 µsChange
ESP32-S3 (8 MB PSRAM)encode/18705.827120.5418.21% faster
ESP32-S3 (8 MB PSRAM)decode/18508.988683.682.05% slower
RP2040 (16 MB flash)encode/17248.565118.9229.38% faster
RP2040 (16 MB flash)decode/17105.506400.789.92% faster
WaveShare ESP32-S3-DEV-KIT-N32R16Vencode/16080.324064.5633.15% faster
WaveShare ESP32-S3-DEV-KIT-N32R16Vdecode/15853.324600.3021.41% faster

The partial/deferred APIs are new in 0.2.0, so no v0.1.1 measurements exist for them.

FunctionESP32-S3 0.2.0 µsRP2040 0.2.0 µsWaveShare N32R16V 0.2.0 µs
partial_decode/113109.009875.487518.24
partial_decode/213326.809928.187570.60
partial_deep_decode/110805.888243.965911.32
partial_value_bytes/1767.53579.66562.63
partial_contents/1959.06506.69607.53
partial_skip/1380.07228.66295.30
partial_type/1382.08229.65295.48
partial_count/1381.27229.65296.33
partial_tag/1438.55253.83360.91
partial_size/1455.71260.95382.75
partial_offset/1379.89227.56296.28
partial_length/1382.62234.45295.84

The older ESP32-S3 decode/1 regression is measured and reproducible. It is accepted under the unchanged 5% release threshold because 0.2.0 retains the new fail-closed node and string-byte accounting on this general decode path. The same workload improves on RP2040 and WaveShare, so no single hardware or security-check cause is claimed. The full report includes the complete technical interpretation and maximum-bound testing.

Public term representation

CBOR valueErlang value
unsigned or negative integerinteger
byte stringbinary
text string{text, Binary}
arraylist
map{map, [{Key, Value}, ...]}
semantic tag{tag, Number, Value}
boolean/null/undefinedtrue, false, null, undefined
simple value{simple, Number}
floatfloat

Core API

avm_cbor:decode(Binary).
avm_cbor:decode(Binary, Options).
avm_cbor:decode_all(Binary, Options).
avm_cbor:decode_sequence(Binary, Options).
avm_cbor:encode(Value, Options).

decode/1,2 consumes one value and returns {ok, Value, Rest}. decode_all/1,2 requires a complete input. decode_sequence/1,2 returns all complete sequence items and retains a truncated final item as Rest.

Pull-based continuation decode

{ok, State0} = avm_cbor:decode_start(Binary, Options),
case avm_cbor:decode_continue(State0, Budget) of
{done, Value, Rest} -> use(Value, Rest);
{more, State1} -> schedule_next(State1);
{error, Reason} -> reject(Reason)
end.

Budget is a positive bound on explicit parser transitions. The returned state is immutable and opaque. The decoder never sleeps or yields; event-loop pacing belongs to the caller. This is not a streaming-input API: Binary must already exist when decode_start/2 is called.

Partial and deferred decode

partial_decode/1,2 validates and measures one value without eagerly constructing nested Erlang terms. It returns an opaque descriptor; callers must use the public accessors rather than matching its internal shape.

{ok, Descriptor, Rest} = avm_cbor:partial_decode(Binary),
Type = avm_cbor:partial_type(Descriptor),
Length = avm_cbor:partial_length(Descriptor),
{ok, Value} = avm_cbor:partial_deep_decode(Descriptor).

Available accessors are partial_value_bytes/1, partial_deep_decode/1, partial_skip/1, partial_type/1, partial_count/1, partial_tag/1, partial_size/1, partial_offset/1, partial_length/1, and partial_contents/1.

Options and limits

OptionDefaultPartial defaultPurpose
max_depth128128nesting depth
max_items409664global node/work budget
max_bytes10485761048576input/output bytes
max_string_bytes6553665536one string or chunk
max_total_string_bytes10485761048576cumulative decoded string bytes
max_string_sizenot used8192partial byte/text content
allow_floatstruetruefloat support
allow_simpletruetruesimple values
allow_tagstruetruesemantic tags
allow_indefinitetruetrueindefinite-length values
preferredfalsefalsepreferred serialization checks/output
deterministicfalsefalsedeterministic encoding and strict decode validation

Options are normalized once into fixed internal state. Recursive paths do not repeatedly scan or rebuild option proplists.

deterministic=true rejects indefinite-length input, non-preferred width choices, duplicate map keys, and map keys not ordered by their original encoded bytes. ble_options/0 provides a stricter device-oriented profile.

Treat options as trusted application policy. Do not allow a network client to raise them. Enforce the same or a smaller byte ceiling at transport ingress before buffering the complete request, use a lower fixed profile on targets that cannot allocate the defaults, and require empty trailing Rest for protocols that permit exactly one item.

Large definite containers made entirely of preferred one-byte unsigned values use a bounded fixed-cost path. It is independently capped at 4095 direct nodes, so caller-raised limits cannot turn it into an unbounded speculative allocation. Mixed, malformed, deterministic, nested, multi-byte, or larger containers use the general bounded decoder and retain the same errors.

Documentation and validation

Run the public validation suite:

scripts/release-check.sh 0.2.0

Run the reproducible host benchmark:

scripts/bench.sh

Benchmark values depend on the host, OTP version, and architecture. Published comparisons are measured context, not guarantees for other devices.

License

MIT. See LICENSE.