Files
reasampler/src/core/package/package_format.h
T
daniel 043558a54d Land src/core/package: the pure RSBK container — format ladder, JSON manifest, framing/layout codec
Two-integer ladder (formatVersion/minReaderVersion), bare-name-only entries
validated on encode and decode, prefix decode that proves exact file size
without ever reading a payload.
2026-08-02 17:19:29 -04:00

81 lines
3.8 KiB
C++

#pragma once
// package_format — the RSBK bank-package contract: magic, the version ladder,
// the readability classification, and the entry-name rule. Pure: standard
// library only. The framing codec that acts on this contract is bank_package;
// the manifest grammar is package_manifest.
#include <cstdint>
#include <string>
namespace reasampler::package {
// Version ladder for the RSBK container (read-and-validate, like the origin
// ledger's "v"):
//
// format 1 (current) magic "RSBK" | u32 formatVersion | u32 minReaderVersion
// | u32 len + writer semver | u32 len + JSON manifest
// | payloads concatenated in manifest entry order.
// All integers little-endian.
//
// Two integers, two jobs: formatVersion is what the writer emitted (monotonic,
// bumped on ANY change); minReaderVersion is the oldest reader that can read the
// package safely (bumped only on a STRUCTURAL change — a field's meaning shifts,
// a section is removed, framing moves; an additive change — a new optional
// manifest key, a new enum value with a defined degrade — leaves it alone). The
// reader's whole rule: read iff minReaderVersion <= kPackageFormatVersion.
// formatVersion beyond that is message text and log material only.
//
// FROZEN FOR ALL FUTURE VERSIONS: the fields through the writer semver. A
// too-new package must still yield the writer's version so the refusal can name
// what to install — a structural change may rearrange anything after the semver,
// never before it.
inline constexpr char kPackageMagic[4] = {'R', 'S', 'B', 'K'};
inline constexpr std::uint32_t kPackageFormatVersion = 1;
inline constexpr std::uint32_t kPackageMinReaderVersion = 1;
// Hostile-input allocation caps (error signaled, never a multi-gigabyte
// allocation off a forged length field). Generous against real content: a
// semver is ~10 bytes; a manifest for hundreds of samples is well under 1 MB.
inline constexpr std::uint32_t kMaxWriterVersionBytes = 64;
inline constexpr std::uint32_t kMaxManifestBytes = 64u * 1024u * 1024u;
inline constexpr std::size_t kMaxEntryNameBytes = 255;
// The three-way read verdict (the FutureVersion precedent): TooNew refuses the
// whole package before anything is produced; Malformed is a header no honest
// writer emits. Also the status of a full prefix decode in bank_package.
enum class PackageReadability {
Readable,
TooNew,
Malformed,
};
// Classify a stored header pair against THIS build's ladder. minReaderVersion
// above kPackageFormatVersion is TooNew; a zero version or minReader >
// formatVersion is Malformed (a writer cannot require a reader newer than what
// it wrote).
PackageReadability classifyPackageVersion(std::uint32_t formatVersion,
std::uint32_t minReaderVersion);
// The entry-name rule that makes path expression structurally impossible: a
// bare file name only. Rejects empty, ".", any ".." occurrence, any '/', '\\'
// or ':' (which also bans every absolute form — drive, UNC, rooted), and names
// over kMaxEntryNameBytes. Enforced on encode AND decode by package_manifest.
bool isValidEntryName(const std::string& name);
// The fixed header, informational semver included. writerVersion is
// version::stampVersion() on the write side — it exists so a TooNew refusal can
// tell the user which build to install; it never gates.
struct PackageHeader {
std::uint32_t formatVersion = kPackageFormatVersion;
std::uint32_t minReaderVersion = kPackageMinReaderVersion;
std::string writerVersion;
bool operator==(const PackageHeader& o) const {
return formatVersion == o.formatVersion &&
minReaderVersion == o.minReaderVersion &&
writerVersion == o.writerVersion;
}
};
} // namespace reasampler::package