frame-codec — the TLV wire codec (L2/L3)¶
In one paragraph
The codec turns wire bytes into a borrowed tlv_t tree and back. A TLV is a 4- or
6-byte header (type, an opt bitfield, a length) + payload + an optional trailer
(timestamp, CRC). decode never copies payloads — they are std::spans into
the input buffer; encode serializes a tlv_t and recomputes the CRC. The
bit-level walkthrough shows every bit.
What it does¶
decode(bytes) → std::expected<tlv_t, err_t> parses exactly one TLV that fills the
input: it reads the header, rejects reserved bits and bad structure, verifies the
trailer CRC, and — when opt.PL=1 (payload-is-structured) — walks child TLVs
iteratively with a depth cap of 32 (no recursion blow-ups). The result borrows
the input, so holding it requires keeping the bytes alive (that is what
views provide). encode(tlv) does the reverse, recomputing the CRC
over the body when opt.CR is set.
flowchart TD
H["read header:<br/>type · opt · length"] --> V{"bounds ok?<br/>reserved bits zero?"}
V -->|no| E["err_t"]
V -->|yes| P{"opt.PL?"}
P -->|"1 — structured"| C["push children region<br/>(depth cap 32)"]
P -->|"0 — opaque"| O["payload = span into input"]
C --> T["verify trailer CRC"]
O --> T
T --> N{"more bytes<br/>in region?"}
N -->|yes| H
N -->|no| D["tlv_t tree (borrowed)"]
The opt byte is the protocol’s compactness lever: six 1-bit flags select
structure, trailer contents, and field widths, so the common frame is just 4
bytes of header. CRCs are CRC-32C (default) or CRC-16-CCITT, both
constexpr tables built at compile time.
Interface¶
enum class type_t : std::uint8_t { VALUE=0x01, NAME=0x02, /*…*/ STATUS=0x09, ROUTER=0x0D };
struct opt_t { bool pl, ts, cr, ll, cw, tf; // the 6 option bits
std::uint8_t encode() const; static opt_t decode(std::uint8_t); };
struct tlv_t {
type_t type; opt_t opt;
std::span<const std::byte> payload; // opaque TLVs (borrowed)
std::vector<tlv_t> children; // structured TLVs (opt.PL=1)
std::optional<trailer_t> trailer; // {timestamp_t?, crc_t?}
};
std::expected<tlv_t, err_t> decode(std::span<const std::byte>); // borrowed, depth-capped
std::vector<std::byte> encode(const tlv_t&); // recomputes CRC
namespace crc { std::uint32_t crc32c(...); std::uint16_t crc16_ccitt(...); } // constexpr
Frame shape¶
byte: 0 1 2 3 4 … (4+len-1) … trailer …
┌──────┬────────┬───────────────┬───────────────────┬─────────────────┐
│ type │ opt │ length (u16) │ payload │ [timestamp][crc]│
└──────┴────────┴───────────────┴───────────────────┴─────────────────┘
│ (u32 if LL=1) opaque bytes, OR TS? then CR?
│ concatenated child
│ TLVs when PL=1
opt bits (MSB→LSB): R · PL · TS · CR · LL · CW · TF · R
(bits 7 and 0 are reserved-must-be-zero)
The terminus arena sibling¶
Alongside the owning tlv_t model, the codec ships a second decoder for the FWD
terminus: wire::decode_into(span, std::pmr::memory_resource&) → tlv_arena_t
(public header tlv_arena.hpp). It parses the same frames with the same
validation (bounds, reserved bits, depth cap, trailer CRC), but the result is a
flat, pre-order array of arena_tlv_t span-nodes — {type, opt, wire (trailer-excluded), body, end, canonical_path} — every span borrowing the input
frame, every node drawn from the injected pmr resource. Zero-copy, no owning
vectors; a resolve-scoped view, never stored. The codec’s tlv_t model,
decode, and encode are unchanged alongside it.
Benefits¶
Self-describing & compact — one 4-byte header covers the common case; the
optbits opt into width/trailer only when needed.Decode = cast, not copy — payloads are spans; structured TLVs are sub-spans of the same buffer. Pairs with views for true zero-copy.
Bounded & safe — fixed-width length (no varint ambiguity), iterative parse with a depth cap, CRC verified before you trust the bytes.
API reference¶
Generated from core/include/libtracer/frame.hpp by Doxygen.
-
struct tlv_t¶
A decoded TLV — the materialized, eager representation of one wire frame node.
For opaque TLVs (
opt.pl == 0) payload holds the bytes and children is empty; for structured TLVs (opt.pl == 1) children holds the parsed sub-TLVs and payload is empty. payload (and child payloads) BORROW the input buffer.Public Members
-
std::span<const std::byte> payload = {}¶
Opaque bytes (borrowed); empty when structured.
-
std::span<const std::byte> payload = {}¶
-
struct trailer_t¶
A decoded TLV trailer: an optional timestamp_t and/or crc_t.
Public Functions
-
std::expected<tlv_t, err_t> tr::wire::decode(std::span<const std::byte> input)¶
Decode exactly one TLV that fills
input.- Parameters:
input – The bytes to decode — must be exactly one TLV; trailing bytes ⇒
FrameInvalid.- Returns:
The decoded tlv_t (borrowing
input), or anerr_ton failure.
-
std::vector<std::byte> tr::wire::encode(const tlv_t &tlv)¶
Encode a TLV to its wire bytes (recomputing the trailer CRC when
opt.cris set).- Parameters:
tlv – The TLV tree to serialize.
- Returns:
The encoded frame bytes.
See the reference data-format for the normative rules and wire-format-bits for worked byte dumps.