BlackboxReader — THE DECODER. It ships in the same chunk as the encoder, because a format nothing can read is not a record: the first time a blackbox genuinely matters is a competition afternoon, and a file that cannot be opened that afternoon is worth exac…
This header declares 3 types (19 members) and 1 free function.
Extracted from include/shulib/diag/blackbox_reader.hpp — this page is that header's documentation, reformatted, so it cannot disagree with the code. Prose about how to think about the API lives in the user guide; worked recipes live in the cookbook; this page is the complete, mechanical list of what exists.
enum class ReadStatusreadStatusName— free functionclass BlackboxReader
enum class ReadStatus : std::uint8_tWhy a file is or is not readable by THIS build. Anything other than Ok means no frames are delivered at all — the refuse-don't-misread rule.
enum class, declared at include/shulib/diag/blackbox_reader.hpp:68.
Ok = 0header parsed and this build can read this version
enumerator, declared at include/shulib/diag/blackbox_reader.hpp:69.
Empty = 1nothing at all was written (a run with nothing to say)
enumerator, declared at include/shulib/diag/blackbox_reader.hpp:70.
HeaderTruncated = 2fewer bytes than a complete header
enumerator, declared at include/shulib/diag/blackbox_reader.hpp:71.
BadMagic = 3not a shulib blackbox
enumerator, declared at include/shulib/diag/blackbox_reader.hpp:72.
UnsupportedVersion = 4a version this build was not written for — REFUSED
enumerator, declared at include/shulib/diag/blackbox_reader.hpp:73.
LayoutMismatch = 5right version, wrong record width — REFUSED (header note)
enumerator, declared at include/shulib/diag/blackbox_reader.hpp:74.
[[nodiscard]] constexpr const char* readStatusName(ReadStatus s) noexceptThe §18.5 spelling of a ReadStatus, for messages. Never returns null.
free function, declared at include/shulib/diag/blackbox_reader.hpp:78.
class BlackboxReaderThe decoder for a blackbox file: point it at bytes the caller already holds — it BORROWS the span, copies nothing and allocates nothing — and pull frames with next() until that returns false. Three rules it will not bend. A file this build cannot interpret is REFUSED WHOLE: status() says why and not one frame is delivered, because a number read wrongly but confidently sends an investigation somewhere false. It never throws and never reads past the end, which matters most for the damaged files that matter most. And truncation is a RESULT, not an error: every frame before the cut is delivered, truncated() is true, sawEnd() is false — the exact signature a brownout leaves. Frame kinds this build does not know are skipped by their declared length and counted in skippedFrames(), which is what lets a v1 reader survive a later writer. There is deliberately NO per-frame checksum, so a bit flip landing on a merely-wrong value inside a known frame decodes silently as that value (measured, not assumed — the header states the boundary in full). A consumer needing end-to-end integrity must add its own check rather than inherit one from here that does not exist.
class, declared at include/shulib/diag/blackbox_reader.hpp:104.
explicit BlackboxReader(std::span<const std::byte> file) noexceptParse the header of file and position at the first frame. Never throws; the verdict is in status(). The span must outlive the reader.
function, declared at include/shulib/diag/blackbox_reader.hpp:115.
[[nodiscard]] ReadStatus status() const noexceptWhether this build can read this file at all (header note, rule 1).
function, declared at include/shulib/diag/blackbox_reader.hpp:142.
[[nodiscard]] bool usable() const noexceptShorthand for status() == Ok.
function, declared at include/shulib/diag/blackbox_reader.hpp:144.
[[nodiscard]] const BlackboxHeader& header() const noexceptThe decoded header. Meaningful for Ok and UnsupportedVersion (a refused file still tells you which version it claims to be — that is how you find the build that wrote it).
function, declared at include/shulib/diag/blackbox_reader.hpp:148.
[[nodiscard]] bool next(Frame& out) noexceptDeliver the next KNOWN frame, skipping (and counting) unknown ones. Returns false at the end of the file, on a cut, or when the file is not usable.
function, declared at include/shulib/diag/blackbox_reader.hpp:152.
[[nodiscard]] bool truncated() const noexceptTrue when the file ended mid-frame — the run was cut short (brownout, pulled card, a program that never closed). The frames delivered before the cut are all valid; this says the story stops there.
function, declared at include/shulib/diag/blackbox_reader.hpp:193.
[[nodiscard]] std::uint8_t truncatedFrameType() const noexceptThe raw frame-type byte of the frame that was cut, when truncated() (0 if the cut fell in a frame prefix).
function, declared at include/shulib/diag/blackbox_reader.hpp:196.
[[nodiscard]] bool sawEnd() const noexceptTrue once the graceful-end frame has been delivered. Its ABSENCE at the end of iteration is the honest signal that the run did not close cleanly.
function, declared at include/shulib/diag/blackbox_reader.hpp:199.
[[nodiscard]] std::uint32_t framesRead() const noexceptKnown frames delivered so far.
function, declared at include/shulib/diag/blackbox_reader.hpp:201.
[[nodiscard]] std::uint32_t skippedFrames() const noexceptFrames skipped because this build does not know their type, or because a known type carried a payload of the wrong size (a corruption signal that must not stop the rest of the file from being read).
function, declared at include/shulib/diag/blackbox_reader.hpp:205.
[[nodiscard]] std::size_t bytesConsumed() const noexceptBytes consumed so far, including the header.
function, declared at include/shulib/diag/blackbox_reader.hpp:207.
struct FrameOne frame as delivered by next(): its type and a view of its payload, valid for as long as the caller's file buffer is.
struct, declared at include/shulib/diag/blackbox_reader.hpp:108.
FrameType type = FrameType::Tickthe frame's kind (known types only)
field, declared at include/shulib/diag/blackbox_reader.hpp:109.
std::span<const std::byte> payload{}exactly the declared payload bytes
field, declared at include/shulib/diag/blackbox_reader.hpp:110.
The header opens with the reasoning behind these shapes. It is reproduced here in full because a reference that only lists signatures teaches nobody why.
The header’s own reasoning — 55 lines, click to expand
BlackboxReader — THE DECODER. It ships in the same chunk as the encoder, because a
format nothing can read is not a record: the first time a blackbox genuinely matters
is a competition afternoon, and a file that cannot be opened that afternoon is worth
exactly nothing. (WS13, chunk E1; format in diag/blackbox_format.hpp.)
It is also the thing that makes the encoder TESTABLE. The round trip — encode a known
stream, decode it, compare field by field — is the chunk's central test, and it needs
both halves to exist in the same suite.
── Three rules it will not bend ────────────────────────────────────────────────────
1. REFUSE, NEVER MISREAD. A file whose version this build does not know is rejected
whole; so is a file whose self-declared tick width disagrees with this build's.
A wrong number read confidently is worse than no number: it sends an investigation
somewhere false with full confidence. (The width cross-check exists for one very
human failure: changing the layout and forgetting to bump the version.)
2. NEVER THROW, NEVER READ PAST THE END. The files that matter most are the damaged
ones — a run that died mid-write, a card pulled at the wrong moment. Every read
goes through a bounds-latching cursor, and a non-finite heading (which the Angle
type refuses by precondition) decodes to zero with `corrupt()` raised rather than
aborting the program that is trying to read the evidence.
3. TRUNCATION IS A RESULT, NOT AN ERROR. A cut file decodes up to the cut and SAYS
SO: truncated() is true, the frames before the cut are all delivered, and sawEnd()
is false because the graceful-end frame never arrived. That combination — good
frames, no end stamp — is exactly what a brownout leaves behind.
Forward compatibility: a frame type this build does not know is SKIPPED BY ITS
DECLARED LENGTH and counted in skippedFrames(). That is what lets a v1 reader survive
a later writer that appended a frame kind, without ever guessing at its content.
── The boundary of rule 1, stated so nobody over-trusts it ─────────────────────────
"Refuse, never misread" is about INTERPRETATION, not INTEGRITY, and the difference
matters to anyone building on this format (H1's SHUL/2 in particular).
What is detected: a file that is not a blackbox (BadMagic), a version this build does
not know (UnsupportedVersion), a layout whose declared widths disagree (LayoutMismatch),
a cut (truncated()), a frame kind this build lacks (skippedFrames()), and a decoded
value that is implausible on its face — a non-finite heading raises corrupt() rather
than tripping Angle's precondition.
What is NOT detected: there is deliberately NO per-frame checksum, so a bit flip inside
a frame this build CAN interpret, landing on a value that is merely wrong rather than
impossible, decodes silently as that wrong value. That was measured, not assumed:
flipping a payload byte mid-file leaves status Ok and every frame delivered.
This is a considered trade, not an oversight — a CRC costs bytes and cycles on every
tick of a fixed-width budget, and the SD card already carries hardware ECC beneath us.
Recorded because the honest scope of a guarantee is part of the guarantee: if a future
consumer needs end-to-end integrity (a wire protocol over a lossy link is the obvious
case, and F9 is exactly that), it must add its own frame check rather than inherit one
from here that does not exist. (Boundary verified during E1's independent review.)
Allocation-free and PROS-free, like everything in this tree: it reads a span the
caller already holds (a whole file loaded into a buffer, or the bytes a test just
captured), and iterates.