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 owntlv_view_tover a subrope. A sibling nobody looks at has its payload walked by nobody.Validation is staged.
overanchors 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 handswire()or abody()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.