tlv_view_t: a frame whose bytes are scattered (L1 + L2/L3)

This is the hinge between the two domains. Memory composition and TLV composition are orthogonal (CONTEXT.md §Two compositions), so a rope link boundary may fall anywhere — including in the middle of a TLV header — and the decoder must not care. A CAN reassembly group and a fragmented WebSocket message both arrive exactly like this.

tlv_view_t::over (ADR-0053) adopts the rope as one lazy TLV: it parses the root header with the CRC walk deferred and requires the declared total to match the rope’s length. Nothing that is not accessed is ever decoded.

What to notice

  • The split is deliberately mid-header. The example cuts the frame at byte 6, inside the first child’s header, and the child still materializes. A frame arriving as the links a transport happened to receive it in is the normal case, not the awkward one — and it crosses the receiver seam as the rope it already is, never flattened at ingress.

  • Children come one header at a time. children().next() parses exactly one child header per call and yields it as its own tlv_view_t over a subrope. A sibling nobody looks at has its payload walked by nobody.

  • Validation is staged. over anchors the bounds; child headers are grammar-checked as they are stepped over; verify() is the deferred CRC walk, run by whichever consumer wants the integrity guarantee. An endpoint applying several members as one transaction verifies first and applies second.

  • materialize() is the single explicit copy point. Everything the lazy tier deferred is paid there, once, by the consumer that asked: one contiguous copy plus the full grammar walk. A hop that only forwards never calls it — it hands wire() or a body() subrope onward and the links stay refcount-alive across the hop.

  • Nothing here is conditional — the target builds and runs under every CI leg, net plane on or off.

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 — `tlv_view_t`: decoding a frame whose bytes are scattered across a rope.
 9 *
10 * This is the L1↔L2 hinge. Memory composition and TLV composition are orthogonal
11 * (`CONTEXT.md` §Two compositions), so a link boundary may fall ANYWHERE — including
12 * mid-header — and the decoder must not care. `tlv_view_t::over` (ADR-0053) adopts the rope
13 * as one lazy TLV: it parses the root header with the CRC walk DEFERRED, and nothing that is
14 * not accessed is ever decoded.
15 *
16 * The frame here is split deliberately inside the first child's header, so the two links a
17 * transport happened to receive it in are not a TLV boundary at all. `children().next()`
18 * still materializes one child at a time, `verify()` is the deferred CRC walk run when a
19 * consumer wants it, and `materialize()` is the SINGLE explicit copy point — everything the
20 * lazy tier deferred, paid once, by whoever asked.
21 *
22 * Runs under ctest as `example_wire_lazy_view`; returns non-zero on any failed check.
23 */
24
25#include <cstddef>
26#include <cstdio>
27#include <span>
28#include <vector>
29
30#include "libtracer/tlv_view.hpp"
31#include "libtracer/tracer.hpp"
32
33namespace {
34
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;
54    point.opt.cr = true;  // a CRC trailer, so verify() has real work to do
55    point.children.push_back(tlv_t{.type = type_t::VALUE, .payload = std::span(x)});
56    point.children.push_back(tlv_t{.type = type_t::VALUE, .payload = std::span(y)});
57    const std::vector<std::byte> frame = tr::wire::encode(point);
58
59    // Split at byte 6: the root header is bytes 0-3, so this cuts the FIRST CHILD's header
60    // in half. Two segments, two links, one logical frame — assembled by chaining.
61    const std::size_t cut = 6;
62    tr::view::rope_t rope;
63    rope.append(*tr::view::over_bytes(std::span(frame).first(cut)));
64    rope.append(*tr::view::over_bytes(std::span(frame).subspan(cut)));
65    std::printf("frame of %zu bytes delivered as %zu links, split mid-header at %zu\n",
66                frame.size(), rope.link_count(), cut);
67
68    const auto lazy = tr::wire::tlv_view_t::over(rope);
69    check(ok, lazy.has_value(), "tlv_view_t::over anchors the bounds without walking the body");
70    if (!lazy) return 1;
71    check(ok, lazy->type() == type_t::POINT, "the root type reads back");
72    check(ok, lazy->structured() && lazy->body_size() == frame.size() - 4 - 4,
73          "as does the body size — header and CRC trailer excluded");
74
75    auto kids = lazy->children();
76    const auto first = kids.next();
77    const auto second = kids.next();
78    check(ok, first && *first && (*first)->type() == type_t::VALUE, "one child header at a time");
79    check(ok, second && *second, "then the next, straight across the link boundary");
80    const auto past_end = kids.next();
81    check(ok, past_end && !*past_end && kids.exhausted(), "and the region ends cleanly");
82
83    check(ok, lazy->verify().has_value(), "verify() is the deferred CRC walk, run on demand");
84
85    const auto flat = lazy->materialize();
86    check(ok, flat.has_value(), "materialize() is the one explicit copy point");
87    check(ok, flat && flat->root.children.size() == 2,
88          "and it yields the same eager tree an ordinary decode would");
89    return ok ? 0 : 1;
90}

See also: views module · frame codec · views & ownership reference · rope scatter-gather.