diff --git a/tests/test_dm_media.c b/tests/test_dm_media.c index fe16bcdb..1396619e 100644 --- a/tests/test_dm_media.c +++ b/tests/test_dm_media.c @@ -24,6 +24,7 @@ #include "../src/routing_layer/etcp_router.h" #include "../src/transport_layer/secure_channel.h" #include "../src/transport_layer/etcp.h" +#include "../src/media_delivery/file_transfer.h" #include "test_utils.h" #define GROUP 4242424242424243ULL @@ -166,6 +167,48 @@ static void control(struct media_test* t, int dst, uint64_t source, uint8_t cmd, inst->router_bindings.callbacks[ETCP_RT_ID_DM_MEDIA](conn, e); } +static void cancelled(void* arg, int error) { + int* calls = arg; + *calls += error == -2 ? 1 : 100; +} + +/* Ошибки регистрации не расходуют seq; квота проверяется до получения байтов. */ +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'; + 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; + 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; + 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); + metadata[0] ^= 0x80; + uint64_t size = 2 * 1024 * 1024; + memcpy(metadata + 16, &size, 8); + if (sc_ed25519_sign(a->my_ed25519_privkey, request + 8, t->body_len - DM_MSG_SIG_SIZE, + request + 8 + t->body_len - DM_MSG_SIG_SIZE) || chat_setting_set(m, "dm_media_storage_mb", "1")) return -1; + control(t, M, t->ids[S], DM_MEDIA_STORE, request, 8 + t->body_len); + if (value(m, "SELECT count(*) FROM dm_custody") != 0 || chat_setting_set(m, "dm_media_storage_mb", "1024")) return -1; + char path[768]; snprintf(path, sizeof(path), "%s/cancelled.download", t->dir); + int calls = 0; + struct file_transfer_op* op = file_transfer_pull(t->nodes[B]->file_transfer, GROUP, t->ids[A], t->id, + dm_media_cipher_size(FILE_BYTES), path, cancelled, &calls); + if (!op) return -1; + file_transfer_cancel_object(t->nodes[B]->file_transfer, t->id); + if (calls != 1) return -1; + DEBUG_INFO(DEBUG_CATEGORY_DM, "media protocol checks passed: SQL rollback, quota, JSON state, CM operation cancellation"); + return 0; +} + /* Таймер служит heartbeat: большие криптозадачи не должны останавливать сценарий. */ static void tick(void* arg) { struct media_test* t = arg; @@ -193,6 +236,7 @@ static void tick(void* arg) { value(m, "SELECT count(*) FROM dm_custody") != 0) { failure(t, "direct transfer/custody isolation"); return; } uint8_t bad[DM_MEDIA_RECEIPT_SIZE]; memcpy(bad, t->receipt, sizeof(bad)); bad[56] ^= 1; if (!dm_media_verify_receipt(a, bad, t->body, t->body_len)) { failure(t, "forged media receipt accepted"); return; } + if (protocol_checks(t)) { failure(t, "media protocol invariants"); return; } utun_instance_destroy(b); t->nodes[B] = NULL; t->phase++; } break;