Wire codec round-trip (L2/L3)

The frame codec is where a tlv_t model becomes bytes and bytes become a validated tlv_node_t. This example builds a packed PATH TLV for /sensor/temp (RFC-0018) with a CRC trailer, encodes it to wire bytes, reads those bytes back with tlv_node_t::over, and proves the round-trip is exact.

What to notice

  • The read borrows, never copies — the node’s packed body is a std::span that points into the encoded buffer; the example checks the payload address lies inside wire. Keeping a node means keeping its backing bytes alive (that is what views provide).

  • The CRC trailer is verified on read — opt.cr makes encode append a CRC-32C over the body; over recomputes and checks it, and trailer() surfaces the parsed crc_t.

  • The round-trip invariant — re-encoding what was read reproduces the exact wire bytes (encode(decode(bytes)) == bytes), CRC and all.

Source

  1/*
  2 * SPDX-License-Identifier: Apache-2.0
  3 * SPDX-FileCopyrightText: Copyright 2026 avatarsd LLC
  4 */
  5
  6/**
  7 * @file
  8 * @brief L2/L3 wire codec round-trip — build a TLV, encode to bytes, decode back.
  9 *
 10 * The wire codec is where a `tlv_t` model becomes bytes and bytes become a validated
 11 * `tlv_node_t` read in place (`docs/modules/frame-codec.md`). This example builds a packed
 12 * PATH TLV (`/sensor/temp`, RFC-0018) with a CRC trailer, `encode`s it to wire bytes, reads
 13 * those bytes back with `tlv_node_t::over`, and checks that re-encoding what it read
 14 * reproduces the exact wire bytes.
 15 * It also shows the zero-copy nature of the read: the node's payload is a `std::span`
 16 * that BORROWS the encoded buffer, so no payload bytes are copied.
 17 *
 18 * Runs under ctest as `example_wire_roundtrip`: it checks structure, byte-identity,
 19 * and the verified CRC trailer, returning non-zero on any mismatch.
 20 */
 21
 22#include <algorithm>
 23#include <cstddef>
 24#include <cstdio>
 25#include <span>
 26#include <string>
 27#include <vector>
 28
 29#include "libtracer/tracer.hpp"
 30
 31namespace {
 32
 33using tr::wire::opt_t;
 34using tr::wire::tlv_t;
 35using tr::wire::type_t;
 36
 37/** @brief A byte span over the characters of @p s (no copy; @p s must outlive the span). */
 38std::span<const std::byte> bytes_of(const std::string& s) {
 39    return {reinterpret_cast<const std::byte*>(s.data()), s.size()};
 40}
 41
 42/** @brief A NAME TLV borrowing @p name's bytes. */
 43tlv_t name_tlv(const std::string& name) {
 44    tlv_t t;
 45    t.type = type_t::NAME;
 46    t.payload = bytes_of(name);
 47    return t;
 48}
 49
 50/** @brief Record a failed expectation on @p ok and report it. */
 51void check(bool& ok, bool cond, const char* what) {
 52    if (!cond) {
 53        std::printf("  [FAIL] %s\n", what);
 54        ok = false;
 55    }
 56}
 57
 58}  // namespace
 59
 60int main() {
 61    // The NAME segment bytes must outlive every TLV that borrows them.
 62    const std::string seg0 = "sensor";
 63    const std::string seg1 = "temp";
 64
 65    // Build a PACKED PATH TLV (RFC-0018 — `opt.PL = 0`, the body is a run of
 66    // `[u8 len][bytes]` segment records) with a CRC trailer (opt.cr — encode recomputes
 67    // the CRC-32C over the body).
 68    std::vector<std::byte> packed;
 69    (void)tr::wire::emit_path_segment(packed, seg0);
 70    (void)tr::wire::emit_path_segment(packed, seg1);
 71    tlv_t path;
 72    path.type = type_t::PATH;
 73    path.opt = opt_t{.cr = true};
 74    path.payload = std::span<const std::byte>(packed);
 75
 76    // Encode the model to wire bytes, then decode those bytes back into a tree.
 77    const std::vector<std::byte> wire = tr::wire::encode(path);
 78    std::printf("encoded /sensor/temp PATH TLV: %zu bytes\n", wire.size());
 79
 80    const std::expected<tr::wire::tlv_node_t, tr::wire::err_t> decoded =
 81        tr::wire::tlv_node_t::over(std::span<const std::byte>(wire));
 82
 83    bool ok = true;
 84    check(ok, decoded.has_value(), "decode succeeds (CRC trailer verifies)");
 85    if (decoded) {
 86        const auto trailer = decoded->trailer();
 87        std::printf("decoded: type=0x%02X, %s children, trailer.crc=%s\n",
 88                    static_cast<unsigned>(decoded->type()),
 89                    decoded->children().empty() ? "no" : "has",
 90                    (trailer && trailer->crc) ? "present" : "absent");
 91        check(ok, decoded->type() == type_t::PATH, "decoded root is a PATH");
 92        check(ok, !decoded->opt().pl, "a packed PATH is NOT structured (opt.PL = 0, RFC-0018)");
 93        check(ok, decoded->children().empty(), "a packed PATH has no child TLVs");
 94        check(ok, decoded->payload().size() == 1 + seg0.size() + 1 + seg1.size(),
 95              "the body is one length byte per segment plus the segment text");
 96        check(ok, trailer && trailer->crc.has_value(),
 97              "decoded PATH carries the verified CRC trailer");
 98        // The decoded payload borrows the encoded buffer — zero copy.
 99        {
100            const auto body = decoded->payload();
101            check(ok, body.data() >= wire.data() && body.data() < wire.data() + wire.size(),
102                  "the packed body is a span INTO the encoded buffer (zero copy)");
103            const auto want = bytes_of(seg0);
104            const bool same =
105                body.size() > want.size() &&
106                static_cast<std::size_t>(static_cast<std::uint8_t>(body[0])) == want.size() &&
107                std::equal(want.begin(), want.end(), body.begin() + 1);
108            check(ok, same, "the first packed record round-trips to \"sensor\"");
109        }
110        // The strongest round-trip invariant: re-encoding what was read reproduces the
111        // exact wire bytes (byte-identical, CRC recomputed and all).
112        tlv_t again;
113        again.type = decoded->type();
114        again.opt = decoded->opt();
115        again.payload = decoded->payload();
116        check(ok, tr::wire::encode(again) == wire, "encode(decode(bytes)) == bytes");
117    }
118
119    std::printf("%s\n", ok ? "round-trip OK" : "round-trip FAILED");
120    return ok ? 0 : 1;
121}

See also: frame-codec module · bit-level wire walkthrough · data-format reference.