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.
 
 
 
 
 
 

589 lines
25 KiB

// test_chat_join.c — компонентные + интеграционные тесты join-протокола.
//
// Задача 1 (компонентные): криптография дерева приглашений (build_sign_msg / sign_pubkey /
// verify_signature), сериализация мембера, chat_join_process_request (инвайтер), хранилище
// ключей (register_key self / lookup_inviter), member_sync_validate_pubkey.
//
// Задача 2 (интеграция): два инстанса A(инвайтер) и C(connection) — регистрация join-ключа
// через etcp_router, после сходимости BGP lookup_inviter(C) == A.node_id.
//
// chat_core/chat_sync (глобальные синглтоны) НЕ используются: конфиг без секции [chatserver],
// member_sync/chat_join инициализируются вручную.
#include "chat_join.h"
#include "member_sync.h"
#include "../routing_layer/topo_node_sqlite.h"
#include "../utun_instance.h"
#include "../transport_layer/etcp.h"
#include "../transport_layer/secure_channel.h"
#include "../config_parser.h"
#include "../config_updater.h"
#include "../routing_layer/topo_group.h"
#include "../routing_layer/topo_node.h"
#include "../lib/u_async.h"
#include "../lib/debug_config.h"
#include "../lib/mem.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>
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)
#define POLL_MS 5
#define CH_COMP "42424242424"
#define CH_INT "53535353535"
#define JOIN_KEY 0x0BADCAFEF00DF00DULL
#define MAX_NODES 2
static struct UASYNC* g_ua = NULL;
static struct UTUN_INSTANCE* g_inst[MAX_NODES];
static uint64_t g_nid[MAX_NODES];
static char g_tdir[256] = "/tmp/utun_cj_XXXXXX";
static char g_cfg[MAX_NODES][256];
static char g_db_dir[MAX_NODES][320];
static int g_port[MAX_NODES];
static uint64_t g_group_id = 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);
}
/* ed25519 из x25519: priv = SHA512(x_priv)[0:32], pub = sc_derive_ed25519_pubkey(x_priv) */
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);
}
/* ── DB-хелперы ── */
static sqlite3* open_db(void) {
sqlite3* db = NULL;
sqlite3_open(":memory:", &db);
topo_node_sqlite_init(db);
return db;
}
static void make_channel(sqlite3* db, const char* ch_id,
uint8_t ch_x_pub[32], uint8_t ch_x_priv[32],
uint8_t ch_ed_pub[32], uint8_t ch_ed_priv[32]) {
gen_x25519(ch_x_pub, ch_x_priv);
gen_ed25519(ch_ed_pub, ch_ed_priv);
uint8_t ch_sig[64];
sc_ed25519_sign(ch_ed_priv, (const uint8_t*)ch_id, strlen(ch_id), ch_sig);
topo_node_sqlite_channel_put(db, ch_id, "test", 0, ch_x_pub, ch_x_priv, ch_ed_pub, ch_ed_priv, ch_sig);
}
/* ── джойнер: ключи + join_sig + сериализованный member-блок ── */
struct joiner {
uint8_t x_pub[32], x_priv[32], ed_pub[32], ed_priv[32];
uint64_t node_id;
uint8_t join_sig[64];
uint64_t join_ts;
};
static void make_joiner(struct joiner* j, const uint8_t ch_x_pub[32], const uint8_t ch_ed_pub[32]) {
gen_x25519(j->x_pub, j->x_priv);
x25519_to_ed25519(j->x_priv, j->ed_priv, j->ed_pub);
j->node_id = sc_derive_node_id_from_pubkey(j->x_pub);
j->join_ts = (uint64_t)time(NULL);
uint8_t jm[128];
int jl = member_sync_build_join_msg(ch_x_pub, ch_ed_pub, j->node_id, j->x_pub, j->join_ts,
jm, (int)sizeof(jm));
sc_ed25519_sign(j->ed_priv, jm, (size_t)jl, j->join_sig);
}
static int serialize_member(uint64_t node_id, const uint8_t* x25519, const uint8_t* ed25519,
const uint8_t* join_sig, uint64_t join_ts,
uint8_t* buf, size_t sz, size_t* len) {
struct join_member_data m;
memset(&m, 0, sizeof(m));
m.node_id = node_id; m.x25519 = x25519; m.ed25519 = ed25519;
m.join_sig = join_sig; m.join_ts = join_ts;
snprintf(m.userinfo, sizeof(m.userinfo), "{\"name\":\"J\"}");
return chat_join_serialize_member(&m, buf, sz, len);
}
/* ── 5.1.1 build_sign_msg ── */
static void t_build_sign_msg(void) {
TEST("member_sync_build_sign_msg: 42 bytes"); {
uint8_t x[32], priv[32], out[64]; gen_x25519(x, priv);
int n = member_sync_build_sign_msg(x, out, (int)sizeof(out));
if (n == MEMBER_SIGN_MSG_SIZE && memcmp(out, x, 32) == 0
&& memcmp(out + 32, MEMBER_SIGN_SUFFIX, MEMBER_SIGN_SUFFIX_LEN) == 0) OK();
else FAIL("n=%d", n);
}
TEST("member_sync_build_sign_msg: short buffer -> -1"); {
uint8_t x[32], priv[32], out[41]; gen_x25519(x, priv);
if (member_sync_build_sign_msg(x, out, (int)sizeof(out)) == -1) OK(); else FAIL();
}
}
/* ── 5.1.2 sign_pubkey / verify_signature ── */
static void t_sign_verify(void) {
sqlite3* db = open_db();
uint8_t ch_x_pub[32], ch_x_priv[32], ch_ed_pub[32], ch_ed_priv[32];
make_channel(db, CH_COMP, ch_x_pub, ch_x_priv, ch_ed_pub, ch_ed_priv);
(void)ch_x_priv;
/* joiner (подписываемый pubkey) и «другой» pubkey для подмены */
uint8_t jx[32], jpriv[32]; gen_x25519(jx, jpriv);
uint8_t other_x[32], other_priv[32]; gen_x25519(other_x, other_priv);
(void)jpriv; (void)other_priv;
/* родитель (для ребёнка) */
uint8_t p_x_pub[32], p_x_priv[32], p_ed_pub[32], p_ed_priv[32];
gen_x25519(p_x_pub, p_x_priv);
x25519_to_ed25519(p_x_priv, p_ed_priv, p_ed_pub);
uint64_t parent_id = sc_derive_node_id_from_pubkey(p_x_pub);
topo_node_sqlite_node_update_verified(db, parent_id, "parent", p_x_pub, p_ed_pub,
(uint64_t)time(NULL), time(NULL));
struct ms_member_rec rec;
uint8_t sig[64];
TEST("verify_signature: root (signed_by=0, channel ed25519) == 1"); {
member_sync_sign_pubkey(ch_ed_priv, jx, sig);
memset(&rec, 0, sizeof(rec)); rec.x25519 = jx; rec.signature = sig; rec.signed_by = 0;
if (member_sync_verify_signature(db, CH_COMP, &rec) == 1) OK(); else FAIL();
}
TEST("verify_signature: child (signed_by=parent) == 1"); {
member_sync_sign_pubkey(p_ed_priv, jx, sig);
memset(&rec, 0, sizeof(rec)); rec.x25519 = jx; rec.signature = sig; rec.signed_by = parent_id;
if (member_sync_verify_signature(db, CH_COMP, &rec) == 1) OK(); else FAIL();
}
TEST("verify_signature: tampered x25519 == 0"); {
member_sync_sign_pubkey(ch_ed_priv, jx, sig);
memset(&rec, 0, sizeof(rec)); rec.x25519 = other_x; rec.signature = sig; rec.signed_by = 0;
if (member_sync_verify_signature(db, CH_COMP, &rec) == 0) OK(); else FAIL();
}
TEST("verify_signature: parent not in nodes == -1"); {
member_sync_sign_pubkey(ch_ed_priv, jx, sig);
memset(&rec, 0, sizeof(rec)); rec.x25519 = jx; rec.signature = sig; rec.signed_by = 0x7777ULL;
if (member_sync_verify_signature(db, CH_COMP, &rec) == -1) OK(); else FAIL();
}
TEST("verify_signature: no channel == -1"); {
member_sync_sign_pubkey(ch_ed_priv, jx, sig);
memset(&rec, 0, sizeof(rec)); rec.x25519 = jx; rec.signature = sig; rec.signed_by = 0;
if (member_sync_verify_signature(db, "99999999999", &rec) == -1) OK(); else FAIL();
}
sqlite3_close(db);
}
/* ── 5.1.3 serialize_member / parse_member ── */
static void t_serialize_parse(void) {
uint8_t x[32], xp[32], ed[32], edp[32], jsig[64], usig[64];
gen_x25519(x, xp); gen_ed25519(ed, edp);
memset(jsig, 0x11, 64); memset(usig, 0x22, 64);
struct join_member_data m;
memset(&m, 0, sizeof(m));
m.node_id = 0x1234567890abcdefULL; m.x25519 = x; m.ed25519 = ed;
m.join_sig = jsig; m.join_ts = 1234567ULL;
m.update_sig = usig; m.update_ts = 7654321ULL;
strcpy(m.userinfo, "{\"name\":\"J\"}");
uint8_t buf[512]; size_t len = 0;
TEST("serialize_member + parse_member roundtrip"); {
struct join_member_data p; memset(&p, 0, sizeof(p));
if (chat_join_serialize_member(&m, buf, sizeof(buf), &len) != 0
|| chat_join_parse_member(buf, len, &p) != 0
|| p.node_id != m.node_id
|| memcmp(p.x25519, x, 32) || memcmp(p.ed25519, ed, 32)
|| memcmp(p.join_sig, jsig, 64) || p.join_ts != m.join_ts
|| memcmp(p.update_sig, usig, 64) || p.update_ts != m.update_ts
|| strcmp(p.userinfo, m.userinfo) != 0) FAIL();
else OK();
}
TEST("serialize_member: small buffer -> -1"); {
if (chat_join_serialize_member(&m, buf, 10, &len) == -1) OK(); else FAIL();
}
TEST("parse_member: short input -> -1"); {
struct join_member_data p;
if (chat_join_parse_member(buf, 10, &p) == -1) OK(); else FAIL();
}
TEST("serialize_member: userinfo up to 255"); {
char ui[256]; memset(ui, 'a', 255); ui[255] = '\0';
struct join_member_data ml = m; ml.userinfo[0] = '\0';
memcpy(ml.userinfo, ui, 256);
uint8_t b2[512]; size_t l2 = 0;
struct join_member_data p; memset(&p, 0, sizeof(p));
if (chat_join_serialize_member(&ml, b2, sizeof(b2), &l2) != 0
|| chat_join_parse_member(b2, l2, &p) != 0
|| strlen(p.userinfo) != 255 || memcmp(p.userinfo, ui, 255) != 0) FAIL();
else OK();
}
}
/* ── config/instance хелперы ── */
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 char* gv(const char* p, const char* k) {
struct utun_config* c = parse_config(p); if (!c) return NULL;
char* r = (strcmp(k, "pub") == 0) ? u_strdup(c->global.my_public_key_hex) : u_strdup(c->global.my_private_key_hex);
free_config(c); return r;
}
static void t_rmdir(const char* p) { char cmd[512]; snprintf(cmd, sizeof(cmd), "rm -rf %s", p); system(cmd); }
/* пишет конфиг n0 (component) — без клиентов */
static void write_comp_config(void) {
snprintf(g_cfg[0], sizeof(g_cfg[0]), "%s/comp.conf", g_tdir);
snprintf(g_db_dir[0], sizeof(g_db_dir[0]), "%s/dbcomp", g_tdir);
utun_mkdir(g_db_dir[0], 0755);
g_port[0] = 54000 + (getpid() % 4000);
wf(g_cfg[0], "[global]\ntun_ip=10.98.70.1/24\ntun_ifname=tun70\ndb_path=%s\n"
"[server: s1]\naddr=127.0.0.1:%d\ntype=public\n[allowed_keys]\nallow_all=1\n",
g_db_dir[0], g_port[0]);
config_ensure_keys_and_node_id(g_cfg[0]);
struct utun_config* c = parse_config(g_cfg[0]); g_nid[0] = c->global.my_node_id; free_config(c);
}
/* пишет конфиги a.conf (инвайтер, сервер) и c.conf (connection, клиент → a) */
static void write_int_configs(void) {
for (int i = 0; i < 2; i++) {
snprintf(g_cfg[i], sizeof(g_cfg[i]), "%s/%s.conf", g_tdir, i ? "c" : "a");
snprintf(g_db_dir[i], sizeof(g_db_dir[i]), "%s/db%s", g_tdir, i ? "c" : "a");
utun_mkdir(g_db_dir[i], 0755);
g_port[i] = 55000 + (getpid() % 3000) + i * 1000;
}
for (int i = 0; i < 2; i++) {
wf(g_cfg[i], "[global]\ntun_ip=10.98.%d.1/24\ntun_ifname=tun%d0\ndb_path=%s\n"
"[server: s1]\naddr=127.0.0.1:%d\ntype=public\n[allowed_keys]\nallow_all=1\n",
i, i, g_db_dir[i], g_port[i]);
config_ensure_keys_and_node_id(g_cfg[i]);
struct utun_config* c = parse_config(g_cfg[i]); g_nid[i] = c->global.my_node_id; free_config(c);
}
/* c (i=1) — клиент к a (i=0) */
{
char* pr = gv(g_cfg[1], "priv"); char* pu = gv(g_cfg[1], "pub");
char* p0_pu = gv(g_cfg[0], "pub");
wf(g_cfg[1], "[global]\nmy_private_key=%s\nmy_public_key=%s\ntun_ip=10.98.1.1/24\ntun_ifname=tun10\n"
"db_path=%s\n[server: s1]\naddr=127.0.0.1:%d\ntype=public\n"
"[client: to_a]\nkeepalive=1\npeer_public_key=%s\nlink=s1:127.0.0.1:%d\n[allowed_keys]\nallow_all=1\n",
pr, pu, g_db_dir[1], g_port[1], p0_pu, g_port[0]);
u_free(pr); u_free(pu); u_free(p0_pu);
}
}
static int create_instances(int n) {
for (int i = 0; i < n; i++) {
g_inst[i] = utun_instance_create(g_ua, g_cfg[i]);
if (!g_inst[i]) { printf("FAIL: create n%d\n", i); return -1; }
utun_instance_init(g_inst[i]);
member_sync_init(g_inst[i]);
}
return 0;
}
static void destroy_instances(int n) {
for (int i = 0; i < n; i++) {
if (g_inst[i]) { g_inst[i]->running = 0; utun_instance_destroy(g_inst[i]); g_inst[i] = NULL; }
}
}
static int wait_connected(int n) {
if (n <= 1) return 1;
int need = n * (n - 1);
for (int a = 0; a < 2000; a++) {
int ok = 0;
for (int i = 0; i < n; i++) {
struct ll_entry* e = g_inst[i]->connections->head;
while (e) {
struct conn_queue_entry* ce = (struct conn_queue_entry*)e->data;
if (ce->conn && ce->conn->links) {
struct ETCP_LINK* l;
for (l = ce->conn->links; l; l = l->next)
if (l->initialized && ce->conn->crypto_ctx.initialized) ok++;
}
e = e->next;
}
}
if (ok >= need) return 1;
uasync_poll(g_ua, POLL_MS);
}
return 0;
}
static int wait_chat_bgp(int n) {
for (int a = 0; a < 8000; a++) {
int ok = 1;
for (int i = 0; i < n && ok; i++) {
struct TOPO_GROUP* g = topo_groups_find(g_inst[i]->topo_groups, g_group_id);
if (!g) { ok = 0; break; }
for (int m = 0; m < n; m++) {
if (m == i) continue;
struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(g, g_nid[m]);
if (!nq || !nq->paths || !nq->paths->head) { ok = 0; break; }
}
}
if (ok) return 1;
uasync_poll(g_ua, POLL_MS);
}
return 0;
}
/* ── 5.1.4 chat_join_process_request (инвайтер) ── */
static void t_process_request(void) {
struct UTUN_INSTANCE* inst = g_inst[0];
sqlite3* db = inst->topo_sqlite_db;
uint8_t ch_x_pub[32], ch_x_priv[32], ch_ed_pub[32], ch_ed_priv[32];
make_channel(db, CH_COMP, ch_x_pub, ch_x_priv, ch_ed_pub, ch_ed_priv);
(void)ch_x_priv; (void)ch_ed_priv;
/* инвайтер (self) в nodes — чтобы verify_signature резолвил signed_by */
topo_node_sqlite_node_update_verified(db, inst->node_id, "inviter",
inst->my_keys.public_key, inst->my_ed25519_pubkey, (uint64_t)time(NULL), time(NULL));
uint64_t group_id = strtoull(CH_COMP, NULL, 10);
/* 0. неизвестный join-ключ → отклонён (ключ ещё не зарегистрирован) */
TEST("process_request: unknown join_key rejected"); {
struct joiner j; make_joiner(&j, ch_x_pub, ch_ed_pub);
uint8_t buf[512]; size_t len = 0;
serialize_member(j.node_id, j.x_pub, j.ed_pub, j.join_sig, j.join_ts, buf, sizeof(buf), &len);
chat_join_process_request(inst, group_id, j.node_id, JOIN_KEY, buf, len);
if (!topo_node_sqlite_member_in_channel(db, CH_COMP, j.node_id)) OK(); else FAIL();
}
/* регистрируем ключ локально — дальше process_request его принимает */
chat_join_register_key(inst, group_id, inst->node_id, JOIN_KEY);
/* 1. валидный join_sig → мембер добавлен, signed_by=self, подпись валидна */
TEST("process_request: valid join_sig accepted"); {
struct joiner j; make_joiner(&j, ch_x_pub, ch_ed_pub);
uint8_t buf[512]; size_t len = 0;
serialize_member(j.node_id, j.x_pub, j.ed_pub, j.join_sig, j.join_ts, buf, sizeof(buf), &len);
chat_join_process_request(inst, group_id, j.node_id, JOIN_KEY, buf, len);
uint64_t sb = 0; uint8_t sig[64]; memset(sig, 0, sizeof(sig));
int in_ch = topo_node_sqlite_member_in_channel(db, CH_COMP, j.node_id);
int gs = topo_node_sqlite_member_get_sign(db, CH_COMP, j.node_id, &sb, sig);
struct ms_member_rec rec; memset(&rec, 0, sizeof(rec));
rec.x25519 = j.x_pub; rec.signature = sig; rec.signed_by = sb;
int vs = (gs == 0) ? member_sync_verify_signature(db, CH_COMP, &rec) : -99;
if (in_ch == 1 && gs == 0 && sb == inst->node_id && vs == 1) OK();
else FAIL("in_ch=%d gs=%d sb=%016llx vs=%d", in_ch, gs, (unsigned long long)sb, vs);
}
/* 2. невалидный join_sig → отклонён */
TEST("process_request: invalid join_sig rejected"); {
struct joiner j; make_joiner(&j, ch_x_pub, ch_ed_pub);
uint8_t bad_sig[64]; memcpy(bad_sig, j.join_sig, 64); bad_sig[0] ^= 0xFF;
uint8_t buf[512]; size_t len = 0;
serialize_member(j.node_id, j.x_pub, j.ed_pub, bad_sig, j.join_ts, buf, sizeof(buf), &len);
chat_join_process_request(inst, group_id, j.node_id, JOIN_KEY, buf, len);
if (!topo_node_sqlite_member_in_channel(db, CH_COMP, j.node_id)) OK(); else FAIL();
}
/* 3. node_id != derive(x25519) → отклонён */
TEST("process_request: node_id mismatch rejected"); {
struct joiner j; make_joiner(&j, ch_x_pub, ch_ed_pub);
uint8_t buf[512]; size_t len = 0;
serialize_member(j.node_id ^ 0x12345ULL, j.x_pub, j.ed_pub, j.join_sig, j.join_ts, buf, sizeof(buf), &len);
chat_join_process_request(inst, group_id, j.node_id, JOIN_KEY, buf, len);
if (!topo_node_sqlite_member_in_channel(db, CH_COMP, j.node_id)) OK(); else FAIL();
}
}
/* ── 5.1.5 chat_join_register_key(self) / lookup_inviter ── */
static void t_register_key_self(void) {
struct UTUN_INSTANCE* inst = g_inst[0];
uint64_t group_id = strtoull(CH_COMP, NULL, 10);
TEST("register_key(target=self) -> lookup_inviter == self"); {
chat_join_register_key(inst, group_id, inst->node_id, JOIN_KEY);
uint64_t inv = chat_join_lookup_inviter(inst, group_id, JOIN_KEY);
if (inv == inst->node_id) OK(); else FAIL("inv=%016llx", (unsigned long long)inv);
}
TEST("lookup_inviter: unknown key -> 0"); {
if (chat_join_lookup_inviter(inst, group_id, JOIN_KEY + 1) == 0) OK(); else FAIL();
}
}
/* ── 5.1.6 member_sync_validate_pubkey ── */
static void t_validate_pubkey(void) {
struct UTUN_INSTANCE* inst = g_inst[0];
sqlite3* db = inst->topo_sqlite_db;
uint8_t ch_x_pub[32], ch_x_priv[32], ch_ed_pub[32], ch_ed_priv[32];
make_channel(db, "61616161616", ch_x_pub, ch_x_priv, ch_ed_pub, ch_ed_priv);
(void)ch_x_priv; (void)ch_ed_priv;
struct joiner j; make_joiner(&j, ch_x_pub, ch_ed_pub);
uint64_t gid = strtoull("61616161616", NULL, 10);
uint8_t buf[512]; size_t len = 0;
serialize_member(j.node_id, j.x_pub, j.ed_pub, j.join_sig, j.join_ts, buf, sizeof(buf), &len);
chat_join_register_key(inst, gid, inst->node_id, JOIN_KEY);
chat_join_process_request(inst, gid, j.node_id, JOIN_KEY, buf, len);
TEST("validate_pubkey: member -> 1"); {
if (member_sync_validate_pubkey(inst, "61616161616", j.x_pub) == 1) OK(); else FAIL();
}
TEST("validate_pubkey: non-member -> 0"); {
uint8_t ox[32], op[32]; gen_x25519(ox, op); (void)op;
if (member_sync_validate_pubkey(inst, "61616161616", ox) == 0) OK(); else FAIL();
}
}
/* ── 5.2 интеграция: register_key через etcp_router ── */
static void t_integration(void) {
g_group_id = strtoull(CH_INT, NULL, 10);
write_int_configs();
if (create_instances(2) != 0) return;
TEST("integration: A<->C connected"); {
if (wait_connected(2)) OK(); else FAIL();
}
/* CHAT-группа + канал + мемберы (после connect — чтобы сработал passive topo_group_new_conn) */
{
uint8_t ch_x_pub[32], ch_x_priv[32], ch_ed_pub[32], ch_ed_priv[32];
gen_x25519(ch_x_pub, ch_x_priv); gen_ed25519(ch_ed_pub, ch_ed_priv);
uint8_t ch_sig[64];
sc_ed25519_sign(ch_ed_priv, (const uint8_t*)CH_INT, strlen(CH_INT), ch_sig);
for (int i = 0; i < 2; i++) {
sqlite3* db = g_inst[i]->topo_sqlite_db;
topo_node_sqlite_channel_put(db, CH_INT, "test", g_nid[0], ch_x_pub, NULL, ch_ed_pub, NULL, ch_sig);
if (!topo_groups_find(g_inst[i]->topo_groups, g_group_id))
topo_groups_create_group(g_inst[i]->topo_groups, g_group_id, TOPO_GROUP_TYPE_CHAT, CH_INT);
for (int m = 0; m < 2; m++) {
member_sync_put(g_inst[i], CH_INT, g_nid[m],
g_inst[m]->my_keys.public_key, g_inst[m]->my_ed25519_pubkey,
NULL, 0, NULL, 0, "{\"name\":\"node\"}", NULL, NULL, 0, 0, NULL);
topo_node_sqlite_node_update_verified(g_inst[i]->topo_sqlite_db, g_nid[m],
"node", g_inst[m]->my_keys.public_key, g_inst[m]->my_ed25519_pubkey,
(uint64_t)time(NULL), time(NULL));
}
chat_join_init(g_inst[i]);
}
}
TEST("integration: CHAT group BGP converged"); {
if (wait_chat_bgp(2)) OK(); else FAIL();
}
TEST("integration: register_key(A->C) -> lookup_inviter(C) == A"); {
/* A=инвайтер (node 0), C=connection (node 1) */
chat_join_register_key(g_inst[0], g_group_id, g_nid[1], JOIN_KEY);
int ok = 0;
for (int a = 0; a < 4000; a++) {
if (chat_join_lookup_inviter(g_inst[1], g_group_id, JOIN_KEY) == g_nid[0]) { ok = 1; break; }
uasync_poll(g_ua, POLL_MS);
}
if (ok) OK(); else FAIL("inviter=%016llx", (unsigned long long)chat_join_lookup_inviter(g_inst[1], g_group_id, JOIN_KEY));
}
TEST("integration: re-register overwrites (lookup still A)"); {
chat_join_register_key(g_inst[0], g_group_id, g_nid[1], JOIN_KEY);
int ok = 0;
for (int a = 0; a < 2000; a++) {
if (chat_join_lookup_inviter(g_inst[1], g_group_id, JOIN_KEY) == g_nid[0]) { ok = 1; break; }
uasync_poll(g_ua, POLL_MS);
}
if (ok) OK(); else FAIL();
}
for (int i = 0; i < 2; i++) { if (g_inst[i]) chat_join_destroy(g_inst[i]); }
destroy_instances(2);
}
/* ── main ── */
int main(void) {
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", "info");
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));
printf("=== test_chat_join ===\n");
{ char t[256] = "/tmp/utun_cj_XXXXXX";
(void)!mkdtemp(t); snprintf(g_tdir, sizeof(g_tdir), "%s", t); }
g_ua = uasync_create();
if (!g_ua) { printf("FAIL: uasync_create\n"); return 1; }
/* ── компонентные (без инстанса) ── */
t_build_sign_msg();
t_sign_verify();
t_serialize_parse();
/* ── компонентные (один инстанс, без [chatserver]) ── */
write_comp_config();
if (create_instances(1) == 0) {
t_process_request();
t_register_key_self();
t_validate_pubkey();
destroy_instances(1);
}
/* ── интеграция (2 инстанса через etcp_router) ── */
t_integration();
fflush(stdout); fflush(stderr);
printf("\n%d/%d passed, %d failed\n", G_PASSED, G_TOTAL, G_FAILED); fflush(stdout);
if (g_ua) { uasync_destroy(g_ua, 0); g_ua = NULL; }
t_rmdir(g_tdir);
return G_FAILED > 0 ? 1 : 0;
}