Run clang-format (the repo's .clang-format: LLVM base, 120 cols, tabs, Allman functions) over every source file so the tree is formatter-clean. Whitespace only -- no behavior change; full x64 + x86 suites pass. Also set SortIncludes: false in .clang-format. Windows include order is load-bearing (windows.h must precede tlhelp32.h / mmreg.h / xinput.h / dinput.h; winsock2.h must precede windows.h), and the default alphabetical sort reorders tlhelp32.h ahead of windows.h -- a build break. Leaving order alone keeps the manual, correct grouping.
154 lines
5.4 KiB
C++
154 lines
5.4 KiB
C++
// 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 <cstdint>
|
|
#include <cstdio>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include <windows.h>
|
|
|
|
#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<std::uint8_t>& 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<std::uint8_t>& 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<std::uint8_t> build_wav(std::uint16_t tag, std::uint16_t channels, std::uint32_t rate, std::uint16_t bits,
|
|
const std::vector<std::uint8_t>& data, long long declared_data_size = -1,
|
|
const char* junk_id = nullptr, std::uint32_t junk_size = 0)
|
|
{
|
|
std::vector<std::uint8_t> 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<std::uint16_t>(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<std::uint32_t>(data.size())
|
|
: static_cast<std::uint32_t>(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<std::uint8_t> 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<std::uint8_t> 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<std::uint8_t> 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<std::uint8_t> 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<std::uint8_t> 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<std::uint8_t> 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;
|
|
}
|