fix(ingest): convert-on-import to 32f WAV, undo-group bank+assign, overflow guards

Non-WAV sources decode via PCM_source::GetSamples and land as canonical 32f
RIFF/WAVE; hash taken post-conversion so re-imports dedup. Undo block covers
bank mutation + assign_request atomically. Overflow guards + adversarial tests.
This commit is contained in:
2026-07-26 21:56:09 -04:00
parent 8074e21057
commit 373948c18a
5 changed files with 378 additions and 78 deletions
+40
View File
@@ -131,6 +131,43 @@ static void testTrailingGarbageRejected() {
CHECK(!decodeAssignmentRequest(wire + "0:").has_value());
}
// --- overflow / adversarial integer inputs ------------------------------------
// A 21-digit length field overflows SIZE_MAX and must be rejected safely (no UB,
// no wrap-around that could make a huge length appear small and pass the bounds check).
static void testOverflowFieldLength() {
// Craft a wire where the bankId length token is 21 digits that exceed SIZE_MAX.
// The decoder must fail cleanly, not access memory out of bounds.
// "rsassign1" + "999999999999999999999:" (21 nines) + junk: rejects before OOB.
const std::string wire = std::string("rsassign1") + "999999999999999999999:junk";
CHECK(!decodeAssignmentRequest(wire).has_value());
}
// A 20-digit generation (exceeds the 19-digit cap) must be rejected safely.
static void testOverflowFieldInt64() {
// Encode a valid record then manually substitute the generation with a 20-digit value.
// We cannot use encode (it would produce a correct 19-digit generation), so we
// build the wire manually. Generation "99999999999999999999" (20 nines) exceeds cap.
// bankId = "pool" (4 bytes), sampleId = "s1" (2 bytes).
const std::string wire = std::string("rsassign1")
+ "4:pool"
+ "2:s1"
+ "20:99999999999999999999";
CHECK(!decodeAssignmentRequest(wire).has_value());
}
// SIZE_MAX as a length field (20 digits, within the digit-count cap) must not UB or
// wrap. The overflow-guard in field() caps the multiplication; even if the value itself
// does not trigger the multiply guard (SIZE_MAX accumulates cleanly digit by digit),
// the subsequent "len > s_.size() - start" bounds check catches it because the actual
// string is tiny — no OOB access, no wraparound, clean rejection.
static void testOverflowExactSizeMax() {
// 18446744073709551615 = SIZE_MAX on 64-bit. 20 digits: within the digit cap, but the
// trailing bounds check rejects it because the wire string is far smaller than SIZE_MAX.
const std::string wire = std::string("rsassign1") + "18446744073709551615:X";
CHECK(!decodeAssignmentRequest(wire).has_value());
}
int main() {
testRoundTrip();
testRoundTripPoolAndZeroGeneration();
@@ -139,6 +176,9 @@ int main() {
testLargeGeneration();
testMalformedParse();
testTrailingGarbageRejected();
testOverflowFieldLength();
testOverflowFieldInt64();
testOverflowExactSizeMax();
if (g_fail == 0) std::printf("All tests passed.\n");
return g_fail ? 1 : 0;