subrope and the iovec egress (L1 views)

A rope’s sub-range is taken by trimming the covering links with subview and re-chaining them, so a window whose start falls mid-link costs one arithmetic pass and a few refcount bumps (reference 08). This is the region primitive of the lazy decode tier: a child TLV, a routed path suffix, a payload handed to the next hop is a subrope of the inbound frame, never a copy of it.

It also narrows ownership — the sub-rope keeps alive exactly the segments its window touches, and releases the rest.

What to notice

  • The window is not link-aligned, and does not need to be. subrope(2, 7) over three 4-byte links starts two bytes into the first and stops one byte into the third: the first link is trimmed to its tail, the last to its head, and the trimmed link still addresses the original segment’s bytes.

  • walk() is what a parser or a CRC does. It visits each link’s contiguous bytes in order, so a logically contiguous read never requires physically contiguous storage.

  • to_iovec is the transport-agnostic scatter-gather form. One span per link, straight into writev/sendmsg. Each transport lowers the rope to its native DMA — the substrate does not choose it.

  • try_to_iovec is the nothrow twin, and it exists for a reason. to_iovec’s reserve throws on OOM, which under -fno-exceptions is an abort(); a terminus that builds this table per send would take the node down on a fragmented heap. The nothrow form refills a caller’s vector and soft-fails instead.

  • 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 — `subrope` re-links a byte range; `to_iovec` hands it to the DMA.
 9 *
10 * A rope's sub-range is taken by TRIMMING the covering links with `view_t::subview` and
11 * re-chaining them, so a window whose start falls mid-link costs one arithmetic pass and a
12 * few refcount bumps — no bytes move (`docs/reference/08-views-and-ownership.md`). This is
13 * the region primitive of the lazy decode tier: a child TLV, a routed path suffix, or a
14 * payload handed to the next hop is a **subrope** of the inbound frame, never a copy of it.
15 * It also narrows ownership — the sub-rope keeps alive exactly the segments its window
16 * touches, and no others.
17 *
18 * The egress half is `to_iovec`: one span per link, straight into `writev`/`sendmsg`-style
19 * scatter-gather. `try_to_iovec` is its nothrow twin, refilling a caller's vector so a
20 * per-send table costs no allocation and cannot abort the node on a fragmented heap.
21 *
22 * Runs under ctest as `example_view_rope_subrope`; returns non-zero on any failed check.
23 */
24
25#include <cstddef>
26#include <cstdio>
27#include <optional>
28#include <span>
29#include <vector>
30
31#include "libtracer/tracer.hpp"
32
33namespace {
34
35/** @brief Report expectation @p what and record a failure on @p ok. */
36void check(bool& ok, bool cond, const char* what) {
37    std::printf("  [%s] %s\n", cond ? "ok" : "FAIL", what);
38    ok = ok && cond;
39}
40
41/** @brief A fresh 4-byte heap segment filled with @p fill, as a whole-segment view. */
42tr::view::view_t block(std::byte fill) {
43    tr::view::segment_ptr_t seg = tr::view::heap_alloc(4);
44    for (std::byte& b : seg->bytes) b = fill;
45    return tr::view::view_t::over(std::move(seg));
46}
47
48}  // namespace
49
50int main() {
51    bool ok = true;
52    tr::view::rope_t frame;
53    frame.append(block(std::byte{0xA0}));
54    frame.append(block(std::byte{0xB0}));
55    frame.append(block(std::byte{0xC0}));
56    check(ok, frame.link_count() == 3 && frame.total_length() == 12, "three 4-byte links");
57
58    // [2, 9): starts two bytes into link 0 and stops one byte into link 2.
59    const tr::view::rope_t region = frame.subrope(2, 7);
60    std::printf("subrope(2,7): %zu links, %zu bytes, no memcpy\n", region.link_count(),
61                region.total_length());
62    check(ok, region.total_length() == 7, "the window is exactly the bytes asked for");
63    check(ok, region.link_count() == 3, "spread over the three links it overlaps");
64    check(ok, region.links()[0].length == 2, "the first link is trimmed to its tail");
65    check(ok, region.links()[2].length == 1, "and the last to its head");
66    check(ok, region.links()[0].bytes().data() == frame.links()[0].bytes().data() + 2,
67          "the trimmed link still addresses the ORIGINAL segment's bytes");
68
69    // walk() visits each link's contiguous bytes in order — what a parser or a CRC does.
70    std::vector<std::byte> seen;
71    region.walk([&seen](std::span<const std::byte> chunk) {
72        seen.insert(seen.end(), chunk.begin(), chunk.end());
73    });
74    check(ok, seen.size() == 7 && seen[0] == std::byte{0xA0} && seen[6] == std::byte{0xC0},
75          "walk() sees one logical byte sequence across three segments");
76
77    std::vector<std::span<const std::byte>> iov;
78    check(ok, region.try_to_iovec(iov), "try_to_iovec fills the caller's table, nothrow");
79    check(ok, iov.size() == region.link_count(), "one span per link — the scatter-gather list");
80    check(ok, region.to_iovec().size() == iov.size(),
81          "to_iovec is the same list, freshly allocated");
82    return ok ? 0 : 1;
83}

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