// Unit test for the WAV reader's robustness on malformed input (common/include/coop/wav.hpp). // tone_analysis_test already round-trips the writer's own output; this feeds hand-built byte streams: // truncated headers, bad magic, missing chunks, an over-long `data` size (must clamp), an odd-sized // chunk before data (word-align skip), and a corrupt huge chunk_size (must not hang/overflow). #include #include #include #include #include #include "coop/wav.hpp" using namespace coop; namespace { int g_failures = 0; int g_counter = 0; void check(bool ok, const char* what) { std::printf("%s %s\n", ok ? " ok:" : "FAIL:", what); if (!ok) { ++g_failures; } } std::wstring write_temp(const std::vector& bytes) { wchar_t dir[MAX_PATH] = {}; GetTempPathW(MAX_PATH, dir); std::wstring path = std::wstring(dir) + L"coop_wavtest_" + std::to_wstring(GetCurrentProcessId()) + L"_" + std::to_wstring(g_counter++) + L".wav"; FILE* f = nullptr; if (_wfopen_s(&f, path.c_str(), L"wb") == 0 && f != nullptr) { if (!bytes.empty()) { std::fwrite(bytes.data(), 1, bytes.size(), f); } std::fclose(f); } return path; } void put4(std::vector& b, const char* s) { b.insert(b.end(), s, s + 4); } // Build a WAV where the `data` chunk's declared size can differ from the bytes actually appended // (declared_data_size < 0 means "use the real size"), and an optional junk chunk can be inserted // before `data` with a chosen size field (to exercise the chunk walk). std::vector build_wav(std::uint16_t tag, std::uint16_t channels, std::uint32_t rate, std::uint16_t bits, const std::vector& data, long long declared_data_size = -1, const char* junk_id = nullptr, std::uint32_t junk_size = 0) { std::vector b; put4(b, "RIFF"); detail::wav_put_u32(b, 0); // riff size (reader ignores it) put4(b, "WAVE"); put4(b, "fmt "); detail::wav_put_u32(b, 16); detail::wav_put_u16(b, tag); detail::wav_put_u16(b, channels); detail::wav_put_u32(b, rate); detail::wav_put_u32(b, rate * channels * (bits / 8)); detail::wav_put_u16(b, static_cast(channels * (bits / 8))); detail::wav_put_u16(b, bits); if (junk_id != nullptr) { put4(b, junk_id); detail::wav_put_u32(b, junk_size); b.insert(b.end(), junk_size, 0xAB); // junk body if (junk_size & 1) { b.push_back(0); // RIFF pads odd chunks to an even boundary (what the reader's & 1 skips) } } put4(b, "data"); detail::wav_put_u32(b, declared_data_size < 0 ? static_cast(data.size()) : static_cast(declared_data_size)); b.insert(b.end(), data.begin(), data.end()); return b; } } // namespace int main() { WavData out; // Truncated (< 44 bytes). check(!wav_read(write_temp({'R', 'I', 'F', 'F', 0, 0, 0, 0}), out), "truncated file (<44B) rejected"); // Right size but wrong magic. { std::vector b(64, 0); b[0] = 'R'; // not "RIFF"...."WAVE" check(!wav_read(write_temp(b), out), "bad RIFF/WAVE magic rejected"); } // Valid 16-bit PCM round-trips. { const std::vector data(800, 0x42); out = WavData{}; check(wav_read(write_temp(build_wav(1, 2, 44100, 16, data)), out), "valid PCM parses"); check(out.format_tag == 1 && out.channels == 2 && out.sample_rate == 44100 && out.bits == 16, "valid PCM: format fields correct"); check(out.pcm.size() == 800, "valid PCM: full data recovered"); } // fmt present but no data chunk -> rejected (have_data == false). { std::vector b; put4(b, "RIFF"); detail::wav_put_u32(b, 0); put4(b, "WAVE"); put4(b, "fmt "); detail::wav_put_u32(b, 16); detail::wav_put_u16(b, 1); detail::wav_put_u16(b, 2); detail::wav_put_u32(b, 48000); detail::wav_put_u32(b, 48000 * 4); detail::wav_put_u16(b, 4); detail::wav_put_u16(b, 16); check(!wav_read(write_temp(b), out), "fmt-only (no data chunk) rejected"); } // data chunk declares MORE than the file holds -> clamp to what's there, still parse. { const std::vector data(100, 0x7F); out = WavData{}; check(wav_read(write_temp(build_wav(3, 1, 48000, 32, data, /*declared=*/1000000)), out), "over-long data size still parses"); check(out.pcm.size() == 100, "over-long data size clamps to available bytes"); } // An odd-sized junk chunk before data -> the word-align skip must still find data. { const std::vector data(40, 0x11); out = WavData{}; check(wav_read(write_temp(build_wav(1, 2, 44100, 16, data, -1, "LIST", /*odd*/ 3)), out), "odd-sized chunk before data: word-align skip finds data"); check(out.pcm.size() == 40, "data after an odd chunk recovered"); } // A corrupt, huge chunk_size before data must not hang or overflow -- the walk stops. { const std::vector data(40, 0x22); out = WavData{}; // junk chunk claims ~4 GB; the guard breaks before reaching the real data chunk -> no data found. const bool parsed = wav_read(write_temp(build_wav(1, 2, 44100, 16, data, -1, "junk", 0xFFFFFFF0u)), out); check(!parsed, "corrupt huge chunk_size: walk stops cleanly (no hang/overflow), data not reached"); } std::printf(g_failures == 0 ? "PASS wav_test\n" : "FAILED wav_test (%d)\n", g_failures); return g_failures == 0 ? 0 : 1; }