diff --git a/tests/test_attachment.c b/tests/test_attachment.c new file mode 100644 index 00000000..b9943dc6 --- /dev/null +++ b/tests/test_attachment.c @@ -0,0 +1,51 @@ +/* Строгие метаданные и реальный Opus-файл: границы, повреждения, длительность. */ +#include +#include +#include "../lib/mem.h" +#include "../lib/debug_config.h" +#include "../lib/opus_codec.h" +#include "../src/media_async/attachment.h" +#include "../src/media_async/voice_file.h" +#define REQUIRE(value) do { if (!(value)) { fprintf(stderr, "attachment check failed line=%d\n", __LINE__); return 1; } } while (0) + +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; + memset(in.waveform, 'A', 100); + uint8_t wire[ATTACHMENT_WIRE_MAX + 1]; + int length = attachment_encode(&in, wire, sizeof(wire)); + REQUIRE(length > 0 && !attachment_decode(wire, length, &out)); + REQUIRE(out.kind == in.kind && out.duration_ms == 1200 && !strcmp(in.name, out.name) && !strcmp(in.waveform, out.waveform)); + for (int i = 0; i < length; i++) REQUIRE(attachment_decode(wire, i, &out) < 0); + REQUIRE(attachment_decode(wire, length + 1, &out) < 0); + wire[0] = 99; REQUIRE(attachment_decode(wire, length, &out) < 0); + wire[0] = ATTACHMENT_VOICE; wire[length - 1] = '?'; REQUIRE(attachment_decode(wire, length, &out) < 0); + in.kind = ATTACHMENT_VIDEO; in.waveform[0] = 0; in.width = 640; in.height = 480; + 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); + 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); + 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++; + } + REQUIRE(frames == 50 && fgetc(file) == EOF); + fclose(file); remove(path); opus_codec_decoder_destroy(decoder); u_free(pcm); + printf("test_attachment: strict metadata and shared Opus encode/decode PASS\n"); + return 0; +} diff --git a/tests/test_dm_media.c b/tests/test_dm_media.c index aad3bdb6..ab11ca5e 100644 --- a/tests/test_dm_media.c +++ b/tests/test_dm_media.c @@ -178,17 +178,21 @@ static int protocol_checks(struct media_test* t) { struct UTUN_INSTANCE* a = t->nodes[A]; struct UTUN_INSTANCE* m = t->nodes[M]; const uint8_t* desc = dm_message_media(t->body, t->body_len); - uint8_t input[DM_MEDIA_DESC_SIZE + 1]; - memcpy(input, desc, DM_MEDIA_DESC_SIZE); input[DM_MEDIA_DESC_SIZE] = 'x'; + uint8_t input[DM_MEDIA_DESC_SIZE + ATTACHMENT_WIRE_MAX]; + struct attachment_info info = { .kind = ATTACHMENT_FILE, .name = "rollback.bin" }; + int metadata_len = attachment_encode(&info, input + DM_MEDIA_DESC_SIZE, ATTACHMENT_WIRE_MAX); + if (metadata_len < 0) return -1; + memcpy(input, desc, DM_MEDIA_DESC_SIZE); if (sqlite3_exec(a->topo_sqlite_db, "CREATE TEMP TRIGGER reject_dm_file BEFORE INSERT ON dm_files" " BEGIN SELECT RAISE(ABORT,'injected media registration failure'); END", NULL, NULL, NULL)) return -1; - int rejected = dm_send(a, t->conv, "file", input, sizeof(input)) < 0; + int rejected = dm_send(a, t->conv, "file", input, DM_MEDIA_DESC_SIZE + metadata_len) < 0; if (sqlite3_exec(a->topo_sqlite_db, "DROP TRIGGER reject_dm_file", NULL, NULL, NULL)) return -1; if (!rejected || value(a, "SELECT last_out_seq FROM dm_conversations") != 1 || value(a, "SELECT count(*) FROM dm_messages") != 1 || value(a, "SELECT count(*) FROM dm_outbox") != 0) return -1; char json[8192]; size_t len; if (dm_list_messages_json(a, t->conv, 200, 0, json, sizeof(json), &len) || - !strstr(json, "\"media_status\":\"delivered\"") || !strstr(json, "\"media_path\":")) return -1; + !strstr(json, "\"media_status\":\"delivered\"") || !strstr(json, "\"media_path\":") || + !strstr(json, "\"content_type\":\"audio/opus\"") || !strstr(json, "\"duration_ms\":3000")) return -1; uint8_t request[8 + sizeof(t->body)]; memcpy(request, &t->ids[B], 8); memcpy(request + 8, t->body, t->body_len); uint8_t* metadata = (uint8_t*)dm_message_media(request + 8, t->body_len); @@ -212,6 +216,15 @@ static int protocol_checks(struct media_test* t) { } /* Таймер служит heartbeat: большие криптозадачи не должны останавливать сценарий. */ +/* Одни и те же transfer/custody гарантии для разных типов вложений. */ +static int send_fixture(struct media_test* t, enum attachment_kind kind, const char* name) { + struct attachment_info info = { .kind = kind }; + snprintf(info.name, sizeof(info.name), "%s", name); + if (kind == ATTACHMENT_VOICE) { info.duration_ms = 3000; memset(info.waveform, 'A', 100); } + if (kind == ATTACHMENT_VIDEO) { info.duration_ms = 3000; info.width = 640; info.height = 480; } + return dm_send_file(t->nodes[A], t->conv, t->file, &info, 0); +} + static void tick(void* arg) { struct media_test* t = arg; t->timer = NULL; t->ticks++; t->heartbeats++; @@ -228,7 +241,7 @@ static void tick(void* arg) { break; case 1: if (live(a, t->ids[B]) && live(m, t->ids[A])) { - if (dm_send_file(a, t->conv, t->file, "direct.bin")) { failure(t, "direct file enqueue"); return; } + if (send_fixture(t, ATTACHMENT_VOICE, "direct.opus")) { failure(t, "direct voice enqueue"); return; } t->phase++; } break; @@ -244,7 +257,7 @@ static void tick(void* arg) { break; case 3: if (!live(a, t->ids[B])) { - if (dm_send_file(a, t->conv, t->file, "offline.bin")) { failure(t, "offline file enqueue"); return; } + if (send_fixture(t, ATTACHMENT_VIDEO, "offline.mp4")) { failure(t, "offline video enqueue"); return; } t->phase++; } break; @@ -282,7 +295,7 @@ static void tick(void* arg) { break; case 7: if (!live(a, t->ids[B])) { - if (dm_send_file(a, t->conv, t->file, "expires.bin")) { failure(t, "TTL file enqueue"); return; } + if (send_fixture(t, ATTACHMENT_FILE, "expires.bin")) { failure(t, "TTL file enqueue"); return; } t->phase++; } break;