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 opt bits 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

type_t type = {}

The TLV type code.

opt_t opt = {}

The option bits (pl / cr / ll / …).

std::span<const std::byte> payload = {}

Opaque bytes (borrowed); empty when structured.

std::vector<tlv_t> children = {}

Parsed sub-TLVs; empty when opaque.

std::optional<trailer_t> trailer = {}

The decoded trailer, if present.

struct trailer_t

A decoded TLV trailer: an optional timestamp_t and/or crc_t.

Public Functions

constexpr bool operator==(const trailer_t&) const noexcept = default

Value equality over both optional fields.

Public Members

std::optional<timestamp_t> ts

The trailer timestamp, if present.

std::optional<crc_t> crc

The trailer CRC, if present.

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 an err_t on 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.cr is 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.