What the frame reader refuses, and who each refusal accuses (L2/L3 codec)

tlv_node_t::over answers std::expected<tlv_node_t, err_t>, and the error side is the RFC-0002 registry (status module) rather than a decode-private vocabulary — so err_path, severity and disposition come for free. Four verdicts are reachable, and they do not all mean the same kind of thing.

Three are permanent accusations about the bytes: FRAME_TRUNCATED (the frame stops mid-TLV), FRAME_INVALID (a clean TLV with bytes left over — or a reserved opt bit set) and FRAME_CRC_FAIL (see the trailer).

What to notice

  • over consumes the whole input. A single trailing byte after a well-formed TLV is FRAME_INVALID, not a successful parse of the prefix. A stream reader frames first and decodes exactly one TLV’s worth.

  • A reserved opt bit is checked before anything is believed. Bits 7 and 0 are MUST-be-zero; a peer that sets one has said something this version cannot interpret, and the frame is refused rather than partially honoured.

  • TLV_NESTING_TOO_DEEP is different in kind. It means “exceeds this receiver’s decode resources” (RFC-0006, CONTEXT.md §Resource bound). The structural walk starts in inline stack slots and spills into a caller-injected block_source_t; tlv_node_t::over(bytes, mem::null_source()) is the spelling of “no spill at all”, and the same bytes validate once the caller injects a source that can serve. The bound is the injected resource, and two receivers may legitimately disagree about one frame.

  • The example asserts the verdict, never a depth number. There is no constant to assert. The inline slot count is a tuning knob whose overflow changes cost, not behaviour — see the arena decode for the same seam used deliberately, and note that neither a “depth cap” nor a kMaxDepth exists to point at.

  • 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 — the four verdicts `tlv_node_t::over` returns, and who each one accuses.
 9 *
10 * `tlv_node_t::over` answers `std::expected<tlv_node_t, err_t>`, and the error side is the RFC-0002
11 * registry
12 * (`libtracer/error.hpp`) rather than a decode-private vocabulary. The three a reader hits
13 * first are permanent accusations against the bytes: `FRAME_TRUNCATED` (the frame stops
14 * mid-TLV), `FRAME_INVALID` (well-formed prefix, trailing bytes after it — or a reserved
15 * `opt` bit set) and `FRAME_CRC_FAIL` (see `wire_trailer`).
16 *
17 * The fourth is different in kind. `TLV_NESTING_TOO_DEEP` means "exceeds **this receiver's**
18 * decode resources" (RFC-0006, `CONTEXT.md` §Resource bound): the walk stack starts in inline
19 * slots and spills into a caller-injected `block_source_t`, so the SAME bytes that a
20 * heap-spilled decode accepts are refused by `tlv_node_t::over(bytes, mem::null_source())` — the
21 * spelling of "no spill at all". Nothing here asserts a depth number, because there is no
22 * constant to assert: the bound is the source the caller passed.
23 *
24 * Runs under ctest as `example_wire_decode_refusals`; returns non-zero on any failed check.
25 */
26
27#include <cstddef>
28#include <cstdint>
29#include <cstdio>
30#include <span>
31#include <utility>
32#include <vector>
33
34#include "libtracer/tracer.hpp"
35
36namespace {
37
38using tr::wire::err_t;
39using tr::wire::tlv_t;
40using tr::wire::type_t;
41
42/** @brief Report expectation @p what and record a failure on @p ok. */
43void check(bool& ok, bool cond, const char* what) {
44    std::printf("  [%s] %s\n", cond ? "ok" : "FAIL", what);
45    ok = ok && cond;
46}
47
48/** @brief `depth` nested structured `POINT`s wrapping one `VALUE` over @p body. */
49tlv_t nest(std::span<const std::byte> body, int depth) {
50    tlv_t cur{.type = type_t::VALUE, .payload = body};
51    for (int i = 0; i < depth; ++i) {
52        tlv_t parent;
53        parent.type = type_t::POINT;
54        parent.opt.pl = true;
55        parent.children.push_back(std::move(cur));
56        cur = std::move(parent);
57    }
58    return cur;
59}
60
61}  // namespace
62
63int main() {
64    bool ok = true;
65    const std::vector<std::byte> body(4, std::byte{0x5A});
66
67    std::vector<std::byte> frame = tr::wire::encode(tlv_t{.type = type_t::VALUE, .payload = body});
68    check(ok, tr::wire::tlv_node_t::over(frame).has_value(), "the reference frame decodes cleanly");
69
70    std::vector<std::byte> short_frame(frame.begin(), frame.end() - 1);
71    const auto truncated = tr::wire::tlv_node_t::over(short_frame);
72    check(ok, !truncated && truncated.error() == err_t::FRAME_TRUNCATED,
73          "one byte short is FRAME_TRUNCATED");
74
75    std::vector<std::byte> extra = frame;
76    extra.push_back(std::byte{0x00});
77    const auto trailing = tr::wire::tlv_node_t::over(extra);
78    check(ok, !trailing && trailing.error() == err_t::FRAME_INVALID,
79          "a byte after the one TLV is FRAME_INVALID — decode consumes the whole input");
80
81    std::vector<std::byte> reserved = frame;
82    reserved[1] |= std::byte{0x01};  // bit 0 of opt is reserved-MUST-be-zero
83    const auto bad_opt = tr::wire::tlv_node_t::over(reserved);
84    check(ok, !bad_opt && bad_opt.error() == err_t::FRAME_INVALID,
85          "a set reserved opt bit is FRAME_INVALID");
86
87    // The receiver-resource verdict. Same bytes, two injected spill sources, two answers.
88    const std::vector<std::byte> deep = tr::wire::encode(nest(body, 16));
89    std::printf("a 16-deep frame is %zu bytes\n", deep.size());
90    const auto no_spill = tr::wire::tlv_node_t::over(deep, tr::mem::null_source());
91    check(ok, !no_spill && no_spill.error() == err_t::TLV_NESTING_TOO_DEEP,
92          "with a source that serves nothing, the walk stack cannot spill");
93    check(ok, tr::wire::tlv_node_t::over(deep, tr::mem::heap_source()).has_value(),
94          "and the very same bytes decode once the caller injects one that can");
95    return ok ? 0 : 1;
96}

See also: frame codec · status module · data-format reference.