From 359a9f3dfcb5ad8610426beb662f236f640bfefd Mon Sep 17 00:00:00 2001 From: evgeny Date: Sat, 24 Oct 2026 15:26:11 +0200 Subject: [PATCH] test(media): cover voice duration bounds and early rejection --- tests/Makefile.am | 3 + tests/test_attachment.c | 123 +++++++++++++++++++++++++++++++++------- 2 files changed, 106 insertions(+), 20 deletions(-) diff --git a/tests/Makefile.am b/tests/Makefile.am index 9b9ae8eb..4fbdb7bc 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -388,6 +388,9 @@ test_reality_config_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS) test_dm_SOURCES = test_dm.c test_attachment_SOURCES = test_attachment.c test_attachment_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS) +if OS_LINUX +test_attachment_LDFLAGS = -Wl,--wrap=opus_codec_encoder_create +endif test_encrypted_file_SOURCES = test_encrypted_file.c test_encrypted_file_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS) if OS_LINUX diff --git a/tests/test_attachment.c b/tests/test_attachment.c index dee371f9..f707a6fc 100644 --- a/tests/test_attachment.c +++ b/tests/test_attachment.c @@ -6,8 +6,89 @@ #include "../lib/opus_codec.h" #include "../src/media_async/attachment.h" #include "../src/media_async/voice_file.h" +#ifdef __linux__ +#include +#endif #define REQUIRE(value) do { if (!(value)) { fprintf(stderr, "attachment check failed line=%d\n", __LINE__); return 1; } } while (0) +/* Проверяем настоящий контейнер и число декодируемых кадров, включая отбрасывание хвоста. */ +static int check_voice_roundtrip(const int16_t* pcm, size_t count, int rate, int channels, int preset, uint32_t duration_ms) { + struct attachment_info info = { .name = "voice.opus" }; + const char* path = "test_attachment_voice.opus"; + REQUIRE(!voice_file_encode(pcm, count, rate, channels, preset, path, &info)); + REQUIRE(info.duration_ms == duration_ms && strlen(info.waveform) == 100 && !attachment_validate(&info)); + FILE* file = fopen(path, "rb"); REQUIRE(file != NULL); + uint8_t header[12]; REQUIRE(fread(header, 1, 12, file) == 12); + uint32_t magic, saved_rate; uint16_t saved_channels, frame; + memcpy(&magic, header, 4); memcpy(&saved_rate, header + 4, 4); + memcpy(&saved_channels, header + 8, 2); memcpy(&frame, header + 10, 2); + REQUIRE(magic == 0x5355504f && saved_rate == (uint32_t)rate && saved_channels == channels && frame == rate / 50); + opus_codec_decoder_t* decoder = opus_codec_decoder_create(rate, channels); REQUIRE(decoder != NULL); + uint8_t packet[4000]; int16_t decoded[1920]; unsigned frames = 0; + for (;;) { + uint16_t n; REQUIRE(fread(&n, 2, 1, file) == 1); + if (!n) break; + REQUIRE(n <= sizeof(packet) && fread(packet, 1, n, file) == n); + REQUIRE(opus_codec_decode(decoder, packet, n, decoded, frame) == frame); frames++; + } + REQUIRE(frames == duration_ms / 20 && fgetc(file) == EOF); + REQUIRE(!fclose(file) && !remove(path)); opus_codec_decoder_destroy(decoder); + return 0; +} + +#ifdef __linux__ +static int encoder_probe; +static unsigned encoder_calls; +opus_codec_encoder_t* __real_opus_codec_encoder_create(int rate, int channels); +opus_codec_encoder_t* __wrap_opus_codec_encoder_create(int rate, int channels) { + if (!encoder_probe) return __real_opus_codec_encoder_create(rate, channels); + encoder_calls++; + return NULL; /* Останавливаем только probe: PCM ещё не прочитан, файл/метаданные не изменены. */ +} + +#if SIZE_MAX > UINT32_MAX +/* Виртуальный PCM позволяет проверить суточную границу без выделения физической памяти/суточного Opus. */ +static int check_voice_duration(void) { + static const int rates[] = {8000, 12000, 16000, 24000, 48000}; + static const uint32_t durations[] = {0, 479, 480, 499, 500, 519, 86399980, 86400000, 86400019, 86400020, 86400980, 86401000}; + const char* path = "test_attachment_duration.opus"; + static const char marker[] = "existing voice output"; + size_t bytes = (size_t)48000 * 2 * 86401 * sizeof(int16_t); + int16_t* pcm = mmap(NULL, bytes, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0); + REQUIRE(pcm != MAP_FAILED); + FILE* file = fopen(path, "wb"); REQUIRE(file != NULL); + REQUIRE(fwrite(marker, 1, sizeof(marker), file) == sizeof(marker) && !fclose(file)); + encoder_probe = 1; + unsigned cases = 0, rejected = 0; + debug_set_category_level(DEBUG_CATEGORY_MEDIA, DEBUG_LEVEL_DEBUG); + for (size_t r = 0; r < sizeof(rates) / sizeof(rates[0]); r++) { + for (int channels = 1; channels <= 2; channels++) { + for (size_t d = 0; d < sizeof(durations) / sizeof(durations[0]); d++) { + uint32_t duration = durations[d]; + size_t count = (size_t)rates[r] * channels * duration / 1000; + int allowed = duration >= 500 && duration < 86400020; + struct attachment_info info = { .kind = ATTACHMENT_FILE, .name = "unchanged.bin" }; + unsigned char before[sizeof(info)]; memcpy(before, &info, sizeof(info)); + encoder_calls = 0; + REQUIRE(voice_file_encode(pcm, count, rates[r], channels, 0, path, &info) == -1); + REQUIRE(encoder_calls == (unsigned)allowed && !memcmp(&info, before, sizeof(info))); + file = fopen(path, "rb"); REQUIRE(file != NULL); + char saved[sizeof(marker)]; REQUIRE(fread(saved, 1, sizeof(saved), file) == sizeof(saved)); + REQUIRE(!memcmp(saved, marker, sizeof(saved)) && fgetc(file) == EOF && !fclose(file)); + cases++; rejected += !allowed; + } + } + } + encoder_probe = 0; + REQUIRE(!munmap(pcm, bytes) && !remove(path)); + DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "voice duration: cases=%u rejected_before_encoder=%u file/metadata preserved", cases, rejected); + debug_set_category_level(DEBUG_CATEGORY_MEDIA, DEBUG_LEVEL_ERROR); + REQUIRE(cases == 120 && rejected == 70); + return 0; +} +#endif +#endif + int main(void) { debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR); struct attachment_info in = { .kind = ATTACHMENT_VOICE, .name = "голос.opus", .duration_ms = 1200 }, out; @@ -24,35 +105,37 @@ int main(void) { length = attachment_encode(&in, wire, sizeof(wire)); REQUIRE(length > 0 && !attachment_decode(wire, length, &out)); REQUIRE(out.width == 640 && out.height == 480); in.width = 0; REQUIRE(attachment_validate(&in) < 0); + REQUIRE(ATTACHMENT_DURATION_MAX_MS == 86400000); + for (int kind = ATTACHMENT_VOICE; kind <= ATTACHMENT_VIDEO; kind++) { + in = (struct attachment_info){ .kind = kind, .name = "duration.bin", .duration_ms = 86400000 }; + if (kind == ATTACHMENT_VOICE) memset(in.waveform, 'A', 100); + else { in.width = 640; in.height = 480; } + length = attachment_encode(&in, wire, sizeof(wire)); REQUIRE(length > 0 && !attachment_decode(wire, length, &out)); + in.duration_ms++; REQUIRE(attachment_validate(&in) < 0 && attachment_encode(&in, wire, sizeof(wire)) < 0); + wire[6]++; REQUIRE(attachment_decode(wire, length, &out) < 0); + } in = (struct attachment_info){ .kind = ATTACHMENT_FILE, .name = "../unsafe" }; REQUIRE(attachment_validate(&in) < 0); strcpy(in.name, "\xc0\xaf"); REQUIRE(attachment_validate(&in) < 0); memset(in.name, 'n', 255); in.name[255] = 0; REQUIRE(attachment_encode(&in, wire, 265) < 0); REQUIRE(attachment_encode(&in, wire, 266) == 266); - int16_t* pcm = u_calloc(48001, sizeof(*pcm)); REQUIRE(pcm != NULL); - for (unsigned i = 0; i < 48001; i++) pcm[i] = (int16_t)((i % 200) * 100 - 10000); - in = (struct attachment_info){ .name = "voice.opus" }; - const char* path = "test_attachment_voice.opus"; - REQUIRE(!voice_file_encode(pcm, 48001, 48000, 1, 1, path, &in)); - REQUIRE(in.duration_ms == 1000 && strlen(in.waveform) == 100 && !attachment_validate(&in)); - FILE* file = fopen(path, "rb"); REQUIRE(file != NULL); - uint8_t header[12]; REQUIRE(fread(header, 1, 12, file) == 12); - uint32_t magic, rate; uint16_t channels, frame; - memcpy(&magic, header, 4); memcpy(&rate, header + 4, 4); memcpy(&channels, header + 8, 2); memcpy(&frame, header + 10, 2); - REQUIRE(magic == 0x5355504f && rate == 48000 && channels == 1 && frame == 960); - opus_codec_decoder_t* decoder = opus_codec_decoder_create(48000, 1); REQUIRE(decoder != NULL); - uint8_t packet[4000]; int16_t decoded[960]; unsigned frames = 0; - for (;;) { - uint16_t n; REQUIRE(fread(&n, 2, 1, file) == 1); - if (!n) break; - REQUIRE(n <= sizeof(packet) && fread(packet, 1, n, file) == n); - REQUIRE(opus_codec_decode(decoder, packet, n, decoded, 960) == 960); frames++; + int16_t* pcm = u_calloc(49920, sizeof(*pcm)); REQUIRE(pcm != NULL); + for (unsigned i = 0; i < 49920; i++) pcm[i] = (int16_t)((i % 200) * 100 - 10000); + REQUIRE(!check_voice_roundtrip(pcm, 48001, 48000, 1, 1, 1000)); + static const int rates[] = {8000, 12000, 16000, 24000, 48000}; + for (size_t r = 0; r < sizeof(rates) / sizeof(rates[0]); r++) { + for (int channels = 1; channels <= 2; channels++) { + size_t frame = (size_t)(rates[r] / 50) * channels; + REQUIRE(!check_voice_roundtrip(pcm, frame * 26 - 1, rates[r], channels, (r + channels) % 3, 500)); + } } - REQUIRE(frames == 50 && fgetc(file) == EOF); - fclose(file); remove(path); opus_codec_decoder_destroy(decoder); + const char* path = "test_attachment_voice.opus"; REQUIRE(voice_file_encode(pcm, 23999, 48000, 1, 1, path, &in) < 0); REQUIRE(voice_file_encode(pcm, 48000, 48000, 1, 3, path, &in) < 0); REQUIRE(voice_file_encode(pcm, 48000, 44100, 1, 1, path, &in) < 0); u_free(pcm); +#if defined(__linux__) && SIZE_MAX > UINT32_MAX + REQUIRE(!check_voice_duration()); +#endif printf("test_attachment: strict metadata and shared Opus encode/decode PASS\n"); return 0; }