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 <iterator>
28#include <span>
29#include <vector>
30
31#include "libtracer/tlv_view.hpp"
32#include "libtracer/tracer.hpp"
33
34namespace {
35
36using tr::wire::tlv_t;
37using tr::wire::type_t;
38
39/** @brief Report expectation @p what and record a failure on @p ok. */
40void check(bool& ok, bool cond, const char* what) {
41    std::printf("  [%s] %s\n", cond ? "ok" : "FAIL", what);
42    ok = ok && cond;
43}
44
45}  // namespace
46
47int main() {
48    bool ok = true;
49    const std::vector<std::byte> x{std::byte{0x11}, std::byte{0x22}};
50    const std::vector<std::byte> y{std::byte{0x33}, std::byte{0x44}};
51
52    tlv_t point;
53    point.type = type_t::POINT;
54    point.opt.pl = true;
55    point.opt.cr = true;  // a CRC trailer, so verify() has real work to do
56    point.children.push_back(tlv_t{.type = type_t::VALUE, .payload = std::span(x)});
57    point.children.push_back(tlv_t{.type = type_t::VALUE, .payload = std::span(y)});
58    const std::vector<std::byte> frame = tr::wire::encode(point);
59
60    // Split at byte 6: the root header is bytes 0-3, so this cuts the FIRST CHILD's header
61    // in half. Two segments, two links, one logical frame — assembled by chaining.
62    const std::size_t cut = 6;
63    tr::view::rope_t rope;
64    rope.append(*tr::view::over_bytes(std::span(frame).first(cut)));
65    rope.append(*tr::view::over_bytes(std::span(frame).subspan(cut)));
66    std::printf("frame of %zu bytes delivered as %zu links, split mid-header at %zu\n",
67                frame.size(), rope.link_count(), cut);
68
69    const auto lazy = tr::wire::tlv_view_t::over(rope);
70    check(ok, lazy.has_value(), "tlv_view_t::over anchors the bounds without walking the body");
71    if (!lazy) return 1;
72    check(ok, lazy->type() == type_t::POINT, "the root type reads back");
73    check(ok, lazy->structured() && lazy->body_size() == frame.size() - 4 - 4,
74          "as does the body size — header and CRC trailer excluded");
75
76    auto kids = lazy->children();
77    const auto first = kids.next();
78    const auto second = kids.next();
79    check(ok, first && *first && (*first)->type() == type_t::VALUE, "one child header at a time");
80    check(ok, second && *second, "then the next, straight across the link boundary");
81    const auto past_end = kids.next();
82    check(ok, past_end && !*past_end && kids.exhausted(), "and the region ends cleanly");
83
84    check(ok, lazy->verify().has_value(), "verify() is the deferred CRC walk, run on demand");
85
86    const auto flat = lazy->materialize();
87    check(ok, flat.has_value(), "materialize() is the one explicit copy point");
88    check(ok, flat && std::ranges::distance(flat->root.children()) == 2,
89          "and it yields the same validated node `tlv_node_t::over` would");
90    return ok ? 0 : 1;
91}

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