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
NAMEpayloads arestd::spans that point into the encoded buffer; the example checks the payload address lies insidewire. Keeping a decodedtlv_tmeans keeping its backing bytes alive (that is what views provide).The CRC trailer is verified on decode —
opt.crmakesencodeappend a CRC-32C over the body;decoderecomputes and checks it, and surfaces the parsedcrc_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.