// sample_usage.cpp — see sample_usage.h. Pure: standard library only. #include "core/wire/sample_usage.h" #include #include "core/wire/wire.h" namespace reasampler::wire { namespace { constexpr const char* kMagic = "rsusage1"; using wire::putField; using Cursor = wire::Cursor; } // 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.fieldSizeT(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; UsageRecord cleanMine = mine; cleanMine.unioned = false; plan.wire = encodeUsageRecord(cleanMine); if (!existing || existing->empty()) { return plan; // fresh key — write mine } const std::optional theirs = decodeUsageRecord(*existing); if (!theirs) { // Corrupt value under my key: remint rather than overwrite. Overwriting // would clear the prune-side abort currently protecting a same-key // sibling's (possibly unprotected) holds; leaving the corrupt key in // place keeps that abort firing until the sibling republishes. 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 last and it was never unioned // by another writer — content is provably all mine. if (plan.wire == *existing) plan.skipWrite = true; // idle reload tick return plan; } if (theirs->trackGuid == mine.trackGuid || (nonceMatch && theirs->unioned)) { // Same-track sibling, my own last-session record, or an already-unioned // record — no hold in it may be dropped. Union, existing-first, de-duped, // poisoned unioned=true so a future clean replace can never drop it. 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 only // flip ownerNonce; skip. A false->true unioned flip is NEVER skipped. plan.skipWrite = true; } plan.wire = encodeUsageRecord(merged); return plan; } // Foreign value from another track: a cross-track copy or move. 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) { // Present-but-unreadable record: it may protect anything, so halt. // Belt-and-braces: also return the protect-all set (every readable // record's paths, bypassing the liveness filter) so the fail-safe // holds even if a future caller forgets to check abortPrune first. result.abortPrune = true; 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); // Class-UID byte-order in fx_ident is unverified on Windows COM layout, // hence the two name fallbacks below (see header for the protect-all net). if (!uidHexUpper.empty() && up.find(uidHexUpper) != std::string::npos) return true; if (!outputNameUpper.empty() && up.find(outputNameUpper) != std::string::npos) return true; return !nameUpper.empty() && up.find(nameUpper) != std::string::npos; } } // namespace reasampler::wire