// sample_usage.cpp — see sample_usage.h. Pure: standard library only. #include "sample_usage.h" #include #include #include namespace reasampler { namespace { constexpr const char* kMagic = "rsusage1"; // Append one length-prefixed field: ':' . The same wire idiom as // assignment_request / provenance — one grammar across every ext-state seam. void putField(std::string& out, const std::string& field) { out += std::to_string(field.size()); out += ':'; out += field; } // Bounds-checked cursor over the encoded string (the assignment_request Cursor, trimmed // to the two field kinds this record needs). A short read latches ok_ false. class Cursor { public: explicit Cursor(const std::string& s) : s_(s) {} bool ok() const { return ok_; } bool atEnd() const { return pos_ >= s_.size(); } bool field(std::string& out) { if (!ok_) return false; const std::size_t colon = s_.find(':', pos_); if (colon == std::string::npos) return fail(); if (colon == pos_) return fail(); // empty length token if (colon - pos_ > 20u) return fail(); // SIZE_MAX is 20 decimal digits std::size_t len = 0; for (std::size_t i = pos_; i < colon; ++i) { const char c = s_[i]; if (c < '0' || c > '9') return fail(); const std::size_t digit = static_cast(c - '0'); if (len > (std::numeric_limits::max() - digit) / 10u) return fail(); len = len * 10u + digit; } const std::size_t start = colon + 1; if (start > s_.size() || len > s_.size() - start) return fail(); out.assign(s_, start, len); pos_ = start + len; return true; } // Consumes an exact literal at the cursor (the magic tag). Fails if absent. bool literal(const char* lit) { if (!ok_) return false; std::size_t i = 0; for (; lit[i] != '\0'; ++i) { if (pos_ + i >= s_.size() || s_[pos_ + i] != lit[i]) return fail(); } pos_ += i; return true; } // A length-prefixed unsigned decimal (the hold count). Fails on empty, non-digit, // or a value past a sane ceiling (a record cannot hold more entries than bytes). bool fieldCount(std::size_t& out) { std::string f; if (!field(f)) return false; if (f.empty() || f.size() > 10u) return fail(); std::size_t v = 0; for (const char c : f) { if (c < '0' || c > '9') return fail(); v = v * 10u + static_cast(c - '0'); } out = v; return true; } private: bool fail() { ok_ = false; return false; } const std::string& s_; std::size_t pos_ = 0; bool ok_ = true; }; } // namespace std::string encodeUsageRecord(const UsageRecord& rec) { std::string out = kMagic; putField(out, rec.trackGuid); putField(out, rec.ownerNonce); putField(out, rec.unioned ? "1" : "0"); putField(out, std::to_string(rec.holds.size())); for (const UsageHold& h : rec.holds) { putField(out, h.sampleId); putField(out, h.relativePath); } return out; } std::optional decodeUsageRecord(const std::string& wire) { Cursor c(wire); if (!c.literal(kMagic)) return std::nullopt; UsageRecord rec; if (!c.field(rec.trackGuid)) return std::nullopt; if (!c.field(rec.ownerNonce)) return std::nullopt; std::string unionedField; if (!c.field(unionedField)) return std::nullopt; if (unionedField == "1") rec.unioned = true; else if (unionedField == "0") rec.unioned = false; else return std::nullopt; // anything else is corruption -> reject whole std::size_t count = 0; if (!c.fieldCount(count)) return std::nullopt; // Each hold needs at least 4 wire bytes ("0:0:"), so a count past wire.size()/4 is // provably bogus — reject before looping rather than iterating a crafted huge count. if (count > wire.size() / 4u + 1u) return std::nullopt; rec.holds.reserve(count); for (std::size_t i = 0; i < count; ++i) { UsageHold h; if (!c.field(h.sampleId)) return std::nullopt; if (!c.field(h.relativePath)) return std::nullopt; rec.holds.push_back(std::move(h)); } if (!c.ok() || !c.atEnd()) return std::nullopt; // trailing garbage -> reject whole return rec; } UsagePublishPlan planUsagePublish(const std::optional& existing, const UsageRecord& mine) { UsagePublishPlan plan; // The written form of "just mine": mine's identity + holds, unioned=false (the plan // computes the flag; a sole-writer record is un-poisoned). UsageRecord cleanMine = mine; cleanMine.unioned = false; plan.wire = encodeUsageRecord(cleanMine); if (!existing || existing->empty()) { // Fresh key — write mine. return plan; } const std::optional theirs = decodeUsageRecord(*existing); if (!theirs) { // Undecodable existing value under MY key: corruption (a sibling sharing // this key via copy always writes decodable records). REMINT rather than // overwrite: writing mine over the corrupt key would clear the prune-side // abort, but a same-key sibling B's holds would then be unprotected until // B publishes again. Leaving the corrupt key in place keeps the prune-side // abort firing (foldUsageRecords.abortPrune) so the window where B's holds // might be unprotected can never resolve toward delete. Mine is published // under the new key that remint produces. // NOTE (>16 MB gap): readReasamplerExtState returning nullopt for a value // larger than 16 MB is indistinguishable from "absent" at the publish site; // that narrow case takes the fresh-write branch above rather than remint. // Both outcomes are safe (fresh write is also correct for a truly absent key); // the gap is documented in the header's fail-safe list. plan.remint = true; return plan; } const bool nonceMatch = !mine.ownerNonce.empty() && theirs->ownerNonce == mine.ownerNonce; if (nonceMatch && !theirs->unioned) { // Exactly THIS incarnation wrote the key (the per-lifetime nonce is the exact // ownership proof — a same-track sibling's byte-identical hold set can NOT pass // this test, its nonce differs) AND no other writer has ever unioned into it, // so the content is provably all mine. Clean replace: released holds drop. if (plan.wire == *existing) plan.skipWrite = true; // idle reload tick return plan; } if (theirs->trackGuid == mine.trackGuid || (nonceMatch && theirs->unioned)) { // A foreign writer on MY OWN track (a same-track copy-sibling, or my own // last-session record — indistinguishable by construction), or a record I // wrote last but that carries unioned holds from an earlier multi-writer // merge. Either way no hold in it may be dropped by me — union, existing- // first, de-duped, and the record is (or stays) POISONED unioned=true so no // future nonce-matching write can clean-replace a sibling's holds away. UsageRecord merged; merged.trackGuid = mine.trackGuid; merged.ownerNonce = mine.ownerNonce; merged.unioned = true; merged.holds = theirs->holds; for (const UsageHold& h : mine.holds) { bool dup = false; for (const UsageHold& e : merged.holds) { if (e == h) { dup = true; break; } } if (!dup) merged.holds.push_back(h); } if (theirs->unioned && merged.holds == theirs->holds) { // Already poisoned and the union adds nothing — the write would flip only // the ownerNonce. Skip the redundant ext-state churn. (A false->true // unioned flip is NEVER skipped: it is the poison that protects the other // writer's holds from the last writer's future clean replace.) plan.skipWrite = true; } plan.wire = encodeUsageRecord(merged); return plan; } // Foreign value from ANOTHER track: this instance is a cross-track copy (or was // moved). Take a fresh identity; never overwrite the other's record. plan.remint = true; return plan; } std::vector usageHeldPaths( const std::vector& records, const std::unordered_set& liveTrackGuids, bool anyInstanceLive) { std::vector out; std::unordered_set seen; // FAIL-SAFE NET: records exist but not one instance was identified live anywhere — // indistinguishable from an identity-matcher failure, so protect EVERY record's // paths rather than none (zero-identified must never degrade toward delete). const bool protectAll = !records.empty() && !anyInstanceLive; for (const UsageRecord& rec : records) { const bool live = protectAll || (rec.trackGuid.empty() ? anyInstanceLive : (liveTrackGuids.count(rec.trackGuid) != 0)); if (!live) continue; for (const UsageHold& h : rec.holds) { if (h.relativePath.empty()) continue; if (seen.insert(h.relativePath).second) out.push_back(h.relativePath); } } return out; } UsageFoldResult foldUsageRecords( const std::vector>& decoded, const std::unordered_set& liveTrackGuids, bool anyInstanceLive) { UsageFoldResult result; std::vector records; records.reserve(decoded.size()); for (const std::optional& rec : decoded) { if (!rec) { // A present-but-unreadable record: it may protect ANYTHING, so the prune // must halt outright. Belt-and-braces: return the PROTECT-ALL set (all // readable records' paths) so the fail-safe holds even under a future // caller that forgets to check abortPrune before using heldPaths. The // abort flag is still the authoritative signal; heldPaths is the // maximum-protection fallback. result.abortPrune = true; // Collect EVERY path from EVERY readable record, bypassing the liveness // filter entirely (on abort the protected set is unknowable, so every // decoded hold must be included regardless of track-guid membership). std::unordered_set seen; for (const std::optional& r : decoded) { if (!r) continue; for (const UsageHold& h : r->holds) { if (h.relativePath.empty()) continue; if (seen.insert(h.relativePath).second) result.heldPaths.push_back(h.relativePath); } } return result; } records.push_back(*rec); } result.heldPaths = usageHeldPaths(records, liveTrackGuids, anyInstanceLive); return result; } std::string toUpperAscii(const std::string& s) { std::string out = s; for (char& c : out) c = static_cast(std::toupper(static_cast(c))); return out; } bool identityMatches(const std::string& identity, const std::string& uidHexUpper, const std::string& nameUpper, const std::string& outputNameUpper) { if (identity.empty()) return false; const std::string up = toUpperAscii(identity); // Primary: the 32-hex class UID embedded in REAPER's fx_ident rendering. Not // guaranteed on every platform/REAPER build (byte-order of the rendered FUID vs // REAPER's hex is unverified on Windows COM layout), hence the two name nets below // — and the protect-all fold above them (see usageHeldPaths). if (!uidHexUpper.empty() && up.find(uidHexUpper) != std::string::npos) return true; // The module filename base ("REASAMPLER_9000") — fx_ident carries the .vst3 module // path, so this is the alternative that works in the common case (the display name // "REASAMPLER 9000", space-separated, can never match the filename form). if (!outputNameUpper.empty() && up.find(outputNameUpper) != std::string::npos) return true; // The factory display name — matches original_name / renamed-instance renderings. // Beta-substring over-protect is deliberate (see the header note): stable needles // are substrings of beta ones, widening protection only — never a delete. return !nameUpper.empty() && up.find(nameUpper) != std::string::npos; } } // namespace reasampler