Files
utun2/tests/test_dm_e2e.c
T

606 lines
23 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// test_dm_e2e.c — интеграционный тест прямого чата (DM): прямая отправка + offline-хранение.
//
// Однопоточный: 3 узла (A, B, C) в одном процессе, один UASYNC, без fork.
// Топология — цепочка B→C→A (A владелец канала, C storage, B target).
// Сценарий управляется одной master state-machine (таймер dm_tick).
//
// Сценарий:
// фаза 1 (прямая): A,B,C online. A dm_start(B) → dm_send("hello1") → B получает напрямую.
// фаза 2 (offline): B1 завершается (A теряет B). A dm_send("hello2") → недоступен →
// кладёт в mailbox на C (storage=yes). C видит блоб в dm_mail.
// фаза 3 (reconnect): B2 переподключается к C → dm_on_conn_status → PULL mailbox → получает "hello2".
#include "chat_core.h"
#include "chat_sync.h"
#include "member_sync.h"
#include "../dm/dm_core.h"
#include "../dm/dm_crypto.h"
#include "../routing_layer/topo_node_sqlite.h"
#include "../routing_layer/topo_group.h"
#include "../routing_layer/topo_node.h"
#include "../utun_instance.h"
#include "../transport_layer/etcp.h"
#include "../transport_layer/secure_channel.h"
#include "../ntp_time.h"
#include "../lib/u_async.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "../lib/platform_compat.h"
#include "test_utils.h"
#include <sqlite3.h>
#define OPENSSL_API_COMPAT 0x10100000L
#include <openssl/evp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <time.h>
#include <unistd.h>
#define CH_ID "4242424242424242"
#define IDX_A 0
#define IDX_B 1
#define IDX_C 2
#define TICK_TB 100 /* 10 ms на такт state machine */
#define MAX_TICKS 3000 /* глобальный таймаут ≈ 30 c */
/* фазы master state-machine */
enum dm_phase {
P_WAIT_CONN, /* ждём подъёма всех соединений */
P_SETUP, /* однократная настройка канала/мемберов на A,B,C */
P_WAIT_BGP, /* ждём сходимости BGP CHAT-группы */
P_SEND1, /* A: dm_start + hello1 */
P_WAIT_MSG1, /* ждём hello1 у B */
P_B_OFFLINE, /* destroy B1 */
P_WAIT_B_OFFLINE, /* ждём ухода B из BGP у A */
P_SEND2, /* A: hello2 → mailbox */
P_WAIT_MAIL, /* ждём блоб в dm_mail у C */
P_RECONNECT, /* create B2 */
P_WAIT_B2, /* ждём hello2 у B2 и опустошение dm_mail у C */
P_DONE,
};
struct dm_ctx {
struct UASYNC* ua;
struct UTUN_INSTANCE* inst[3];
enum dm_phase phase;
int result; /* 0=running, 1=ok, 2=fail */
int ticks;
void* timer;
};
/* общие данные (ключи узлов и канала) */
struct dm_shared {
uint8_t ch_x_pub[32], ch_ed_pub[32], ch_ed_priv[32], ch_sig[64];
uint64_t nid[3];
uint8_t x_pub[3][32], x_priv[3][32], ed_pub[3][32];
int port[3];
char conv_id[64];
};
static struct dm_shared g_sh;
static int G_PASSED = 0, G_FAILED = 0, G_TOTAL = 0;
#define TEST(n) do { G_TOTAL++; printf(" %-58s", n); fflush(stdout); } while(0)
#define OK() do { G_PASSED++; printf("OK\n"); } while(0)
#define FAIL(f,...) do { G_FAILED++; printf("FAIL: " f "\n", ##__VA_ARGS__); } while(0)
/* ── крипто-хелперы ── */
static void gen_x25519(uint8_t pub[32], uint8_t priv[32]) {
EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_X25519, NULL);
EVP_PKEY* pkey = NULL;
EVP_PKEY_keygen_init(ctx); EVP_PKEY_keygen(ctx, &pkey); EVP_PKEY_CTX_free(ctx);
size_t l = 32; EVP_PKEY_get_raw_public_key(pkey, pub, &l);
l = 32; EVP_PKEY_get_raw_private_key(pkey, priv, &l);
EVP_PKEY_free(pkey);
}
static void gen_ed25519(uint8_t pub[32], uint8_t priv[32]) {
EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, NULL);
EVP_PKEY* pkey = NULL;
EVP_PKEY_keygen_init(ctx); EVP_PKEY_keygen(ctx, &pkey); EVP_PKEY_CTX_free(ctx);
size_t l = 32; EVP_PKEY_get_raw_public_key(pkey, pub, &l);
l = 32; EVP_PKEY_get_raw_private_key(pkey, priv, &l);
EVP_PKEY_free(pkey);
}
static void to_hex(const uint8_t* bin, size_t n, char* out) {
for (size_t i = 0; i < n; i++) sprintf(out + i * 2, "%02x", bin[i]);
out[n * 2] = '\0';
}
static int wf(const char* p, const char* f, ...) {
va_list ap; FILE* fp = fopen(p, "w"); if (!fp) return -1;
va_start(ap, f); vfprintf(fp, f, ap); va_end(ap); fclose(fp); return 0;
}
/* ── генерация данных сценария + конфигов ── */
static void fill_shared(int base_port) {
memset(&g_sh, 0, sizeof(g_sh));
for (int i = 0; i < 3; i++) {
gen_x25519(g_sh.x_pub[i], g_sh.x_priv[i]);
sc_derive_ed25519_pubkey(g_sh.x_priv[i], g_sh.ed_pub[i]);
g_sh.nid[i] = sc_derive_node_id_from_pubkey(g_sh.x_pub[i]);
g_sh.port[i] = base_port + i * 1000;
}
{ uint8_t ch_x_priv[32]; gen_x25519(g_sh.ch_x_pub, ch_x_priv); }
gen_ed25519(g_sh.ch_ed_pub, g_sh.ch_ed_priv);
/* подпись канала: Ed25519(ch_ed_priv, ch_id||'\0'||name||'\0'||owner(8)||ch_x_pub(32)||ch_ed_pub(32)) */
{
uint8_t msg[256]; size_t off = 0;
off += (size_t)snprintf((char*)msg + off, sizeof(msg) - off, "%s", CH_ID) + 1;
off += (size_t)snprintf((char*)msg + off, sizeof(msg) - off, "%s", "dm-test") + 1;
memcpy(msg + off, &g_sh.nid[IDX_A], 8); off += 8;
memcpy(msg + off, g_sh.ch_x_pub, 32); off += 32;
memcpy(msg + off, g_sh.ch_ed_pub, 32); off += 32;
sc_ed25519_sign(g_sh.ch_ed_priv, msg, off, g_sh.ch_sig);
}
uint64_t conv = dm_derive_conv_id(g_sh.nid[IDX_A], g_sh.nid[IDX_B]);
snprintf(g_sh.conv_id, sizeof(g_sh.conv_id), "%llu", (unsigned long long)conv);
}
static void write_configs(const char* dir) {
for (int i = 0; i < 3; i++) {
char priv[65], pub[65], path[512], dbd[512];
to_hex(g_sh.x_priv[i], 32, priv);
to_hex(g_sh.x_pub[i], 32, pub);
snprintf(path, sizeof(path), "%s/%s.conf", dir, i == IDX_A ? "a" : i == IDX_B ? "b" : "c");
snprintf(dbd, sizeof(dbd), "%s/db%s", dir, i == IDX_A ? "a" : i == IDX_B ? "b" : "c");
utun_mkdir(dbd, 0755);
char client[512] = "";
if (i == IDX_C) {
/* C → A (клиент к A) */
char apub[65]; to_hex(g_sh.x_pub[IDX_A], 32, apub);
snprintf(client, sizeof(client),
"[client: to_a]\npeer_public_key=%s\nlink=s1:127.0.0.1:%d\n",
apub, g_sh.port[IDX_A]);
} else if (i == IDX_B) {
/* B → C (клиент к C) */
char cpub[65]; to_hex(g_sh.x_pub[IDX_C], 32, cpub);
snprintf(client, sizeof(client),
"[client: to_c]\npeer_public_key=%s\nlink=s1:127.0.0.1:%d\n",
cpub, g_sh.port[IDX_C]);
}
wf(path,
"[global]\ntun_ip=10.97.%d.1/24\ntun_ifname=tun%d0\ndb_path=%s/db%s\n"
"my_private_key=%s\nmy_public_key=%s\n"
"[server: s1]\naddr=127.0.0.1:%d\ntype=public\n"
"%s"
"[chatserver]\nstorage_autoload=0\n[allowed_keys]\nallow_all=1\n",
i, i, dir, i == IDX_A ? "a" : i == IDX_B ? "b" : "c",
priv, pub, g_sh.port[i], client);
}
}
/* ── чистые проверки (без опроса) ── */
static int conn_up(struct UTUN_INSTANCE* inst, uint64_t nid) {
struct ETCP_CONN* c = instance_find_conn(inst, nid);
return c && c->initialized && c->links_up;
}
static int bgp_has(struct UTUN_INSTANCE* inst, uint64_t nid) {
uint64_t gid = strtoull(CH_ID, NULL, 10);
struct TOPO_GROUP* g = topo_groups_find(inst->topo_groups, gid);
if (!g) return 0;
struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(g, nid);
return nq && nq->paths && nq->paths->head;
}
/* есть ли расшифрованный текст needle в беседе (одна проверка) */
static int dm_msg_has(struct UTUN_INSTANCE* inst, const char* needle) {
char buf[8192]; size_t len = 0;
if (dm_list_messages_json(inst, g_sh.conv_id, 100, 0, buf, sizeof(buf), &len) != 0) return 0;
return strstr(buf, needle) != NULL;
}
/* количество блобов в dm_mail для recipient=B от sender=A (одна проверка) */
static int dm_mail_count(struct UTUN_INSTANCE* inst) {
sqlite3_stmt* s = NULL;
int n = 0;
if (sqlite3_prepare_v2(inst->topo_sqlite_db,
"SELECT COUNT(*) FROM dm_mail 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]);
if (sqlite3_step(s) == SQLITE_ROW) n = sqlite3_column_int(s, 0);
}
if (s) sqlite3_finalize(s);
return n;
}
/* ── настройка канала и мемберов (однократно) ── */
static void channel_put_shared(struct UTUN_INSTANCE* inst, int has_priv) {
topo_node_sqlite_channel_put(inst->topo_sqlite_db, CH_ID, "dm-test", g_sh.nid[IDX_A],
g_sh.ch_x_pub, NULL, g_sh.ch_ed_pub, has_priv ? g_sh.ch_ed_priv : NULL, g_sh.ch_sig);
chat_core_ensure_channel_ready(inst, CH_ID);
}
static void member_put_self(struct UTUN_INSTANCE* inst, const char* name) {
uint64_t join_ts = (uint64_t)ntp_time_get_seconds(inst);
uint8_t jm[128];
int jl = member_sync_build_join_msg(g_sh.ch_x_pub, g_sh.ch_ed_pub, inst->node_id,
inst->my_keys.public_key, join_ts, jm, (int)sizeof(jm));
uint8_t join_sig[64]; sc_ed25519_sign(inst->my_ed25519_privkey, jm, (size_t)jl, join_sig);
char userinfo[256]; snprintf(userinfo, sizeof(userinfo), "{\"name\":\"%s\"}", name);
member_sync_put(inst, CH_ID, inst->node_id, inst->my_keys.public_key, inst->my_ed25519_pubkey,
join_sig, join_ts, NULL, 0, userinfo, NULL, NULL, 0, 0, NULL);
}
static void member_put_placeholder(struct UTUN_INSTANCE* inst, int idx, const char* name) {
char userinfo[256]; snprintf(userinfo, sizeof(userinfo), "{\"name\":\"%s\"}", name);
member_sync_put(inst, CH_ID, g_sh.nid[idx], g_sh.x_pub[idx], g_sh.ed_pub[idx],
NULL, 0, NULL, 0, userinfo, NULL, NULL, 0, 0, NULL);
}
/* мембер 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\"}");
uint8_t ats[64];
uint8_t amsg[256]; size_t off = strlen(adm_tags);
memcpy(amsg, adm_tags, off);
memcpy(amsg + off, &g_sh.nid[idx], 8); off += 8;
sc_ed25519_sign(g_sh.ch_ed_priv, amsg, off, ats);
member_sync_put(inst, CH_ID, g_sh.nid[idx], g_sh.x_pub[idx], g_sh.ed_pub[idx],
NULL, 0, NULL, 0, userinfo, adm_tags, ats, 0, 0, NULL);
}
/* однократная настройка роли */
static void do_setup(struct UTUN_INSTANCE* inst, int role) {
uint64_t nc = g_sh.nid[IDX_C];
switch (role) {
case IDX_A:
channel_put_shared(inst, 1);
member_put_self(inst, "A");
member_put_placeholder(inst, IDX_B, "B");
member_put_storage(inst, IDX_C, "C");
member_sync_start(inst, nc, CH_ID, NULL, NULL);
break;
case IDX_B:
channel_put_shared(inst, 0);
member_put_self(inst, "B");
member_put_placeholder(inst, IDX_A, "A");
member_put_storage(inst, IDX_C, "C");
member_sync_start(inst, nc, CH_ID, NULL, NULL);
break;
case IDX_C:
channel_put_shared(inst, 0);
member_put_self(inst, "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);
member_sync_start(inst, g_sh.nid[IDX_B], CH_ID, NULL, NULL);
break;
}
}
/* ── master state-machine (движется таймером) ── */
static void dm_tick(void* arg) {
struct dm_ctx* t = arg;
t->timer = NULL;
if (t->result) return;
struct UTUN_INSTANCE* A = t->inst[IDX_A];
struct UTUN_INSTANCE* B = t->inst[IDX_B];
struct UTUN_INSTANCE* C = t->inst[IDX_C];
uint64_t na = g_sh.nid[IDX_A], nb = g_sh.nid[IDX_B], nc = g_sh.nid[IDX_C];
switch (t->phase) {
case P_WAIT_CONN:
/* C→A и B→C должны быть up */
if (conn_up(A, nc) && conn_up(C, na) && conn_up(B, nc) && conn_up(C, nb))
t->phase = P_SETUP;
break;
case P_SETUP:
do_setup(A, IDX_A);
do_setup(B, IDX_B);
do_setup(C, IDX_C);
t->phase = P_WAIT_BGP;
break;
case P_WAIT_BGP:
/* A видит C и B; C видит A и B; B видит A (и C) */
if (bgp_has(A, nc) && bgp_has(A, nb) && bgp_has(C, na) && bgp_has(C, nb) && bgp_has(B, na))
t->phase = P_SEND1;
break;
case P_SEND1:
if (dm_start(A, nb, g_sh.x_pub[IDX_B], g_sh.ed_pub[IDX_B], "B", CH_ID) != 0
|| dm_send(A, g_sh.conv_id, "text", (const uint8_t*)"hello1", 6) != 0) {
fprintf(stderr, "A: dm_start/send hello1 failed\n");
t->result = 2;
} else {
t->phase = P_WAIT_MSG1;
}
break;
case P_WAIT_MSG1:
if (dm_msg_has(B, "hello1")) t->phase = P_B_OFFLINE;
break;
case P_B_OFFLINE:
utun_instance_destroy(B);
t->inst[IDX_B] = NULL;
t->phase = P_WAIT_B_OFFLINE;
break;
case P_WAIT_B_OFFLINE:
if (!bgp_has(A, nb)) t->phase = P_SEND2;
break;
case P_SEND2:
if (dm_send(A, g_sh.conv_id, "text", (const uint8_t*)"hello2", 6) != 0) {
fprintf(stderr, "A: dm_send hello2 failed\n");
t->result = 2;
} else {
t->phase = P_WAIT_MAIL;
}
break;
case P_WAIT_MAIL:
if (dm_mail_count(C) > 0) t->phase = P_RECONNECT;
break;
case P_RECONNECT: {
char cfg[512];
snprintf(cfg, sizeof(cfg), "%s/b.conf", getenv("UTUN_TEST_DIR") ? getenv("UTUN_TEST_DIR") : ".");
struct UTUN_INSTANCE* b2 = utun_instance_create(t->ua, cfg);
if (!b2) { fprintf(stderr, "B2 create failed\n"); t->result = 2; break; }
utun_instance_init(b2);
t->inst[IDX_B] = b2;
t->phase = P_WAIT_B2;
break;
}
case P_WAIT_B2:
if (dm_msg_has(B, "hello2") && dm_mail_count(C) == 0) t->phase = P_DONE;
break;
case P_DONE:
t->result = 1;
uasync_stop(t->ua);
return;
}
if (t->phase == P_DONE) { t->result = 1; uasync_stop(t->ua); return; }
if (++t->ticks > MAX_TICKS) {
fprintf(stderr, "test: timeout in phase %d (A_conn=%d C_conn=%d B_conn=%d)\n",
(int)t->phase,
conn_up(A, nc), conn_up(C, na), B ? conn_up(B, nc) : -1);
t->result = 2;
uasync_stop(t->ua);
return;
}
t->timer = uasync_set_timeout(t->ua, TICK_TB, t, dm_tick, "dm_tick");
}
/* ── сценарий 2: первое сообщение в offline (у получателя нет беседы) ──
* A,B,C online → B уходит offline → A шлёт ПЕРВОЕ сообщение "first" → mailbox →
* B2 переподключается (без беседы) → тянет mailbox → получает "first". */
enum dm2_phase {
S2_WAIT_CONN,
S2_SETUP,
S2_WAIT_BGP,
S2_B_OFFLINE,
S2_WAIT_B_OFFLINE,
S2_SEND, /* A: dm_start + dm_send "first" → mailbox (B offline, первое) */
S2_WAIT_MAIL, /* C: блоб в dm_mail */
S2_RECONNECT, /* create B2 (та же БД B, но без беседы) */
S2_WAIT_B2, /* B2: получил "first" */
S2_DONE,
};
struct dm2_ctx {
struct UASYNC* ua;
struct UTUN_INSTANCE* inst[3];
enum dm2_phase phase;
int result;
int ticks;
void* timer;
};
static void dm2_tick(void* arg) {
struct dm2_ctx* t = arg;
t->timer = NULL;
if (t->result) return;
struct UTUN_INSTANCE* A = t->inst[IDX_A];
struct UTUN_INSTANCE* B = t->inst[IDX_B];
struct UTUN_INSTANCE* C = t->inst[IDX_C];
uint64_t na = g_sh.nid[IDX_A], nb = g_sh.nid[IDX_B], nc = g_sh.nid[IDX_C];
switch (t->phase) {
case S2_WAIT_CONN:
if (conn_up(A, nc) && conn_up(C, na) && conn_up(B, nc) && conn_up(C, nb))
t->phase = S2_SETUP;
break;
case S2_SETUP:
do_setup(A, IDX_A);
do_setup(B, IDX_B);
do_setup(C, IDX_C);
t->phase = S2_WAIT_BGP;
break;
case S2_WAIT_BGP:
if (bgp_has(A, nc) && bgp_has(A, nb) && bgp_has(C, na) && bgp_has(C, nb) && bgp_has(B, na))
t->phase = S2_B_OFFLINE;
break;
case S2_B_OFFLINE:
utun_instance_destroy(B);
t->inst[IDX_B] = NULL;
t->phase = S2_WAIT_B_OFFLINE;
break;
case S2_WAIT_B_OFFLINE:
if (!bgp_has(A, nb)) t->phase = S2_SEND;
break;
case S2_SEND:
if (dm_start(A, nb, g_sh.x_pub[IDX_B], g_sh.ed_pub[IDX_B], "B", CH_ID) != 0
|| dm_send(A, g_sh.conv_id, "text", (const uint8_t*)"first", 5) != 0) {
fprintf(stderr, "A: dm_start/send first failed\n");
t->result = 2;
} else {
t->phase = S2_WAIT_MAIL;
}
break;
case S2_WAIT_MAIL:
if (dm_mail_count(C) > 0) t->phase = S2_RECONNECT;
break;
case S2_RECONNECT: {
char cfg[512];
snprintf(cfg, sizeof(cfg), "%s/b.conf", getenv("UTUN_TEST_DIR") ? getenv("UTUN_TEST_DIR") : ".");
struct UTUN_INSTANCE* b2 = utun_instance_create(t->ua, cfg);
if (!b2) { fprintf(stderr, "B2 create failed\n"); t->result = 2; break; }
utun_instance_init(b2);
t->inst[IDX_B] = b2;
t->phase = S2_WAIT_B2;
break;
}
case S2_WAIT_B2:
if (dm_msg_has(B, "first") && dm_mail_count(C) == 0) t->phase = S2_DONE;
break;
case S2_DONE:
t->result = 1;
uasync_stop(t->ua);
return;
}
if (t->phase == S2_DONE) { t->result = 1; uasync_stop(t->ua); return; }
if (++t->ticks > MAX_TICKS) {
fprintf(stderr, "s2: timeout in phase %d (A_conn=%d C_conn=%d B_conn=%d)\n",
(int)t->phase,
conn_up(A, nc), conn_up(C, na), B ? conn_up(B, nc) : -1);
t->result = 2;
uasync_stop(t->ua);
return;
}
t->timer = uasync_set_timeout(t->ua, TICK_TB, t, dm2_tick, "dm2_tick");
}
/* ── main ── */
int main(int argc, char** argv) {
(void)argc; (void)argv;
debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR);
if (getenv("UTUN_TEST_DEBUG")) {
debug_set_category_level_by_name("dm", "trace");
debug_set_category_level_by_name("chat_sync", "info");
debug_set_category_level_by_name("member_sync", "info");
debug_set_category_level_by_name("bgp", "info");
debug_set_category_level_by_name("etcp_route", "info");
debug_set_category_level_by_name("crypto", "trace");
}
utun_instance_set_tun_init_enabled(0);
srand((unsigned)time(NULL));
char dir[512]; snprintf(dir, sizeof(dir), "/tmp/utun_dm_e2e_XXXXXX");
if (test_mkdtemp(dir) != 0) { fprintf(stderr, "mkdtemp failed\n"); return 1; }
test_setenv("UTUN_TEST_DIR", dir, 1);
fill_shared(58000 + (getpid() % 2000));
write_configs(dir);
printf("=== test_dm_e2e ===\n"); fflush(stdout);
TEST("single-thread: direct send + offline mailbox + reconnect pull"); {
struct UASYNC* ua = uasync_create();
if (!ua) { FAIL("uasync_create failed"); return 1; }
struct dm_ctx t;
memset(&t, 0, sizeof(t));
t.ua = ua;
t.phase = P_WAIT_CONN;
char cfgA[512], cfgB[512], cfgC[512];
snprintf(cfgA, sizeof(cfgA), "%s/a.conf", dir);
snprintf(cfgB, sizeof(cfgB), "%s/b.conf", dir);
snprintf(cfgC, sizeof(cfgC), "%s/c.conf", dir);
t.inst[IDX_A] = utun_instance_create(ua, cfgA);
t.inst[IDX_B] = utun_instance_create(ua, cfgB);
t.inst[IDX_C] = utun_instance_create(ua, cfgC);
if (!t.inst[IDX_A] || !t.inst[IDX_B] || !t.inst[IDX_C]) {
FAIL("instance create failed A=%p B=%p C=%p",
(void*)t.inst[IDX_A], (void*)t.inst[IDX_B], (void*)t.inst[IDX_C]);
uasync_destroy(ua, 0);
return 1;
}
utun_instance_init(t.inst[IDX_A]);
utun_instance_init(t.inst[IDX_C]);
/* B стартует после C, чтобы B→C пришлось на уже поднятый C (как в сценарии) */
utun_instance_init(t.inst[IDX_B]);
t.timer = uasync_set_timeout(ua, TICK_TB, &t, dm_tick, "dm_tick");
uasync_mainloop(ua);
int ok = (t.result == 1);
if (t.inst[IDX_A]) utun_instance_destroy(t.inst[IDX_A]);
if (t.inst[IDX_B]) utun_instance_destroy(t.inst[IDX_B]);
if (t.inst[IDX_C]) utun_instance_destroy(t.inst[IDX_C]);
uasync_destroy(ua, 0);
if (ok) OK(); else FAIL("result=%d phase=%d", t.result, (int)t.phase);
}
char cmd[512]; snprintf(cmd, sizeof(cmd), "rm -rf %s", dir); (void)!system(cmd);
/* сценарий 2: отдельная топология (свежие ключи/БД) */
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));
write_configs(dir2);
TEST("first message offline: mailbox delivery without prior conversation"); {
struct UASYNC* ua = uasync_create();
if (!ua) { FAIL("uasync_create failed"); return 1; }
struct dm2_ctx t;
memset(&t, 0, sizeof(t));
t.ua = ua;
t.phase = S2_WAIT_CONN;
char cfgA[512], cfgB[512], cfgC[512];
snprintf(cfgA, sizeof(cfgA), "%s/a.conf", dir2);
snprintf(cfgB, sizeof(cfgB), "%s/b.conf", dir2);
snprintf(cfgC, sizeof(cfgC), "%s/c.conf", dir2);
t.inst[IDX_A] = utun_instance_create(ua, cfgA);
t.inst[IDX_B] = utun_instance_create(ua, cfgB);
t.inst[IDX_C] = utun_instance_create(ua, cfgC);
if (!t.inst[IDX_A] || !t.inst[IDX_B] || !t.inst[IDX_C]) {
FAIL("instance create failed A=%p B=%p C=%p",
(void*)t.inst[IDX_A], (void*)t.inst[IDX_B], (void*)t.inst[IDX_C]);
uasync_destroy(ua, 0);
return 1;
}
utun_instance_init(t.inst[IDX_A]);
utun_instance_init(t.inst[IDX_C]);
utun_instance_init(t.inst[IDX_B]);
t.timer = uasync_set_timeout(ua, TICK_TB, &t, dm2_tick, "dm2_tick");
uasync_mainloop(ua);
int ok = (t.result == 1);
if (t.inst[IDX_A]) utun_instance_destroy(t.inst[IDX_A]);
if (t.inst[IDX_B]) utun_instance_destroy(t.inst[IDX_B]);
if (t.inst[IDX_C]) utun_instance_destroy(t.inst[IDX_C]);
uasync_destroy(ua, 0);
if (ok) OK(); else FAIL("result=%d phase=%d", t.result, (int)t.phase);
}
snprintf(cmd, sizeof(cmd), "rm -rf %s", dir2); (void)!system(cmd);
printf("\n%d/%d passed, %d failed\n", G_PASSED, G_TOTAL, G_FAILED);
return G_FAILED > 0 ? 1 : 0;
}