You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
480 lines
20 KiB
480 lines
20 KiB
// test_chat_join_e2e.c — E2E тест join-протокола через реальные chat_core/chat_sync. |
|
// |
|
// Много-процессный: chat_core/chat_sync — глобальные синглтоны на процесс, поэтому |
|
// роли A(инвайтер)/C(connection)/J(джойнер) разнесены по fork-процессам. |
|
// Статические данные (ключи канала, node_id, pubkey) заполняются родителем ДО fork и |
|
// наследуются детьми через память; динамический обмен — invite-ссылка через файл. |
|
// Координация результата — коды возврата (0=OK, 1=FAIL). |
|
// |
|
// Сценарии: |
|
// happy A≠C: A регистрирует ключ на C, J входит через C, у A и C мембер J с signed_by=A. |
|
// degraded A==C: J входит напрямую в A (ключ локально, форварда нет). |
|
// invalid неверный ключ: J отклонён, мембера J нет. |
|
|
|
#include "chat_core.h" |
|
#include "chat_sync.h" |
|
#include "chat_join.h" |
|
#include "invite_link.h" |
|
#include "invite_build.h" |
|
#include "member_sync.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 <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> |
|
#include <sys/wait.h> |
|
|
|
#define POLL_MS 5 |
|
#define CH_NAME "test-channel" |
|
|
|
#define IDX_A 0 |
|
#define IDX_C 1 |
|
#define IDX_J 2 |
|
|
|
/* общие данные (заполняются родителем, наследуются детьми через fork) */ |
|
struct e2e_shared { |
|
char ch_id[64]; |
|
uint8_t ch_x_pub[32], ch_x_priv[32], ch_ed_pub[32], ch_ed_priv[32], ch_sig[64]; |
|
uint64_t owner; |
|
uint64_t nid[3]; |
|
uint8_t x_pub[3][32], x_priv[3][32], ed_pub[3][32]; |
|
int port[3]; |
|
int degraded; |
|
}; |
|
|
|
static struct e2e_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 x25519_to_ed25519(const uint8_t* x_priv, uint8_t ed_priv[32], uint8_t ed_pub[32]) { |
|
EVP_MD_CTX* c = EVP_MD_CTX_new(); |
|
uint8_t h[64]; unsigned int hl = 0; |
|
EVP_DigestInit_ex(c, EVP_sha512(), NULL); |
|
EVP_DigestUpdate(c, x_priv, 32); |
|
EVP_DigestFinal_ex(c, h, &hl); |
|
EVP_MD_CTX_free(c); |
|
memcpy(ed_priv, h, 32); |
|
sc_derive_ed25519_pubkey(x_priv, ed_pub); |
|
} |
|
|
|
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, int degraded) { |
|
memset(&g_sh, 0, sizeof(g_sh)); |
|
g_sh.degraded = degraded; |
|
snprintf(g_sh.ch_id, sizeof(g_sh.ch_id), "4242424242424242"); |
|
|
|
for (int i = 0; i < 3; i++) { |
|
gen_x25519(g_sh.x_pub[i], g_sh.x_priv[i]); |
|
uint8_t ed_priv[32]; |
|
x25519_to_ed25519(g_sh.x_priv[i], ed_priv, 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; |
|
} |
|
|
|
gen_x25519(g_sh.ch_x_pub, g_sh.ch_x_priv); |
|
gen_ed25519(g_sh.ch_ed_pub, g_sh.ch_ed_priv); |
|
g_sh.owner = g_sh.nid[IDX_A]; |
|
|
|
{ |
|
uint8_t msg[256]; size_t off = 0; |
|
off += (size_t)snprintf((char*)msg + off, sizeof(msg) - off, "%s", g_sh.ch_id) + 1; |
|
off += (size_t)snprintf((char*)msg + off, sizeof(msg) - off, "%s", CH_NAME) + 1; |
|
memcpy(msg + off, &g_sh.owner, 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); |
|
} |
|
} |
|
|
|
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_C ? "c" : "j"); |
|
snprintf(dbd, sizeof(dbd), "%s/db%s", dir, i == IDX_A ? "a" : i == IDX_C ? "c" : "j"); |
|
utun_mkdir(dbd, 0755); |
|
|
|
char client[256] = ""; |
|
if (i == IDX_C && !g_sh.degraded) { |
|
char apub[65]; to_hex(g_sh.x_pub[IDX_A], 32, apub); |
|
snprintf(client, sizeof(client), |
|
"[client: to_a]\nkeepalive=1\npeer_public_key=%s\nlink=s1:127.0.0.1:%d\n", |
|
apub, g_sh.port[IDX_A]); |
|
} |
|
|
|
wf(path, |
|
"[global]\ntun_ip=10.98.%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_C ? "c" : "j", |
|
priv, pub, g_sh.port[i], client); |
|
} |
|
} |
|
|
|
/* ── poll-хелперы (дети) ── */ |
|
|
|
static int wait_conn(struct UTUN_INSTANCE* inst, uint64_t nid, int max_iter) { |
|
for (int a = 0; a < max_iter; a++) { |
|
struct ETCP_CONN* c = instance_find_conn(inst, nid); |
|
if (c && c->initialized && c->links_up) return 1; |
|
uasync_poll(inst->ua, POLL_MS); |
|
} |
|
{ |
|
struct ETCP_CONN* c = instance_find_conn(inst, nid); |
|
fprintf(stderr, " [conn-fail] self=0x%016llx target=0x%016llx conn=%p init=%d links_up=%d\n", |
|
(unsigned long long)inst->node_id, (unsigned long long)nid, (void*)c, |
|
c ? c->initialized : -1, c ? c->links_up : -1); |
|
} |
|
return 0; |
|
} |
|
|
|
static int wait_bgp(struct UTUN_INSTANCE* inst, uint64_t nid, int max_iter) { |
|
uint64_t gid = strtoull(g_sh.ch_id, NULL, 10); |
|
for (int a = 0; a < max_iter; a++) { |
|
struct TOPO_GROUP* g = topo_groups_find(inst->topo_groups, gid); |
|
if (g) { |
|
struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(g, nid); |
|
if (nq && nq->paths && nq->paths->head) return 1; |
|
} |
|
uasync_poll(inst->ua, POLL_MS); |
|
} |
|
{ |
|
struct TOPO_GROUP* g = topo_groups_find(inst->topo_groups, gid); |
|
struct TOPO_GROUP_NODE* nq = g ? topo_node_find_by_id(g, nid) : NULL; |
|
fprintf(stderr, " [bgp-fail] gid=%016llx group=%p nq=%p paths_head=%p self=0x%016llx target=0x%016llx\n", |
|
(unsigned long long)gid, (void*)g, (void*)nq, |
|
(void*)(nq && nq->paths ? nq->paths->head : NULL), |
|
(unsigned long long)inst->node_id, (unsigned long long)nid); |
|
} |
|
return 0; |
|
} |
|
|
|
/* ждать, пока мембер получит подпись дерева от want_sb (не плейсхолдер) */ |
|
static int wait_signed(struct UTUN_INSTANCE* inst, uint64_t nid, uint64_t want_sb, int max_iter) { |
|
for (int a = 0; a < max_iter; a++) { |
|
uint64_t sb = 0; uint8_t sig[64]; |
|
if (topo_node_sqlite_member_get_sign(inst->topo_sqlite_db, g_sh.ch_id, nid, &sb, sig) == 0 |
|
&& sb == want_sb) return 1; |
|
uasync_poll(inst->ua, POLL_MS); |
|
} |
|
{ |
|
uint64_t sb = 0; uint8_t sig[64]; |
|
int gr = topo_node_sqlite_member_get_sign(inst->topo_sqlite_db, g_sh.ch_id, nid, &sb, sig); |
|
fprintf(stderr, " [signed-fail] nid=0x%016llx want_sb=0x%016llx get_sign_rc=%d cur_sb=0x%016llx\n", |
|
(unsigned long long)nid, (unsigned long long)want_sb, gr, (unsigned long long)sb); |
|
} |
|
return 0; |
|
} |
|
|
|
static int wait_not_member(struct UTUN_INSTANCE* inst, uint64_t nid, int max_iter) { |
|
for (int a = 0; a < max_iter; a++) { |
|
if (topo_node_sqlite_member_in_channel(inst->topo_sqlite_db, g_sh.ch_id, nid)) return 0; |
|
uasync_poll(inst->ua, POLL_MS); |
|
} |
|
return 1; |
|
} |
|
|
|
static int wait_file(struct UTUN_INSTANCE* inst, const char* path, int max_iter) { |
|
for (int a = 0; a < max_iter; a++) { |
|
if (access(path, F_OK) == 0) return 1; |
|
uasync_poll(inst->ua, POLL_MS); |
|
} |
|
return 0; |
|
} |
|
|
|
/* J получил JOIN_READY: cs_handle_join_ready вызвал topo_group_new_conn → senders_list непуст */ |
|
static int wait_group_started(struct UTUN_INSTANCE* inst, int max_iter) { |
|
uint64_t gid = strtoull(g_sh.ch_id, NULL, 10); |
|
for (int a = 0; a < max_iter; a++) { |
|
struct TOPO_GROUP* g = topo_groups_find(inst->topo_groups, gid); |
|
if (g && g->senders_list && g->senders_list->head) return 1; |
|
uasync_poll(inst->ua, POLL_MS); |
|
} |
|
{ |
|
struct TOPO_GROUP* g = topo_groups_find(inst->topo_groups, gid); |
|
fprintf(stderr, " [join-ready-fail] self=0x%016llx group=%p senders_head=%p\n", |
|
(unsigned long long)inst->node_id, (void*)g, |
|
(void*)(g && g->senders_list ? g->senders_list->head : NULL)); |
|
} |
|
return 0; |
|
} |
|
|
|
/* ── настройка канала и мемберов ── */ |
|
|
|
static void channel_put_shared(struct UTUN_INSTANCE* inst, int has_priv) { |
|
topo_node_sqlite_channel_put(inst->topo_sqlite_db, g_sh.ch_id, CH_NAME, g_sh.owner, |
|
g_sh.ch_x_pub, has_priv ? g_sh.ch_x_priv : NULL, |
|
g_sh.ch_ed_pub, has_priv ? g_sh.ch_ed_priv : NULL, g_sh.ch_sig); |
|
chat_core_ensure_channel_ready(g_sh.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); |
|
uint8_t root_sig[64]; |
|
member_sync_sign_pubkey(g_sh.ch_ed_priv, inst->my_keys.public_key, root_sig); |
|
char userinfo[256]; snprintf(userinfo, sizeof(userinfo), "{\"name\":\"%s\"}", name); |
|
member_sync_put(inst, g_sh.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, root_sig); |
|
} |
|
|
|
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, g_sh.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); |
|
} |
|
|
|
/* ── child_main ── */ |
|
|
|
static int child_main(const char* role, const char* dir, int invalid_key) { |
|
struct UASYNC* ua = uasync_create(); |
|
if (!ua) { fprintf(stderr, "%s: uasync_create failed\n", role); return 1; } |
|
|
|
char cfg[512]; snprintf(cfg, sizeof(cfg), "%s/%s.conf", dir, |
|
strcmp(role, "a") == 0 ? "a" : strcmp(role, "c") == 0 ? "c" : "j"); |
|
struct UTUN_INSTANCE* inst = utun_instance_create(ua, cfg); |
|
if (!inst) { fprintf(stderr, "%s: create failed\n", role); uasync_destroy(ua, 0); return 1; } |
|
utun_instance_init(inst); |
|
|
|
int rc = 1; |
|
uint64_t ch_num = strtoull(g_sh.ch_id, NULL, 10); |
|
sqlite3* db = inst->topo_sqlite_db; |
|
|
|
if (strcmp(role, "a") == 0) { |
|
if (!g_sh.degraded && !wait_conn(inst, g_sh.nid[IDX_C], 4000)) { |
|
fprintf(stderr, "A: no conn to C\n"); goto out; |
|
} |
|
channel_put_shared(inst, 1); |
|
member_put_self(inst, "A"); |
|
if (!g_sh.degraded) { |
|
member_put_placeholder(inst, IDX_C, "C"); |
|
/* канал добавлен уже после коннекта — явно запускаем merkle-сессию A↔C */ |
|
member_sync_start(inst, g_sh.nid[IDX_C], g_sh.ch_id, NULL, NULL); |
|
} |
|
|
|
if (!g_sh.degraded && !wait_bgp(inst, g_sh.nid[IDX_C], 8000)) { |
|
fprintf(stderr, "A: no BGP to C\n"); goto out; |
|
} |
|
|
|
uint64_t target = g_sh.degraded ? g_sh.nid[IDX_A] : g_sh.nid[IDX_C]; |
|
char link[1024]; |
|
/* invite_build требует conn_presence узла — может отставать от paths; ретраим */ |
|
{ int built = 0; |
|
for (int a = 0; a < 1000; a++) { |
|
if (chat_invite_build_link(ch_num, target, NULL, link, sizeof(link)) == 0) { built = 1; break; } |
|
uasync_poll(inst->ua, POLL_MS); |
|
} |
|
if (!built) { fprintf(stderr, "A: invite_build_link failed (target=0x%016llx)\n", (unsigned long long)target); goto out; } |
|
} |
|
char lf[512]; snprintf(lf, sizeof(lf), "%s/link.txt", dir); |
|
wf(lf, "%s", link); |
|
|
|
if (invalid_key) { |
|
/* J с неверным ключом — мембером стать не должен */ |
|
if (!wait_not_member(inst, g_sh.nid[IDX_J], 1500)) { |
|
fprintf(stderr, "A: J unexpectedly became member\n"); goto out; |
|
} |
|
} else { |
|
if (!wait_signed(inst, g_sh.nid[IDX_J], g_sh.nid[IDX_A], 8000)) { |
|
fprintf(stderr, "A: J not signed by me\n"); goto out; |
|
} |
|
uint64_t sb = 0; uint8_t sig[64]; memset(sig, 0, sizeof(sig)); |
|
topo_node_sqlite_member_get_sign(db, g_sh.ch_id, g_sh.nid[IDX_J], &sb, sig); |
|
struct ms_member_rec rec; memset(&rec, 0, sizeof(rec)); |
|
rec.x25519 = g_sh.x_pub[IDX_J]; rec.signature = sig; rec.signed_by = sb; |
|
if (member_sync_verify_signature(db, g_sh.ch_id, &rec) != 1) { |
|
fprintf(stderr, "A: J signature invalid\n"); goto out; |
|
} |
|
} |
|
rc = 0; |
|
} else if (strcmp(role, "c") == 0) { |
|
if (!wait_conn(inst, g_sh.nid[IDX_A], 4000)) { |
|
fprintf(stderr, "C: no conn to A\n"); goto out; |
|
} |
|
channel_put_shared(inst, 0); |
|
member_put_placeholder(inst, IDX_C, "C"); |
|
member_put_placeholder(inst, IDX_A, "A"); |
|
/* явно запускаем merkle-сессию C↔A */ |
|
member_sync_start(inst, g_sh.nid[IDX_A], g_sh.ch_id, NULL, NULL); |
|
if (!wait_bgp(inst, g_sh.nid[IDX_A], 8000)) { |
|
fprintf(stderr, "C: no BGP to A\n"); goto out; |
|
} |
|
|
|
if (invalid_key) { |
|
if (!wait_not_member(inst, g_sh.nid[IDX_J], 1500)) { |
|
fprintf(stderr, "C: J unexpectedly became member\n"); goto out; |
|
} |
|
} else { |
|
if (!wait_signed(inst, g_sh.nid[IDX_J], g_sh.nid[IDX_A], 8000)) { |
|
fprintf(stderr, "C: J not signed by A\n"); goto out; |
|
} |
|
uint64_t sb = 0; uint8_t sig[64]; memset(sig, 0, sizeof(sig)); |
|
topo_node_sqlite_member_get_sign(db, g_sh.ch_id, g_sh.nid[IDX_J], &sb, sig); |
|
struct ms_member_rec rec; memset(&rec, 0, sizeof(rec)); |
|
rec.x25519 = g_sh.x_pub[IDX_J]; rec.signature = sig; rec.signed_by = sb; |
|
if (member_sync_verify_signature(db, g_sh.ch_id, &rec) != 1) { |
|
fprintf(stderr, "C: J signature invalid\n"); goto out; |
|
} |
|
} |
|
rc = 0; |
|
} else { |
|
char lf[512]; snprintf(lf, sizeof(lf), "%s/link.txt", dir); |
|
if (!wait_file(inst, lf, 8000)) { fprintf(stderr, "J: no link file\n"); goto out; } |
|
|
|
char link[1024]; |
|
{ FILE* f = fopen(lf, "r"); if (!f) { fprintf(stderr, "J: link open failed\n"); goto out; } |
|
size_t n = fread(link, 1, sizeof(link) - 1, f); fclose(f); link[n] = '\0'; } |
|
|
|
struct InviteData d; char err[256]; |
|
if (invite_link_decode(link, strlen(link), &d, err, sizeof(err)) < 0) { |
|
fprintf(stderr, "J: decode failed: %s\n", err); goto out; |
|
} |
|
uint8_t addrs_buf[2048]; |
|
int addrs_len = invite_serialize_addrs(&d, addrs_buf, sizeof(addrs_buf)); |
|
if (addrs_len <= 0) { fprintf(stderr, "J: serialize addrs failed\n"); goto out; } |
|
|
|
uint64_t key = invalid_key ? (d.join_key ^ 0xDEADBEEFCAFEBABEULL) : d.join_key; |
|
chat_sync_connect_from_invite(d.channelId, d.nodeId, d.pubkey, |
|
addrs_buf, d.addrCount, addrs_len, d.password_len ? d.password : NULL, key); |
|
|
|
if (invalid_key) { |
|
rc = wait_not_member(inst, g_sh.nid[IDX_J], 200) ? 0 : 1; |
|
if (rc) fprintf(stderr, "J: unexpectedly became member with wrong key\n"); |
|
} else { |
|
/* критерий J по ТЗ: получил JOIN_READY (topo_group_new_conn → senders_list) */ |
|
if (!wait_group_started(inst, 8000)) { |
|
fprintf(stderr, "J: no JOIN_READY\n"); goto out; |
|
} |
|
rc = 0; |
|
} |
|
} |
|
|
|
out: |
|
fprintf(stderr, "%s: %s\n", role, rc == 0 ? "OK" : "FAIL"); |
|
fflush(stdout); fflush(stderr); |
|
_exit(rc); |
|
} |
|
|
|
/* ── parent ── */ |
|
|
|
static int run_scenario(const char* name, int degraded, int invalid_key) { |
|
char dir[512]; snprintf(dir, sizeof(dir), "/tmp/utun_e2e_%s_XXXXXX", name); |
|
(void)!mkdtemp(dir); |
|
|
|
fill_shared(56000 + (getpid() % 2000), degraded); |
|
write_configs(dir); |
|
|
|
const char* roles[4]; |
|
if (degraded) { roles[0] = "a"; roles[1] = "j"; roles[2] = NULL; roles[3] = NULL; } |
|
else { roles[0] = "a"; roles[1] = "c"; roles[2] = "j"; roles[3] = NULL; } |
|
|
|
pid_t pids[3]; int n = 0; |
|
for (int i = 0; roles[i]; i++) { |
|
pid_t p = fork(); |
|
if (p == 0) { |
|
int r = child_main(roles[i], dir, invalid_key); |
|
_exit(r); |
|
} |
|
if (p > 0) pids[n++] = p; |
|
} |
|
|
|
int ok = 1; |
|
for (int i = 0; i < n; i++) { |
|
int st = 0; waitpid(pids[i], &st, 0); |
|
if (!WIFEXITED(st) || WEXITSTATUS(st) != 0) ok = 0; |
|
} |
|
|
|
char cmd[512]; snprintf(cmd, sizeof(cmd), "rm -rf %s", dir); (void)!system(cmd); |
|
return ok; |
|
} |
|
|
|
static int parent_main(void) { |
|
printf("=== test_chat_join_e2e ===\n"); fflush(stdout); |
|
|
|
TEST("happy path A!=C: J joins via C (signed_by=A)"); { |
|
if (run_scenario("happy", 0, 0)) OK(); else FAIL(); |
|
} |
|
TEST("degraded A==C: J joins directly via A"); { |
|
if (run_scenario("deg", 1, 0)) OK(); else FAIL(); |
|
} |
|
TEST("invalid key: J rejected (no member)"); { |
|
if (run_scenario("inv", 0, 1)) OK(); else FAIL(); |
|
} |
|
|
|
printf("\n%d/%d passed, %d failed\n", G_PASSED, G_TOTAL, G_FAILED); |
|
return G_FAILED > 0 ? 1 : 0; |
|
} |
|
|
|
int main(int argc, char** argv) { |
|
debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR); |
|
if (getenv("UTUN_TEST_DEBUG")) { |
|
debug_set_category_level_by_name("chat_sync", "info"); |
|
debug_set_category_level_by_name("member_sync", "trace"); |
|
debug_set_category_level_by_name("etcp_route", "info"); |
|
debug_set_category_level_by_name("bgp", "trace"); |
|
} |
|
utun_instance_set_tun_init_enabled(0); |
|
srand((unsigned)time(NULL)); |
|
|
|
if (argc >= 3) { |
|
int invalid = (argc >= 4 && strcmp(argv[3], "invalid") == 0); |
|
return child_main(argv[1], argv[2], invalid); |
|
} |
|
return parent_main(); |
|
}
|
|
|