libtracer protocol — version 1 (DRAFT)

Status: DRAFT. The wire format is not yet stable. Pin to a specific commit if you depend on this.

This document is normative. It uses the keywords MUST, MUST NOT, SHOULD, SHOULD NOT, MAY as defined in RFC 2119.

1. Scope

This specification defines the libtracer protocol: the wire format (frame header, options bits, fixed-width lengths, trailer timestamp/CRC), the core TLV type registry and each type’s payload layout, addressing (canonical PATH form and constraints), the error model (the tr::<concept>::<error> registry), the data API semantics an implementation must expose (read / write / await plus the :-field control surface — there are no connect/disconnect/subscribe wire operations; subscription is a SUBSCRIBER write into :subscribers[]), and the conformance procedure.

It deliberately does not specify: the module/host ABI (implementation- defined, ADR-0013); a transport’s native framing below the TLV layer (each transport documents its own binding); application payload semantics (a VALUE is opaque to the protocol); or discovery (a separate, layered concern).

2. Terminology

The canonical project vocabulary — vertex, edge, path, view, segment, rope, TLV, address-shift slicing, transport vertex, and the rest — is defined in the root glossary, CONTEXT.md, which this specification incorporates by reference for terminology. Where this document and the glossary disagree, this document wins.

For the purposes of section 3.1, the following additional terms are defined:

  • Path handle. An opaque, stable identifier for a vertex address that points (directly or indirectly) at a pre-encoded PATH TLV in the implementation’s read-only data segment. A path handle MUST resolve to the same byte sequence for the lifetime of the node.

  • Pre-encoded PATH TLV. A PATH TLV whose wire bytes are emitted at build time (e.g., as static const data in .rodata) or computed exactly once at node initialization, and never re-encoded thereafter.

  • Hot path. Any code path executed per-message during steady-state operation (typically inside a publisher’s sample loop, an ISR, or a router dispatch). The hot path is contrasted with the init path, which executes once at node startup.

3. Wire format

The wire format is specified normatively by incorporation (ADR-0007, RFC-0001 §A.2):

  • docs/reference/01-data-format.md — the frame layout: 4/6-byte header, the opt bits (R|PL|TS|CR|LL|CW|TF|R, reserved bits MUST be zero), fixed-width u16/u32 length per opt.LL, the optional trailer (timestamp per opt.TS/opt.TF, CRC-32C or CRC-16-CCITT per opt.CR/opt.CW), little-endian multi-byte fields, and the iterative- parsing requirement (nesting depth receiver-resource-bounded, RFC-0006).

  • docs/reference/05-protocol-tlvs.md — the type-code registry (0x00 sentinel; 0x010x1F core; 0x05 reserved with no assigned meaning; 0x0D reserved codepoint with no mechanism) and every core type’s byte-precise payload layout, including the ERROR model (RFC-0002, accepted) and the remote-operation FWD/FIELD frames (RFC-0004).

  • docs/reference/03-addressing.md §path syntax — canonical PATH constraints (segment limit 32, NAME limit 64 bytes, total ≤ 1024 bytes, reserved characters, UTF-8).

Those documents’ MUST/SHOULD/MAY clauses are normative clauses of this specification. Behavioral notes marked informative in them are not. Where this section’s incorporated documents and any other document disagree, the incorporated text wins. There is no per-frame protocol-version field: version negotiation is a discovery-layer concern (ADR-0002); an incompatible peer is reported as tr::version::mismatch.

3.1 Static path-handle conformance

This subsection is normative. It exists to make libtracer usable on MCU-class devices (Cortex-M0/M3/M4, ESP32, RISC-V microcontrollers) where the hot path MUST NOT allocate, format strings, or walk a parser tree to address a vertex.

3.1.1 Path-handle invariant

A conforming implementation MUST provide a way for an application to obtain a path handle for any path it intends to address more than once. The handle:

  1. MUST resolve, deterministically and without allocation, to a byte sequence equal to the canonical PATH TLV (type=0x06, opt.PL=1, NAME children per docs/reference/05-protocol-tlvs.md §0x06 PATH) for the named vertex.

  2. MUST remain valid for the lifetime of the node, or until the application explicitly releases it.

  3. MUST be cheap to copy (the implementation defines whether this is a pointer, a small integer index, or a struct-by-value).

  4. MUST NOT require string parsing, snprintf, or malloc on the hot path.

The byte-equivalence requirement (1) is the load-bearing one: a write through a path handle MUST be indistinguishable on the wire from a write through the equivalent string-form path, after both are canonicalized.

3.1.2 Static encoding at build time

A conforming implementation MUST permit pre-encoded PATH TLVs to be placed in read-only memory (.rodata, flash, or equivalent) and used directly as the source of bytes for a path handle. No copy from .rodata to RAM is required.

The PATH TLV byte layout is fully specified by docs/reference/01-data-format.md and docs/reference/05-protocol-tlvs.md §0x06. A build-time encoder (macro, code generator, or hand-written byte literal) producing those bytes MUST satisfy:

  • type byte = 0x06.

  • opt.PL = 1.

  • length field width selected per opt.LL; total payload size MUST match the sum of child NAME TLV sizes.

  • Each NAME child is type 0x02, opt.PL=0, payload is the segment’s UTF-8 bytes (no NUL terminator).

  • All multi-byte fields in little-endian per docs/reference/01-data-format.md §frame layout.

  • Path constraints from docs/reference/03-addressing.md §path syntax (segment limit 32, name limit 64 bytes, total ≤ 1024 bytes) MUST be checked at encode time. A pre-encoded PATH TLV that violates these limits is non-conforming.

3.1.3 Init-time registration

When path handles cannot be statically derived (e.g., interpolated peer-id mounts under /peer/{peer_id}), a conforming implementation MUST provide an init-time registration call that:

  1. Accepts a string-form path.

  2. Validates per the rules of docs/reference/03-addressing.md.

  3. Allocates a single, reusable PATH TLV (in a long-lived segment).

  4. Returns a path handle equivalent to the build-time form.

The registration call MAY perform allocation. The path handle it returns MUST satisfy 3.1.1 thereafter — including the no-allocation requirement on subsequent uses.

3.1.4 Hot-path API requirement

The implementation MUST expose a write entry point that takes a path handle (not a string) and a value TLV. Calling this entry point MUST NOT:

  • Allocate memory (other than what the dispatch / fanout itself requires for subscriber views).

  • Format a string from numeric components.

  • Parse the path handle’s bytes (the handle is already a validated, canonicalized PATH TLV).

  • Invoke per-segment hashing or comparison if the implementation uses a path-handle-keyed dispatch table.

The corresponding read and await entry points MUST satisfy the same constraints.

A string-form convenience entry point (e.g., tracer_write_str("/sensor/temp", tlv)) MAY be provided for ergonomics on hosts where the cost of string parsing is acceptable. It is NOT required, and a minimum-feature implementation (P0) MAY omit it entirely.

3.1.5 Inline-NAMEs vs reference-NAMEs (informative)

Two implementation strategies satisfy 3.1.1–3.1.4. Both are conformant.

  • Inline-NAMEs: the pre-encoded PATH TLV holds its NAME children inline as concatenated NAME TLVs in .rodata. The handle is { const uint8_t *bytes; size_t len; }. Suited to bare-metal / Cortex-M targets.

  • Reference-NAMEs: the pre-encoded PATH TLV holds NAME children as offsets into an interned-NAME table. The handle is a small integer index. Suited to hosts where the same NAME segment recurs across many paths and table reuse pays off.

Senders MAY choose either strategy. The wire bytes a path handle resolves to MUST be identical regardless of strategy.

4. Conformance

An implementation is libtracer v1 compatible if and only if it:

  1. Honors every MUST clause in this document.

  2. Passes every test vector under tests/conformance/vectors/v1/.

Vectors are organized vectors/v1/<category>/<case>/ (framing, crc, tlv-types, path, fwd, field, errors, acl, …), each case a self-describing input.bin with a human-readable expected.json; a conforming codec MUST round-trip each input.bin byte-exactly (encode(decode(x)) == x). New categories are added alongside spec additions; adding a vector is not a spec change, changing an existing vector’s bytes is.

There is no other compatibility test.

5. Versioning

Spec versions are integers. v1 is immutable once finalized — corrections that change conformance behavior require v2.

Changelog

  • unreleased — initial draft.

  • unreleased — added §3.1 static path-handle conformance (MCU-friendly addressing without runtime string formatting).

  • unreleased — §1 scope, §2 terminology (glossary by reference), §3 wire format normative-by-incorporation of reference/01 + 05 + 03 §path-syntax, §4 vector layout (RFC-0001 §A.2/§B/§E; RFC-0002 error model).