#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 #include 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