The trailer: an opt-in CRC and an opt-in timestamp (L2/L3 codec)

Integrity rides at the end of a TLV, and it is optional (reference 01 §trailer). opt.cr says a CRC-32C follows the body — encode computes it, decode checks it. opt.ts says a wire-time stamp follows. Neither is in the header, and a TLV that wants neither pays for neither, which is what makes the codec usable as a transparent byte router over live memory.

What to notice

  • A flipped byte is a verdict, not a guess. One XOR into the payload turns the frame into err_t::FRAME_CRC_FAIL. The example flips a byte it wrote itself, so the failure is about the bytes — a receiver never learns anything about intent from a CRC.

  • stamp_ts sets the bit and the value together. That is not a convenience; it is what keeps a stamped TLV away from encode’s refusal below. It writes the absolute (TF=0) form only, deliberately: the relative form is anchored to the parent’s stamp, and the anchorless-reject rule is a conformance gap the reference codec has not closed.

  • The refusal is loud. A TLV whose opt.ts is set by hand with no trailer value behind it makes encode return an empty vector (#1109) rather than emit a silently zero stamp. Empty is unambiguous: a well-formed TLV always carries at least its four-byte header, so nothing valid encodes to nothing.

  • The trailer timestamp is transport time. Sample-acquisition or control-deadline time is application-domain and rides the payload as a TIME child instead — a different clock with different meaning.

  • 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 optional trailer: a per-TLV CRC and a per-TLV timestamp.
 9 *
10 * Integrity is OPT-IN and it rides at the END, not in the header (`CONTEXT.md` §Wire format;
11 * `docs/reference/01-data-format.md` §trailer). `opt.cr` says a CRC-32C follows the body and
12 * `encode` recomputes it, so a single flipped payload byte turns the frame into
13 * `err_t::FRAME_CRC_FAIL` — a receiver's verdict about the bytes, not about the sender's
14 * intent. `opt.ts` says a wire-time stamp follows; `stamp_ts` sets the bit and the value
15 * together, which matters because a TLV claiming `opt.ts` with no value in hand is REFUSED
16 * loudly by `encode` (an empty vector, #1109) rather than emitted with a silent zero.
17 *
18 * Runs under ctest as `example_wire_trailer`; returns non-zero on any failed check.
19 */
20
21#include <cstddef>
22#include <cstdint>
23#include <cstdio>
24#include <span>
25#include <vector>
26
27#include "libtracer/tracer.hpp"
28
29namespace {
30
31using tr::wire::opt_t;
32using tr::wire::tlv_t;
33using tr::wire::type_t;
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}  // namespace
42
43int main() {
44    bool ok = true;
45    const std::vector<std::byte> body(4, std::byte{0x5A});
46
47    tlv_t v;
48    v.type = type_t::VALUE;
49    v.opt.cr = true;  // ask for the CRC trailer; encode computes it
50    v.payload = std::span(body);
51
52    std::vector<std::byte> frame = tr::wire::encode(v);
53    std::printf("VALUE with a CRC trailer: %zu bytes (4 header + 4 body + 4 CRC-32C)\n",
54                frame.size());
55    const auto good = tr::wire::decode(frame);
56    check(ok, good.has_value(), "the frame decodes");
57    check(ok, good && good->trailer && good->trailer->crc.has_value(), "and carries a CRC");
58
59    // One flipped payload byte. The CRC is what makes that a verdict rather than a guess.
60    std::vector<std::byte> corrupt = frame;
61    corrupt[4] ^= std::byte{0x01};
62    const auto bad = tr::wire::decode(corrupt);
63    check(ok, !bad && bad.error() == tr::wire::err_t::FRAME_CRC_FAIL,
64          "a single flipped body byte is FRAME_CRC_FAIL");
65
66    // The timestamp half. stamp_ts sets opt.ts, clears opt.tf and writes the value at once.
67    tlv_t stamped;
68    stamped.type = type_t::VALUE;
69    stamped.payload = std::span(body);
70    tr::wire::stamp_ts(stamped, 1'700'000'000'000'000'000);
71    const auto rt = tr::wire::decode(tr::wire::encode(stamped));
72    check(ok, rt && rt->trailer && rt->trailer->ts, "a stamped TLV round-trips its timestamp");
73    check(ok, rt && rt->trailer->ts->value == 1'700'000'000'000'000'000, "with the value intact");
74
75    // The loud refusal: the bit without the value is never emitted as a silent zero.
76    tlv_t claims_ts;
77    claims_ts.type = type_t::VALUE;
78    claims_ts.opt.ts = true;  // set by hand, with no trailer value behind it
79    claims_ts.payload = std::span(body);
80    check(ok, tr::wire::encode(claims_ts).empty(),
81          "opt.ts with no value refuses to encode at all (empty vector)");
82    return ok ? 0 : 1;
83}

See also: frame codec · wire-format bits · data-format reference · wire codec round-trip.