Structured or opaque — one bit decides (L2/L3 codec)¶
opt.PL is the whole of TLV composition. Clear, and the body is payload bytes — an
opaque TLV, the leaf. Set, and the body is a packed run of sub-TLVs — a structured
TLV, the composite, whose type code says what the children mean
(CONTEXT.md §Two compositions). There is no LIST type and no array flag;
nesting is this bit plus a purpose type byte.
The example makes the point by encoding POINT{VALUE, VALUE} once, clearing PL on a copy,
and decoding the same bytes as one opaque payload whose length is exactly the children
region’s.
What to notice¶
Meaning composes at L3; storage composes at L1, and they do not constrain each other. A structured TLV’s children are a meaning tree. The rope that physically holds those bytes is a storage tree, and a link boundary may fall anywhere, including mid-header — see the lazy view. That decoupling is the zero-copy story.
A decoded TLV is one or the other, never both.
childrenis populated andpayloadempty for a structured TLV; the reverse for an opaque one. Reading the wrong field gets you a legitimately empty container, not an error.The children region is contiguous bytes with no framing of its own. That is why the same body reads as one opaque payload of
frame.size() - 4: sub-TLVs are self-describing, so no count field and no separators are needed.Nothing here is conditional — the target builds and runs under every CI leg.
Source¶
1/*
2 * SPDX-License-Identifier: Apache-2.0
3 * SPDX-FileCopyrightText: Copyright 2026 avatarsd LLC
4 */
5
6/**
7 * @file
8 * @brief ONE CONCEPT — `opt.PL`: an opaque TLV holds bytes, a structured TLV holds children.
9 *
10 * TLV composition (L3) composes MEANING: the leaf is an opaque TLV (`opt.PL = 0`, its body
11 * is payload bytes), the composite is a structured TLV (`opt.PL = 1`, its body is a packed
12 * run of sub-TLVs) whose type code says what the children mean (`CONTEXT.md` §Two
13 * compositions). One bit decides which, and it decides it for the SAME body bytes: this
14 * example encodes `POINT{VALUE, VALUE}`, clears `PL` on the copy, and decodes the very same
15 * bytes as one opaque payload of exactly the children's length.
16 *
17 * That is the decoupling from memory composition (L1): meaning is `opt.PL`, storage is the
18 * view/rope chain, and neither constrains the other.
19 *
20 * Runs under ctest as `example_wire_structured_vs_opaque`; returns non-zero on any failure.
21 */
22
23#include <cstddef>
24#include <cstdint>
25#include <cstdio>
26#include <iterator>
27#include <span>
28#include <vector>
29
30#include "libtracer/tracer.hpp"
31
32namespace {
33
34using tr::wire::opt_t;
35using tr::wire::tlv_t;
36using tr::wire::type_t;
37
38/** @brief Report expectation @p what and record a failure on @p ok. */
39void check(bool& ok, bool cond, const char* what) {
40 std::printf(" [%s] %s\n", cond ? "ok" : "FAIL", what);
41 ok = ok && cond;
42}
43
44} // namespace
45
46int main() {
47 bool ok = true;
48 const std::vector<std::byte> x{std::byte{0x11}, std::byte{0x22}};
49 const std::vector<std::byte> y{std::byte{0x33}, std::byte{0x44}};
50
51 tlv_t point;
52 point.type = type_t::POINT;
53 point.opt.pl = true; // structured: the body is children, not bytes
54 point.children.push_back(tlv_t{.type = type_t::VALUE, .payload = std::span(x)});
55 point.children.push_back(tlv_t{.type = type_t::VALUE, .payload = std::span(y)});
56
57 std::vector<std::byte> frame = tr::wire::encode(point);
58 std::printf("POINT{VALUE,VALUE} is %zu bytes: 4 header + 2 x (4 header + 2 body)\n",
59 frame.size());
60
61 const auto structured = tr::wire::tlv_node_t::over(frame);
62 check(ok, structured.has_value(), "the structured frame decodes");
63 check(ok, structured && std::ranges::distance(structured->children()) == 2,
64 "PL = 1 gives two children");
65 check(ok, structured && structured->payload().empty(), "and no opaque payload at all");
66
67 // The SAME body bytes, read with PL cleared. Byte 1 is the root's opt (see
68 // wire_tlv_header): clearing bit 6 tells the decoder "this body is payload".
69 std::vector<std::byte> as_opaque = frame;
70 as_opaque[1] = static_cast<std::byte>(static_cast<std::uint8_t>(as_opaque[1]) & 0xBFu);
71
72 const auto opaque = tr::wire::tlv_node_t::over(as_opaque);
73 check(ok, opaque.has_value(), "the same bytes with PL = 0 also decode");
74 check(ok, opaque && opaque->children().empty(), "PL = 0 gives no children");
75 check(ok, opaque && opaque->payload().size() == frame.size() - 4,
76 "the payload IS the children region, byte for byte");
77 return ok ? 0 : 1;
78}
See also: frame codec · data-format reference · protocol TLVs.