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. children is populated and payload empty 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 <span>
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 Report expectation @p what and record a failure on @p ok. */
38void check(bool& ok, bool cond, const char* what) {
39    std::printf("  [%s] %s\n", cond ? "ok" : "FAIL", what);
40    ok = ok && cond;
41}
42
43}  // namespace
44
45int main() {
46    bool ok = true;
47    const std::vector<std::byte> x{std::byte{0x11}, std::byte{0x22}};
48    const std::vector<std::byte> y{std::byte{0x33}, std::byte{0x44}};
49
50    tlv_t point;
51    point.type = type_t::POINT;
52    point.opt.pl = true;  // structured: the body is children, not bytes
53    point.children.push_back(tlv_t{.type = type_t::VALUE, .payload = std::span(x)});
54    point.children.push_back(tlv_t{.type = type_t::VALUE, .payload = std::span(y)});
55
56    std::vector<std::byte> frame = tr::wire::encode(point);
57    std::printf("POINT{VALUE,VALUE} is %zu bytes: 4 header + 2 x (4 header + 2 body)\n",
58                frame.size());
59
60    const auto structured = tr::wire::decode(frame);
61    check(ok, structured.has_value(), "the structured frame decodes");
62    check(ok, structured && structured->children.size() == 2, "PL = 1 gives two children");
63    check(ok, structured && structured->payload.empty(), "and no opaque payload at all");
64
65    // The SAME body bytes, read with PL cleared. Byte 1 is the root's opt (see
66    // wire_tlv_header): clearing bit 6 tells the decoder "this body is payload".
67    std::vector<std::byte> as_opaque = frame;
68    as_opaque[1] = static_cast<std::byte>(static_cast<std::uint8_t>(as_opaque[1]) & 0xBFu);
69
70    const auto opaque = tr::wire::decode(as_opaque);
71    check(ok, opaque.has_value(), "the same bytes with PL = 0 also decode");
72    check(ok, opaque && opaque->children.empty(), "PL = 0 gives no children");
73    check(ok, opaque && opaque->payload.size() == frame.size() - 4,
74          "the payload IS the children region, byte for byte");
75    return ok ? 0 : 1;
76}

See also: frame codec · data-format reference · protocol TLVs.