fix(chat): select invite peers and read signed addresses consistently
This commit is contained in:
1 parent
1efd8fb26c
commit
054ade0dcd
5 files changed
+281
-305
No files matched your search
+168
-296
@@ -14,7 +14,6 @@
|
||||
#include "../routing_layer/topo_group.h"
|
||||
#include "../routing_layer/topo_node.h"
|
||||
#include "../utun_instance.h"
|
||||
#include "../transport_layer/etcp_connections.h"
|
||||
#include "../../lib/mem.h"
|
||||
#include "../../lib/platform_compat.h"
|
||||
|
||||
@@ -22,6 +21,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <openssl/crypto.h>
|
||||
|
||||
/* ── живость узла в канале (BGP/прямое/через посредника) ── */
|
||||
|
||||
@@ -38,7 +38,7 @@ static int node_is_alive(struct UTUN_INSTANCE* inst, struct TOPO_GROUP* grp, uin
|
||||
if (!grp) return 0;
|
||||
struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(grp, node_id);
|
||||
if (!nq) return 0;
|
||||
return (nq->conn_presence & (NCONN_DIRECT | NCONN_INDIRECT | NCONN_BGP)) != 0;
|
||||
return topo_node_best_path(nq) != NULL;
|
||||
}
|
||||
|
||||
/* есть ли у узла публичные адреса v4 и v6 (addr_type=DIRECT) */
|
||||
@@ -48,86 +48,63 @@ static int node_has_both_public(struct UTUN_INSTANCE* inst, uint64_t node_id) {
|
||||
sqlite3_stmt* st = NULL;
|
||||
char sql[256];
|
||||
snprintf(sql, sizeof(sql),
|
||||
"SELECT family FROM node_addresses WHERE node_id=? AND addr_type=%d", ADDR_TYPE_DIRECT);
|
||||
if (sqlite3_prepare_v2(cc->db, sql, -1, &st, NULL) != SQLITE_OK) return 0;
|
||||
sqlite3_bind_int64(st, 1, (sqlite3_int64)node_id);
|
||||
while (sqlite3_step(st) == SQLITE_ROW) {
|
||||
"SELECT family FROM node_addresses WHERE node_id=? AND addr_type=%d AND port>0", ADDR_TYPE_DIRECT);
|
||||
int rc = sqlite3_prepare_v2(cc->db, sql, -1, &st, NULL);
|
||||
if (rc == SQLITE_OK) rc = sqlite3_bind_int64(st, 1, (sqlite3_int64)node_id);
|
||||
if (rc == SQLITE_OK) while ((rc = sqlite3_step(st)) == SQLITE_ROW) {
|
||||
int family_type = sqlite3_column_type(st, 0);
|
||||
int fam = sqlite3_column_int(st, 0);
|
||||
if (family_type != SQLITE_INTEGER || (fam != 4 && fam != 6)) { rc = SQLITE_CORRUPT; break; }
|
||||
if (fam == 4) has_v4 = 1;
|
||||
else if (fam == 6) has_v6 = 1;
|
||||
}
|
||||
sqlite3_finalize(st);
|
||||
int final = sqlite3_finalize(st);
|
||||
if (rc != SQLITE_DONE || final != SQLITE_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "invite_build: public families node=%016llx rc=%d finalize=%d: %s",
|
||||
(unsigned long long)node_id, rc, final, sqlite3_errmsg(cc->db)); return -1;
|
||||
}
|
||||
return (has_v4 && has_v6) ? 1 : 0;
|
||||
}
|
||||
|
||||
/* ── Фаза 1-2: лучший достижимый узел в канале ── */
|
||||
/* Один снимок для auto/manual/list: полные неудалённые мемберы с живым путём, self — из той же таблицы. */
|
||||
struct invite_selection {
|
||||
uint64_t* ids;
|
||||
int capacity, count;
|
||||
uint64_t best, wanted;
|
||||
int priority, found;
|
||||
};
|
||||
|
||||
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=super, 2=regular, 3=self, 4=invalid */
|
||||
int best_has_both = 0;
|
||||
|
||||
struct TOPO_GROUP* grp = resolve_chat_group(inst, channel_id);
|
||||
if (!grp) {
|
||||
*out_has_both_public = 0;
|
||||
*out_priority = best_priority;
|
||||
return 0;
|
||||
static int select_nodes(struct UTUN_INSTANCE* inst, uint64_t channel, struct invite_selection* result) {
|
||||
struct chat_core_ctx* cc = inst ? CC(inst) : NULL;
|
||||
struct TOPO_GROUP* group = resolve_chat_group(inst, channel);
|
||||
if (!cc || !cc->initialized || !cc->db || cc->my_node_id != inst->node_id || !channel || channel > INT64_MAX || !group) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "invite_build: inactive context/invalid channel=%llu", (unsigned long long)channel); return -1;
|
||||
}
|
||||
|
||||
char ch_str[32];
|
||||
snprintf(ch_str, sizeof(ch_str), "%llu", (unsigned long long)channel_id);
|
||||
char peers_tbl[80];
|
||||
peers_table_name(ch_str, peers_tbl, sizeof(peers_tbl));
|
||||
|
||||
char sql[512];
|
||||
snprintf(sql, sizeof(sql), "SELECT node_id, node_type FROM \"%s\" ORDER BY node_id", peers_tbl);
|
||||
sqlite3_stmt* pst = NULL;
|
||||
if (sqlite3_prepare_v2(cc->db, sql, -1, &pst, NULL) != SQLITE_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "invite_build: pick_best — query failed ch=%llu err=%s",
|
||||
(unsigned long long)channel_id, sqlite3_errmsg(cc->db));
|
||||
*out_has_both_public = 0;
|
||||
*out_priority = best_priority;
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (sqlite3_step(pst) == SQLITE_ROW) {
|
||||
uint64_t nid = (uint64_t)sqlite3_column_int64(pst, 0);
|
||||
int ntype = sqlite3_column_int(pst, 1);
|
||||
if (!node_is_alive(inst, grp, nid)) continue;
|
||||
|
||||
int has_both = node_has_both_public(inst, nid);
|
||||
|
||||
int priority;
|
||||
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;
|
||||
best_priority = priority;
|
||||
best_has_both = has_both;
|
||||
char sql[256];
|
||||
snprintf(sql, sizeof(sql), "SELECT node_id,node_type FROM peers_%llu WHERE source=0 AND deleted=0 ORDER BY node_id<0,node_id",
|
||||
(unsigned long long)channel);
|
||||
sqlite3_stmt* st = NULL; result->priority = 4;
|
||||
int rc = sqlite3_prepare_v2(cc->db, sql, -1, &st, NULL);
|
||||
if (rc == SQLITE_OK) while ((rc = sqlite3_step(st)) == SQLITE_ROW) {
|
||||
int id_type = sqlite3_column_type(st, 0), role_type = sqlite3_column_type(st, 1);
|
||||
uint64_t id = (uint64_t)sqlite3_column_int64(st, 0); int type = sqlite3_column_int(st, 1);
|
||||
if (!id || id_type != SQLITE_INTEGER || role_type != SQLITE_INTEGER || type < 0 || type > 4) {
|
||||
rc = SQLITE_CORRUPT; break;
|
||||
}
|
||||
if (!node_is_alive(inst, group, id)) continue;
|
||||
if (result->count < result->capacity) result->ids[result->count++] = id;
|
||||
if (id == result->wanted) result->found = 1;
|
||||
int priority = type == 4 ? 1 : id == cc->my_node_id ? 3 : 2;
|
||||
if (priority < result->priority) { result->best = id; result->priority = priority; }
|
||||
}
|
||||
sqlite3_finalize(pst);
|
||||
|
||||
*out_has_both_public = best_has_both;
|
||||
*out_priority = best_priority;
|
||||
return best_nid;
|
||||
}
|
||||
|
||||
/* авто-узел: pick_best_node + фолбэк на self */
|
||||
static uint64_t resolve_auto_node(struct UTUN_INSTANCE* inst, uint64_t channel_id, int* out_has_both, int* out_priority) {
|
||||
struct chat_core_ctx* cc = CC(inst);
|
||||
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;
|
||||
int final = sqlite3_finalize(st);
|
||||
if (rc != SQLITE_DONE || final != SQLITE_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "invite_build: candidates ch=%llu rc=%d finalize=%d: %s",
|
||||
(unsigned long long)channel, rc, final, sqlite3_errmsg(cc->db)); return -1;
|
||||
}
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CHAT_SYNC, "invite_build: candidates ch=%llu returned=%d best=%016llx priority=%d wanted_found=%d",
|
||||
(unsigned long long)channel, result->count, (unsigned long long)result->best, result->priority, result->found);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ── Фаза 3: pubkey узла ── */
|
||||
@@ -140,174 +117,111 @@ static int get_node_pubkey(struct UTUN_INSTANCE* inst, uint64_t node_id, int is_
|
||||
return 0;
|
||||
}
|
||||
sqlite3_stmt* st = NULL;
|
||||
if (sqlite3_prepare_v2(cc->db, "SELECT x25519_pubkey FROM nodes WHERE node_id=?",
|
||||
-1, &st, NULL) != SQLITE_OK) return -1;
|
||||
sqlite3_bind_int64(st, 1, (sqlite3_int64)node_id);
|
||||
int ok = 0;
|
||||
if (sqlite3_step(st) == SQLITE_ROW) {
|
||||
uint8_t key[INVITE_PUBKEY_SIZE]; int ok = 0;
|
||||
int rc = sqlite3_prepare_v2(cc->db, "SELECT x25519_pubkey FROM nodes WHERE node_id=?", -1, &st, NULL);
|
||||
if (rc == SQLITE_OK) rc = sqlite3_bind_int64(st, 1, (sqlite3_int64)node_id);
|
||||
if (rc == SQLITE_OK) rc = sqlite3_step(st);
|
||||
if (rc == SQLITE_ROW) {
|
||||
int type = sqlite3_column_type(st, 0);
|
||||
const void* pk = sqlite3_column_blob(st, 0);
|
||||
int pklen = sqlite3_column_bytes(st, 0);
|
||||
if (pk && pklen >= INVITE_PUBKEY_SIZE) { memcpy(pubkey_out, pk, INVITE_PUBKEY_SIZE); ok = 1; }
|
||||
}
|
||||
sqlite3_finalize(st);
|
||||
return ok ? 0 : -1;
|
||||
}
|
||||
|
||||
/* ── Фаза 4a: свои адреса из сокетов ── */
|
||||
|
||||
static int collect_self_addrs(struct UTUN_INSTANCE* inst, struct InviteAddr* out, int max_cnt) {
|
||||
int cnt = 0;
|
||||
if (!inst) return 0;
|
||||
struct ETCP_SOCKET* s;
|
||||
|
||||
/* UDP */
|
||||
for (s = inst->etcp_sockets; s && cnt < max_cnt; s = s->next) {
|
||||
if (s->is_tcp) continue;
|
||||
struct sockaddr_storage* sa = s->interface_addr.ss_family ? &s->interface_addr : &s->local_addr;
|
||||
if (sa->ss_family == AF_INET) {
|
||||
struct sockaddr_in* sin = (struct sockaddr_in*)sa;
|
||||
out[cnt].family = 4; out[cnt].socketId = s->sock_id; out[cnt].proto = INVITE_PROTO_UDP;
|
||||
memcpy(out[cnt].address, &sin->sin_addr, 4);
|
||||
out[cnt].port = ntohs(sin->sin_port); cnt++;
|
||||
} else if (sa->ss_family == AF_INET6) {
|
||||
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)sa;
|
||||
out[cnt].family = 6; out[cnt].socketId = s->sock_id; out[cnt].proto = INVITE_PROTO_UDP;
|
||||
memcpy(out[cnt].address, &sin6->sin6_addr, 16);
|
||||
out[cnt].port = ntohs(sin6->sin6_port); cnt++;
|
||||
if (type == SQLITE_BLOB && pk && pklen == INVITE_PUBKEY_SIZE) {
|
||||
memcpy(key, pk, INVITE_PUBKEY_SIZE); ok = sc_derive_node_id_from_pubkey(key) == node_id;
|
||||
}
|
||||
}
|
||||
/* TCP */
|
||||
for (s = inst->etcp_sockets; s && cnt < max_cnt; s = s->next) {
|
||||
if (!s->is_tcp) continue;
|
||||
struct sockaddr_storage* sa = s->interface_addr.ss_family ? &s->interface_addr : &s->local_addr;
|
||||
uint8_t proto = INVITE_PROTO_TCP | (s->reality_enabled ? INVITE_PROTO_REALITY : 0);
|
||||
if (sa->ss_family == AF_INET) {
|
||||
struct sockaddr_in* sin = (struct sockaddr_in*)sa;
|
||||
out[cnt].family = 4; out[cnt].socketId = s->sock_id; out[cnt].proto = proto;
|
||||
memcpy(out[cnt].address, &sin->sin_addr, 4);
|
||||
out[cnt].port = ntohs(sin->sin_port); cnt++;
|
||||
} else if (sa->ss_family == AF_INET6) {
|
||||
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)sa;
|
||||
out[cnt].family = 6; out[cnt].socketId = s->sock_id; out[cnt].proto = proto;
|
||||
memcpy(out[cnt].address, &sin6->sin6_addr, 16);
|
||||
out[cnt].port = ntohs(sin6->sin6_port); cnt++;
|
||||
}
|
||||
int final = sqlite3_finalize(st);
|
||||
if (!ok || final != SQLITE_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "invite_build: node key unavailable/invalid node=%016llx rc=%d finalize=%d",
|
||||
(unsigned long long)node_id, rc, final); return -1;
|
||||
}
|
||||
return cnt;
|
||||
memcpy(pubkey_out, key, sizeof(key)); return 0;
|
||||
}
|
||||
|
||||
/* reality-параметры self-узла из конфига (node-global, как в topo_node_update_my_addresses). */
|
||||
static void collect_self_reality(struct UTUN_INSTANCE* inst, struct InviteReality* out) {
|
||||
memset(out, 0, sizeof(*out));
|
||||
if (!inst || !inst->config) return;
|
||||
const struct reality_config* rc = &inst->config->global.reality;
|
||||
if (!rc->enabled || !rc->has_private_key) return;
|
||||
int any = 0;
|
||||
for (struct ETCP_SOCKET* s = inst->etcp_sockets; s; s = s->next)
|
||||
if (s->is_tcp && s->reality_enabled) { any = 1; break; }
|
||||
if (!any) return;
|
||||
uint8_t pub[32];
|
||||
if (reality_pubkey_from_priv(rc->private_key, pub) != REALITY_OK) return;
|
||||
out->has = 1;
|
||||
if (rc->short_id_count > 0) memcpy(out->short_id, rc->short_ids[0], 8);
|
||||
else memcpy(out->short_id, rc->short_id, 8);
|
||||
memcpy(out->server_pubkey, pub, 32);
|
||||
memcpy(out->version, rc->version, 3);
|
||||
strncpy(out->server_name, rc->server_name, INVITE_SNAME_MAX - 1);
|
||||
out->server_name[INVITE_SNAME_MAX - 1] = '\0';
|
||||
}
|
||||
|
||||
/* ── Фаза 4b: адреса узла из БД ── */
|
||||
/* Адреса self/пира из общей проекции NODEINFO;port=0 и unspecified IP остаются только hints. */
|
||||
|
||||
static int collect_node_addrs_from_db(struct UTUN_INSTANCE* inst, uint64_t node_id, struct InviteAddr* out, int max_cnt, int dir_only,
|
||||
struct InviteReality* reality) {
|
||||
struct chat_core_ctx* cc = CC(inst);
|
||||
char sql[256];
|
||||
if (dir_only)
|
||||
snprintf(sql, sizeof(sql),
|
||||
"SELECT family,socket_id,protocol,address,port,options FROM node_addresses"
|
||||
" WHERE node_id=? AND addr_type=%d", ADDR_TYPE_DIRECT);
|
||||
else
|
||||
snprintf(sql, sizeof(sql),
|
||||
"SELECT family,socket_id,protocol,address,port,options FROM node_addresses WHERE node_id=?");
|
||||
|
||||
sqlite3_stmt* st = NULL;
|
||||
if (sqlite3_prepare_v2(cc->db, sql, -1, &st, NULL) != SQLITE_OK) return 0;
|
||||
sqlite3_bind_int64(st, 1, (sqlite3_int64)node_id);
|
||||
|
||||
int cnt = 0;
|
||||
while (sqlite3_step(st) == SQLITE_ROW && cnt < max_cnt) {
|
||||
int family = sqlite3_column_int(st, 0);
|
||||
out[cnt].family = family;
|
||||
out[cnt].socketId = (uint8_t)sqlite3_column_int(st, 1);
|
||||
out[cnt].proto = (uint8_t)sqlite3_column_int(st, 2);
|
||||
const uint8_t* addr = sqlite3_column_blob(st, 3);
|
||||
int addr_len = sqlite3_column_bytes(st, 3);
|
||||
int ip_len = (family == 6) ? 16 : 4;
|
||||
memset(out[cnt].address, 0, sizeof(out[cnt].address));
|
||||
if (addr && addr_len >= ip_len) memcpy(out[cnt].address, addr, (size_t)ip_len);
|
||||
out[cnt].port = (uint16_t)sqlite3_column_int(st, 4);
|
||||
if (reality && !reality->has && (out[cnt].proto & INVITE_PROTO_REALITY)
|
||||
&& sqlite3_column_type(st, 5) == SQLITE_BLOB
|
||||
&& sqlite3_column_bytes(st, 5) == TOPO_ADDR_REALITY_OPTS_SIZE) {
|
||||
const struct TOPO_ADDR_REALITY_OPTS* o = (const struct TOPO_ADDR_REALITY_OPTS*)sqlite3_column_blob(st, 5);
|
||||
reality->has = 1;
|
||||
memcpy(reality->short_id, o->short_id, 8);
|
||||
memcpy(reality->server_pubkey, o->server_pubkey, 32);
|
||||
memcpy(reality->version, o->version, 3);
|
||||
memcpy(reality->server_name, o->server_name, INVITE_SNAME_MAX);
|
||||
reality->server_name[INVITE_SNAME_MAX - 1] = '\0';
|
||||
sqlite3* db = CC(inst)->db; char sql[384];
|
||||
snprintf(sql, sizeof(sql), "SELECT family,socket_id,protocol,address,port,options FROM node_addresses"
|
||||
" WHERE node_id=? AND (%d=0 OR addr_type=%d) ORDER BY family,protocol,socket_id,address_kind,address,port",
|
||||
dir_only, ADDR_TYPE_DIRECT);
|
||||
sqlite3_stmt* st = NULL; int count = 0; struct InviteReality parameters = {0};
|
||||
int rc = sqlite3_prepare_v2(db, sql, -1, &st, NULL);
|
||||
if (rc == SQLITE_OK) rc = sqlite3_bind_int64(st, 1, (sqlite3_int64)node_id);
|
||||
if (rc == SQLITE_OK) while ((rc = sqlite3_step(st)) == SQLITE_ROW) {
|
||||
int family_type = sqlite3_column_type(st, 0), socket_type = sqlite3_column_type(st, 1), proto_type = sqlite3_column_type(st, 2);
|
||||
int port_type = sqlite3_column_type(st, 4), address_type = sqlite3_column_type(st, 3);
|
||||
int family = sqlite3_column_int(st, 0), socket = sqlite3_column_int(st, 1), proto = sqlite3_column_int(st, 2);
|
||||
int port = sqlite3_column_int(st, 4), bytes = sqlite3_column_bytes(st, 3);
|
||||
const uint8_t* address = sqlite3_column_blob(st, 3);
|
||||
if (family_type != SQLITE_INTEGER || socket_type != SQLITE_INTEGER || proto_type != SQLITE_INTEGER ||
|
||||
port_type != SQLITE_INTEGER || address_type != SQLITE_BLOB || (family != 4 && family != 6) || socket < 0 || socket > 255 ||
|
||||
(proto != INVITE_PROTO_UDP && proto != INVITE_PROTO_TCP && proto != (INVITE_PROTO_TCP | INVITE_PROTO_REALITY)) ||
|
||||
!address || bytes != (family == 4 ? 4 : 16) || port < 0 || port > 65535) { rc = SQLITE_CORRUPT; break; }
|
||||
int nonzero = 0; for (int i = 0; i < bytes; i++) nonzero |= address[i];
|
||||
if (!port || !nonzero) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CHAT_SYNC, "invite_build: skip address hint node=%016llx family=%d socket=%d port=%d",
|
||||
(unsigned long long)node_id, family, socket, port); continue;
|
||||
}
|
||||
if (proto & INVITE_PROTO_REALITY) {
|
||||
struct TOPO_ADDR_REALITY_OPTS options;
|
||||
int options_type = sqlite3_column_type(st, 5);
|
||||
const void* blob = sqlite3_column_blob(st, 5);
|
||||
if (options_type != SQLITE_BLOB || !blob || sqlite3_column_bytes(st, 5) != sizeof(options)) {
|
||||
rc = SQLITE_CORRUPT; break;
|
||||
}
|
||||
memcpy(&options, blob, sizeof(options));
|
||||
size_t name_length = strnlen(options.server_name, sizeof(options.server_name));
|
||||
if (!name_length || name_length == sizeof(options.server_name) || name_length >= INVITE_SNAME_MAX) { rc = SQLITE_CORRUPT; break; }
|
||||
struct InviteReality current = {.has = 1};
|
||||
memcpy(current.short_id, options.short_id, 8); memcpy(current.server_pubkey, options.server_pubkey, 32);
|
||||
memcpy(current.version, options.version, 3); strcpy(current.server_name, options.server_name);
|
||||
if (parameters.has && memcmp(¶meters, ¤t, sizeof(current))) { rc = SQLITE_CORRUPT; break; }
|
||||
parameters = current;
|
||||
}
|
||||
if (count < max_cnt) {
|
||||
out[count] = (struct InviteAddr){.family = family, .socketId = (uint8_t)socket, .proto = (uint8_t)proto, .port = (uint16_t)port};
|
||||
memcpy(out[count++].address, address, (size_t)bytes);
|
||||
}
|
||||
cnt++;
|
||||
}
|
||||
sqlite3_finalize(st);
|
||||
return cnt;
|
||||
int final = sqlite3_finalize(st);
|
||||
if (rc != SQLITE_DONE || final != SQLITE_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "invite_build: addresses node=%016llx count=%d rc=%d finalize=%d: %s",
|
||||
(unsigned long long)node_id, count, rc, final, sqlite3_errmsg(db)); return -1;
|
||||
}
|
||||
if (reality) *reality = parameters;
|
||||
return count;
|
||||
}
|
||||
|
||||
/* ── публичный API ── */
|
||||
|
||||
static int prepare_link(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint64_t target_node_id,
|
||||
const char* password, char* out, size_t out_size, uint64_t* target, uint64_t* key) {
|
||||
struct chat_core_ctx* cc = CC(inst);
|
||||
if (!out || out_size == 0) return -1;
|
||||
struct chat_core_ctx* cc = inst ? CC(inst) : NULL;
|
||||
if (!out || out_size == 0) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "invite_build: missing link output"); return -1; }
|
||||
out[0] = '\0';
|
||||
if (!cc || !cc->initialized || !inst || !cc->db) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "invite_build: not initialized (inst=%p db=%p)",
|
||||
(void*)inst, (void*)(cc ? cc->db : NULL));
|
||||
return -1;
|
||||
}
|
||||
if (channel_id == 0) {
|
||||
if (!channel_id || channel_id > INT64_MAX) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "invite_build: invalid channel_id");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* ── выбор узла ── */
|
||||
uint64_t target_nid = target_node_id;
|
||||
int use_self = 0, has_both_public = 0;
|
||||
|
||||
if (target_nid == 0) {
|
||||
int prio = 4;
|
||||
target_nid = resolve_auto_node(inst, channel_id, &has_both_public, &prio);
|
||||
use_self = (target_nid == cc->my_node_id) ? 1 : 0;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "invite_build: auto ch=%llu best=0x%016llx prio=%d self=%d both=%d",
|
||||
(unsigned long long)channel_id, (unsigned long long)target_nid,
|
||||
prio, use_self, has_both_public);
|
||||
} else {
|
||||
if (target_nid == cc->my_node_id) {
|
||||
use_self = 1;
|
||||
} else {
|
||||
struct TOPO_GROUP* grp = resolve_chat_group(inst, channel_id);
|
||||
if (!node_is_alive(inst, grp, target_nid)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC,
|
||||
"invite_build: node 0x%016llx not reachable in ch=%llu",
|
||||
(unsigned long long)target_nid, (unsigned long long)channel_id);
|
||||
return -1;
|
||||
}
|
||||
has_both_public = node_has_both_public(inst, target_nid);
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "invite_build: manual ch=%llu target=0x%016llx self=%d both=%d",
|
||||
(unsigned long long)channel_id, (unsigned long long)target_nid, use_self, has_both_public);
|
||||
struct invite_selection selection = {.wanted = target_node_id};
|
||||
if (select_nodes(inst, channel_id, &selection) || (!target_node_id && !selection.best) ||
|
||||
(target_node_id && !selection.found)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "invite_build: no eligible target ch=%llu wanted=%016llx",
|
||||
(unsigned long long)channel_id, (unsigned long long)target_node_id); return -1;
|
||||
}
|
||||
uint64_t target_nid = target_node_id ? target_node_id : selection.best;
|
||||
int use_self = target_nid == cc->my_node_id;
|
||||
int has_both_public = node_has_both_public(inst, target_nid);
|
||||
if (has_both_public < 0) return -1;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "invite_build: selected ch=%llu node=%016llx auto=%d self=%d both=%d",
|
||||
(unsigned long long)channel_id, (unsigned long long)target_nid, !target_node_id, use_self, has_both_public);
|
||||
|
||||
/* ── pubkey ── */
|
||||
struct InviteData data;
|
||||
@@ -325,14 +239,10 @@ static int prepare_link(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint64_
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* ── адреса ── */
|
||||
if (use_self) {
|
||||
data.addrCount = (uint8_t)collect_self_addrs(inst, data.addrs, INVITE_ADDR_MAX);
|
||||
collect_self_reality(inst, &data.reality);
|
||||
} else {
|
||||
int dir_only = has_both_public ? 1 : 0;
|
||||
data.addrCount = (uint8_t)collect_node_addrs_from_db(inst, target_nid, data.addrs, INVITE_ADDR_MAX, dir_only, &data.reality);
|
||||
}
|
||||
/* Общая проекция NODEINFO уже исключает private/probe сокеты и содержит NAT/REALITY. */
|
||||
int count = collect_node_addrs_from_db(inst, target_nid, data.addrs, INVITE_ADDR_MAX, has_both_public, &data.reality);
|
||||
if (count < 0) return -1;
|
||||
data.addrCount = (uint8_t)count;
|
||||
if (data.addrCount == 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "invite_build: no addrs for node=0x%016llx",
|
||||
(unsigned long long)target_nid);
|
||||
@@ -340,7 +250,7 @@ static int prepare_link(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint64_
|
||||
}
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "invite_build: node=0x%016llx addrs=%d dir_only=%d",
|
||||
(unsigned long long)target_nid, data.addrCount, (!use_self && has_both_public) ? 1 : 0);
|
||||
(unsigned long long)target_nid, data.addrCount, has_both_public);
|
||||
|
||||
/* ── encode ── */
|
||||
if (invite_link_encode(&data, password, out, out_size) < 0) {
|
||||
@@ -357,14 +267,11 @@ static int prepare_link(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint64_
|
||||
}
|
||||
|
||||
int chat_invite_best_node(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint64_t* out_node_id) {
|
||||
struct chat_core_ctx* cc = CC(inst);
|
||||
if (!out_node_id) return -1;
|
||||
if (!cc || !cc->initialized || !inst || !cc->db) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "invite_build: best_node — not initialized");
|
||||
return -1;
|
||||
}
|
||||
*out_node_id = resolve_auto_node(inst, channel_id, NULL, NULL);
|
||||
return 0;
|
||||
if (!out_node_id) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "invite_build: missing best-node output"); return -1; }
|
||||
struct invite_selection selection = {0};
|
||||
if (select_nodes(inst, channel_id, &selection)) return -1;
|
||||
if (!selection.best) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "invite_build: no reachable full member"); return -1; }
|
||||
*out_node_id = selection.best; return 0;
|
||||
}
|
||||
|
||||
struct chat_invite_build {
|
||||
@@ -378,6 +285,7 @@ static void invite_registered(void* arg, int result) {
|
||||
struct chat_invite_build* req = arg;
|
||||
req->registration = NULL;
|
||||
req->callback(req->arg, result, result == CHAT_JOIN_OK ? req->link : NULL);
|
||||
OPENSSL_cleanse(req->link, sizeof(req->link));
|
||||
u_free(req);
|
||||
}
|
||||
|
||||
@@ -401,84 +309,44 @@ void chat_invite_build_cancel(struct chat_invite_build* req) {
|
||||
}
|
||||
|
||||
int chat_invite_candidate_nodes(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint64_t* out_ids, int max_ids) {
|
||||
struct chat_core_ctx* cc = CC(inst);
|
||||
if (!out_ids || max_ids <= 0) return -1;
|
||||
if (!cc || !cc->initialized || !inst || !cc->db) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "invite_build: candidates — not initialized");
|
||||
return -1;
|
||||
if (!out_ids || max_ids <= 0 || max_ids > UINT16_MAX) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "invite_build: invalid candidate output capacity=%d", max_ids); return -1;
|
||||
}
|
||||
struct TOPO_GROUP* grp = resolve_chat_group(inst, channel_id);
|
||||
if (!grp) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "invite_build: candidates — group not found ch=%llu",
|
||||
(unsigned long long)channel_id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
char ch_str[32];
|
||||
snprintf(ch_str, sizeof(ch_str), "%llu", (unsigned long long)channel_id);
|
||||
char peers_tbl[80];
|
||||
peers_table_name(ch_str, peers_tbl, sizeof(peers_tbl));
|
||||
|
||||
char sql[512];
|
||||
snprintf(sql, sizeof(sql), "SELECT node_id FROM \"%s\" ORDER BY node_id", peers_tbl);
|
||||
sqlite3_stmt* st = NULL;
|
||||
if (sqlite3_prepare_v2(cc->db, sql, -1, &st, NULL) != SQLITE_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "invite_build: candidates — query failed ch=%llu err=%s",
|
||||
(unsigned long long)channel_id, sqlite3_errmsg(cc->db));
|
||||
return -1;
|
||||
}
|
||||
|
||||
int cnt = 0;
|
||||
while (sqlite3_step(st) == SQLITE_ROW && cnt < max_ids) {
|
||||
uint64_t nid = (uint64_t)sqlite3_column_int64(st, 0);
|
||||
if (node_is_alive(inst, grp, nid)) out_ids[cnt++] = nid;
|
||||
}
|
||||
sqlite3_finalize(st);
|
||||
return cnt;
|
||||
uint64_t* ids = u_malloc((uint32_t)max_ids * sizeof(*ids));
|
||||
if (!ids) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "invite_build: candidate allocation failed count=%d", max_ids); return -1; }
|
||||
struct invite_selection selection = {.ids = ids, .capacity = max_ids};
|
||||
int rc = select_nodes(inst, channel_id, &selection);
|
||||
if (!rc && selection.count) memcpy(out_ids, ids, (size_t)selection.count * sizeof(*ids));
|
||||
u_free(ids); return rc ? -1 : selection.count;
|
||||
}
|
||||
|
||||
/* ── трамплины для gui_bridge ── */
|
||||
|
||||
void chat_invite_candidates_trampoline(void* arg) {
|
||||
struct chat_invite_cand_req* req = (struct chat_invite_cand_req*)arg;
|
||||
if (!req) return;
|
||||
struct UTUN_INSTANCE* inst = req->inst;
|
||||
char ch_id[64];
|
||||
strncpy(ch_id, req->ch_id, sizeof(ch_id) - 1);
|
||||
ch_id[sizeof(ch_id) - 1] = '\0';
|
||||
u_free(arg);
|
||||
if (!CC(inst) || !CC(inst)->initialized) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "invite_build: candidates — not initialized"); return; }
|
||||
|
||||
uint64_t channel_id = strtoull(ch_id, NULL, 10);
|
||||
uint64_t auto_id = 0;
|
||||
chat_invite_best_node(inst, channel_id, &auto_id);
|
||||
uint64_t ids[INVITE_ADDR_MAX];
|
||||
int cnt = chat_invite_candidate_nodes(inst, channel_id, ids, INVITE_ADDR_MAX);
|
||||
if (cnt < 0) cnt = 0;
|
||||
|
||||
size_t ch_len = strlen(ch_id);
|
||||
size_t evt_sz = 1 + ch_len + 8 + 2 + (size_t)cnt * 8;
|
||||
uint8_t* evt = u_malloc(evt_sz);
|
||||
if (!evt) return;
|
||||
evt[0] = (uint8_t)ch_len;
|
||||
memcpy(evt + 1, ch_id, ch_len);
|
||||
memcpy(evt + 1 + ch_len, &auto_id, 8);
|
||||
uint16_t u16 = (uint16_t)cnt;
|
||||
memcpy(evt + 1 + ch_len + 8, &u16, 2);
|
||||
for (int i = 0; i < cnt; i++) memcpy(evt + 1 + ch_len + 8 + 2 + i * 8, &ids[i], 8);
|
||||
chat_event_post(inst, CHAT_EVT_INVITE_CANDIDATES, evt, (int)evt_sz);
|
||||
u_free(evt);
|
||||
struct chat_invite_cand_req* req = arg;
|
||||
if (!req) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "invite_build: null candidates request"); return; }
|
||||
struct UTUN_INSTANCE* inst = req->inst; char channel[64];
|
||||
if (!memchr(req->ch_id, 0, sizeof(req->ch_id)) || !chat_channel_id(req->ch_id)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "invite_build: invalid candidates channel"); u_free(req); return;
|
||||
}
|
||||
strcpy(channel, req->ch_id); u_free(req);
|
||||
uint64_t ids[INVITE_ADDR_MAX]; struct invite_selection selection = {.ids = ids, .capacity = INVITE_ADDR_MAX};
|
||||
if (select_nodes(inst, chat_channel_id(channel), &selection)) return;
|
||||
size_t length = strlen(channel); uint8_t event[1 + 64 + 8 + 2 + INVITE_ADDR_MAX * 8];
|
||||
event[0] = (uint8_t)length; memcpy(event + 1, channel, length); memcpy(event + 1 + length, &selection.best, 8);
|
||||
uint16_t count = (uint16_t)selection.count; memcpy(event + 9 + length, &count, 2);
|
||||
memcpy(event + 11 + length, ids, (size_t)count * 8);
|
||||
chat_event_post(inst, CHAT_EVT_INVITE_CANDIDATES, event, (int)(11 + length + (size_t)count * 8));
|
||||
}
|
||||
|
||||
static void gui_invite_ready(void* arg, int result, const char* link) {
|
||||
struct chat_invite_build_req* req = arg;
|
||||
req->inst->invite_gui_request = NULL;
|
||||
if (result == CHAT_JOIN_CANCELLED) { u_free(req); return; }
|
||||
if (req->inst->invite_gui_request == req) req->inst->invite_gui_request = NULL;
|
||||
if (result == CHAT_JOIN_CANCELLED) { OPENSSL_cleanse(req->password, sizeof(req->password)); u_free(req); return; }
|
||||
size_t ch_len = strlen(req->ch_id);
|
||||
size_t link_len = link ? strlen(link) : 0;
|
||||
size_t evt_sz = 1 + ch_len + 8 + 2 + link_len;
|
||||
uint8_t* evt = u_malloc(evt_sz);
|
||||
if (!evt) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "invite_build: event allocation failed"); u_free(req); return; }
|
||||
uint8_t evt[1 + 64 + 8 + 2 + INVITE_LINK_MAX];
|
||||
evt[0] = (uint8_t)ch_len;
|
||||
memcpy(evt + 1, req->ch_id, ch_len);
|
||||
uint16_t u16 = (uint16_t)link_len;
|
||||
@@ -486,17 +354,21 @@ static void gui_invite_ready(void* arg, int result, const char* link) {
|
||||
memcpy(evt + 1 + ch_len + 8, &u16, 2);
|
||||
if (link_len) memcpy(evt + 1 + ch_len + 10, link, link_len);
|
||||
chat_event_post(req->inst, CHAT_EVT_INVITE_LINK_READY, evt, (int)evt_sz);
|
||||
u_free(evt);
|
||||
OPENSSL_cleanse(evt, evt_sz); OPENSSL_cleanse(req->password, sizeof(req->password));
|
||||
u_free(req);
|
||||
}
|
||||
|
||||
void chat_invite_build_link_trampoline(void* arg) {
|
||||
struct chat_invite_build_req* req = arg;
|
||||
if (!req) return;
|
||||
if (!req->inst) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "invite_build: GUI request without instance"); u_free(req); return; }
|
||||
if (!req) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "invite_build: null GUI request"); return; }
|
||||
if (!req->inst || !memchr(req->ch_id, 0, sizeof(req->ch_id)) || !memchr(req->password, 0, sizeof(req->password)) ||
|
||||
!chat_channel_id(req->ch_id)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "invite_build: invalid GUI request");
|
||||
OPENSSL_cleanse(req->password, sizeof(req->password)); u_free(req); return;
|
||||
}
|
||||
if (req->inst->invite_gui_request) chat_invite_build_cancel(req->inst->invite_gui_request->pending);
|
||||
req->inst->invite_gui_request = req;
|
||||
req->pending = chat_invite_build_link(req->inst, strtoull(req->ch_id, NULL, 10), req->target_node_id,
|
||||
req->pending = chat_invite_build_link(req->inst, chat_channel_id(req->ch_id), req->target_node_id,
|
||||
req->password[0] ? req->password : NULL, gui_invite_ready, req);
|
||||
if (!req->pending) gui_invite_ready(req, CHAT_JOIN_ERROR, NULL);
|
||||
}
|
||||
+11
-4
@@ -2,8 +2,11 @@
|
||||
* invite_build.h — сборка invite-ссылок utun:// (выбор узла + адреса)
|
||||
*
|
||||
* Общий модуль для всех GUI (desktop chatgui, Android, headless CLI).
|
||||
* Логика выбора «лучшего узла» перенесена из tools/chatgui-android/jni_bridge.
|
||||
* Работает в uasync-потоке, использует chat_core_ctx конкретного UTUN_INSTANCE.
|
||||
* Кандидаты — полные неудалённые участники активной CHAT-группы: self либо узлы
|
||||
* с живым BGP-путём. Приоритет auto: суперузел (включая self), другой участник, self;
|
||||
* при равенстве — меньший unsigned node_id. Ошибка SQLite не подменяется self.
|
||||
* Адреса берутся из общей проекции NODEINFO; нулевые IP/порты в ссылку не входят.
|
||||
*/
|
||||
#ifndef INVITE_BUILD_H
|
||||
#define INVITE_BUILD_H
|
||||
@@ -32,14 +35,18 @@ struct chat_invite_build* chat_invite_build_link(struct UTUN_INSTANCE* inst, uin
|
||||
void chat_invite_build_cancel(struct chat_invite_build* request);
|
||||
|
||||
/* Список достижимых узлов-кандидатов канала (включая self) — тот же фильтр, что в auto.
|
||||
* Возвращает количество (0..max_ids), либо -1. */
|
||||
* Возвращает количество (0..max_ids), либо -1; max_ids=1..65535.
|
||||
* Порядок — unsigned node_id по возрастанию, при переполнении берётся начало списка.
|
||||
* При ошибке out_ids не изменяется; присутствие self также требует полной записи. */
|
||||
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. */
|
||||
/* Возвращает 0 и выбранный узел; -1 при отсутствии кандидатов/ошибке, out_node_id не изменяется. */
|
||||
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)).
|
||||
* Результат: CHAT_EVT_INVITE_LINK_READY. */
|
||||
* Принимает владение arg. Канал — канонический положительный decimal ID <= INT64_MAX.
|
||||
* Новый корректный запрос отменяет прежний GUI-запрос этого instance.
|
||||
* Результат: CHAT_EVT_INVITE_LINK_READY; некорректный arg только логируется. */
|
||||
struct chat_invite_build_req {
|
||||
struct UTUN_INSTANCE* inst;
|
||||
char ch_id[64];
|
||||
|
||||
@@ -309,6 +309,11 @@ check_PROGRAMS += test_chat_profile
|
||||
check_PROGRAMS += test_chat_preferences
|
||||
check_PROGRAMS += test_chat_whisper test_chat_whisper_disabled
|
||||
check_PROGRAMS += test_chat_headless_control
|
||||
check_PROGRAMS += test_invite_build
|
||||
|
||||
test_invite_build_SOURCES = test_invite_build.c
|
||||
test_invite_build_LDADD = $(LIBUTUN) $(COMMON_LIBS)
|
||||
test_invite_build_LDFLAGS = -Wl,--wrap=sqlite3_prepare_v2 -Wl,--wrap=sqlite3_bind_int64 -Wl,--wrap=sqlite3_step -Wl,--wrap=sqlite3_finalize
|
||||
|
||||
test_chat_headless_control_SOURCES = test_chat_headless_control.c
|
||||
test_chat_headless_control_LDADD = $(LIBUTUN) $(COMMON_LIBS)
|
||||
|
||||
@@ -491,8 +491,8 @@ static int check_invite_priority(struct UTUN_INSTANCE* inst) {
|
||||
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; }
|
||||
while (!topo_node_best_path(peer) && get_time_tb() < deadline) poll_checked(inst);
|
||||
if (!topo_node_best_path(peer)) { 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;
|
||||
@@ -500,7 +500,7 @@ static int check_invite_priority(struct UTUN_INSTANCE* inst) {
|
||||
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;
|
||||
struct ll_queue* paths = peer->paths;
|
||||
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;
|
||||
@@ -512,11 +512,11 @@ static int check_invite_priority(struct UTUN_INSTANCE* inst) {
|
||||
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;
|
||||
peer->paths = NULL;
|
||||
if (chat_invite_best_node(inst, channel_id, &selected) || selected != inst->node_id) goto out;
|
||||
ok = 1;
|
||||
out:
|
||||
peer->conn_presence = presence;
|
||||
peer->paths = paths;
|
||||
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));
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
/* Реальные core/SQLite/локальная регистрация invite;проверяем отказы каждого SQL-этапа чтения. */
|
||||
#define _GNU_SOURCE
|
||||
#include "../src/chat/invite_build.c"
|
||||
#include <assert.h>
|
||||
|
||||
enum fault { NONE, PREPARE, BIND, STEP, FINALIZE };
|
||||
static enum fault fault;
|
||||
static const char* target;
|
||||
static unsigned skip_steps, calls;
|
||||
static int last_result;
|
||||
static uint64_t expected_channel;
|
||||
static struct UTUN_INSTANCE* active;
|
||||
static int matches(sqlite3_stmt* st) { return st && target && !strncmp(sqlite3_sql(st), target, strlen(target)); }
|
||||
extern int __real_sqlite3_prepare_v2(sqlite3*, const char*, int, sqlite3_stmt**, const char**);
|
||||
int __wrap_sqlite3_prepare_v2(sqlite3* db, const char* sql, int bytes, sqlite3_stmt** st, const char** tail) {
|
||||
if (fault == PREPARE && target && !strncmp(sql, target, strlen(target))) { fault = NONE; *st = NULL; return SQLITE_IOERR; }
|
||||
return __real_sqlite3_prepare_v2(db, sql, bytes, st, tail);
|
||||
}
|
||||
extern int __real_sqlite3_bind_int64(sqlite3_stmt*, int, sqlite3_int64);
|
||||
int __wrap_sqlite3_bind_int64(sqlite3_stmt* st, int index, sqlite3_int64 value) {
|
||||
if (fault == BIND && matches(st)) { fault = NONE; return SQLITE_IOERR; }
|
||||
return __real_sqlite3_bind_int64(st, index, value);
|
||||
}
|
||||
extern int __real_sqlite3_step(sqlite3_stmt*);
|
||||
int __wrap_sqlite3_step(sqlite3_stmt* st) {
|
||||
if (fault == STEP && matches(st)) {
|
||||
if (skip_steps) skip_steps--; else { fault = NONE; return SQLITE_IOERR; }
|
||||
}
|
||||
return __real_sqlite3_step(st);
|
||||
}
|
||||
extern int __real_sqlite3_finalize(sqlite3_stmt*);
|
||||
int __wrap_sqlite3_finalize(sqlite3_stmt* st) {
|
||||
int fail = fault == FINALIZE && matches(st); if (fail) fault = NONE;
|
||||
int rc = __real_sqlite3_finalize(st); return fail ? SQLITE_IOERR : rc;
|
||||
}
|
||||
static void ready(void* arg, int result, const char* link) {
|
||||
(void)arg; calls++; last_result = result;
|
||||
if (result == CHAT_JOIN_OK) {
|
||||
struct InviteData decoded; char error[128];
|
||||
assert(link && !invite_link_decode(link, strlen(link), &decoded, error, sizeof(error)));
|
||||
assert(decoded.channelId == expected_channel && decoded.nodeId == active->node_id && !strcmp(decoded.password, "p"));
|
||||
assert(chat_join_lookup_inviter(active, expected_channel, decoded.join_key) == active->node_id);
|
||||
} else assert(!link);
|
||||
}
|
||||
static int read_query(unsigned query, struct UTUN_INSTANCE* inst, uint64_t channel) {
|
||||
uint64_t ids[4] = {77,88,99,111}, before[4]; memcpy(before, ids, sizeof(ids));
|
||||
uint8_t key[32], old[32]; memset(key, 0xa5, sizeof(key)); memcpy(old, key, sizeof(key));
|
||||
struct InviteAddr addresses[INVITE_ADDR_MAX]; struct InviteReality reality;
|
||||
int rc;
|
||||
if (!query) {
|
||||
rc = chat_invite_candidate_nodes(inst, channel, ids, 4);
|
||||
if (rc < 0) assert(!memcmp(ids, before, sizeof(ids)));
|
||||
} else if (query == 1) rc = node_has_both_public(inst, inst->node_id);
|
||||
else if (query == 2) {
|
||||
rc = get_node_pubkey(inst, inst->node_id, 0, key); if (rc < 0) assert(!memcmp(old, key, sizeof(key)));
|
||||
} else rc = collect_node_addrs_from_db(inst, inst->node_id, addresses, INVITE_ADDR_MAX, 0, &reality);
|
||||
return rc;
|
||||
}
|
||||
int main(void) {
|
||||
debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN); debug_set_category_level(DEBUG_CATEGORY_CHAT_SYNC, DEBUG_LEVEL_DEBUG);
|
||||
size_t baseline = u_get_allocated_count(); utun_instance_set_tun_init_enabled(0);
|
||||
struct UASYNC* ua = uasync_create(); assert(ua);
|
||||
active = utun_instance_create_from_str(ua, "[global]\nmy_node_name=invite test\n[server: udp]\naddr=127.0.0.1:0\n"
|
||||
"type=public\n[ntp]\nenabled=no\n[chatserver]\ngroup_autoconnect=0\nstorage_autoload=0\n");
|
||||
assert(active && !utun_core_start(active) && !chat_service_start(active));
|
||||
expected_channel = chat_core_create_channel_auto(active, "invite test"); assert(expected_channel);
|
||||
const char* queries[] = {"SELECT node_id,node_type", "SELECT family FROM node_addresses", "SELECT x25519_pubkey", "SELECT family,socket_id"};
|
||||
unsigned cases = 0;
|
||||
for (unsigned q = 0; q < 4; q++) for (enum fault point = PREPARE; point <= FINALIZE; point++) {
|
||||
if (!q && point == BIND) continue;
|
||||
target = queries[q]; fault = point; skip_steps = !q && point == STEP ? 1 : 0;
|
||||
assert(read_query(q, active, expected_channel) < 0 && fault == NONE);
|
||||
assert(read_query(q, active, expected_channel) >= 0); cases++;
|
||||
}
|
||||
target = queries[0]; fault = FINALIZE; uint64_t selected = 77;
|
||||
assert(chat_invite_best_node(active, expected_channel, &selected) < 0 && fault == NONE && selected == 77);
|
||||
assert(!chat_invite_best_node(active, expected_channel, &selected) && selected == active->node_id);
|
||||
target = NULL;
|
||||
struct chat_invite_build* pending = chat_invite_build_link(active, expected_channel, 0, "p", ready, NULL);
|
||||
assert(pending && !calls);
|
||||
for (unsigned i = 0; i < 100 && !calls; i++) uasync_poll(ua, 1);
|
||||
assert(calls == 1 && last_result == CHAT_JOIN_OK);
|
||||
pending = chat_invite_build_link(active, expected_channel, active->node_id, "p", ready, NULL); assert(pending);
|
||||
chat_invite_build_cancel(pending); assert(calls == 2 && last_result == CHAT_JOIN_CANCELLED);
|
||||
assert(!chat_invite_build_link(active, expected_channel, 42, NULL, ready, NULL));
|
||||
selected = 77; assert(chat_invite_best_node(NULL, expected_channel, &selected) < 0 && selected == 77);
|
||||
assert(chat_invite_best_node(active, 0, &selected) < 0 && selected == 77);
|
||||
utun_instance_destroy(active); active = NULL; uasync_poll(ua, 0); uasync_destroy(ua, 0);
|
||||
assert(u_get_allocated_count() == baseline);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "PASS invite:%u SQL fault/retries,atomic outputs,shared address projection,self ACK/cancel,invalid target", cases);
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user