From 937ef9fcef86beb63748e0859c0f6463ee7be7ad Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 1 Oct 2026 14:20:19 +0300 Subject: [PATCH] Test CCM limits and offline DM custody across restart --- tests/test_dm.c | 32 ++++++++++++++++++++++++++++++++ tests/test_dm_e2e.c | 30 +++++++++++++++++++++++------- 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/tests/test_dm.c b/tests/test_dm.c index 05c5ba59..44d8b973 100644 --- a/tests/test_dm.c +++ b/tests/test_dm.c @@ -13,6 +13,7 @@ #include #include +#include "../lib/mem.h" int main(void) { debug_config_init(); @@ -100,6 +101,37 @@ int main(void) { } } + /* Границы CCM, включая пустое тело и порции медиа; повреждённый tag не принимается. */ + uint8_t* large = u_malloc(DM_CCM_PLAIN_MAX + DM_TAG_SIZE + 1); + uint8_t* cipher = u_malloc(DM_CCM_PLAIN_MAX + DM_TAG_SIZE + 1); + uint8_t* restored = u_malloc(DM_CCM_PLAIN_MAX + 1); + if (!large || !cipher || !restored) return 1; + memset(large, 0x91, DM_CCM_PLAIN_MAX + 1); + const size_t sizes[] = {0, DM_MEDIA_PLAIN_SIZE, DM_CCM_PLAIN_MAX}; + for (size_t i = 0; i < sizeof(sizes) / sizeof(sizes[0]); i++) { + size_t n = sizes[i], clen = 0, plen = 0; + dm_build_nonce(id_ab, a, 100 + i, nonce); + if (dm_encrypt(key1, nonce, n ? large : NULL, n, cipher, &clen) != 0 || clen != n + DM_TAG_SIZE || + dm_decrypt(key2, nonce, cipher, clen, restored, &plen) != 0 || plen != n || memcmp(large, restored, n)) failures++; + cipher[clen - 1] ^= 1; + if (dm_decrypt(key2, nonce, cipher, clen, restored, &plen) == 0 || plen != 0) failures++; + } + if (dm_encrypt(key1, nonce, large, DM_CCM_PLAIN_MAX + 1, cipher, &enc_len) == 0) failures++; + uint8_t media_id[16] = {1}, media_nonce[DM_NONCE_SIZE], other_nonce[DM_NONCE_SIZE]; + dm_build_media_nonce(a, media_id, 0, media_nonce); + dm_build_media_nonce(a, media_id, 1, other_nonce); + if (!memcmp(media_nonce, other_nonce, DM_NONCE_SIZE)) failures++; + dm_build_media_nonce(b, media_id, 0, other_nonce); + if (!memcmp(media_nonce, other_nonce, DM_NONCE_SIZE)) failures++; + media_id[1] = 1; + dm_build_media_nonce(a, media_id, 0, other_nonce); + if (!memcmp(media_nonce, other_nonce, DM_NONCE_SIZE)) failures++; + dm_build_nonce(id_ab, a, 0, other_nonce); + if (!memcmp(media_nonce, other_nonce, DM_NONCE_SIZE)) failures++; + u_free(large); + u_free(cipher); + u_free(restored); + if (failures == 0) { printf("TEST PASSED\n"); return 0; diff --git a/tests/test_dm_e2e.c b/tests/test_dm_e2e.c index f42ef6eb..98ac01d6 100644 --- a/tests/test_dm_e2e.c +++ b/tests/test_dm_e2e.c @@ -36,7 +36,6 @@ #include #include #include -#include #define CH_ID "4242424242424242" @@ -208,9 +207,9 @@ static int dm_msg_has(struct UTUN_INSTANCE* inst, const char* needle) { /* количество блобов в dm_mail для recipient=B от sender=A (одна проверка) */ static int dm_mail_count(struct UTUN_INSTANCE* inst) { sqlite3_stmt* s = NULL; - int n = 0; + int n = -1; if (sqlite3_prepare_v2(inst->topo_sqlite_db, - "SELECT COUNT(*) FROM dm_mail WHERE recipient=? AND sender=?", + "SELECT COUNT(*) FROM dm_pending_messages WHERE recipient=? AND sender=?", -1, &s, NULL) == SQLITE_OK) { sqlite3_bind_int64(s, 1, (sqlite3_int64)g_sh.nid[IDX_B]); sqlite3_bind_int64(s, 2, (sqlite3_int64)g_sh.nid[IDX_A]); @@ -248,7 +247,7 @@ static void member_put_placeholder(struct UTUN_INSTANCE* inst, int idx, const ch /* мембер C со флагом storage=yes (adm_tags подписан канальным ключом) */ static void member_put_storage(struct UTUN_INSTANCE* inst, int idx, const char* name) { char userinfo[256]; snprintf(userinfo, sizeof(userinfo), "{\"name\":\"%s\"}", name); - char adm_tags[64]; snprintf(adm_tags, sizeof(adm_tags), "{\"storage\":\"yes\",\"ver\":\"1\"}"); + char adm_tags[80]; snprintf(adm_tags, sizeof(adm_tags), "{\"storage\":\"yes\",\"supernode\":\"yes\",\"ver\":\"1\"}"); uint8_t ats[64]; uint8_t amsg[256]; size_t off = strlen(adm_tags); memcpy(amsg, adm_tags, off); @@ -279,6 +278,7 @@ static void do_setup(struct UTUN_INSTANCE* inst, int role) { case IDX_C: channel_put_shared(inst, 0); member_put_self(inst, "C"); + member_put_storage(inst, IDX_C, "C"); member_put_placeholder(inst, IDX_A, "A"); member_put_placeholder(inst, IDX_B, "B"); member_sync_start(inst, g_sh.nid[IDX_A], CH_ID, NULL, NULL); @@ -447,7 +447,23 @@ static void dm2_tick(void* arg) { } break; case S2_WAIT_MAIL: - if (dm_mail_count(C) > 0) t->phase = S2_RECONNECT; + if (dm_mail_count(C) > 0) { + /* У отправителя больше нет процесса; очередь суперузла также проходит restart. */ + utun_instance_destroy(A); + t->inst[IDX_A] = NULL; + utun_instance_destroy(C); + t->inst[IDX_C] = NULL; + char cfg[512]; + snprintf(cfg, sizeof(cfg), "%s/c.conf", getenv("UTUN_TEST_DIR")); + t->inst[IDX_C] = utun_instance_create(t->ua, cfg); + if (!t->inst[IDX_C] || utun_instance_init(t->inst[IDX_C]) != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_DM, "s2: supernode restart failed"); + t->result = 2; + uasync_stop(t->ua); + return; + } + t->phase = S2_RECONNECT; + } break; case S2_RECONNECT: { char cfg[512]; @@ -554,10 +570,10 @@ int main(int argc, char** argv) { char dir2[512]; snprintf(dir2, sizeof(dir2), "/tmp/utun_dm_e2e2_XXXXXX"); if (test_mkdtemp(dir2) != 0) { fprintf(stderr, "mkdtemp dir2 failed\n"); return 1; } test_setenv("UTUN_TEST_DIR", dir2, 1); - fill_shared(70000 + (getpid() % 2000)); + fill_shared(61000 + (getpid() % 1000)); write_configs(dir2); - TEST("first message offline: mailbox delivery without prior conversation"); { + TEST("first offline DM: supernode restart, sender remains offline"); { struct UASYNC* ua = uasync_create(); if (!ua) { FAIL("uasync_create failed"); return 1; }