diff --git a/src/chat/invite_build.c b/src/chat/invite_build.c index 0e99f7f4..4069a09f 100644 --- a/src/chat/invite_build.c +++ b/src/chat/invite_build.c @@ -65,7 +65,7 @@ static int node_has_both_public(struct UTUN_INSTANCE* inst, uint64_t node_id) { static uint64_t pick_best_node(struct UTUN_INSTANCE* inst, uint64_t channel_id, int* out_has_both_public, int* out_priority) { struct chat_core_ctx* cc = CC(inst); uint64_t best_nid = 0; - int best_priority = 4; /* 1=regular, 2=self, 3=super, 4=invalid */ + int best_priority = 4; /* 1=super, 2=regular, 3=self, 4=invalid */ int best_has_both = 0; struct TOPO_GROUP* grp = resolve_chat_group(inst, channel_id); @@ -99,9 +99,9 @@ static uint64_t pick_best_node(struct UTUN_INSTANCE* inst, uint64_t channel_id, int has_both = node_has_both_public(inst, nid); int priority; - if (nid == cc->my_node_id) priority = 2; /* self */ - else if (ntype == 4) priority = 3; /* supernode */ - else priority = 1; /* regular */ + if (ntype == 4) priority = 1; /* supernode, включая self */ + else if (nid == cc->my_node_id) priority = 3; + else priority = 2; if (priority <= best_priority) { best_nid = nid; @@ -122,6 +122,9 @@ static uint64_t resolve_auto_node(struct UTUN_INSTANCE* inst, uint64_t channel_i int has_both = 0, prio = 4; uint64_t best = pick_best_node(inst, channel_id, &has_both, &prio); if (best == 0) best = cc->my_node_id; + DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "invite_build: auto selection ch=%llu node=0x%016llx role=%s self=%d", + (unsigned long long)channel_id, (unsigned long long)best, + prio == 1 ? "supernode" : prio == 2 ? "regular" : "self", best == cc->my_node_id); if (out_has_both) *out_has_both = has_both; if (out_priority) *out_priority = prio; return best; diff --git a/src/chat/invite_build.h b/src/chat/invite_build.h index 382be756..1d3f1cec 100644 --- a/src/chat/invite_build.h +++ b/src/chat/invite_build.h @@ -20,7 +20,7 @@ struct chat_invite_build; typedef void (*chat_invite_ready_fn)(void* arg, int result, const char* link); /* Собрать invite-ссылку для канала. - * target_node_id = 0 → auto (лучший достижимый мембер, иначе self). + * target_node_id = 0 → auto (достижимый суперузел → обычный узел → self). * target_node_id != 0 → конкретный узел (должен быть достижимым мембером канала). * password = NULL → без пароля. * NULL=ошибка запуска, callback не вызывается. Иначе callback после возврата, @@ -35,7 +35,7 @@ void chat_invite_build_cancel(struct chat_invite_build* request); * Возвращает количество (0..max_ids), либо -1. */ int chat_invite_candidate_nodes(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint64_t* out_ids, int max_ids); -/* Авто-узел: лучший достижимый мембер, иначе self. Возвращает 0 и заполняет out_node_id, либо -1. */ +/* Авто-узел: достижимый суперузел → обычный узел → self. Возвращает 0 и заполняет out_node_id, либо -1. */ int chat_invite_best_node(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint64_t* out_node_id); /* Трамплин для gui_bridge (arg = u_malloc(struct chat_invite_build_req)). diff --git a/tests/test_chat_join_e2e.c b/tests/test_chat_join_e2e.c index 6e437daa..9f6ecbfa 100644 --- a/tests/test_chat_join_e2e.c +++ b/tests/test_chat_join_e2e.c @@ -12,6 +12,7 @@ // invalid неверный ключ: J отклонён, мембера J нет. #include "chat_core.h" +#include "chat_core_priv.h" #include "chat_sync.h" #include "chat_join.h" #include "chat_headless_control.h" @@ -432,6 +433,46 @@ out: /* ── child_main ── */ +/* Проверяем выбор на реальных мемберах канала; изменения производных полей откатываем до следующего poll. */ +static int check_invite_priority(struct UTUN_INSTANCE* inst) { + uint64_t channel_id = strtoull(g_sh.ch_id, NULL, 10), selected = 0; + struct TOPO_GROUP* group = topo_groups_find(inst->topo_groups, channel_id); + struct TOPO_GROUP_NODE* peer = topo_node_find_by_id(group, g_sh.nid[IDX_C]); + if (!peer) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "test invite priority: missing peer"); return 0; } + uint64_t deadline = get_time_tb() + 10000; + while (!peer->conn_presence && get_time_tb() < deadline) poll_checked(inst); + if (!peer->conn_presence) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "test invite priority: peer unreachable"); return 0; } + char table[80], sql[256]; + peers_table_name(g_sh.ch_id, table, sizeof(table)); + sqlite3* db = inst->topo_sqlite_db; + if (sqlite3_exec(db, "SAVEPOINT invite_priority", NULL, NULL, NULL) != SQLITE_OK) { + DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "test invite priority: savepoint failed: %s", sqlite3_errmsg(db)); return 0; + } + int ok = 0; + uint8_t presence = peer->conn_presence; + snprintf(sql, sizeof(sql), "UPDATE \"%s\" SET node_type=1", table); + if (sqlite3_exec(db, sql, NULL, NULL, NULL) != SQLITE_OK) goto out; + if (chat_invite_best_node(inst, channel_id, &selected) || selected != g_sh.nid[IDX_C]) goto out; + snprintf(sql, sizeof(sql), "UPDATE \"%s\" SET node_type=4 WHERE node_id=%lld", table, (long long)inst->node_id); + if (sqlite3_exec(db, sql, NULL, NULL, NULL) != SQLITE_OK) goto out; + if (chat_invite_best_node(inst, channel_id, &selected) || selected != inst->node_id) goto out; + snprintf(sql, sizeof(sql), "UPDATE \"%s\" SET node_type=1 WHERE node_id=%lld", table, (long long)inst->node_id); + if (sqlite3_exec(db, sql, NULL, NULL, NULL) != SQLITE_OK) goto out; + snprintf(sql, sizeof(sql), "UPDATE \"%s\" SET node_type=4 WHERE node_id=%lld", table, (long long)g_sh.nid[IDX_C]); + if (sqlite3_exec(db, sql, NULL, NULL, NULL) != SQLITE_OK) goto out; + if (chat_invite_best_node(inst, channel_id, &selected) || selected != g_sh.nid[IDX_C]) goto out; + peer->conn_presence = 0; + if (chat_invite_best_node(inst, channel_id, &selected) || selected != inst->node_id) goto out; + ok = 1; +out: + peer->conn_presence = presence; + if (sqlite3_exec(db, "ROLLBACK TO invite_priority; RELEASE invite_priority", NULL, NULL, NULL) != SQLITE_OK) ok = 0; + if (!ok) DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "test invite priority failed: selected=%016llx err=%s", + (unsigned long long)selected, sqlite3_errmsg(db)); + else DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "test invite priority: regular, self-supernode, supernode, unreachable PASS"); + return ok; +} + 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; } @@ -477,6 +518,7 @@ static int child_main(const char* role, const char* dir, int invalid_key) { if (!g_sh.degraded && !wait_bgp(inst, g_sh.nid[IDX_C], 8000)) { fprintf(stderr, "A: no BGP to C\n"); goto out; } + if (!g_sh.degraded && !check_invite_priority(inst)) goto out; uint64_t target = g_sh.degraded ? g_sh.nid[IDX_A] : g_sh.nid[IDX_C]; struct invite_result result = {0}; diff --git a/tools/chatgui/resources/bg.png b/tools/chatgui/resources/bg.png index 91191ffe..6fd80417 100644 Binary files a/tools/chatgui/resources/bg.png and b/tools/chatgui/resources/bg.png differ diff --git a/tools/chatgui/resources/sounds/call_incoming.mp3 b/tools/chatgui/resources/sounds/call_incoming.mp3 index 932a00b2..54510952 100644 Binary files a/tools/chatgui/resources/sounds/call_incoming.mp3 and b/tools/chatgui/resources/sounds/call_incoming.mp3 differ