Wire codec round-trip (L2/L3)

The frame codec is the one place bytes become a tlv_t tree and back. This example builds a structured PATH TLV for /sensor/temp (two NAME children) with a CRC trailer, encodes it to wire bytes, decodes those bytes into a fresh tree, and proves the round-trip is exact.

What to notice

  • Decode borrows, never copies — the decoded NAME payloads are std::spans that point into the encoded buffer; the example checks the payload address lies inside wire. Keeping a decoded tlv_t means keeping its backing bytes alive (that is what views provide).

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

  • The round-trip invariant — re-encoding the decoded tree 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 the one place bytes become a `tlv_t` tree and back
 11 * (`docs/modules/frame-codec.md`). This example builds a structured PATH TLV
 12 * (`/sensor/temp` — two NAME children) with a CRC trailer, `encode`s it to wire
 13 * bytes, `decode`s those bytes into a fresh tree, and checks that re-encoding
 14 * reproduces the exact wire bytes.
 15 * It also shows the zero-copy nature of decode: the decoded payloads are
 16 * `std::span`s that BORROW 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 structured PATH TLV (opt.pl = payload-is-children) with a CRC trailer
 66    // (opt.cr — encode recomputes the CRC-32C over the body).
 67    tlv_t path;
 68    path.type = type_t::PATH;
 69    path.opt = opt_t{.pl = true, .cr = true};
 70    path.children = {name_tlv(seg0), name_tlv(seg1)};
 71
 72    // Encode the model to wire bytes, then decode those bytes back into a tree.
 73    const std::vector<std::byte> wire = tr::wire::encode(path);
 74    std::printf("encoded /sensor/temp PATH TLV: %zu bytes\n", wire.size());
 75
 76    const std::expected<tlv_t, tr::wire::err_t> decoded =
 77        tr::wire::decode(std::span<const std::byte>(wire));
 78
 79    bool ok = true;
 80    check(ok, decoded.has_value(), "decode succeeds (CRC trailer verifies)");
 81    if (decoded) {
 82        std::printf("decoded: type=0x%02X, %zu children, trailer.crc=%s\n",
 83                    static_cast<unsigned>(decoded->type), decoded->children.size(),
 84                    (decoded->trailer && decoded->trailer->crc) ? "present" : "absent");
 85        check(ok, decoded->type == type_t::PATH, "decoded root is a PATH");
 86        check(ok, decoded->opt.pl, "decoded root is structured (opt.pl)");
 87        check(ok, decoded->children.size() == 2, "decoded PATH has two NAME children");
 88        check(ok, decoded->trailer && decoded->trailer->crc.has_value(),
 89              "decoded PATH carries the verified CRC trailer");
 90        // The decoded payloads borrow the encoded buffer — zero copy.
 91        if (decoded->children.size() == 2) {
 92            const auto c0 = decoded->children[0].payload;
 93            check(ok, c0.data() >= wire.data() && c0.data() < wire.data() + wire.size(),
 94                  "decoded NAME payload is a span INTO the encoded buffer (zero copy)");
 95            const auto want = bytes_of(seg0);
 96            const bool same =
 97                c0.size() == want.size() && std::equal(c0.begin(), c0.end(), want.begin());
 98            check(ok, same, "first NAME child round-trips to \"sensor\"");
 99        }
100        // The strongest round-trip invariant: re-encoding the decoded tree
101        // reproduces the exact wire bytes (byte-identical, CRC and all).
102        check(ok, tr::wire::encode(*decoded) == wire, "encode(decode(bytes)) == bytes");
103    }
104
105    std::printf("%s\n", ok ? "round-trip OK" : "round-trip FAILED");
106    return ok ? 0 : 1;
107}

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