Wire format, bit by bit¶
A byte-level tour of libtracer’s TLV — the protobuf-encoding-guide view of the
format. Every example is a real frame, reproducible with tr::wire::encode
(core/include/libtracer/frame.hpp).
This page is implementation-independent. It describes the bytes any conformant
implementation puts on the wire, in any language; it lives under docs/modules/
for URL stability, not because anything here is specific to the C++ reference
implementation. The normative text is the
data-format reference and the
TLV catalog;
frame-codec is the C++ API that reads and writes these bytes.
The whole protocol is one shape, recursively: a Type-Length-Value. There are no field tags, no varints, no schema needed to walk the bytes — the header tells you everything, and a structured value is just more TLVs concatenated.
The header (4 bytes, or 6)¶
┌────────┬────────┬────────────────┐ ┌────────┬────────┬────────────────────────────────┐
│ type │ opt │ length (u16) │ or │ type │ opt │ length (u32) │
│ u8 │ u8 │ little-endian │ LL=1 │ u8 │ u8 │ little-endian │
└────────┴────────┴────────────────┘ └────────┴────────┴────────────────────────────────┘
byte 0 byte 1 bytes 2..3 byte 0 byte 1 bytes 2..5
type — one byte.
0x01VALUE,0x02NAME,0x06PATH,0x07POINT,0x09STATUS,0x0BSETTINGS,0x0CTIME,0x0FFWD … (0x80–0xFFis yours).opt — eight flag bits (below).
length — payload size, fixed-width little-endian:
u16normally,u32whenopt.LL=1. It counts payload bytes only: neither the header nor a trailer is included. Fixed width means a parser jumpsheader + lengthto the next TLV with no scanning — the basis of the iterative (non-recursive) walk.
The opt byte, bit by bit¶
bit: 7 6 5 4 3 2 1 0
┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐
│ R │ PL │ TS │ CR │ LL │ CW │ TF │ R │
└──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘
│ │ │ │ │ │ │ │
reserved┘ │ │ │ │ │ │ └reserved (both MUST be 0;
(=0) │ │ │ │ │ │ non-zero ⇒ invalid)
│ │ │ │ │ │ └ TF timestamp form: 0=abs u64 ns, 1=rel i32
│ │ │ │ │ └ CW CRC width: 0=CRC-32C, 1=CRC-16-CCITT
│ │ │ │ └ LL length width: 0=u16, 1=u32
│ │ │ └ CR trailer carries a CRC
│ │ └ TS trailer carries a timestamp
│ └ PL payload is structured (children), not opaque bytes
└ (reserved)
So opt = 0x40 is 0b0100_0000 → PL=1 (structured). opt = 0x10 →
CR=1 (CRC trailer). A frame pays bytes only for the options it sets; the default
opt = 0x00 is a bare opaque value with a 4-byte header.
Worked frames¶
1 · empty STATUS = OK (4 bytes)¶
The smallest frame — a write acknowledgement.
09 00 00 00
│ │ └──┴── length = 0x0000 = 0 (no payload)
│ └─────── opt = 0x00 (no flags)
└────────── type = 0x09 STATUS
An empty STATUS is “OK”. No body, no enum — absence is the signal.
2 · a VALUE carrying one byte (5 bytes)¶
A boolean true.
01 00 01 00 01
│ │ └──┴─ │ length = 0x0001 = 1
│ │ └─ payload[0] = 0x01 ← the value 'true'
│ └────────── opt = 0x00
└───────────── type = 0x01 VALUE
The payload bytes are opaque to the protocol — 0x01 means true only because
the application’s schema says so. libtracer never interprets application data (just
like JSON does not know a field is a temperature).
3 · a VALUE with a CRC trailer (13 bytes)¶
Same VALUE, payload AA BB CC DD EE, integrity-checked with CRC-32C.
01 10 05 00 AA BB CC DD EE B6 C9 12 23
│ │ └──┴─ └──────────────┘ └──────────┘
│ │ len=5 payload trailer: CRC-32C(payload) = 0x2312C9B6,
│ │ stored little-endian
│ └─ opt = 0x10 → CR=1 (trailer has a CRC)
└──── type = 0x01 VALUE
The CRC lives in the trailer, after the payload — not the header. That is what
lets a recorder or forwarder attach integrity at egress and strip it at ingress
without touching the payload bytes: at rest a value is header+payload; in
transit it grows a trailer; the payload is byte-identical through both.
4 · a packed PATH /sensor/temp (16 bytes)¶
opt.PL=0 — the payload is not child TLVs. It is a self-delimiting run of
segment records, each [u8 len][utf8]
(RFC-0018).
06 00 0C 00 │ 06 73 65 6E 73 6F 72 │ 04 74 65 6D 70
└──┬───────┘ └──────────┬────────┘ └───────┬──────┘
│ "sensor" (7 bytes) "temp" (5 bytes)
│ 06=len s e n s o r
│
type=0x06 PATH · opt=0x00 (PL=0) · length=0x000C=12 (= 7 + 5 record bytes)
Walking it is cheaper than the outer frame’s loop, not the same one: read one
length byte, jump 1 + len, repeat — no option decode and no header to construct.
A record carries no type byte and no option byte, which is the whole point: an
address has exactly one spelling. And those 12 payload bytes are exactly the
vertex-map key (path; path_t::key,
core/include/libtracer/path.hpp): the address on the wire and the address in
memory are the same bytes. len == 0 is the reserved escape record
(00 <kind> <len> <bytes>) — a forwarder steps over one, a key rejects it.
5 · a FWD frame (the remote-operation envelope)¶
A remote write carried by the source-routed FWD (0x0F,
reference/05 §reserved range): the op code, the
explicit route to the target (dst), the accumulated way back (src), then the
payload TLV — FWD{ op=WRITE, dst=/b/temp, src=(empty), VALUE 0x2A }, 29 bytes:
0F 40 19 00 ← FWD · opt=0x40 (PL=1) · length=0x0019=25
│ 01 00 01 00 01 ← VALUE op: 1 byte, WRITE=0x01
│ 06 00 07 00 ← PATH dst (PL=0), 7 body bytes
│ 01 62 ← record "b" (the next-hop link)
│ 04 74 65 6D 70 ← record "temp" (the target on the peer)
│ 06 00 00 00 ← PATH src (PL=0), empty — grows per hop
│ 01 00 01 00 2A ← VALUE payload: the byte 0x2A
Every child is one of the shapes above — the frame is examples 2 and 4,
concatenated. A forwarding hop reads just the three leading headers by offset:
it strips the record 01 62 ("b") from dst (shrinking it toward the target),
prepends its own segment record for the inbound link to src (the return route), and sends the rest of the
frame onward untouched — the payload bytes are never copied or re-encoded
(rebuild_fwd_forward, core/include/libtracer/fwd_frame_view.hpp, emits two
rebuilt headers and gathers every other region as an offset window into the source).
When dst no longer starts with a link name, the frame has arrived: the terminus
decodes it and applies the op.
Nothing wraps a FWD: routing is explicit and source-routed, and 0x0D ROUTER is a
reserved, decodable codepoint with no implemented mechanism
(reference/05 §0x0D).
The same bytes, three ways¶
There is no separate “decode into a struct” step. The wire bytes, the in-memory value, and the graph node are one buffer.
flowchart LR
B["bytes:<br/>06 00 0C 00 06 "sensor" …"]:::b
B --> W["on the wire<br/>(a frame)"]
B --> M["in memory<br/>(a borrowed view, no copy)"]
B --> G["in the graph<br/>(the vertex's value / key)"]
classDef b fill:#dbeafe,stroke:#1e40af;
Consequences of the layout¶
In the bytes |
Consequence |
|---|---|
4-byte header ( |
tiny per-message overhead; fits MCU MTUs |
fixed-width length |
jump to the next TLV with no varint scan → an iterative, bounded, recursion-free parser |
|
timestamp/CRC/wide-length cost bytes only when set; the default frame is 4 bytes |
trailer-positioned CRC/TS |
attach/strip integrity & time without rewriting the payload (rest ⇄ transit) |
|
structure with no list type; a structured value is parsed in place as sub-spans |
payload = opaque bytes |
the protocol is a transparent carrier; the application’s schema gives bytes meaning |
the key bytes = the PATH payload |
one address for wire and memory; dispatch is a byte compare |
The bytes received are the bytes kept: a decoded value is a set of spans into the received buffer (views), so reading a field is a pointer load and handing a value to N subscribers is N refcount bumps rather than N copies.
API reference¶
Generated from core/include/libtracer/tlv.hpp by Doxygen.
-
struct opt_t¶
The 1-byte
optoptions bitfield of a TLV header.Bits, MSB→LSB: R | PL | TS | CR | LL | CW | TF | R. Bits 7 and 0 are reserved-MUST-be-zero (a set reserved bit ⇒
frame::invalid). See docs/reference/01-data-format.md §header + opt.Public Functions
-
inline constexpr std::uint8_t encode() const noexcept¶
Pack back into the raw
optbyte (reserved bits always zero).
-
inline constexpr opt_t without_trailer() const noexcept¶
The same opt with the trailer bits (TS/CR/CW/TF) cleared — only the structural bits (PL/LL) survive.
An ADR-0041 §4 trailer-sliced whole-TLV copy (op_resolve.cpp) applies this so the copy, whose bytes exclude the trailer by construction, stays self-consistent — the typed replacement for the raw
opt & 0x48mask that once encoded these bits.
Public Members
-
bool pl = false¶
bit 6: payload-is-structured (children, not opaque bytes).
-
bool ts = false¶
bit 5: trailer carries a timestamp.
-
bool cr = false¶
bit 4: trailer carries a CRC.
-
bool ll = false¶
bit 3: length width (false = u16, true = u32).
-
bool cw = false¶
bit 2: CRC width (false = CRC-32C, true = CRC-16-CCITT).
-
bool tf = false¶
bit 1: timestamp form (false = abs u64, true = rel i32).
Public Static Functions
-
static inline constexpr bool reserved_set(std::uint8_t b) noexcept¶
True iff a reserved bit is set in raw byte
b(⇒ the frame is invalid).
Public Static Attributes
-
static constexpr std::uint8_t kReservedMask = 0b1000'0001¶
The reserved-MUST-be-zero bit mask (bits 7 and 0).
-
inline constexpr std::uint8_t encode() const noexcept¶
-
enum class tr::wire::type_t : std::uint8_t¶
The core TLV type-code registry (0x01-0x10, docs/reference/05 §per-type layout).
0x05 is retired (was LIST, ADR-0003). 0x0E SPEC is the in-band vertex-creation spec (ADR-0017); 0x0F FWD and 0x10 FIELD are the remote-operation frames (RFC-0004 / ADR-0035, the v1 fast-track range 0x0F-0x1F). All are structured (opt.PL=1) and handled generically by the codec. Codes 0x11-0x13 are transport-plane route-handle control frames (RFC-0004 §E.1, ADR-0035 slice 4): they ride a full-TLV link (ws/UDP) ALONGSIDE FWD to compact an established,
delivery_compact-flagged flow into a per-link label. They are NOT part of the FWD frame and NOT cross-core conformance TLVs — a peer that ignores them simply keeps the full-route delivery path — but are self-describing (opt.PL=1) so the codec parses them generically. 0x14 PATH_REF is the bound-path address form (RFC-0024 §4) and 0x15 PATH_REF_REVERSE the reverse-direction list a mint-flagged request accumulates (§7.1 amendment 2): the two types whose body is NOT self-describing — a fixed-stride 8-byte record array (opt.PL=0), whose shape the grammar therefore checks by type (path_ref.hpp, gated byis_path_ref_type).One code beyond the core range is named here: 0x80 BATCH, the single assignment inside the user range (0x80-0xFF, RFC-0025 §4.1.2 clause 6). It is an ordinary structured TLV to the codec — the enumerator exists so the one convention libtracer itself emits has a spelling, not because the codec treats it specially.
Values:
-
enumerator VALUE¶
Opaque scalar value.
-
enumerator NAME¶
UTF-8 name segment.
-
enumerator DESCRIPTION¶
Human-readable description.
-
enumerator SUBSCRIBER¶
Subscriber registration edge.
-
enumerator PATH¶
Path address; opaque body of packed
[u8 len][utf8]segment records (RFC-0018), NOTNAMEchildren.
-
enumerator POINT¶
A point in a path/graph.
-
enumerator ERROR¶
Error report.
-
enumerator STATUS¶
Status report.
-
enumerator ACL¶
Access-control list.
-
enumerator SETTINGS¶
QoS settings.
-
enumerator TIME¶
An APPLICATION-DOMAIN timestamp carried inside a structured payload — RESERVED, deliberately emitted and consumed by no core code (#1109). The wire-trailer TS (
opt.ts) is transport-time; sample-acquisition / control-deadline time rides the payload as a TIME child instead (docs/reference/01-data-format.md §application-domain timestamps). What a TIME body means is the embedder’s schema, so core assigns the code and nothing else.
-
enumerator ROUTER¶
Router-wrapped frame.
-
enumerator SPEC¶
In-band vertex-creation spec (structured; ADR-0017).
-
enumerator FWD¶
Remote-operation forward frame (RFC-0004 §B / ADR-0035).
-
enumerator FIELD¶
Control-plane
:fieldselector (RFC-0004 §C / ADR-0035).
-
enumerator ADVERTISE¶
Route-handle: VALUE label(u16) + PATH route — bind label→route, swapped per hop.
-
enumerator COMPACT¶
Route-handle: VALUE label(u16) + payload TLV — a label-compacted delivery.
-
enumerator HANDLE_NACK¶
Route-handle: VALUE label(u16) — stale/unknown label seen; prompts re-advertise.
-
enumerator PATH_REF¶
Bound path: a bare array of 8-byte node-scoped vertex refs (RFC-0024 §4).
-
enumerator PATH_REF_REVERSE¶
The REVERSE-direction bound path a mint-flagged request accumulates (RFC-0024 §7.1 amendment 2) — same body grammar as
PATH_REF, different role.A distinct code rather than a positional rule: every other element of this grammar self-describes by type, and “the only trailing child” would break the moment a future RFC adds a second trailing child to a mint-flagged request. It also un-forecloses a raw
PATH_REF-typed payload on such a request. The code costs nothing: a reader already compares the child’s type byte, so a different constant is the same instruction (peek_trailing_mint).
-
enumerator BATCH¶
The ONE assigned user-range code: the BATCH record (RFC-0025 §4.1.2, Amendment 3 clause 6) — a structured (
opt.PL=1) written value whose children are the sample frames of one flush (batch.hpp).0x80–0xFFis the range the protocol does not opine on, and the assignment does not change that: a deployment already using0x80for its own record is not made non-conforming, no core-range code is minted, nooptbit is added, and the graph still never interprets the body (claim 5). What the assignment buys is ONE number — so the reference helpers, the §4.3 descriptor and the conformance vectors stop each picking their own. Until Amendment 3 this code appeared only as the worked example of docs/reference/05-protocol-tlvs.md §0x0C, which reads the same either way.
-
enumerator VALUE¶
See: frame-codec · the normative data-format reference · the TLV catalog.