1827 lines
105 KiB
C
1827 lines
105 KiB
C
#include "topo_node_sqlite.h"
|
|
#include "topo_node.h"
|
|
#include "topo_group.h"
|
|
#include "etcp_connections.h"
|
|
#include "../config_parser.h"
|
|
#include "../lib/debug_config.h"
|
|
#include "../lib/mem.h"
|
|
#include "../lib/memory_pool.h"
|
|
#include "../lib/json_flat.h"
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <openssl/crypto.h>
|
|
|
|
#define PEERS_JOIN_SIG_SIZE 64
|
|
#define PEERS_JOIN_TS_SIZE 8
|
|
|
|
static void sanitize_ch_id(const char* ch_id, char* out, size_t out_sz) {
|
|
size_t i = 0;
|
|
while (*ch_id && i < out_sz - 1) {
|
|
char c = *ch_id++;
|
|
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
|
|
|| (c >= '0' && c <= '9') || c == '_')
|
|
out[i++] = c;
|
|
else
|
|
out[i++] = '_';
|
|
}
|
|
out[i] = '\0';
|
|
}
|
|
|
|
static void peers_table_name(const char* ch_id, char* buf, size_t sz) {
|
|
char san[64]; sanitize_ch_id(ch_id, san, sizeof(san));
|
|
snprintf(buf, sz, "peers_%s", san);
|
|
}
|
|
|
|
static int adm_tags_deleted(const char* tags) {
|
|
if (!tags || !tags[0]) return 0;
|
|
char buf[16];
|
|
return json_flat_get(tags, "deleted", buf, sizeof(buf)) == 0 && strcmp(buf, "yes") == 0;
|
|
}
|
|
|
|
/* ── Классификация адресов (локально, по IP) ── */
|
|
|
|
static int addr_type_from_ip(int family, const uint8_t* addr) {
|
|
if (!addr) return ADDR_TYPE_NETIF;
|
|
if (family == 4) {
|
|
uint8_t b0 = addr[0], b1 = addr[1];
|
|
if (b0 == 0 || b0 == 10 || b0 == 127) return ADDR_TYPE_NETIF;
|
|
if (b0 == 169 && b1 == 254) return ADDR_TYPE_NETIF;
|
|
if (b0 >= 224 && b0 <= 239) return ADDR_TYPE_NETIF;
|
|
if (b0 == 172 && b1 >= 16 && b1 <= 31) return ADDR_TYPE_NETIF;
|
|
if (b0 == 100 && b1 >= 64 && b1 <= 127) return ADDR_TYPE_NETIF; /* 100.64.0.0/10 (CGNAT) */
|
|
if (b0 == 192 && b1 == 168) return ADDR_TYPE_NETIF;
|
|
{ uint32_t ip; memcpy(&ip, addr, 4); if (ip == 0 || ip == 0xffffffff) return ADDR_TYPE_NETIF; }
|
|
return ADDR_TYPE_DIRECT;
|
|
} else {
|
|
if (addr[0] == 0xfe && (addr[1] & 0xc0) == 0x80) return ADDR_TYPE_NETIF;
|
|
if (addr[0] == 0xfc || addr[0] == 0xfd) return ADDR_TYPE_NETIF;
|
|
{ static const uint8_t z[16]; if (!memcmp(addr, z, 16)) return ADDR_TYPE_NETIF; }
|
|
{ static const uint8_t lb[16]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}; if (!memcmp(addr,lb,16)) return ADDR_TYPE_NETIF; }
|
|
return ADDR_TYPE_DIRECT;
|
|
}
|
|
}
|
|
|
|
/* Итоговая классификация: конфиг + автоопределение + IP (всё локально). */
|
|
static int addr_type_classify(int kind, int protocol, int config_type, int global, const struct address_profile *profile) {
|
|
if (config_type == CFG_SERVER_TYPE_PRIVATE || config_type == CFG_SERVER_TYPE_LOCAL) return ADDR_TYPE_NETIF;
|
|
if (!profile) return ADDR_TYPE_NAT;
|
|
if ((protocol & TOPO_PROTO_TCP) && profile->source == ADDRESS_SOURCE_ETCP && profile->mapping == ADDRESS_MAP_UNKNOWN)
|
|
return ADDR_TYPE_NAT;
|
|
if (profile->mapping == ADDRESS_MAP_EIM)
|
|
return profile->inbound == ADDRESS_IN_UNKNOWN ? ADDR_TYPE_NAT_EIM_UNVERIFIED : ADDR_TYPE_NAT_EIM;
|
|
if (profile->mapping == ADDRESS_MAP_DEPENDENT) return ADDR_TYPE_NAT_STRICT;
|
|
if (kind == TOPO_ADDR_NAT) return ADDR_TYPE_NAT;
|
|
/* Публичный адрес интерфейса известен без определения входящих и не становится NAT из-за UNKNOWN. */
|
|
if (global && kind == TOPO_ADDR_INTERFACE && (profile->mapping == ADDRESS_MAP_UNKNOWN ||
|
|
(profile->mapping == ADDRESS_MAP_UNCHANGED && profile->inbound == ADDRESS_IN_UNKNOWN))) return ADDR_TYPE_NETIF;
|
|
if (!global) return config_type == CFG_SERVER_TYPE_NAT ? ADDR_TYPE_NAT : ADDR_TYPE_NETIF;
|
|
if (profile->mapping == ADDRESS_MAP_UNCHANGED && profile->inbound == ADDRESS_IN_OPEN) return ADDR_TYPE_DIRECT;
|
|
if (profile->mapping == ADDRESS_MAP_UNCHANGED && profile->inbound == ADDRESS_IN_CONTACT) return ADDR_TYPE_CONTACT;
|
|
return ADDR_TYPE_NAT;
|
|
}
|
|
|
|
static const struct address_profile *smeta4_lookup(struct TOPO_SOCKMETA4 *head, uint8_t sid, uint8_t *config) {
|
|
*config = CFG_SERVER_TYPE_UNKNOWN;
|
|
for (const struct TOPO_SOCKMETA4 *meta = head; meta; meta = meta->next)
|
|
if (meta->id == sid) { *config = meta->config_type; return &meta->address_profile; }
|
|
return NULL;
|
|
}
|
|
|
|
static const struct address_profile *smeta6_lookup(struct TOPO_SOCKMETA6 *head, uint8_t sid, uint8_t *config) {
|
|
*config = CFG_SERVER_TYPE_UNKNOWN;
|
|
for (const struct TOPO_SOCKMETA6 *meta = head; meta; meta = meta->next)
|
|
if (meta->id == sid) { *config = meta->config_type; return &meta->address_profile; }
|
|
return NULL;
|
|
}
|
|
|
|
/* reality-параметры сокета → фиксированный blob для node_addresses.options.
|
|
Возвращает 1 и заполняет opts, если у сокета есть reality; иначе 0. */
|
|
static int reality_opts_build(struct TOPO_NODE* ni, uint8_t sid, struct TOPO_ADDR_REALITY_OPTS* opts) {
|
|
if (!ni || !opts) return 0;
|
|
for (const struct TOPO_REALITY_SOCK* r = ni->reality_socks; r; r = r->next) {
|
|
if (r->socket_id != sid) continue;
|
|
memcpy(opts->short_id, r->short_id, 8);
|
|
memcpy(opts->server_pubkey, r->server_pubkey, 32);
|
|
memcpy(opts->version, r->version, 3);
|
|
memcpy(opts->server_name, r->server_name, TOPO_REALITY_SNAME_MAX);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* ── Персистенция адресов узла (источник истины — BGP/topo_group) ── */
|
|
|
|
static int address_row_put(sqlite3_stmt *statement, struct TOPO_NODE *node, uint64_t node_id,
|
|
int family, const uint8_t *address, uint16_t port, uint8_t kind, uint8_t socket_id,
|
|
uint8_t protocol, uint8_t config, const struct address_profile *profile) {
|
|
static const struct address_profile unknown = {0};
|
|
if (!profile) profile = &unknown;
|
|
int summary = addr_type_classify(kind, protocol, config, addr_type_from_ip(family, address) == ADDR_TYPE_DIRECT, profile);
|
|
struct TOPO_ADDR_REALITY_OPTS reality;
|
|
int has_reality = (protocol & TOPO_PROTO_REALITY) && reality_opts_build(node, socket_id, &reality);
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)node_id);
|
|
sqlite3_bind_int(statement, 2, family); sqlite3_bind_int(statement, 3, protocol);
|
|
sqlite3_bind_blob(statement, 4, address, family == 4 ? 4 : 16, SQLITE_STATIC);
|
|
sqlite3_bind_int(statement, 5, port); sqlite3_bind_int(statement, 6, summary);
|
|
sqlite3_bind_int(statement, 7, config); sqlite3_bind_int(statement, 8, profile->mapping);
|
|
sqlite3_bind_int(statement, 9, socket_id);
|
|
if (has_reality) sqlite3_bind_blob(statement, 10, &reality, sizeof(reality), SQLITE_STATIC);
|
|
else sqlite3_bind_null(statement, 10);
|
|
sqlite3_bind_int(statement, 11, kind); sqlite3_bind_int(statement, 12, profile->inbound);
|
|
sqlite3_bind_int(statement, 13, profile->source);
|
|
int result = sqlite3_step(statement);
|
|
sqlite3_reset(statement);
|
|
if (result != SQLITE_DONE) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "address projection insert failed node=%016llx family=%d socket=%u kind=%u"
|
|
" mapping=%s inbound=%s: %s", (unsigned long long)node_id, family, socket_id, kind,
|
|
address_mapping_name(profile->mapping), address_inbound_name(profile->inbound),
|
|
sqlite3_errmsg(sqlite3_db_handle(statement)));
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int topo_node_sqlite_addrs_put(sqlite3 *db, uint64_t node_id, struct TOPO_NODE *node) {
|
|
if (!db || !node) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "address projection: invalid arguments"); return -1; }
|
|
if (sqlite3_exec(db, "SAVEPOINT node_address_projection", NULL, NULL, NULL) != SQLITE_OK) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "address projection savepoint failed: %s", sqlite3_errmsg(db)); return -1;
|
|
}
|
|
sqlite3_stmt *statement = NULL;
|
|
if (sqlite3_prepare_v2(db, "DELETE FROM node_addresses WHERE node_id=?", -1, &statement, NULL) != SQLITE_OK) goto fail;
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)node_id);
|
|
if (sqlite3_step(statement) != SQLITE_DONE) goto fail;
|
|
sqlite3_finalize(statement); statement = NULL;
|
|
if (sqlite3_prepare_v2(db, "INSERT INTO node_addresses(node_id,family,protocol,address,port,addr_type,config_type,"
|
|
"nat_mapping,socket_id,options,address_kind,nat_inbound,nat_source)"
|
|
" VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?)", -1, &statement, NULL) != SQLITE_OK) goto fail;
|
|
int written = 0;
|
|
for (const struct TOPO_ADDR4 *address = node->v4_addrs; address; address = address->next) {
|
|
uint8_t config;
|
|
const struct address_profile *profile = smeta4_lookup(node->v4_sock_meta, address->socket_id, &config);
|
|
if (address_row_put(statement, node, node_id, 4, address->addr, address->port, address->type,
|
|
address->socket_id, address->protocol, config, profile)) goto fail;
|
|
written++;
|
|
}
|
|
for (const struct TOPO_ADDR6 *address = node->v6_addrs; address; address = address->next) {
|
|
uint8_t config;
|
|
const struct address_profile *profile = smeta6_lookup(node->v6_sock_meta, address->socket_id, &config);
|
|
if (address_row_put(statement, node, node_id, 6, address->addr, address->port, address->type,
|
|
address->socket_id, address->protocol, config, profile)) goto fail;
|
|
written++;
|
|
}
|
|
sqlite3_finalize(statement); statement = NULL;
|
|
if (sqlite3_exec(db, "RELEASE node_address_projection", NULL, NULL, NULL) != SQLITE_OK) goto fail;
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "address projection stored node=%016llx endpoints=%d",
|
|
(unsigned long long)node_id, written);
|
|
return 0;
|
|
fail:
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "address projection failed node=%016llx: %s", (unsigned long long)node_id, sqlite3_errmsg(db));
|
|
sqlite3_finalize(statement);
|
|
if (sqlite3_exec(db, "ROLLBACK TO node_address_projection; RELEASE node_address_projection", NULL, NULL, NULL) != SQLITE_OK)
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "address projection rollback failed: %s", sqlite3_errmsg(db));
|
|
return -1;
|
|
}
|
|
|
|
int topo_node_sqlite_member_exists(sqlite3* db, uint64_t node_id) {
|
|
if (!db) return 0;
|
|
sqlite3_stmt* s = NULL;
|
|
if (sqlite3_prepare_v2(db,
|
|
"SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'peers\\_%' ESCAPE '\\'",
|
|
-1, &s, NULL) != SQLITE_OK) return 0;
|
|
char sql[256]; int found = 0;
|
|
while (!found && sqlite3_step(s) == SQLITE_ROW) {
|
|
const char* tbl = (const char*)sqlite3_column_text(s, 0);
|
|
if (!tbl) continue;
|
|
snprintf(sql, sizeof(sql), "SELECT 1 FROM \"%s\" WHERE node_id=? AND record_state=0", tbl);
|
|
sqlite3_stmt* m = NULL;
|
|
if (sqlite3_prepare_v2(db, sql, -1, &m, NULL) == SQLITE_OK) {
|
|
sqlite3_bind_int64(m, 1, (sqlite3_int64)node_id);
|
|
if (sqlite3_step(m) == SQLITE_ROW) found = 1;
|
|
sqlite3_finalize(m);
|
|
}
|
|
}
|
|
sqlite3_finalize(s);
|
|
return found;
|
|
}
|
|
|
|
int topo_node_sqlite_get_member_channels(sqlite3* db, uint64_t node_id,
|
|
uint64_t** out_ch_ids, int* out_count) {
|
|
if (out_ch_ids) *out_ch_ids = NULL;
|
|
if (out_count) *out_count = 0;
|
|
if (!db || !out_ch_ids || !out_count) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "member channels: invalid arguments"); return -1; }
|
|
|
|
sqlite3_stmt* s = NULL;
|
|
if (sqlite3_prepare_v2(db,
|
|
"SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'peers\\_%' ESCAPE '\\'",
|
|
-1, &s, NULL) != SQLITE_OK) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "member channels: enumerate tables: %s", sqlite3_errmsg(db)); return -1;
|
|
}
|
|
|
|
uint64_t* ids = NULL; int count = 0, cap = 0;
|
|
char sql[256];
|
|
int rc;
|
|
while ((rc = sqlite3_step(s)) == SQLITE_ROW) {
|
|
const char* tbl = (const char*)sqlite3_column_text(s, 0);
|
|
if (!tbl || strncmp(tbl, "peers_", 6) != 0) continue;
|
|
snprintf(sql, sizeof(sql), "SELECT 1 FROM \"%s\" WHERE node_id=? AND record_state=0", tbl);
|
|
sqlite3_stmt* m = NULL;
|
|
int member_rc = sqlite3_prepare_v2(db, sql, -1, &m, NULL);
|
|
if (member_rc == SQLITE_OK) member_rc = sqlite3_bind_int64(m, 1, (sqlite3_int64)node_id);
|
|
if (member_rc == SQLITE_OK) member_rc = sqlite3_step(m);
|
|
if (member_rc != SQLITE_ROW && member_rc != SQLITE_DONE) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "member channels: table=%s node=%016llx rc=%d: %s",
|
|
tbl, (unsigned long long)node_id, member_rc, sqlite3_errmsg(db));
|
|
sqlite3_finalize(m); u_free(ids); sqlite3_finalize(s); return -1;
|
|
}
|
|
{
|
|
int is_member = member_rc == SQLITE_ROW;
|
|
sqlite3_finalize(m);
|
|
if (is_member) {
|
|
uint64_t ch = strtoull(tbl + 6, NULL, 10);
|
|
if (ch != 0) {
|
|
if (count == cap) {
|
|
cap = cap ? cap * 2 : 8;
|
|
uint64_t* tmp = u_realloc(ids, (size_t)cap * sizeof(uint64_t));
|
|
if (!tmp) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "member channels: allocation failed count=%d", count);
|
|
u_free(ids); sqlite3_finalize(s); return -1;
|
|
}
|
|
ids = tmp;
|
|
}
|
|
ids[count++] = ch;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (rc != SQLITE_DONE) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "member channels: enumeration failed node=%016llx rc=%d: %s",
|
|
(unsigned long long)node_id, rc, sqlite3_errmsg(db));
|
|
u_free(ids); sqlite3_finalize(s); return -1;
|
|
}
|
|
sqlite3_finalize(s);
|
|
*out_ch_ids = ids; *out_count = count;
|
|
return 0;
|
|
}
|
|
|
|
/* Формат BLOB меняется вместе с NODEINFO. Старые байты не декодируем: адресная проекция
|
|
* обеспечивает bootstrap, timestamp сохраняет нижнюю границу новой подписи владельца. */
|
|
static int snapshot_schema(sqlite3* db) {
|
|
sqlite3_stmt* st = NULL;
|
|
int rc = sqlite3_prepare_v2(db, "SELECT count(*) FROM pragma_table_info('node_snapshots') WHERE name='format'", -1, &st, NULL);
|
|
if (rc == SQLITE_OK) rc = sqlite3_step(st);
|
|
int present = rc == SQLITE_ROW && sqlite3_column_int(st, 0);
|
|
int final = sqlite3_finalize(st);
|
|
if (rc != SQLITE_ROW || final != SQLITE_OK) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "snapshot format inspection failed rc=%d finalize=%d: %s", rc, final, sqlite3_errmsg(db)); return -1;
|
|
}
|
|
if (present) return 0;
|
|
const char* migration = "SAVEPOINT snapshot_format;"
|
|
"ALTER TABLE node_snapshots ADD COLUMN format INTEGER NOT NULL DEFAULT 0;"
|
|
"UPDATE node_snapshots SET record=X'';"
|
|
"RELEASE snapshot_format;";
|
|
if (sqlite3_exec(db, migration, NULL, NULL, NULL) != SQLITE_OK) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "snapshot format migration failed: %s", sqlite3_errmsg(db));
|
|
if (sqlite3_exec(db, "ROLLBACK TO snapshot_format; RELEASE snapshot_format", NULL, NULL, NULL) != SQLITE_OK)
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "snapshot format migration rollback failed: %s", sqlite3_errmsg(db));
|
|
return -1;
|
|
}
|
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "NODEINFO cache upgraded: obsolete blobs invalidated; timestamps, addresses, members and keys preserved");
|
|
return 0;
|
|
}
|
|
|
|
int topo_node_sqlite_init(sqlite3* db) {
|
|
if (!db) return -1;
|
|
|
|
/* Проекция адресов производная. Старый формат пересоздаём, не интерпретируя NAT_TYPE_* как новый профиль. */
|
|
sqlite3_stmt *schema = NULL;
|
|
int legacy_profile = 0;
|
|
if (sqlite3_prepare_v2(db, "SELECT sql FROM sqlite_master WHERE type='table' AND name='node_addresses'",
|
|
-1, &schema, NULL) != SQLITE_OK) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "address schema inspection failed: %s", sqlite3_errmsg(db)); return -1;
|
|
}
|
|
int exists = sqlite3_step(schema) == SQLITE_ROW;
|
|
char *zero_port_schema = NULL;
|
|
if (exists) {
|
|
const char *ddl = (const char *)sqlite3_column_text(schema, 0);
|
|
const char *strict_port = ddl ? strstr(ddl, "port > 0") : NULL;
|
|
if (strict_port) {
|
|
zero_port_schema = sqlite3_mprintf("%.*sport >= 0%s", (int)(strict_port - ddl), ddl, strict_port + strlen("port > 0"));
|
|
if (!zero_port_schema) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "address zero-port schema allocation failed"); sqlite3_finalize(schema); return -1;
|
|
}
|
|
}
|
|
}
|
|
sqlite3_finalize(schema); schema = NULL;
|
|
if (zero_port_schema) {
|
|
int failed = sqlite3_exec(db, "SAVEPOINT address_zero_port; ALTER TABLE node_addresses RENAME TO address_previous_ports",
|
|
NULL, NULL, NULL) != SQLITE_OK ||
|
|
sqlite3_exec(db, zero_port_schema, NULL, NULL, NULL) != SQLITE_OK ||
|
|
sqlite3_exec(db, "INSERT INTO node_addresses SELECT * FROM address_previous_ports;"
|
|
"DROP TABLE address_previous_ports; RELEASE address_zero_port", NULL, NULL, NULL) != SQLITE_OK;
|
|
sqlite3_free(zero_port_schema);
|
|
if (failed) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "address zero-port schema rebuild failed: %s", sqlite3_errmsg(db));
|
|
if (sqlite3_exec(db, "ROLLBACK TO address_zero_port; RELEASE address_zero_port", NULL, NULL, NULL) != SQLITE_OK)
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "address zero-port schema rollback failed: %s", sqlite3_errmsg(db));
|
|
return -1;
|
|
}
|
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "address schema now accepts TCP IP-only port=0; stored addresses preserved");
|
|
}
|
|
if (exists) {
|
|
int compatible = sqlite3_prepare_v2(db,
|
|
"SELECT nat_mapping,nat_inbound,nat_source,address_kind FROM node_addresses LIMIT 0",
|
|
-1, &schema, NULL);
|
|
sqlite3_finalize(schema);
|
|
if (compatible == SQLITE_ERROR) {
|
|
if (sqlite3_exec(db, "DROP TABLE node_addresses", NULL, NULL, NULL) != SQLITE_OK) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "old address projection reset failed: %s", sqlite3_errmsg(db)); return -1;
|
|
}
|
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "old address projection reset; signed NODEINFO will rebuild endpoint profiles");
|
|
} else if (compatible != SQLITE_OK) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "address schema read failed: %s", sqlite3_errmsg(db)); return -1;
|
|
} else {
|
|
if (sqlite3_prepare_v2(db, "SELECT count(*) FROM pragma_table_info('node_addresses')"
|
|
" WHERE name IN ('nat_evidence','nat_checked')", -1, &schema, NULL) != SQLITE_OK ||
|
|
sqlite3_step(schema) != SQLITE_ROW) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "address profile schema inspection failed: %s", sqlite3_errmsg(db));
|
|
sqlite3_finalize(schema); return -1;
|
|
}
|
|
int fields = sqlite3_column_int(schema, 0);
|
|
sqlite3_finalize(schema); schema = NULL;
|
|
if (fields == 1) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "incomplete legacy NAT profile schema"); return -1; }
|
|
legacy_profile = fields == 2;
|
|
}
|
|
}
|
|
|
|
const char* sql =
|
|
"CREATE TABLE IF NOT EXISTS node_snapshots ("
|
|
"node_id INTEGER PRIMARY KEY, timestamp INTEGER NOT NULL, record BLOB NOT NULL, format INTEGER NOT NULL DEFAULT 1);"
|
|
"CREATE TABLE IF NOT EXISTS nodes ("
|
|
" node_id INTEGER PRIMARY KEY,"
|
|
" name TEXT,"
|
|
" x25519_pubkey BLOB NOT NULL,"
|
|
" ed25519_pubkey BLOB,"
|
|
" last_seen_at INTEGER,"
|
|
" online INTEGER DEFAULT 0,"
|
|
" client_type INTEGER NOT NULL DEFAULT 0,"
|
|
" update_ts INTEGER DEFAULT 0,"
|
|
" created_at INTEGER DEFAULT (unixepoch())"
|
|
");"
|
|
|
|
"CREATE TABLE IF NOT EXISTS node_addresses ("
|
|
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
|
" node_id INTEGER NOT NULL REFERENCES nodes(node_id) ON DELETE CASCADE,"
|
|
" family INTEGER NOT NULL CHECK(family IN (4, 6)),"
|
|
" protocol INTEGER NOT NULL DEFAULT 1,"
|
|
" address BLOB NOT NULL,"
|
|
" port INTEGER NOT NULL CHECK(port >= 0 AND port <= 65535),"
|
|
" rtt INTEGER,"
|
|
" addr_type INTEGER DEFAULT 0,"
|
|
" config_type INTEGER DEFAULT 0,"
|
|
" nat_mapping INTEGER NOT NULL DEFAULT 0,"
|
|
" nat_inbound INTEGER NOT NULL DEFAULT 0,"
|
|
" nat_source INTEGER NOT NULL DEFAULT 0,"
|
|
" address_kind INTEGER NOT NULL DEFAULT 0,"
|
|
" socket_id INTEGER NOT NULL DEFAULT 0,"
|
|
" options BLOB," // reality-параметры (TOPO_ADDR_REALITY_OPTS)
|
|
" created_at INTEGER DEFAULT (unixepoch())"
|
|
");"
|
|
"CREATE INDEX IF NOT EXISTS idx_na_node ON node_addresses(node_id);"
|
|
"CREATE UNIQUE INDEX IF NOT EXISTS idx_na_unique ON node_addresses(node_id,family,socket_id,protocol,address,port,address_kind);"
|
|
"CREATE INDEX IF NOT EXISTS idx_na_addr_lookup ON node_addresses(family, address, port, socket_id);"
|
|
|
|
"CREATE TABLE IF NOT EXISTS group_policy(channel_id TEXT PRIMARY KEY,version INTEGER NOT NULL,"
|
|
"all_members INTEGER NOT NULL,signature BLOB NOT NULL);"
|
|
"CREATE TABLE IF NOT EXISTS group_admission_pending(channel_id TEXT PRIMARY KEY);"
|
|
"CREATE TABLE IF NOT EXISTS channels ("
|
|
" channel_id TEXT PRIMARY KEY," // ID канала
|
|
" name TEXT NOT NULL," // Название канала
|
|
" owner_node_id INTEGER," // node id создателя (надо выпилить)
|
|
" x25519_pubkey BLOB NOT NULL," // ключ для шифрования канала
|
|
" x25519_privkey BLOB," // если админ - то ключ канала
|
|
" ed25519_pubkey BLOB NOT NULL," // ключ для проверки подписи
|
|
" ed25519_privkey BLOB," // ключ для создания подписи
|
|
" signature BLOB NOT NULL," // подпись при создании канал приватным ключом канала channel_id(NUL-terminated) || name(NUL-terminated) || owner_node_id(8 байт, LE uint64) || x25519_pubkey(32 байта) || ed25519_pubkey(32 байта)
|
|
" avatar_version INTEGER NOT NULL DEFAULT 0,"
|
|
" avatar_data BLOB,"
|
|
" avatar_hash BLOB,"
|
|
" avatar_signature BLOB,"
|
|
" last_read_msg_id INTEGER," // для скроллинга чата
|
|
" last_pos_msg_id INTEGER," // для скроллинга чата
|
|
" last_msg_at INTEGER DEFAULT 0," // время последнего сообщения (unix timestamp)
|
|
" created_at INTEGER DEFAULT (unixepoch())" // когда создан
|
|
");";
|
|
|
|
char* err = NULL;
|
|
int rc = sqlite3_exec(db, sql, NULL, NULL, &err);
|
|
if (rc != SQLITE_OK) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "topo_node_sqlite_init: %s", err);
|
|
sqlite3_free(err);
|
|
return -1;
|
|
}
|
|
|
|
if (snapshot_schema(db) || devgroup_schema(db)) return -1;
|
|
|
|
/* Старые подписи непригодны; адреса и максимальные версии остаются для bootstrap и новой публикации. */
|
|
if (legacy_profile) {
|
|
const char *migration = "SAVEPOINT compact_nat_profile;"
|
|
"ALTER TABLE node_addresses DROP COLUMN nat_evidence;"
|
|
"ALTER TABLE node_addresses DROP COLUMN nat_checked;"
|
|
"UPDATE node_addresses SET nat_mapping=0,nat_inbound=0,nat_source=0,"
|
|
"addr_type=CASE WHEN addr_type=0 OR config_type IN (3,4) THEN 0 ELSE 4 END;"
|
|
"UPDATE node_snapshots SET record=X'';"
|
|
"RELEASE compact_nat_profile;";
|
|
if (sqlite3_exec(db, migration, NULL, NULL, NULL) != SQLITE_OK) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "compact NAT profile migration failed: %s", sqlite3_errmsg(db));
|
|
if (sqlite3_exec(db, "ROLLBACK TO compact_nat_profile; RELEASE compact_nat_profile", NULL, NULL, NULL) != SQLITE_OK)
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "compact NAT profile migration rollback failed: %s", sqlite3_errmsg(db));
|
|
return -1;
|
|
}
|
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "NAT profile compacted to mapping/inbound/source; bootstrap addresses and node versions preserved");
|
|
}
|
|
|
|
/* migration: добавить колонку options в уже существующую node_addresses (idempotent) */
|
|
{
|
|
sqlite3_stmt* cs = NULL;
|
|
int has_options = 0;
|
|
if (sqlite3_prepare_v2(db, "PRAGMA table_info(node_addresses)", -1, &cs, NULL) == SQLITE_OK) {
|
|
while (sqlite3_step(cs) == SQLITE_ROW) {
|
|
const char* cn = (const char*)sqlite3_column_text(cs, 1);
|
|
if (cn && strcmp(cn, "options") == 0) { has_options = 1; break; }
|
|
}
|
|
sqlite3_finalize(cs);
|
|
}
|
|
if (!has_options) {
|
|
char* aerr = NULL;
|
|
int arc = sqlite3_exec(db, "ALTER TABLE node_addresses ADD COLUMN options BLOB", NULL, NULL, &aerr);
|
|
if (arc != SQLITE_OK) {
|
|
DEBUG_WARN(DEBUG_CATEGORY_BGP, "topo_node_sqlite_init: ALTER TABLE ADD options failed: %s", aerr ? aerr : "?");
|
|
if (aerr) sqlite3_free(aerr);
|
|
} else {
|
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "topo_node_sqlite_init: migrated node_addresses (added options column)");
|
|
}
|
|
}
|
|
}
|
|
|
|
/* backfill: вывести deleted из adm_tags (idempotent, самовосстановление) */
|
|
{
|
|
sqlite3_stmt* s = NULL;
|
|
if (sqlite3_prepare_v2(db,
|
|
"SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'peers\\_%' ESCAPE '\\'",
|
|
-1, &s, NULL) == SQLITE_OK) {
|
|
while (sqlite3_step(s) == SQLITE_ROW) {
|
|
const char* tbl = (const char*)sqlite3_column_text(s, 0);
|
|
if (!tbl) continue;
|
|
sqlite3_stmt* sb = NULL; char bsql[256];
|
|
snprintf(bsql, sizeof(bsql), "SELECT node_id, adm_tags, deleted FROM \"%s\" WHERE record_state=0", tbl);
|
|
if (sqlite3_prepare_v2(db, bsql, -1, &sb, NULL) == SQLITE_OK) {
|
|
sqlite3_stmt* ub = NULL; char usql[256];
|
|
snprintf(usql, sizeof(usql), "UPDATE \"%s\" SET deleted=? WHERE node_id=? AND record_state=0", tbl);
|
|
int up_ok = sqlite3_prepare_v2(db, usql, -1, &ub, NULL) == SQLITE_OK;
|
|
int fixed = 0;
|
|
while (sqlite3_step(sb) == SQLITE_ROW) {
|
|
uint64_t nid = (uint64_t)sqlite3_column_int64(sb, 0);
|
|
const char* tags = (const char*)sqlite3_column_text(sb, 1);
|
|
int cur = sqlite3_column_int(sb, 2);
|
|
int d = adm_tags_deleted(tags);
|
|
if (d != cur) {
|
|
if (up_ok) {
|
|
sqlite3_bind_int(ub, 1, d);
|
|
sqlite3_bind_int64(ub, 2, (sqlite3_int64)nid);
|
|
sqlite3_step(ub); sqlite3_reset(ub);
|
|
}
|
|
fixed++;
|
|
}
|
|
}
|
|
if (ub) sqlite3_finalize(ub);
|
|
sqlite3_finalize(sb);
|
|
if (fixed) DEBUG_INFO(DEBUG_CATEGORY_BGP, "backfill deleted: tbl=%s fixed=%d", tbl, fixed);
|
|
}
|
|
}
|
|
sqlite3_finalize(s);
|
|
}
|
|
}
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "topo_node_sqlite tables initialized");
|
|
return 0;
|
|
}
|
|
|
|
|
|
int topo_node_sqlite_member_local_init(sqlite3* db, const char* channel_id) {
|
|
if (!db || !channel_id || !channel_id[0]) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "radio: invalid local member schema arguments"); return -1;
|
|
}
|
|
char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl));
|
|
char sql[160]; snprintf(sql, sizeof(sql), "PRAGMA table_info(\"%s\")", peers_tbl);
|
|
sqlite3_stmt* st = NULL;
|
|
if (sqlite3_prepare_v2(db, sql, -1, &st, NULL) != SQLITE_OK) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "radio: inspect member schema ch=%s: %s", channel_id, sqlite3_errmsg(db)); return -1;
|
|
}
|
|
int has_radio_muted = 0, has_admitted = 0, has_versions = 0, rc;
|
|
while ((rc = sqlite3_step(st)) == SQLITE_ROW) {
|
|
const char* column = (const char*)sqlite3_column_text(st, 1);
|
|
if (column && !strcmp(column, "radio_muted")) has_radio_muted = 1;
|
|
if (column && !strcmp(column, "admitted")) has_admitted = 1;
|
|
if (column && !strcmp(column, "record_state")) has_versions = 1;
|
|
}
|
|
int final = sqlite3_finalize(st);
|
|
if (rc != SQLITE_DONE || final != SQLITE_OK) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "radio: read member schema ch=%s step=%d finalize=%d: %s",
|
|
channel_id, rc, final, sqlite3_errmsg(db)); return -1;
|
|
}
|
|
if (!has_versions) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "unsupported member schema ch=%s: versioned peers table required", channel_id);
|
|
return -1;
|
|
}
|
|
if (!has_admitted) {
|
|
snprintf(sql, sizeof(sql), "ALTER TABLE \"%s\" ADD COLUMN admitted INTEGER NOT NULL DEFAULT 0", peers_tbl);
|
|
if (sqlite3_exec(db, sql, NULL, NULL, NULL) != SQLITE_OK) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "member admission schema ch=%s: %s", channel_id, sqlite3_errmsg(db)); return -1;
|
|
}
|
|
}
|
|
if (!has_radio_muted) {
|
|
snprintf(sql, sizeof(sql), "ALTER TABLE \"%s\" ADD COLUMN radio_muted INTEGER NOT NULL DEFAULT 0", peers_tbl);
|
|
if (sqlite3_exec(db, sql, NULL, NULL, NULL) != SQLITE_OK) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "radio: extend member schema ch=%s: %s", channel_id, sqlite3_errmsg(db)); return -1;
|
|
}
|
|
DEBUG_INFO(DEBUG_CATEGORY_RADIO, "radio: added local member mute setting ch=%s", channel_id);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int topo_node_sqlite_channel_put(sqlite3* db, const char* channel_id,
|
|
const char* name, uint64_t owner_node_id,
|
|
const uint8_t* x25519_pub, const uint8_t* x25519_priv,
|
|
const uint8_t* ed25519_pub, const uint8_t* ed25519_priv,
|
|
const uint8_t* signature) {
|
|
if (!db || !channel_id || !name || !x25519_pub || !ed25519_pub || !signature) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "channel put: invalid database/description arguments"); return -1;
|
|
}
|
|
int rc = sqlite3_exec(db, "SAVEPOINT channel_put", NULL, NULL, NULL);
|
|
if (rc != SQLITE_OK) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "channel put: begin channel=%s rc=%d: %s", channel_id, rc, sqlite3_errmsg(db)); return -1;
|
|
}
|
|
sqlite3_stmt* stmt = NULL;
|
|
const char* stage = "allocate peers schema";
|
|
|
|
char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl));
|
|
|
|
const char* ddl_tmpl =
|
|
"CREATE TABLE IF NOT EXISTS \"%s\" ("
|
|
" id INTEGER PRIMARY KEY AUTOINCREMENT," /* суррогатный ключ */
|
|
" node_id INTEGER NOT NULL," /* ID участника; версии разделяются служебными полями */
|
|
" record_state INTEGER NOT NULL DEFAULT 0 CHECK(record_state BETWEEN 0 AND 2),"
|
|
" sync_id INTEGER NOT NULL DEFAULT 0," /* 0 у опубликованных записей, id маркера приёма у временных */
|
|
" previous_id INTEGER," /* прежняя версия на момент получения; не основание для отката новой */
|
|
" sync_policy BLOB," /* подписанная политика только у маркера приёма (node_id=0) */
|
|
" x25519_pubkey BLOB NOT NULL," /* X25519 публичный ключ (32 байта) */
|
|
" ed25519_pubkey BLOB NOT NULL," /* Ed25519 публичный ключ (32 байта) — верификация join_sig/update_sig */
|
|
" join_sig BLOB NOT NULL," /* Ed25519 подпись присоединения (PEERS_JOIN_SIG_SIZE=64).
|
|
signed: ch_x25519(32)||ch_ed25519(32)||node_id(8 LE)||x25519(32)||join_ts(8 LE) */
|
|
" join_ts INTEGER NOT NULL," /* NTP timestamp подписи join_sig — защита от replay */
|
|
" update_sig BLOB," /* Ed25519 подпись обновления userinfo (PEERS_UPDATE_SIG_SIZE=64).
|
|
signed: join_sig(64)||update_ts(8 LE)||userinfo(NUL-terminated) */
|
|
" update_ts INTEGER DEFAULT 0," /* NTP timestamp подписи update_sig */
|
|
" userinfo TEXT NOT NULL DEFAULT ''," /* JSON с информацией о пользователе: {"name":"<display_name>"} */
|
|
" creator_sig BLOB," /* Ed25519 подпись создателя (PEERS_JOIN_SIG_SIZE=64), пока заглушка */
|
|
" signed_by INTEGER," /* node_id инвайтера (родитель в дереве приглашений); 0 = корень, подписан ключом группы */
|
|
" signature BLOB," /* Ed25519 подпись x25519_pubkey||"group_join" ключом родителя (или канальным ed25519 для корня) */
|
|
" adm_tags TEXT," /* JSON-теги администратора: {"key":"val",...,"ver":"N"}.
|
|
подпись: sc_ed25519_sign(ch_ed_priv, json||node_id_8LE) */
|
|
" adm_tags_sig BLOB," /* Ed25519 подпись над adm_tags (64 байта) канальным ключом */
|
|
" node_type INTEGER NOT NULL DEFAULT 0," /* вычисляемый тип узла (topo_node_sqlite_nodeinfo_updated):
|
|
0=неизвестно 1=прямой(addr_type NETIF/DIRECT/NAT_STRICT)
|
|
2=EIM NAT 3=(не исп.) 4=суперузел(adm_tags.supernode=yes) */
|
|
" node_RTT INTEGER," /* RTT до узла в мс, часть индекса (node_type, node_RTT) */
|
|
" connected INTEGER NOT NULL DEFAULT 0," /* 1 = было активное подключение к узлу */
|
|
" storage INTEGER NOT NULL DEFAULT 0," /* 1 = приоритетный хранитель аттачей */
|
|
" deleted INTEGER NOT NULL DEFAULT 0," /* 1 = аккаунт удалён владельцем (не отображаем, не подключаемся) */
|
|
" local_nick TEXT," /* локальный ник (GUI only, не синхронизируется) */
|
|
" radio_muted INTEGER NOT NULL DEFAULT 0," /* локальное отключение голоса участника */
|
|
" admitted INTEGER NOT NULL DEFAULT 0," /* локальный допуск по текущей политике */
|
|
" source INTEGER NOT NULL DEFAULT 0," /* 0=полная merkle-запись 1=упрощённый плейсхолдер topo_group
|
|
(не участвует в merkle-хеше, дополняется полной записью) */
|
|
" UNIQUE(node_id,record_state,sync_id),"
|
|
" CHECK(record_state<>0 OR sync_id=0)"
|
|
")";
|
|
|
|
int ddl_sz = snprintf(NULL, 0, ddl_tmpl, peers_tbl);
|
|
char* ddl = u_malloc(ddl_sz + 1);
|
|
if (!ddl) { rc = SQLITE_NOMEM; goto error; }
|
|
snprintf(ddl, ddl_sz + 1, ddl_tmpl, peers_tbl); stage = "create peers schema";
|
|
rc = sqlite3_exec(db, ddl, NULL, NULL, NULL); u_free(ddl);
|
|
if (rc != SQLITE_OK) goto error;
|
|
|
|
stage = "local member schema";
|
|
if (topo_node_sqlite_member_local_init(db, channel_id) < 0) { rc = SQLITE_ERROR; goto error; }
|
|
|
|
int idx_sz = snprintf(NULL, 0,
|
|
"CREATE INDEX IF NOT EXISTS \"idx_%s_type_rtt\" ON \"%s\" (node_type, node_RTT)", peers_tbl, peers_tbl);
|
|
char* idx_sql = u_malloc(idx_sz + 1);
|
|
stage = "allocate type index";
|
|
if (!idx_sql) { rc = SQLITE_NOMEM; goto error; }
|
|
snprintf(idx_sql, idx_sz + 1,
|
|
"CREATE INDEX IF NOT EXISTS \"idx_%s_type_rtt\" ON \"%s\" (node_type, node_RTT)", peers_tbl, peers_tbl);
|
|
stage = "create type index"; rc = sqlite3_exec(db, idx_sql, NULL, NULL, NULL); u_free(idx_sql);
|
|
if (rc != SQLITE_OK) goto error;
|
|
|
|
int sidx_sz = snprintf(NULL, 0,
|
|
"CREATE INDEX IF NOT EXISTS \"idx_%s_storage\" ON \"%s\" (storage)", peers_tbl, peers_tbl);
|
|
char* sidx_sql = u_malloc(sidx_sz + 1);
|
|
stage = "allocate storage index";
|
|
if (!sidx_sql) { rc = SQLITE_NOMEM; goto error; }
|
|
snprintf(sidx_sql, sidx_sz + 1,
|
|
"CREATE INDEX IF NOT EXISTS \"idx_%s_storage\" ON \"%s\" (storage)", peers_tbl, peers_tbl);
|
|
stage = "create storage index"; rc = sqlite3_exec(db, sidx_sql, NULL, NULL, NULL); u_free(sidx_sql);
|
|
if (rc != SQLITE_OK) goto error;
|
|
|
|
stage = "prepare channel";
|
|
rc = sqlite3_prepare_v2(db,
|
|
"INSERT INTO channels(channel_id, name, owner_node_id,"
|
|
" x25519_pubkey, x25519_privkey, ed25519_pubkey, ed25519_privkey, signature)"
|
|
" VALUES(?,?,?,?,?,?,?,?) ON CONFLICT(channel_id) DO UPDATE SET"
|
|
" name=excluded.name,owner_node_id=excluded.owner_node_id,signature=excluded.signature,"
|
|
" x25519_privkey=COALESCE(excluded.x25519_privkey,channels.x25519_privkey),"
|
|
" ed25519_privkey=COALESCE(excluded.ed25519_privkey,channels.ed25519_privkey)"
|
|
" WHERE channels.x25519_pubkey=excluded.x25519_pubkey AND channels.ed25519_pubkey=excluded.ed25519_pubkey",
|
|
-1, &stmt, NULL);
|
|
if (rc == SQLITE_OK) { stage = "bind channel"; rc = sqlite3_bind_text(stmt, 1, channel_id, -1, SQLITE_STATIC); }
|
|
if (rc == SQLITE_OK) { stage = "bind name"; rc = sqlite3_bind_text(stmt, 2, name, -1, SQLITE_STATIC); }
|
|
if (rc == SQLITE_OK) { stage = "bind owner"; rc = sqlite3_bind_int64(stmt, 3, (sqlite3_int64)owner_node_id); }
|
|
if (rc == SQLITE_OK) { stage = "bind X25519 public"; rc = sqlite3_bind_blob(stmt, 4, x25519_pub, 32, SQLITE_STATIC); }
|
|
if (rc == SQLITE_OK) {
|
|
stage = "bind X25519 private";
|
|
rc = x25519_priv ? sqlite3_bind_blob(stmt, 5, x25519_priv, 32, SQLITE_STATIC) : sqlite3_bind_null(stmt, 5);
|
|
}
|
|
if (rc == SQLITE_OK) { stage = "bind Ed25519 public"; rc = sqlite3_bind_blob(stmt, 6, ed25519_pub, 32, SQLITE_STATIC); }
|
|
if (rc == SQLITE_OK) {
|
|
stage = "bind Ed25519 private";
|
|
rc = ed25519_priv ? sqlite3_bind_blob(stmt, 7, ed25519_priv, 32, SQLITE_STATIC) : sqlite3_bind_null(stmt, 7);
|
|
}
|
|
if (rc == SQLITE_OK) { stage = "bind signature"; rc = sqlite3_bind_blob(stmt, 8, signature, 64, SQLITE_STATIC); }
|
|
if (rc == SQLITE_OK) { stage = "write channel"; rc = sqlite3_step(stmt); }
|
|
if (rc != SQLITE_DONE) goto error;
|
|
stage = "channel row count";
|
|
if (sqlite3_changes(db) != 1) { rc = SQLITE_CONSTRAINT; goto error; }
|
|
stage = "finalize channel"; rc = sqlite3_finalize(stmt); stmt = NULL;
|
|
if (rc != SQLITE_OK) goto error;
|
|
stage = "release channel"; rc = sqlite3_exec(db, "RELEASE channel_put", NULL, NULL, NULL);
|
|
if (rc == SQLITE_OK) return 0;
|
|
error:
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "channel put: channel=%s stage=%s rc=%d (%s) db=%s",
|
|
channel_id, stage, rc, sqlite3_errstr(rc), sqlite3_errmsg(db));
|
|
{
|
|
int final = sqlite3_finalize(stmt);
|
|
if (final != SQLITE_OK) DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "channel put: cleanup finalize channel=%s rc=%d", channel_id, final);
|
|
}
|
|
if (sqlite3_get_autocommit(db)) return -1;
|
|
rc = sqlite3_exec(db, "ROLLBACK TO channel_put", NULL, NULL, NULL);
|
|
if (rc != SQLITE_OK) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "channel put: rollback channel=%s rc=%d; transaction must be rolled back or closed", channel_id, rc);
|
|
return -1;
|
|
}
|
|
rc = sqlite3_exec(db, "RELEASE channel_put", NULL, NULL, NULL);
|
|
if (rc != SQLITE_OK) DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "channel put: rollback release channel=%s rc=%d", channel_id, rc);
|
|
return -1;
|
|
}
|
|
|
|
/* Удаляет канал и производные таблицы целиком; savepoint сохраняет внешнюю транзакцию. */
|
|
int topo_node_sqlite_channel_delete(sqlite3* db, const char* channel_id) {
|
|
if (!db || !channel_id || !channel_id[0]) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "channel delete: invalid arguments"); return -1;
|
|
}
|
|
int rc = sqlite3_exec(db, "SAVEPOINT delete_topo_channel", NULL, NULL, NULL);
|
|
if (rc != SQLITE_OK) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "channel delete savepoint: channel=%s rc=%d: %s", channel_id, rc, sqlite3_errmsg(db)); return -1;
|
|
}
|
|
sqlite3_stmt* st = NULL;
|
|
char san[64], sql[512]; sanitize_ch_id(channel_id, san, sizeof(san));
|
|
snprintf(sql, sizeof(sql), "DROP TABLE IF EXISTS \"peers_%s\"; DROP TABLE IF EXISTS \"msg_%s\";"
|
|
" DROP TABLE IF EXISTS \"feedback_%s\"", san, san, san);
|
|
if ((rc = sqlite3_exec(db, sql, NULL, NULL, NULL)) != SQLITE_OK) goto fail;
|
|
rc = sqlite3_prepare_v2(db, "DELETE FROM group_policy WHERE channel_id=?", -1, &st, NULL);
|
|
if (rc == SQLITE_OK) rc = sqlite3_bind_text(st, 1, channel_id, -1, SQLITE_STATIC);
|
|
if (rc == SQLITE_OK) rc = sqlite3_step(st);
|
|
if (rc != SQLITE_DONE) goto fail;
|
|
rc = sqlite3_finalize(st); st = NULL;
|
|
if (rc != SQLITE_OK) goto fail;
|
|
rc = sqlite3_prepare_v2(db, "DELETE FROM group_admission_pending WHERE channel_id=?", -1, &st, NULL);
|
|
if (rc == SQLITE_OK) rc = sqlite3_bind_text(st, 1, channel_id, -1, SQLITE_STATIC);
|
|
if (rc == SQLITE_OK) rc = sqlite3_step(st);
|
|
if (rc != SQLITE_DONE) goto fail;
|
|
rc = sqlite3_finalize(st); st = NULL;
|
|
if (rc != SQLITE_OK) goto fail;
|
|
rc = sqlite3_prepare_v2(db, "DELETE FROM channels WHERE channel_id=?", -1, &st, NULL);
|
|
if (rc == SQLITE_OK) rc = sqlite3_bind_text(st, 1, channel_id, -1, SQLITE_STATIC);
|
|
if (rc == SQLITE_OK) rc = sqlite3_step(st);
|
|
if (rc != SQLITE_DONE) goto fail;
|
|
rc = sqlite3_finalize(st); st = NULL;
|
|
if (rc != SQLITE_OK) goto fail;
|
|
/* IGNORE может вернуть DONE, сохранив канал: это не успешное удаление. */
|
|
rc = sqlite3_prepare_v2(db, "SELECT 1 FROM channels WHERE channel_id=?", -1, &st, NULL);
|
|
if (rc == SQLITE_OK) rc = sqlite3_bind_text(st, 1, channel_id, -1, SQLITE_STATIC);
|
|
if (rc == SQLITE_OK) rc = sqlite3_step(st);
|
|
if (rc != SQLITE_DONE) goto fail;
|
|
rc = sqlite3_finalize(st); st = NULL;
|
|
if (rc != SQLITE_OK) goto fail;
|
|
rc = sqlite3_prepare_v2(db, "SELECT 1 FROM sqlite_master WHERE type='table' AND name='merkle_tree_hash'", -1, &st, NULL);
|
|
if (rc != SQLITE_OK) goto fail;
|
|
rc = sqlite3_step(st);
|
|
if (rc != SQLITE_ROW && rc != SQLITE_DONE) goto fail;
|
|
int has_merkle = rc == SQLITE_ROW;
|
|
rc = sqlite3_finalize(st); st = NULL;
|
|
if (rc != SQLITE_OK) goto fail;
|
|
if (has_merkle) {
|
|
rc = sqlite3_prepare_v2(db, "DELETE FROM merkle_tree_hash WHERE substr(namespace,instr(namespace,':')+1)=?", -1, &st, NULL);
|
|
if (rc == SQLITE_OK) rc = sqlite3_bind_text(st, 1, channel_id, -1, SQLITE_STATIC);
|
|
if (rc == SQLITE_OK) rc = sqlite3_step(st);
|
|
if (rc != SQLITE_DONE) goto fail;
|
|
rc = sqlite3_finalize(st); st = NULL;
|
|
if (rc != SQLITE_OK) goto fail;
|
|
rc = sqlite3_prepare_v2(db, "SELECT 1 FROM merkle_tree_hash WHERE substr(namespace,instr(namespace,':')+1)=? LIMIT 1",
|
|
-1, &st, NULL);
|
|
if (rc == SQLITE_OK) rc = sqlite3_bind_text(st, 1, channel_id, -1, SQLITE_STATIC);
|
|
if (rc == SQLITE_OK) rc = sqlite3_step(st);
|
|
if (rc != SQLITE_DONE) goto fail;
|
|
rc = sqlite3_finalize(st); st = NULL;
|
|
if (rc != SQLITE_OK) goto fail;
|
|
}
|
|
if ((rc = sqlite3_exec(db, "RELEASE delete_topo_channel", NULL, NULL, NULL)) != SQLITE_OK) goto fail;
|
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "channel deleted: channel=%s tables=peers/msg/feedback merkle=%d", channel_id, has_merkle);
|
|
return 0;
|
|
fail:
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "channel delete failed: channel=%s rc=%d: %s", channel_id, rc, sqlite3_errmsg(db));
|
|
sqlite3_finalize(st);
|
|
if (sqlite3_exec(db, "ROLLBACK TO delete_topo_channel; RELEASE delete_topo_channel", NULL, NULL, NULL) != SQLITE_OK)
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "channel delete rollback failed: channel=%s: %s", channel_id, sqlite3_errmsg(db));
|
|
return -1;
|
|
}
|
|
|
|
/* Упрощённый плейсхолдер мембера от topo_group: только node_id + pubkeys, source=1.
|
|
* Не участвует в merkle-хеше. Если запись уже есть (полная merkle) — не трогает её.
|
|
* Позже полная merkle-запись дополняет/перезаписывает блоки и сбрасывает source=0. */
|
|
int topo_node_sqlite_member_placeholder_put(sqlite3* db, const char* channel_id, uint64_t node_id,
|
|
const uint8_t* x25519_pubkey, const uint8_t* ed25519_pubkey) {
|
|
if (!db || !channel_id || !x25519_pubkey || !ed25519_pubkey) return -1;
|
|
char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl));
|
|
char sql[512];
|
|
snprintf(sql, sizeof(sql),
|
|
"INSERT INTO \"%s\"(node_id, x25519_pubkey, ed25519_pubkey, join_sig, join_ts, source)"
|
|
" VALUES(?,?,?,?,0,1)"
|
|
" ON CONFLICT(node_id,record_state,sync_id) DO NOTHING", peers_tbl);
|
|
sqlite3_stmt* stmt = NULL;
|
|
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) return -1;
|
|
sqlite3_bind_int64(stmt, 1, (sqlite3_int64)node_id);
|
|
sqlite3_bind_blob(stmt, 2, x25519_pubkey, 32, SQLITE_STATIC);
|
|
sqlite3_bind_blob(stmt, 3, ed25519_pubkey, 32, SQLITE_STATIC);
|
|
static const unsigned char zsig[PEERS_JOIN_SIG_SIZE] = {0};
|
|
sqlite3_bind_blob(stmt, 4, zsig, PEERS_JOIN_SIG_SIZE, SQLITE_STATIC);
|
|
int rc = sqlite3_step(stmt); sqlite3_finalize(stmt);
|
|
return rc == SQLITE_DONE ? 0 : -1;
|
|
}
|
|
|
|
/* Обновляет только блок мембера (keys/join/update_sig/update_ts/userinfo),
|
|
* НЕ трогая adm_tags/adm_tags_sig/storage (блок владельца) и локальные поля. */
|
|
int topo_node_sqlite_member_block_put(sqlite3* db, const char* channel_id, uint64_t node_id,
|
|
const uint8_t* join_sig, uint64_t join_ts,
|
|
const uint8_t* update_sig, uint64_t update_ts,
|
|
const uint8_t* x25519_pubkey, const uint8_t* ed25519_pubkey,
|
|
const char* userinfo, const uint8_t* creator_sig,
|
|
uint64_t signed_by, const uint8_t* signature) {
|
|
if (!db || !channel_id || !x25519_pubkey || !ed25519_pubkey) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "member block: invalid database/channel/key arguments"); return -1;
|
|
}
|
|
char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl));
|
|
char sql[768];
|
|
snprintf(sql, sizeof(sql),
|
|
"INSERT INTO \"%s\"(node_id, x25519_pubkey, ed25519_pubkey,"
|
|
" join_sig, join_ts, update_sig, update_ts, userinfo, creator_sig, signed_by, signature)"
|
|
" VALUES(?,?,?,?,?,?,?,?,?,?,?)"
|
|
" ON CONFLICT(node_id,record_state,sync_id) DO UPDATE SET"
|
|
" x25519_pubkey=excluded.x25519_pubkey,"
|
|
" ed25519_pubkey=excluded.ed25519_pubkey,"
|
|
" join_sig=excluded.join_sig,"
|
|
" join_ts=excluded.join_ts,"
|
|
" update_sig=excluded.update_sig,"
|
|
" update_ts=excluded.update_ts,"
|
|
" userinfo=excluded.userinfo,"
|
|
" creator_sig=excluded.creator_sig,"
|
|
" signed_by=excluded.signed_by,"
|
|
" signature=excluded.signature,"
|
|
" source=0", peers_tbl);
|
|
sqlite3_stmt* stmt = NULL;
|
|
const char* stage = "prepare";
|
|
int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
|
if (rc == SQLITE_OK) { stage = "bind node"; rc = sqlite3_bind_int64(stmt, 1, (sqlite3_int64)node_id); }
|
|
if (rc == SQLITE_OK) { stage = "bind X25519"; rc = sqlite3_bind_blob(stmt, 2, x25519_pubkey, 32, SQLITE_STATIC); }
|
|
if (rc == SQLITE_OK) { stage = "bind Ed25519"; rc = sqlite3_bind_blob(stmt, 3, ed25519_pubkey, 32, SQLITE_STATIC); }
|
|
if (rc == SQLITE_OK) {
|
|
static const uint8_t zero_signature[PEERS_JOIN_SIG_SIZE] = {0}; stage = "bind JOIN signature";
|
|
rc = sqlite3_bind_blob(stmt, 4, join_sig ? join_sig : zero_signature, PEERS_JOIN_SIG_SIZE, SQLITE_STATIC);
|
|
}
|
|
if (rc == SQLITE_OK) { stage = "bind JOIN timestamp"; rc = sqlite3_bind_int64(stmt, 5, (sqlite3_int64)join_ts); }
|
|
if (rc == SQLITE_OK) {
|
|
stage = "bind profile signature";
|
|
rc = update_sig ? sqlite3_bind_blob(stmt, 6, update_sig, PEERS_UPDATE_SIG_SIZE, SQLITE_STATIC) : sqlite3_bind_null(stmt, 6);
|
|
}
|
|
if (rc == SQLITE_OK) { stage = "bind profile timestamp"; rc = sqlite3_bind_int64(stmt, 7, (sqlite3_int64)update_ts); }
|
|
if (rc == SQLITE_OK) { stage = "bind profile"; rc = sqlite3_bind_text(stmt, 8, userinfo ? userinfo : "", -1, SQLITE_STATIC); }
|
|
if (rc == SQLITE_OK) {
|
|
stage = "bind creator signature";
|
|
rc = creator_sig ? sqlite3_bind_blob(stmt, 9, creator_sig, PEERS_JOIN_SIG_SIZE, SQLITE_STATIC) : sqlite3_bind_null(stmt, 9);
|
|
}
|
|
if (rc == SQLITE_OK) { stage = "bind inviter"; rc = sqlite3_bind_int64(stmt, 10, (sqlite3_int64)signed_by); }
|
|
if (rc == SQLITE_OK) {
|
|
stage = "bind invitation signature";
|
|
rc = signature ? sqlite3_bind_blob(stmt, 11, signature, 64, SQLITE_STATIC) : sqlite3_bind_null(stmt, 11);
|
|
}
|
|
if (rc == SQLITE_OK) { stage = "write"; rc = sqlite3_step(stmt); }
|
|
int changed = rc == SQLITE_DONE ? sqlite3_changes(db) : 0;
|
|
if (rc != SQLITE_DONE || changed != 1) DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC,
|
|
"member block: channel=%s node=%016llx stage=%s rc=%d (%s) changed=%d db=%s",
|
|
channel_id, (unsigned long long)node_id, stage, rc, sqlite3_errstr(rc), changed, sqlite3_errmsg(db));
|
|
int final = sqlite3_finalize(stmt);
|
|
if (final != SQLITE_OK) DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "member block: finalize channel=%s node=%016llx rc=%d",
|
|
channel_id, (unsigned long long)node_id, final);
|
|
return rc == SQLITE_DONE && changed == 1 && final == SQLITE_OK ? 0 : -1;
|
|
}
|
|
|
|
/* Обновляет только блок владельца (adm_tags/adm_tags_sig/storage),
|
|
* НЕ трогая блок мембера. */
|
|
int topo_node_sqlite_member_owner_put(sqlite3* db, const char* channel_id, uint64_t node_id,
|
|
const char* adm_tags, const uint8_t* adm_tags_sig, int storage) {
|
|
if (!db || !channel_id) { DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "member owner: missing database/channel"); return -1; }
|
|
char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl));
|
|
char sql[256];
|
|
snprintf(sql, sizeof(sql),
|
|
"UPDATE \"%s\" SET adm_tags=?, adm_tags_sig=?, storage=?, deleted=? WHERE node_id=? AND record_state=0", peers_tbl);
|
|
sqlite3_stmt* u = NULL;
|
|
const char* stage = "prepare"; int rc = sqlite3_prepare_v2(db, sql, -1, &u, NULL);
|
|
if (rc == SQLITE_OK) {
|
|
stage = "bind tags"; rc = adm_tags && adm_tags[0] ? sqlite3_bind_text(u, 1, adm_tags, -1, SQLITE_STATIC) : sqlite3_bind_null(u, 1);
|
|
}
|
|
if (rc == SQLITE_OK) { stage = "bind signature"; rc = adm_tags_sig ? sqlite3_bind_blob(u, 2, adm_tags_sig, 64, SQLITE_STATIC) : sqlite3_bind_null(u, 2); }
|
|
if (rc == SQLITE_OK) { stage = "bind storage"; rc = sqlite3_bind_int(u, 3, storage); }
|
|
if (rc == SQLITE_OK) { stage = "bind deleted"; rc = sqlite3_bind_int(u, 4, adm_tags_deleted(adm_tags)); }
|
|
if (rc == SQLITE_OK) { stage = "bind member"; rc = sqlite3_bind_int64(u, 5, (sqlite3_int64)node_id); }
|
|
if (rc == SQLITE_OK) { stage = "step"; rc = sqlite3_step(u); }
|
|
if (rc == SQLITE_DONE && sqlite3_changes(db) != 1) { stage = "changes != 1"; rc = SQLITE_CONSTRAINT; }
|
|
int final = sqlite3_finalize(u);
|
|
if (rc != SQLITE_DONE || final != SQLITE_OK) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "member owner: ch=%s node=%016llx stage=%s rc=%d finalize=%d: %s",
|
|
channel_id, (unsigned long long)node_id, stage, rc, final, sqlite3_errmsg(db));
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Записывает уже проверенную вызывающим идентичность; любой отказ SQL возвращается вызывающему. */
|
|
int topo_node_sqlite_node_update_verified(sqlite3* db, uint64_t node_id,
|
|
const char* name, const uint8_t* x25519, const uint8_t* ed25519, uint64_t update_ts, time_t created_at) {
|
|
if (!db || !x25519 || !ed25519) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "verified node write: invalid arguments node=%016llx", (unsigned long long)node_id);
|
|
return -1;
|
|
}
|
|
sqlite3_stmt* stmt = NULL;
|
|
const char* stage = "prepare";
|
|
int rc = sqlite3_prepare_v2(db,
|
|
"INSERT INTO nodes(node_id, name, x25519_pubkey, ed25519_pubkey, created_at, update_ts, online)"
|
|
" VALUES(?,?,?,?,?,?,1)"
|
|
" ON CONFLICT(node_id) DO UPDATE SET"
|
|
" name=CASE WHEN excluded.name IS NOT NULL AND excluded.name != ''"
|
|
" THEN excluded.name ELSE nodes.name END,"
|
|
" x25519_pubkey=excluded.x25519_pubkey,"
|
|
" ed25519_pubkey=excluded.ed25519_pubkey,"
|
|
" created_at=COALESCE(nodes.created_at, excluded.created_at),"
|
|
" update_ts=CASE WHEN nodes.update_ts IS NULL OR excluded.update_ts > nodes.update_ts"
|
|
" THEN excluded.update_ts ELSE nodes.update_ts END",
|
|
-1, &stmt, NULL);
|
|
if (rc == SQLITE_OK) {
|
|
stage = "bind";
|
|
rc = sqlite3_bind_int64(stmt, 1, (sqlite3_int64)node_id);
|
|
if (rc == SQLITE_OK) rc = sqlite3_bind_text(stmt, 2, name && name[0] ? name : "", -1, SQLITE_STATIC);
|
|
if (rc == SQLITE_OK) rc = sqlite3_bind_blob(stmt, 3, x25519, 32, SQLITE_STATIC);
|
|
if (rc == SQLITE_OK) rc = sqlite3_bind_blob(stmt, 4, ed25519, 32, SQLITE_STATIC);
|
|
if (rc == SQLITE_OK) rc = sqlite3_bind_int64(stmt, 5, (sqlite3_int64)created_at);
|
|
if (rc == SQLITE_OK) rc = sqlite3_bind_int64(stmt, 6, (sqlite3_int64)update_ts);
|
|
}
|
|
if (rc == SQLITE_OK) { stage = "step"; rc = sqlite3_step(stmt); }
|
|
if (rc == SQLITE_DONE && sqlite3_changes(db) != 1) { stage = "changes != 1"; rc = SQLITE_CONSTRAINT; }
|
|
if (rc != SQLITE_DONE) DEBUG_ERROR(DEBUG_CATEGORY_BGP, "verified node write: node=%016llx stage=%s rc=%d: %s",
|
|
(unsigned long long)node_id, stage, rc, sqlite3_errmsg(db));
|
|
int final_rc = sqlite3_finalize(stmt);
|
|
if (final_rc != SQLITE_OK && rc == SQLITE_DONE) DEBUG_ERROR(DEBUG_CATEGORY_BGP,
|
|
"verified node write: node=%016llx stage=finalize rc=%d: %s", (unsigned long long)node_id, final_rc, sqlite3_errmsg(db));
|
|
if (rc != SQLITE_DONE || final_rc != SQLITE_OK) return -1;
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "verified node stored: node=%016llx timestamp=%llu changed=%d",
|
|
(unsigned long long)node_id, (unsigned long long)update_ts, sqlite3_changes(db));
|
|
return 0;
|
|
}
|
|
|
|
int topo_node_sqlite_member_get_join(sqlite3* db, const char* channel_id, uint64_t node_id,
|
|
uint8_t* join_sig_out, uint64_t* join_ts_out) {
|
|
if (!db || !channel_id || !join_sig_out || !join_ts_out) return -1;
|
|
char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl));
|
|
char sql[256];
|
|
snprintf(sql, sizeof(sql), "SELECT join_sig, join_ts FROM \"%s\" WHERE node_id=? AND record_state=0", peers_tbl);
|
|
sqlite3_stmt* stmt = NULL;
|
|
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) return -1;
|
|
sqlite3_bind_int64(stmt, 1, (sqlite3_int64)node_id);
|
|
int rc = -1;
|
|
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
|
const uint8_t* sig = (const uint8_t*)sqlite3_column_blob(stmt, 0);
|
|
if (sig && sqlite3_column_bytes(stmt, 0) == PEERS_JOIN_SIG_SIZE) {
|
|
memcpy(join_sig_out, sig, PEERS_JOIN_SIG_SIZE); *join_ts_out = (uint64_t)sqlite3_column_int64(stmt, 1); rc = 0;
|
|
} else DEBUG_WARN(DEBUG_CATEGORY_BGP, "invalid stored join signature channel=%s node=%016llx bytes=%d",
|
|
channel_id, (unsigned long long)node_id, sqlite3_column_bytes(stmt, 0));
|
|
}
|
|
sqlite3_finalize(stmt);
|
|
return rc;
|
|
}
|
|
|
|
int topo_node_sqlite_member_get_sign(sqlite3* db, const char* channel_id, uint64_t node_id,
|
|
uint64_t* signed_by_out, uint8_t signature_out[64]) {
|
|
if (!db || !channel_id || !signed_by_out || !signature_out) return -1;
|
|
char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl));
|
|
char sql[256];
|
|
snprintf(sql, sizeof(sql), "SELECT signed_by, signature FROM \"%s\" WHERE node_id=? AND record_state=0", peers_tbl);
|
|
sqlite3_stmt* stmt = NULL;
|
|
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) return -1;
|
|
sqlite3_bind_int64(stmt, 1, (sqlite3_int64)node_id);
|
|
int rc = -1;
|
|
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
|
*signed_by_out = (uint64_t)sqlite3_column_int64(stmt, 0);
|
|
const uint8_t* sig = (const uint8_t*)sqlite3_column_blob(stmt, 1);
|
|
if (sig && sqlite3_column_bytes(stmt, 1) >= 64) { memcpy(signature_out, sig, 64); rc = 0; }
|
|
}
|
|
sqlite3_finalize(stmt);
|
|
return rc;
|
|
}
|
|
|
|
int topo_node_sqlite_member_in_channel(sqlite3* db, const char* channel_id, uint64_t node_id) {
|
|
if (!db || !channel_id || node_id == 0) return 0;
|
|
char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl));
|
|
char sql[256];
|
|
snprintf(sql, sizeof(sql), "SELECT 1 FROM \"%s\" WHERE node_id=? AND record_state=0", peers_tbl);
|
|
sqlite3_stmt* stmt = NULL;
|
|
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) return 0;
|
|
sqlite3_bind_int64(stmt, 1, (sqlite3_int64)node_id);
|
|
int found = (sqlite3_step(stmt) == SQLITE_ROW);
|
|
sqlite3_finalize(stmt);
|
|
return found;
|
|
}
|
|
|
|
static int member_access(sqlite3* db, const char* channel_id, uint64_t node_id, int admitted) {
|
|
if (!db || !channel_id || !channel_id[0] || !node_id) {
|
|
DEBUG_WARN(DEBUG_CATEGORY_BGP, "member admission: invalid channel/node"); return 0;
|
|
}
|
|
char table[80], sql[256]; peers_table_name(channel_id, table, sizeof(table));
|
|
snprintf(sql, sizeof(sql), "SELECT 1 FROM \"%s\" WHERE node_id=? AND record_state=0 AND source=0 AND deleted=0%s",
|
|
table, admitted ? " AND admitted=1" : "");
|
|
sqlite3_stmt* statement = NULL;
|
|
int rc = sqlite3_prepare_v2(db, sql, -1, &statement, NULL);
|
|
if (rc == SQLITE_OK) rc = sqlite3_bind_int64(statement, 1, (sqlite3_int64)node_id);
|
|
if (rc == SQLITE_OK) rc = sqlite3_step(statement);
|
|
int allowed = rc == SQLITE_ROW;
|
|
if (rc != SQLITE_ROW && rc != SQLITE_DONE)
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "member admission read failed channel=%s node=%016llx rc=%d: %s",
|
|
channel_id, (unsigned long long)node_id, rc, sqlite3_errmsg(db));
|
|
int final = sqlite3_finalize(statement);
|
|
if (final != SQLITE_OK) DEBUG_ERROR(DEBUG_CATEGORY_BGP, "member admission finalize failed channel=%s rc=%d", channel_id, final);
|
|
return allowed && final == SQLITE_OK;
|
|
}
|
|
|
|
int topo_node_sqlite_member_allowed(sqlite3* db, const char* channel_id, uint64_t node_id) {
|
|
return member_access(db, channel_id, node_id, 1);
|
|
}
|
|
|
|
int topo_node_sqlite_member_transport_allowed(sqlite3* db, const char* channel_id, uint64_t node_id) {
|
|
return member_access(db, channel_id, node_id, 0);
|
|
}
|
|
|
|
int topo_node_sqlite_member_key_allowed(sqlite3* db, const char* channel_id, const uint8_t key[32]) {
|
|
if (!db || !channel_id || !channel_id[0] || !key) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "member key admission: invalid database/channel/key"); return 0;
|
|
}
|
|
uint64_t node = sc_derive_node_id_from_pubkey(key);
|
|
char table[80], sql[320]; peers_table_name(channel_id, table, sizeof(table));
|
|
snprintf(sql, sizeof(sql), "SELECT 1 FROM \"%s\" WHERE node_id=? AND record_state=0"
|
|
" AND x25519_pubkey=? AND source=0 AND deleted=0 AND admitted=1", table);
|
|
sqlite3_stmt* st = NULL;
|
|
int rc = sqlite3_prepare_v2(db, sql, -1, &st, NULL);
|
|
if (rc == SQLITE_OK) rc = sqlite3_bind_int64(st, 1, (sqlite3_int64)node);
|
|
if (rc == SQLITE_OK) rc = sqlite3_bind_blob(st, 2, key, 32, SQLITE_STATIC);
|
|
if (rc == SQLITE_OK) rc = sqlite3_step(st);
|
|
int allowed = rc == SQLITE_ROW;
|
|
if (rc != SQLITE_ROW && rc != SQLITE_DONE)
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "member key admission failed channel=%s node=%016llx rc=%d: %s",
|
|
channel_id, (unsigned long long)node, rc, sqlite3_errmsg(db));
|
|
if (sqlite3_finalize(st) != SQLITE_OK) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "member key admission finalize failed channel=%s", channel_id); return 0;
|
|
}
|
|
return allowed;
|
|
}
|
|
|
|
int topo_node_sqlite_member_del(sqlite3* db, const char* channel_id, uint64_t node_id) {
|
|
if (!db || !channel_id) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "member delete: invalid arguments"); return -1; }
|
|
char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl));
|
|
char sql[256];
|
|
snprintf(sql, sizeof(sql), "DELETE FROM \"%s\" WHERE node_id=? AND record_state=0", peers_tbl);
|
|
sqlite3_stmt* stmt = NULL;
|
|
int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
|
if (rc == SQLITE_OK) rc = sqlite3_bind_int64(stmt, 1, (sqlite3_int64)node_id);
|
|
if (rc == SQLITE_OK) rc = sqlite3_step(stmt);
|
|
if (rc != SQLITE_DONE) DEBUG_ERROR(DEBUG_CATEGORY_BGP, "member delete: channel=%s node=%016llx rc=%d: %s",
|
|
channel_id, (unsigned long long)node_id, rc, sqlite3_errmsg(db));
|
|
sqlite3_finalize(stmt);
|
|
return rc == SQLITE_DONE ? 0 : -1;
|
|
}
|
|
|
|
int topo_node_sqlite_channel_get(sqlite3* db, const char* channel_id,
|
|
char* name_out, int name_sz, uint64_t* owner_node_id,
|
|
uint8_t* x25519_pub, uint8_t* ed25519_pub, uint8_t* signature) {
|
|
if (!db || !channel_id) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "channel lookup: invalid arguments"); return TOPO_SQLITE_LOOKUP_ERROR;
|
|
}
|
|
sqlite3_stmt* stmt = NULL;
|
|
int rc = sqlite3_prepare_v2(db,
|
|
"SELECT name, owner_node_id, x25519_pubkey, ed25519_pubkey, signature"
|
|
" FROM channels WHERE channel_id=?", -1, &stmt, NULL);
|
|
if (rc == SQLITE_OK) rc = sqlite3_bind_text(stmt, 1, channel_id, -1, SQLITE_STATIC);
|
|
if (rc == SQLITE_OK) rc = sqlite3_step(stmt);
|
|
int result = TOPO_SQLITE_LOOKUP_ERROR;
|
|
if (rc == SQLITE_DONE) { result = TOPO_SQLITE_LOOKUP_MISSING; goto done; }
|
|
if (rc != SQLITE_ROW) goto done;
|
|
for (int col = 2; col <= 4; col++) {
|
|
if (!(col == 2 ? x25519_pub != NULL : col == 3 ? ed25519_pub != NULL : signature != NULL)) continue;
|
|
int size = sqlite3_column_bytes(stmt, col), expected = col == 4 ? 64 : 32;
|
|
if (size == expected) continue;
|
|
if (size == 0 && sqlite3_errcode(db) != SQLITE_NOMEM) result = TOPO_SQLITE_LOOKUP_MISSING;
|
|
else DEBUG_ERROR(DEBUG_CATEGORY_BGP, "channel lookup: invalid field channel=%s column=%d bytes=%d expected=%d",
|
|
channel_id, col, size, expected);
|
|
goto done;
|
|
}
|
|
if (name_out && name_sz > 0) {
|
|
const unsigned char* txt = sqlite3_column_text(stmt, 0);
|
|
if (txt) snprintf(name_out, name_sz, "%s", txt); else name_out[0] = '\0';
|
|
}
|
|
if (owner_node_id) *owner_node_id = (uint64_t)sqlite3_column_int64(stmt, 1);
|
|
if (x25519_pub) memcpy(x25519_pub, sqlite3_column_blob(stmt, 2), 32);
|
|
if (ed25519_pub) memcpy(ed25519_pub, sqlite3_column_blob(stmt, 3), 32);
|
|
if (signature) memcpy(signature, sqlite3_column_blob(stmt, 4), 64);
|
|
result = 0;
|
|
done:
|
|
{
|
|
int final_rc = sqlite3_finalize(stmt);
|
|
if (final_rc != SQLITE_OK) result = TOPO_SQLITE_LOOKUP_ERROR;
|
|
if (result == TOPO_SQLITE_LOOKUP_ERROR)
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "channel lookup failed channel=%s rc=%d finalize=%d: %s",
|
|
channel_id, rc, final_rc, sqlite3_errmsg(db));
|
|
else if (result == TOPO_SQLITE_LOOKUP_MISSING)
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "channel lookup: dependency unavailable channel=%s", channel_id);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/* Выходные ключи публикуются только после успешного завершения SELECT. */
|
|
static int channel_private_keys(sqlite3* db, const char* channel, uint8_t* x25519, uint8_t* ed25519) {
|
|
if (!db || !channel || !ed25519) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "channel private keys: missing argument"); return -1;
|
|
}
|
|
const char* sql = x25519 ? "SELECT x25519_privkey, ed25519_privkey FROM channels WHERE channel_id=?" :
|
|
"SELECT ed25519_privkey FROM channels WHERE channel_id=?";
|
|
sqlite3_stmt* st = NULL; uint8_t keys[64] = {0}; int columns = x25519 ? 2 : 1, result = -1;
|
|
const char* stage = "prepare"; int rc = sqlite3_prepare_v2(db, sql, -1, &st, NULL);
|
|
if (rc != SQLITE_OK) goto done;
|
|
stage = "bind"; rc = sqlite3_bind_text(st, 1, channel, -1, SQLITE_STATIC); if (rc != SQLITE_OK) goto done;
|
|
stage = "step"; rc = sqlite3_step(st); if (rc != SQLITE_ROW) goto done;
|
|
for (int i = 0; i < columns; i++) {
|
|
if (sqlite3_column_type(st, i) == SQLITE_NULL) { rc = SQLITE_DONE; goto done; }
|
|
stage = "key size/type"; rc = SQLITE_CORRUPT;
|
|
if (sqlite3_column_type(st, i) != SQLITE_BLOB || sqlite3_column_bytes(st, i) != 32) goto done;
|
|
const void* key = sqlite3_column_blob(st, i); if (!key) { rc = SQLITE_NOMEM; goto done; }
|
|
memcpy(keys + i * 32, key, 32);
|
|
}
|
|
rc = SQLITE_OK;
|
|
done:
|
|
{
|
|
int final = sqlite3_finalize(st);
|
|
if (rc == SQLITE_OK && final == SQLITE_OK) {
|
|
if (x25519) memcpy(x25519, keys, 32);
|
|
memcpy(ed25519, keys + (columns - 1) * 32, 32); result = 0;
|
|
} else if (rc == SQLITE_DONE && final == SQLITE_OK) {
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "channel private keys unavailable channel=%s", channel);
|
|
} else DEBUG_ERROR(DEBUG_CATEGORY_BGP, "channel private keys channel=%s stage=%s rc=%d finalize=%d: %s",
|
|
channel, stage, rc, final, sqlite3_errmsg(db));
|
|
}
|
|
OPENSSL_cleanse(keys, sizeof(keys)); return result;
|
|
}
|
|
|
|
int topo_node_sqlite_channel_get_priv(sqlite3* db, const char* channel_id, uint8_t* ed25519_priv_out) {
|
|
return channel_private_keys(db, channel_id, NULL, ed25519_priv_out);
|
|
}
|
|
|
|
int topo_node_sqlite_channel_get_privs(sqlite3* db, const char* channel_id,
|
|
uint8_t* x25519_priv_out, uint8_t* ed25519_priv_out) {
|
|
if (!x25519_priv_out) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "channel private keys: X25519 output missing"); return -1;
|
|
}
|
|
return channel_private_keys(db, channel_id, x25519_priv_out, ed25519_priv_out);
|
|
}
|
|
|
|
/* Импорт пары: savepoint охватывает UPDATE и его завершение, внешний commit остаётся вызывающему. */
|
|
int topo_node_sqlite_channel_set_privs(sqlite3* db, const char* channel_id,
|
|
const uint8_t* x25519_priv, const uint8_t* ed25519_priv) {
|
|
if (!db || !channel_id || !channel_id[0] || !x25519_priv || !ed25519_priv) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "channel private keys: invalid arguments"); return -1;
|
|
}
|
|
int own = sqlite3_get_autocommit(db);
|
|
int rc = sqlite3_exec(db, "SAVEPOINT channel_private_keys", NULL, NULL, NULL);
|
|
if (rc != SQLITE_OK) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "channel private keys: channel=%s stage=savepoint rc=%d: %s",
|
|
channel_id, rc, sqlite3_errmsg(db)); return -1;
|
|
}
|
|
sqlite3_stmt* stmt = NULL;
|
|
const char* stage = "prepare";
|
|
rc = sqlite3_prepare_v2(db, "UPDATE channels SET x25519_privkey=?, ed25519_privkey=? WHERE channel_id=?", -1, &stmt, NULL);
|
|
if (rc != SQLITE_OK) goto fail;
|
|
stage = "bind x25519"; rc = sqlite3_bind_blob(stmt, 1, x25519_priv, 32, SQLITE_STATIC);
|
|
if (rc != SQLITE_OK) goto fail;
|
|
stage = "bind ed25519"; rc = sqlite3_bind_blob(stmt, 2, ed25519_priv, 32, SQLITE_STATIC);
|
|
if (rc != SQLITE_OK) goto fail;
|
|
stage = "bind channel"; rc = sqlite3_bind_text(stmt, 3, channel_id, -1, SQLITE_STATIC);
|
|
if (rc != SQLITE_OK) goto fail;
|
|
stage = "update"; rc = sqlite3_step(stmt);
|
|
if (rc != SQLITE_DONE) goto fail;
|
|
int changed = sqlite3_changes(db);
|
|
stage = "finalize"; rc = sqlite3_finalize(stmt); stmt = NULL;
|
|
if (rc != SQLITE_OK) goto fail;
|
|
if (changed != 1) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "channel private keys: channel=%s changed=%d expected=1", channel_id, changed);
|
|
stage = "row count"; rc = SQLITE_NOTFOUND; goto fail;
|
|
}
|
|
stage = "release"; rc = sqlite3_exec(db, "RELEASE channel_private_keys", NULL, NULL, NULL);
|
|
if (rc != SQLITE_OK) goto fail;
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "channel private keys stored: channel=%s committed=%d", channel_id, own);
|
|
return 0;
|
|
fail:
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "channel private keys: channel=%s stage=%s rc=%d: %s",
|
|
channel_id, stage, rc, sqlite3_errmsg(db));
|
|
int final_rc = sqlite3_finalize(stmt);
|
|
if (final_rc != SQLITE_OK) DEBUG_ERROR(DEBUG_CATEGORY_BGP,
|
|
"channel private keys: cleanup finalize channel=%s rc=%d: %s", channel_id, final_rc, sqlite3_errmsg(db));
|
|
if (!sqlite3_get_autocommit(db) &&
|
|
sqlite3_exec(db, "ROLLBACK TO channel_private_keys; RELEASE channel_private_keys", NULL, NULL, NULL) != SQLITE_OK)
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "channel private keys: rollback failed channel=%s: %s", channel_id, sqlite3_errmsg(db));
|
|
return -1;
|
|
}
|
|
|
|
int topo_node_sqlite_node_set_online(sqlite3* db, uint64_t node_id, int online) {
|
|
if (!db) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node online: database is NULL"); return -1; }
|
|
sqlite3_stmt* stmt = NULL;
|
|
int rc = sqlite3_prepare_v2(db, "UPDATE nodes SET online=? WHERE node_id=?", -1, &stmt, NULL);
|
|
if (rc == SQLITE_OK) rc = sqlite3_bind_int(stmt, 1, online ? 1 : 0);
|
|
if (rc == SQLITE_OK) rc = sqlite3_bind_int64(stmt, 2, (sqlite3_int64)node_id);
|
|
if (rc == SQLITE_OK) rc = sqlite3_step(stmt);
|
|
if (rc != SQLITE_DONE) DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node online: node=%016llx online=%d rc=%d: %s",
|
|
(unsigned long long)node_id, online, rc, sqlite3_errmsg(db));
|
|
sqlite3_finalize(stmt);
|
|
return rc == SQLITE_DONE ? 0 : -1;
|
|
}
|
|
|
|
int topo_node_sqlite_node_get_online(sqlite3* db, uint64_t node_id) {
|
|
if (!db) return 0;
|
|
sqlite3_stmt* stmt = NULL;
|
|
if (sqlite3_prepare_v2(db, "SELECT online FROM nodes WHERE node_id=?", -1, &stmt, NULL) != SQLITE_OK) return 0;
|
|
sqlite3_bind_int64(stmt, 1, (sqlite3_int64)node_id);
|
|
int online = 0;
|
|
if (sqlite3_step(stmt) == SQLITE_ROW) online = sqlite3_column_int(stmt, 0);
|
|
sqlite3_finalize(stmt);
|
|
return online;
|
|
}
|
|
|
|
int topo_node_sqlite_get_ed25519_pubkey(sqlite3* db, uint64_t node_id, uint8_t pubkey_out[32])
|
|
{
|
|
if (!db || !pubkey_out) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "get_ed25519_pubkey: invalid arguments node=%016llx", (unsigned long long)node_id);
|
|
return TOPO_SQLITE_LOOKUP_ERROR;
|
|
}
|
|
sqlite3_stmt* stmt = NULL;
|
|
int sql_rc = sqlite3_prepare_v2(db, "SELECT ed25519_pubkey FROM nodes WHERE node_id=?", -1, &stmt, NULL);
|
|
if (sql_rc == SQLITE_OK) sql_rc = sqlite3_bind_int64(stmt, 1, (sqlite3_int64)node_id);
|
|
if (sql_rc == SQLITE_OK) sql_rc = sqlite3_step(stmt);
|
|
int rc = sql_rc == SQLITE_DONE ? TOPO_SQLITE_LOOKUP_MISSING : TOPO_SQLITE_LOOKUP_ERROR;
|
|
uint8_t key[32];
|
|
if (sql_rc == SQLITE_ROW) {
|
|
const void* b = sqlite3_column_blob(stmt, 0);
|
|
int bytes = sqlite3_column_bytes(stmt, 0);
|
|
if (b && bytes == 32) { memcpy(key, b, 32); rc = 0; }
|
|
else if (bytes == 0 && sqlite3_errcode(db) != SQLITE_NOMEM) rc = TOPO_SQLITE_LOOKUP_MISSING;
|
|
}
|
|
int final_rc = sqlite3_finalize(stmt);
|
|
if (final_rc != SQLITE_OK) rc = TOPO_SQLITE_LOOKUP_ERROR;
|
|
if (rc == 0) memcpy(pubkey_out, key, 32);
|
|
if (rc == TOPO_SQLITE_LOOKUP_MISSING)
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_ROUTING, "get_ed25519_pubkey: unknown node=%016llx", (unsigned long long)node_id);
|
|
else if (rc) DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "get_ed25519_pubkey: unavailable node=%016llx sql=%d finalize=%d: %s",
|
|
(unsigned long long)node_id, sql_rc, final_rc, sqlite3_errmsg(db));
|
|
return rc;
|
|
}
|
|
|
|
/* Пересчитывает канальные роли/хранение/удаление; при отказе откатывает всю проекцию узла. */
|
|
int topo_node_sqlite_nodeinfo_updated(sqlite3* db, uint64_t node_id) {
|
|
if (!db) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node classification: database is NULL"); return -1; }
|
|
int rc = sqlite3_exec(db, "SAVEPOINT node_classification", NULL, NULL, NULL);
|
|
if (rc != SQLITE_OK) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node classification savepoint: rc=%d: %s", rc, sqlite3_errmsg(db)); return -1;
|
|
}
|
|
sqlite3_stmt *scan = NULL, *read = NULL, *write = NULL;
|
|
const char* stage = "addresses";
|
|
int best_at = -1;
|
|
rc = sqlite3_prepare_v2(db, "SELECT addr_type FROM node_addresses WHERE node_id=?", -1, &read, NULL);
|
|
if (rc == SQLITE_OK) rc = sqlite3_bind_int64(read, 1, (sqlite3_int64)node_id);
|
|
if (rc != SQLITE_OK) goto fail;
|
|
while ((rc = sqlite3_step(read)) == SQLITE_ROW) {
|
|
int at = sqlite3_column_int(read, 0);
|
|
if (at == ADDR_TYPE_DIRECT) { best_at = ADDR_TYPE_DIRECT; break; }
|
|
if (best_at < 0 || at < best_at) best_at = at;
|
|
}
|
|
if (rc != SQLITE_DONE && rc != SQLITE_ROW) goto fail;
|
|
sqlite3_finalize(read); read = NULL;
|
|
static const int at2type[] = { [ADDR_TYPE_NETIF]=1, [ADDR_TYPE_DIRECT]=1, [ADDR_TYPE_NAT_EIM]=2,
|
|
[ADDR_TYPE_NAT_STRICT]=1, [ADDR_TYPE_NAT]=2, [ADDR_TYPE_CONTACT]=2, [ADDR_TYPE_NAT_EIM_UNVERIFIED]=2 };
|
|
int base_type = best_at < 0 || (unsigned)best_at >= sizeof(at2type) / sizeof(at2type[0]) ? 0 : at2type[best_at];
|
|
if (best_at >= 0 && (unsigned)best_at >= sizeof(at2type) / sizeof(at2type[0]))
|
|
DEBUG_WARN(DEBUG_CATEGORY_BGP, "invalid projected address type=%d node=%016llx", best_at, (unsigned long long)node_id);
|
|
stage = "membership tables";
|
|
rc = sqlite3_prepare_v2(db, "SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'peers\\_%' ESCAPE '\\'",
|
|
-1, &scan, NULL);
|
|
if (rc != SQLITE_OK) goto fail;
|
|
while ((rc = sqlite3_step(scan)) == SQLITE_ROW) {
|
|
const char* table = (const char*)sqlite3_column_text(scan, 0);
|
|
int node_type = base_type, storage = 0, deleted = 0;
|
|
char sql[256]; snprintf(sql, sizeof(sql), "SELECT adm_tags FROM \"%s\" WHERE node_id=? AND record_state=0", table);
|
|
stage = "member tags";
|
|
rc = sqlite3_prepare_v2(db, sql, -1, &read, NULL);
|
|
if (rc == SQLITE_OK) rc = sqlite3_bind_int64(read, 1, (sqlite3_int64)node_id);
|
|
if (rc != SQLITE_OK) goto fail;
|
|
rc = sqlite3_step(read);
|
|
if (rc == SQLITE_ROW) {
|
|
const char* tags = (const char*)sqlite3_column_text(read, 0);
|
|
char value[64];
|
|
if (json_flat_get(tags, "supernode", value, sizeof(value)) == 0 && !strcmp(value, "yes")) node_type = 4;
|
|
if (best_at == ADDR_TYPE_DIRECT && json_flat_get(tags, "storage", value, sizeof(value)) == 0 && !strcmp(value, "yes"))
|
|
storage = 1;
|
|
deleted = adm_tags_deleted(tags);
|
|
} else if (rc != SQLITE_DONE) goto fail;
|
|
sqlite3_finalize(read); read = NULL;
|
|
snprintf(sql, sizeof(sql), "UPDATE \"%s\" SET node_type=?,storage=?,deleted=? WHERE node_id=? AND record_state=0", table);
|
|
stage = "member projection";
|
|
rc = sqlite3_prepare_v2(db, sql, -1, &write, NULL);
|
|
if (rc == SQLITE_OK) rc = sqlite3_bind_int(write, 1, node_type);
|
|
if (rc == SQLITE_OK) rc = sqlite3_bind_int(write, 2, storage);
|
|
if (rc == SQLITE_OK) rc = sqlite3_bind_int(write, 3, deleted);
|
|
if (rc == SQLITE_OK) rc = sqlite3_bind_int64(write, 4, (sqlite3_int64)node_id);
|
|
if (rc == SQLITE_OK) rc = sqlite3_step(write);
|
|
if (rc != SQLITE_DONE) goto fail;
|
|
sqlite3_finalize(write); write = NULL;
|
|
stage = "membership tables";
|
|
}
|
|
if (rc != SQLITE_DONE) goto fail;
|
|
sqlite3_finalize(scan); scan = NULL;
|
|
stage = "release";
|
|
if ((rc = sqlite3_exec(db, "RELEASE node_classification", NULL, NULL, NULL)) != SQLITE_OK) goto fail;
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "node classification stored: node=%016llx best_at=%d base_type=%d",
|
|
(unsigned long long)node_id, best_at, base_type);
|
|
return 0;
|
|
fail:
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node classification failed: node=%016llx stage=%s rc=%d: %s",
|
|
(unsigned long long)node_id, stage, rc, sqlite3_errmsg(db));
|
|
sqlite3_finalize(write); sqlite3_finalize(read); sqlite3_finalize(scan);
|
|
if (sqlite3_exec(db, "ROLLBACK TO node_classification; RELEASE node_classification", NULL, NULL, NULL) != SQLITE_OK)
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node classification rollback failed: node=%016llx: %s",
|
|
(unsigned long long)node_id, sqlite3_errmsg(db));
|
|
return -1;
|
|
}
|
|
|
|
void topo_node_sqlite_update_rtt(sqlite3* db, uint64_t node_id, uint16_t rtt) {
|
|
if (!db) return;
|
|
DEBUG_TRACE(DEBUG_CATEGORY_BGP, "RTT sample: node=%016llx rtt_ms=%.1f; updating address and membership tables",
|
|
(unsigned long long)node_id, rtt / 10.0);
|
|
|
|
{
|
|
sqlite3_stmt* s = NULL;
|
|
if (sqlite3_prepare_v2(db,
|
|
"UPDATE node_addresses SET rtt=? WHERE node_id=?",
|
|
-1, &s, NULL) == SQLITE_OK) {
|
|
sqlite3_bind_int(s, 1, (int)rtt);
|
|
sqlite3_bind_int64(s, 2, (sqlite3_int64)node_id);
|
|
if (sqlite3_step(s) != SQLITE_DONE) DEBUG_ERROR(DEBUG_CATEGORY_BGP,
|
|
"RTT persistence failed: table=node_addresses node=%016llx: %s",
|
|
(unsigned long long)node_id, sqlite3_errmsg(db));
|
|
sqlite3_finalize(s);
|
|
} else DEBUG_ERROR(DEBUG_CATEGORY_BGP, "RTT query preparation failed: table=node_addresses node=%016llx: %s",
|
|
(unsigned long long)node_id, sqlite3_errmsg(db));
|
|
}
|
|
|
|
{
|
|
sqlite3_stmt* s = NULL;
|
|
if (sqlite3_prepare_v2(db,
|
|
"SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'peers\\_%' ESCAPE '\\'",
|
|
-1, &s, NULL) != SQLITE_OK) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "RTT cannot enumerate membership tables: node=%016llx: %s",
|
|
(unsigned long long)node_id, sqlite3_errmsg(db)); return;
|
|
}
|
|
|
|
char sql[256];
|
|
int rc;
|
|
while ((rc = sqlite3_step(s)) == SQLITE_ROW) {
|
|
const char* tbl = (const char*)sqlite3_column_text(s, 0);
|
|
snprintf(sql, sizeof(sql),
|
|
"UPDATE \"%s\" SET node_RTT=? WHERE node_id=? AND record_state=0", tbl);
|
|
sqlite3_stmt* u = NULL;
|
|
if (sqlite3_prepare_v2(db, sql, -1, &u, NULL) == SQLITE_OK) {
|
|
sqlite3_bind_int(u, 1, (int)rtt);
|
|
sqlite3_bind_int64(u, 2, (sqlite3_int64)node_id);
|
|
if (sqlite3_step(u) != SQLITE_DONE) DEBUG_ERROR(DEBUG_CATEGORY_BGP,
|
|
"RTT persistence failed: table=%s node=%016llx: %s", tbl, (unsigned long long)node_id, sqlite3_errmsg(db));
|
|
sqlite3_finalize(u);
|
|
} else DEBUG_ERROR(DEBUG_CATEGORY_BGP, "RTT query preparation failed: table=%s node=%016llx: %s",
|
|
tbl, (unsigned long long)node_id, sqlite3_errmsg(db));
|
|
}
|
|
if (rc != SQLITE_DONE) DEBUG_ERROR(DEBUG_CATEGORY_BGP, "RTT table enumeration failed: node=%016llx: %s",
|
|
(unsigned long long)node_id, sqlite3_errmsg(db));
|
|
sqlite3_finalize(s);
|
|
}
|
|
}
|
|
|
|
int topo_node_sqlite_snapshot_timestamp(sqlite3* db, uint64_t node_id, uint64_t* out) {
|
|
if (!db || !node_id || !out) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "snapshot timestamp: invalid arguments"); return -1; }
|
|
sqlite3_stmt* stmt = NULL;
|
|
uint64_t timestamp = 0;
|
|
int rc = sqlite3_prepare_v2(db, "SELECT timestamp FROM node_snapshots WHERE node_id=?", -1, &stmt, NULL);
|
|
if (rc == SQLITE_OK) rc = sqlite3_bind_int64(stmt, 1, (sqlite3_int64)node_id);
|
|
if (rc == SQLITE_OK) rc = sqlite3_step(stmt);
|
|
if (rc == SQLITE_ROW) {
|
|
if (sqlite3_column_type(stmt, 0) != SQLITE_INTEGER || sqlite3_column_int64(stmt, 0) <= 0) rc = SQLITE_CORRUPT;
|
|
else timestamp = (uint64_t)sqlite3_column_int64(stmt, 0);
|
|
}
|
|
int final = sqlite3_finalize(stmt);
|
|
if ((rc != SQLITE_ROW && rc != SQLITE_DONE) || final != SQLITE_OK) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "snapshot timestamp: node=%016llx rc=%d finalize=%d: %s",
|
|
(unsigned long long)node_id, rc, final, sqlite3_errmsg(db)); return -1;
|
|
}
|
|
*out = timestamp; return 0;
|
|
}
|
|
|
|
int topo_node_sqlite_snapshot_put(sqlite3* db, struct TOPO_NODE* ni) {
|
|
if (!db || topo_node_verify(ni) < 0) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid node snapshot"); return -1; }
|
|
uint8_t record[TOPO_NODE_WIRE_MAX_SIZE];
|
|
struct TOPO_GROUP_NODE nq = {0};
|
|
int len = topo_node_serialize(ni, &nq, 0, 0, record, sizeof(record), 0);
|
|
if (len < 0) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "snapshot serialization failed"); return -1; }
|
|
int own = sqlite3_get_autocommit(db);
|
|
int rc = sqlite3_exec(db, "SAVEPOINT node_snapshot", NULL, NULL, NULL);
|
|
if (rc != SQLITE_OK) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "snapshot begin rc=%d: %s", rc, sqlite3_errmsg(db)); return -1; }
|
|
sqlite3_stmt* stmt = NULL;
|
|
const char* sql = "INSERT INTO node_snapshots(node_id,timestamp,record,format) VALUES(?,?,?,1) "
|
|
"ON CONFLICT(node_id) DO UPDATE SET timestamp=excluded.timestamp,record=excluded.record,format=excluded.format "
|
|
"WHERE excluded.timestamp > node_snapshots.timestamp OR "
|
|
"(excluded.timestamp = node_snapshots.timestamp AND length(node_snapshots.record)=0)";
|
|
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
|
if (rc == SQLITE_OK) rc = sqlite3_bind_int64(stmt, 1, (sqlite3_int64)ni->node_id);
|
|
if (rc == SQLITE_OK) rc = sqlite3_bind_int64(stmt, 2, (sqlite3_int64)ni->timestamp);
|
|
if (rc == SQLITE_OK) rc = sqlite3_bind_blob(stmt, 3, record, len, SQLITE_STATIC);
|
|
if (rc == SQLITE_OK) rc = sqlite3_step(stmt);
|
|
int changed = rc == SQLITE_DONE ? sqlite3_changes(db) : 0;
|
|
int final = sqlite3_finalize(stmt);
|
|
if (rc == SQLITE_DONE && final == SQLITE_OK && changed && devgroup_project(db, ni)) rc = SQLITE_ERROR;
|
|
if (rc == SQLITE_DONE && final == SQLITE_OK) {
|
|
rc = sqlite3_exec(db, "RELEASE node_snapshot", NULL, NULL, NULL);
|
|
if (rc == SQLITE_OK) return 0;
|
|
}
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "snapshot write: node=%016llx rc=%d finalize=%d: %s",
|
|
(unsigned long long)ni->node_id, rc, final, sqlite3_errmsg(db));
|
|
if (!sqlite3_get_autocommit(db)) {
|
|
int rollback = sqlite3_exec(db, "ROLLBACK TO node_snapshot", NULL, NULL, NULL);
|
|
if (rollback == SQLITE_OK) rollback = sqlite3_exec(db, "RELEASE node_snapshot", NULL, NULL, NULL);
|
|
if (rollback != SQLITE_OK) DEBUG_ERROR(DEBUG_CATEGORY_BGP, "snapshot rollback rc=%d own=%d: %s", rollback, own, sqlite3_errmsg(db));
|
|
if (own && !sqlite3_get_autocommit(db)) {
|
|
rollback = sqlite3_exec(db, "ROLLBACK", NULL, NULL, NULL);
|
|
if (rollback != SQLITE_OK) DEBUG_ERROR(DEBUG_CATEGORY_BGP, "snapshot full rollback rc=%d: %s", rollback, sqlite3_errmsg(db));
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
struct TOPO_NODE* topo_node_sqlite_snapshot_load(sqlite3* db, struct TOPO_GROUPS* groups, uint64_t node_id) {
|
|
sqlite3_stmt* stmt = NULL;
|
|
struct TOPO_NODE* ni = NULL;
|
|
if (sqlite3_prepare_v2(db, "SELECT record FROM node_snapshots WHERE node_id=? AND format=1", -1, &stmt, NULL) != SQLITE_OK) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "snapshot load: %s", sqlite3_errmsg(db)); return NULL;
|
|
}
|
|
sqlite3_bind_int64(stmt, 1, (sqlite3_int64)node_id);
|
|
int rc = sqlite3_step(stmt);
|
|
if (rc == SQLITE_ROW) {
|
|
struct TOPO_GROUP group = { .instance = groups->instance };
|
|
const uint8_t* data = sqlite3_column_blob(stmt, 0);
|
|
int len = sqlite3_column_bytes(stmt, 0);
|
|
if (!len) {
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "snapshot invalidated by format change node=%016llx", (unsigned long long)node_id);
|
|
} else if (topo_node_deserialize(&group, data, (size_t)len, &ni, NULL, NULL, NULL, NULL) < 0 ||
|
|
!ni || ni->node_id != node_id || topo_node_verify(ni) < 0) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid persisted snapshot node=%016llx", (unsigned long long)node_id);
|
|
topo_node_destroy(groups, ni); ni = NULL;
|
|
}
|
|
} else if (rc != SQLITE_DONE) DEBUG_ERROR(DEBUG_CATEGORY_BGP, "snapshot read: %s", sqlite3_errmsg(db));
|
|
sqlite3_finalize(stmt);
|
|
return ni;
|
|
}
|
|
|
|
/* LAN-подтверждение может дать первый адрес узлу, чьи ключи уже проверены member_sync. */
|
|
struct TOPO_NODE* topo_node_sqlite_identity_load(sqlite3* db, uint64_t node_id) {
|
|
if (!db || !node_id) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "identity load: invalid arguments"); return NULL; }
|
|
sqlite3_stmt* stmt = NULL;
|
|
struct TOPO_NODE* ni = NULL;
|
|
const char* sql = "SELECT name,x25519_pubkey,ed25519_pubkey,client_type FROM nodes WHERE node_id=?";
|
|
int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
|
if (rc == SQLITE_OK) rc = sqlite3_bind_int64(stmt, 1, (sqlite3_int64)node_id);
|
|
if (rc == SQLITE_OK) rc = sqlite3_step(stmt);
|
|
if (rc == SQLITE_ROW) {
|
|
const uint8_t* x = sqlite3_column_blob(stmt, 1);
|
|
const uint8_t* ed = sqlite3_column_blob(stmt, 2);
|
|
if (!x || !ed || sqlite3_column_bytes(stmt, 1) != 32 || sqlite3_column_bytes(stmt, 2) != 32 ||
|
|
sc_derive_node_id_from_pubkey(x) != node_id) {
|
|
DEBUG_WARN(DEBUG_CATEGORY_BGP, "identity load: invalid keys node=%016llx", (unsigned long long)node_id);
|
|
} else {
|
|
ni = u_calloc(1, sizeof(*ni));
|
|
if (!ni) DEBUG_ERROR(DEBUG_CATEGORY_BGP, "identity load: allocation failed");
|
|
else {
|
|
ni->node_id = node_id; ni->client_type = (uint8_t)sqlite3_column_int(stmt, 3);
|
|
memcpy(ni->public_key, x, 32); memcpy(ni->ed25519_public_key, ed, 32);
|
|
const char* name = (const char*)sqlite3_column_text(stmt, 0);
|
|
if (name && !(ni->node_name = u_strdup(name))) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "identity load: name allocation failed"); u_free(ni); ni = NULL;
|
|
}
|
|
}
|
|
}
|
|
} else if (rc != SQLITE_DONE) DEBUG_ERROR(DEBUG_CATEGORY_BGP, "identity load: %s", sqlite3_errmsg(db));
|
|
else DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "identity load: unknown node=%016llx", (unsigned long long)node_id);
|
|
if (sqlite3_finalize(stmt) != SQLITE_OK) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "identity load finalize: %s", sqlite3_errmsg(db));
|
|
topo_node_destroy(NULL, ni); ni = NULL;
|
|
}
|
|
if (ni) DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "identity loaded without addresses node=%016llx", (unsigned long long)node_id);
|
|
return ni;
|
|
}
|
|
|
|
struct TOPO_NODE* topo_node_sqlite_node_load(sqlite3* db, struct TOPO_GROUPS* groups, uint64_t node_id) {
|
|
if (!db || !groups || !groups->instance || groups->instance->topo_groups != groups) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node load: invalid database/groups node=%016llx", (unsigned long long)node_id); return NULL;
|
|
}
|
|
struct TOPO_NODE* snapshot = topo_node_sqlite_snapshot_load(db, groups, node_id);
|
|
if (snapshot) return snapshot;
|
|
|
|
struct TOPO_NODE* ni = u_calloc(1, sizeof(*ni));
|
|
if (!ni) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node load: allocation failed node=%016llx", (unsigned long long)node_id); return NULL; }
|
|
sqlite3_stmt* s = NULL;
|
|
uint8_t pubkey[32] = {0}, ed_pubkey[32] = {0};
|
|
char* name = NULL;
|
|
uint8_t client_type = 0;
|
|
int found = 0;
|
|
struct TOPO_ADDR4* v4_head = NULL; struct TOPO_ADDR4* v4_tail = NULL;
|
|
struct TOPO_SOCKMETA4* m4_head = NULL; struct TOPO_SOCKMETA4* m4_tail = NULL;
|
|
struct TOPO_ADDR6* v6_head = NULL; struct TOPO_ADDR6* v6_tail = NULL;
|
|
struct TOPO_SOCKMETA6* m6_head = NULL; struct TOPO_SOCKMETA6* m6_tail = NULL;
|
|
struct TOPO_REALITY_SOCK* r_head = NULL; struct TOPO_REALITY_SOCK* r_tail = NULL;
|
|
{
|
|
if (sqlite3_prepare_v2(db, "SELECT name,x25519_pubkey,ed25519_pubkey,client_type FROM nodes WHERE node_id=?",
|
|
-1, &s, NULL) != SQLITE_OK) goto sql_failed;
|
|
if (sqlite3_bind_int64(s, 1, (sqlite3_int64)node_id) != SQLITE_OK) goto sql_failed;
|
|
int rc = sqlite3_step(s);
|
|
if (rc == SQLITE_ROW) {
|
|
const char* n = (const char*)sqlite3_column_text(s, 0);
|
|
if (n) { name = u_strdup(n); if (!name) goto allocation_failed; }
|
|
const void* pk = sqlite3_column_blob(s, 1);
|
|
if (pk && sqlite3_column_bytes(s, 1) >= 32) memcpy(pubkey, pk, 32);
|
|
const void* ep = sqlite3_column_blob(s, 2);
|
|
if (ep && sqlite3_column_bytes(s, 2) >= 32) memcpy(ed_pubkey, ep, 32);
|
|
client_type = (uint8_t)sqlite3_column_int(s, 3);
|
|
found = 1;
|
|
} else if (rc != SQLITE_DONE) goto sql_failed;
|
|
sqlite3_finalize(s); s = NULL;
|
|
}
|
|
if (!found) { DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "node load: missing node=%016llx", (unsigned long long)node_id); goto failed; }
|
|
|
|
{
|
|
if (sqlite3_prepare_v2(db,
|
|
"SELECT address,port,addr_type,socket_id,protocol,config_type,nat_mapping,options,address_kind FROM node_addresses"
|
|
" WHERE node_id=? AND family=4 ORDER BY addr_type",
|
|
-1, &s, NULL) != SQLITE_OK) goto sql_failed;
|
|
if (sqlite3_bind_int64(s, 1, (sqlite3_int64)node_id) != SQLITE_OK) goto sql_failed;
|
|
int rc;
|
|
while ((rc = sqlite3_step(s)) == SQLITE_ROW) {
|
|
const void* addr = sqlite3_column_blob(s, 0);
|
|
if (!addr || sqlite3_column_bytes(s, 0) < 4) continue;
|
|
int port = sqlite3_column_int(s, 1);
|
|
int sock_id_raw = sqlite3_column_int(s, 3);
|
|
uint8_t sock_id = (uint8_t)sock_id_raw;
|
|
uint8_t type = (uint8_t)sqlite3_column_int(s, 8);
|
|
struct TOPO_ADDR4* a4 = memory_pool_alloc(groups->v4_addr_pool);
|
|
if (!a4) goto allocation_failed;
|
|
memset(a4, 0, sizeof(*a4));
|
|
int proto = sqlite3_column_int(s, 4);
|
|
memcpy(a4->addr, addr, 4); a4->port = (uint16_t)port;
|
|
a4->type = type; a4->socket_id = sock_id; a4->protocol = (uint8_t)proto;
|
|
if (!v4_head) v4_head = v4_tail = a4;
|
|
else { v4_tail->next = a4; v4_tail = a4; }
|
|
|
|
struct TOPO_SOCKMETA4* ex = m4_head;
|
|
while (ex && ex->id != sock_id) ex = ex->next;
|
|
if (!ex) {
|
|
struct TOPO_SOCKMETA4* m4 = memory_pool_alloc(groups->v4_sock_meta_pool);
|
|
if (!m4) goto allocation_failed;
|
|
m4->id = sock_id;
|
|
m4->config_type = (uint8_t)sqlite3_column_int(s, 5);
|
|
/* Без подписанного snapshot профиль из SQL-проекции не считается доказательством. */
|
|
memset(&m4->address_profile, 0, sizeof(m4->address_profile));
|
|
m4->next = NULL;
|
|
if (!m4_head) m4_head = m4_tail = m4;
|
|
else { m4_tail->next = m4; m4_tail = m4; }
|
|
}
|
|
if (sqlite3_column_type(s, 7) == SQLITE_BLOB
|
|
&& sqlite3_column_bytes(s, 7) == TOPO_ADDR_REALITY_OPTS_SIZE
|
|
&& (proto & TOPO_PROTO_REALITY)) {
|
|
const struct TOPO_ADDR_REALITY_OPTS* o = (const struct TOPO_ADDR_REALITY_OPTS*)sqlite3_column_blob(s, 7);
|
|
struct TOPO_REALITY_SOCK* r = u_calloc(1, sizeof(*r));
|
|
if (!r) goto allocation_failed;
|
|
r->socket_id = sock_id;
|
|
memcpy(r->short_id, o->short_id, 8);
|
|
memcpy(r->server_pubkey, o->server_pubkey, 32);
|
|
memcpy(r->version, o->version, 3);
|
|
memcpy(r->server_name, o->server_name, TOPO_REALITY_SNAME_MAX);
|
|
r->server_name[TOPO_REALITY_SNAME_MAX - 1] = 0;
|
|
if (!r_head) r_head = r_tail = r;
|
|
else { r_tail->next = r; r_tail = r; }
|
|
}
|
|
}
|
|
if (rc != SQLITE_DONE) goto sql_failed;
|
|
sqlite3_finalize(s); s = NULL;
|
|
}
|
|
/* IPv6 addresses */
|
|
{
|
|
if (sqlite3_prepare_v2(db,
|
|
"SELECT address,port,addr_type,socket_id,protocol,config_type,nat_mapping,options,address_kind FROM node_addresses"
|
|
" WHERE node_id=? AND family=6 ORDER BY addr_type",
|
|
-1, &s, NULL) != SQLITE_OK) goto sql_failed;
|
|
if (sqlite3_bind_int64(s, 1, (sqlite3_int64)node_id) != SQLITE_OK) goto sql_failed;
|
|
int rc;
|
|
while ((rc = sqlite3_step(s)) == SQLITE_ROW) {
|
|
const void* addr = sqlite3_column_blob(s, 0);
|
|
if (!addr || sqlite3_column_bytes(s, 0) < 16) continue;
|
|
int port = sqlite3_column_int(s, 1);
|
|
int sock_id_raw = sqlite3_column_int(s, 3);
|
|
uint8_t sock_id = (uint8_t)sock_id_raw;
|
|
uint8_t type = (uint8_t)sqlite3_column_int(s, 8);
|
|
struct TOPO_ADDR6* a6 = memory_pool_alloc(groups->v6_addr_pool);
|
|
if (!a6) goto allocation_failed;
|
|
memset(a6, 0, sizeof(*a6));
|
|
int proto6 = sqlite3_column_int(s, 4);
|
|
memcpy(a6->addr, addr, 16); a6->port = (uint16_t)port;
|
|
a6->type = type; a6->socket_id = sock_id; a6->protocol = (uint8_t)proto6;
|
|
if (!v6_head) v6_head = v6_tail = a6;
|
|
else { v6_tail->next = a6; v6_tail = a6; }
|
|
|
|
struct TOPO_SOCKMETA6* ex = m6_head;
|
|
while (ex && ex->id != sock_id) ex = ex->next;
|
|
if (!ex) {
|
|
struct TOPO_SOCKMETA6* m6 = memory_pool_alloc(groups->v6_sock_meta_pool);
|
|
if (!m6) goto allocation_failed;
|
|
m6->id = sock_id;
|
|
m6->config_type = (uint8_t)sqlite3_column_int(s, 5);
|
|
memset(&m6->address_profile, 0, sizeof(m6->address_profile));
|
|
m6->next = NULL;
|
|
if (!m6_head) m6_head = m6_tail = m6;
|
|
else { m6_tail->next = m6; m6_tail = m6; }
|
|
}
|
|
if (sqlite3_column_type(s, 7) == SQLITE_BLOB
|
|
&& sqlite3_column_bytes(s, 7) == TOPO_ADDR_REALITY_OPTS_SIZE
|
|
&& (proto6 & TOPO_PROTO_REALITY)) {
|
|
const struct TOPO_ADDR_REALITY_OPTS* o = (const struct TOPO_ADDR_REALITY_OPTS*)sqlite3_column_blob(s, 7);
|
|
struct TOPO_REALITY_SOCK* r = u_calloc(1, sizeof(*r));
|
|
if (!r) goto allocation_failed;
|
|
r->socket_id = sock_id;
|
|
memcpy(r->short_id, o->short_id, 8);
|
|
memcpy(r->server_pubkey, o->server_pubkey, 32);
|
|
memcpy(r->version, o->version, 3);
|
|
memcpy(r->server_name, o->server_name, TOPO_REALITY_SNAME_MAX);
|
|
r->server_name[TOPO_REALITY_SNAME_MAX - 1] = 0;
|
|
if (!r_head) r_head = r_tail = r;
|
|
else { r_tail->next = r; r_tail = r; }
|
|
}
|
|
}
|
|
if (rc != SQLITE_DONE) goto sql_failed;
|
|
sqlite3_finalize(s); s = NULL;
|
|
}
|
|
if (!v4_head && !v6_head) {
|
|
DEBUG_WARN(DEBUG_CATEGORY_BGP, "node load: no bootstrap addresses node=%016llx", (unsigned long long)node_id); goto failed;
|
|
}
|
|
|
|
ni->node_id = node_id; ni->ver = 1; ni->group_ref_count = 0;
|
|
ni->client_type = client_type;
|
|
memcpy(ni->public_key, pubkey, 32); memcpy(ni->ed25519_public_key, ed_pubkey, 32);
|
|
ni->node_name = name;
|
|
ni->v4_addrs = v4_head;
|
|
ni->v6_addrs = v6_head;
|
|
ni->v4_sock_meta = m4_head;
|
|
ni->v6_sock_meta = m6_head;
|
|
ni->reality_socks = r_head;
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "node_load: node 0x%016llx loaded from DB name=%s v4=%d v6=%d",
|
|
(unsigned long long)node_id, name ? name : "",
|
|
topo_list_count((struct _topo_head*)v4_head),
|
|
topo_list_count((struct _topo_head*)v6_head));
|
|
return ni;
|
|
sql_failed:
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node load SQL failed node=%016llx rc=%d: %s",
|
|
(unsigned long long)node_id, sqlite3_extended_errcode(db), sqlite3_errmsg(db));
|
|
goto failed;
|
|
allocation_failed:
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node load: bootstrap allocation failed node=%016llx", (unsigned long long)node_id);
|
|
failed:
|
|
sqlite3_finalize(s);
|
|
ni->node_name = name; ni->v4_addrs = v4_head; ni->v4_sock_meta = m4_head;
|
|
ni->v6_addrs = v6_head; ni->v6_sock_meta = m6_head; ni->reality_socks = r_head;
|
|
topo_node_destroy(groups, ni);
|
|
return NULL;
|
|
}
|
|
|
|
int topo_node_sqlite_set_connected(sqlite3* db, const char* channel_id, uint64_t node_id, int connected) {
|
|
if (!db || !channel_id) return -1;
|
|
char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl));
|
|
char sql[192]; snprintf(sql, sizeof(sql), "UPDATE \"%s\" SET connected=? WHERE node_id=? AND record_state=0", peers_tbl);
|
|
sqlite3_stmt* st = NULL;
|
|
if (sqlite3_prepare_v2(db, sql, -1, &st, NULL) != SQLITE_OK) return -1;
|
|
sqlite3_bind_int(st, 1, connected ? 1 : 0);
|
|
sqlite3_bind_int64(st, 2, (sqlite3_int64)node_id);
|
|
int rc = sqlite3_step(st); sqlite3_finalize(st);
|
|
return (rc == SQLITE_DONE) ? 0 : -1;
|
|
}
|
|
|
|
/* Читаем тип из уже проверенного snapshot, а не из устаревшей проекции nodes.client_type. */
|
|
int topo_node_sqlite_get_connect_peers(sqlite3* db, const char* channel_id,
|
|
struct topo_connect_peer** out, size_t* out_count) {
|
|
if (!db || !channel_id || !out || !out_count) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid group candidate query arguments"); return -1;
|
|
}
|
|
*out = NULL; *out_count = 0;
|
|
char table[80]; peers_table_name(channel_id, table, sizeof(table));
|
|
char sql[512];
|
|
snprintf(sql, sizeof(sql),
|
|
"SELECT p.node_id,p.adm_tags,s.timestamp,s.record FROM \"%s\" p"
|
|
" LEFT JOIN node_snapshots s ON s.node_id=p.node_id AND s.format=1 AND length(s.record)>0"
|
|
" WHERE p.record_state=0 AND p.deleted=0 AND p.source=0 AND p.admitted=1"
|
|
" ORDER BY p.connected DESC,p.node_RTT IS NULL,p.node_RTT,p.node_id", table);
|
|
sqlite3_stmt* stmt = NULL;
|
|
int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
|
if (rc != SQLITE_OK) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "group candidate query channel=%s: %s", channel_id, sqlite3_errmsg(db)); return -1;
|
|
}
|
|
struct topo_connect_peer* peers = NULL;
|
|
size_t count = 0, capacity = 0;
|
|
while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) {
|
|
if (count == capacity) {
|
|
size_t next = capacity ? capacity * 2 : 16;
|
|
struct topo_connect_peer* list = u_realloc(peers, next * sizeof(*list));
|
|
if (!list) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "group candidate query allocation failed channel=%s", channel_id); break; }
|
|
peers = list; capacity = next;
|
|
}
|
|
struct topo_connect_peer* peer = &peers[count++];
|
|
*peer = (struct topo_connect_peer){ .node_id = (uint64_t)sqlite3_column_int64(stmt, 0), .client_type = UINT8_MAX };
|
|
const char* tags = (const char*)sqlite3_column_text(stmt, 1);
|
|
char value[16];
|
|
peer->supernode = tags && json_flat_get(tags, "supernode", value, sizeof(value)) == 0 && strcmp(value, "yes") == 0;
|
|
const void* record = sqlite3_column_blob(stmt, 3);
|
|
if (record) {
|
|
struct TOPOMSG_NODE header;
|
|
if (sqlite3_column_bytes(stmt, 3) < (int)sizeof(header)) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "short candidate snapshot channel=%s node=%016llx", channel_id,
|
|
(unsigned long long)peer->node_id); break;
|
|
}
|
|
memcpy(&header, record, sizeof(header));
|
|
if (header.node_id != peer->node_id || header.timestamp != (uint64_t)sqlite3_column_int64(stmt, 2) ||
|
|
header.client_type > CLIENT_TYPE_MOBILE) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid candidate snapshot channel=%s node=%016llx type=%u", channel_id,
|
|
(unsigned long long)peer->node_id, header.client_type); break;
|
|
}
|
|
peer->client_type = header.client_type;
|
|
}
|
|
}
|
|
if (rc != SQLITE_DONE) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "group candidate read failed channel=%s rc=%d: %s", channel_id, rc, sqlite3_errmsg(db));
|
|
sqlite3_finalize(stmt); u_free(peers); return -1;
|
|
}
|
|
sqlite3_finalize(stmt); *out = peers; *out_count = count; return 0;
|
|
}
|
|
|
|
/* Один проход SQL: частичный массив никогда не публикуется при ошибке чтения/памяти. */
|
|
static int node_ids_query(sqlite3* db, const char* sql, int exclude_self, uint64_t self,
|
|
uint64_t** out_ids, int* out_count) {
|
|
*out_ids = NULL; *out_count = 0;
|
|
sqlite3_stmt* st = NULL;
|
|
uint64_t* ids = NULL;
|
|
size_t count = 0, capacity = 0;
|
|
int rc = sqlite3_prepare_v2(db, sql, -1, &st, NULL);
|
|
if (rc == SQLITE_OK && exclude_self) rc = sqlite3_bind_int64(st, 1, (sqlite3_int64)self);
|
|
if (rc != SQLITE_OK) goto fail;
|
|
while ((rc = sqlite3_step(st)) == SQLITE_ROW) {
|
|
if (count == capacity) {
|
|
size_t next = capacity ? capacity * 2 : 16;
|
|
if (next > UINT32_MAX / sizeof(*ids)) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "peer ID list too large: count=%zu", count); goto fail;
|
|
}
|
|
uint64_t* resized = u_realloc(ids, next * sizeof(*ids));
|
|
if (!resized) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "peer ID list allocation failed: count=%zu", count); goto fail; }
|
|
ids = resized; capacity = next;
|
|
}
|
|
ids[count++] = (uint64_t)sqlite3_column_int64(st, 0);
|
|
}
|
|
if (rc != SQLITE_DONE) goto fail;
|
|
sqlite3_finalize(st);
|
|
*out_ids = ids; *out_count = (int)count;
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "peer ID list read: count=%zu exclude_self=%d", count, exclude_self);
|
|
return 0;
|
|
fail:
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "peer ID query failed: rc=%d rows=%zu sql=%s: %s", rc, count, sql, sqlite3_errmsg(db));
|
|
sqlite3_finalize(st); u_free(ids); return -1;
|
|
}
|
|
|
|
int topo_node_sqlite_get_connected_peers(sqlite3* db, const char* channel_id, uint64_t** out_ids, int* out_count) {
|
|
if (out_ids) *out_ids = NULL;
|
|
if (out_count) *out_count = 0;
|
|
if (!db || !channel_id || !out_ids || !out_count) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "connected peers: invalid arguments"); return -1;
|
|
}
|
|
char table[80], sql[192]; peers_table_name(channel_id, table, sizeof(table));
|
|
snprintf(sql, sizeof(sql), "SELECT node_id FROM \"%s\" WHERE record_state=0 AND connected=1 AND deleted=0", table);
|
|
return node_ids_query(db, sql, 0, 0, out_ids, out_count);
|
|
}
|
|
|
|
int topo_node_sqlite_get_public_peers(sqlite3* db, const char* channel_id,
|
|
uint64_t** out_ids, int* out_count, uint64_t self_node_id) {
|
|
if (out_ids) *out_ids = NULL;
|
|
if (out_count) *out_count = 0;
|
|
if (!db || !channel_id || !out_ids || !out_count) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "public peers: invalid arguments"); return -1;
|
|
}
|
|
char table[80], sql[512]; peers_table_name(channel_id, table, sizeof(table));
|
|
snprintf(sql, sizeof(sql),
|
|
"SELECT DISTINCT na.node_id FROM node_addresses na"
|
|
" WHERE na.node_id!=?1 AND na.family IN (4,6) AND na.addr_type IN (1,2)"
|
|
" AND EXISTS (SELECT 1 FROM nodes n WHERE n.node_id=na.node_id)"
|
|
" AND NOT EXISTS (SELECT 1 FROM \"%s\" p WHERE p.node_id=na.node_id AND p.record_state=0 AND p.deleted=1)", table);
|
|
return node_ids_query(db, sql, 1, self_node_id, out_ids, out_count);
|
|
}
|
|
|
|
int topo_node_sqlite_get_supernode_peers(sqlite3* db, const char* channel_id, uint64_t** out_ids, int* out_count) {
|
|
if (out_ids) *out_ids = NULL;
|
|
if (out_count) *out_count = 0;
|
|
if (!db || !channel_id || !out_ids || !out_count) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "supernode peers: invalid arguments"); return -1;
|
|
}
|
|
char table[80], sql[192]; peers_table_name(channel_id, table, sizeof(table));
|
|
snprintf(sql, sizeof(sql), "SELECT node_id FROM \"%s\" WHERE record_state=0 AND node_type=4 AND deleted=0", table);
|
|
return node_ids_query(db, sql, 0, 0, out_ids, out_count);
|
|
}
|
|
|
|
int topo_node_sqlite_get_local_peers(sqlite3* db, const char* channel_id,
|
|
uint64_t** out_ids, int* out_count, uint64_t self_node_id) {
|
|
if (out_ids) *out_ids = NULL;
|
|
if (out_count) *out_count = 0;
|
|
if (!db || !channel_id || !out_ids || !out_count) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "local peers: invalid arguments"); return -1;
|
|
}
|
|
char table[80], sql[512]; peers_table_name(channel_id, table, sizeof(table));
|
|
snprintf(sql, sizeof(sql),
|
|
"SELECT DISTINCT na.node_id FROM node_addresses na"
|
|
" WHERE na.node_id!=?1 AND na.family IN (4,6) AND na.addr_type IN (0,3)"
|
|
" AND EXISTS (SELECT 1 FROM nodes n WHERE n.node_id=na.node_id)"
|
|
" AND NOT EXISTS (SELECT 1 FROM \"%s\" p WHERE p.node_id=na.node_id AND p.record_state=0 AND p.deleted=1)", table);
|
|
return node_ids_query(db, sql, 1, self_node_id, out_ids, out_count);
|
|
}
|
|
|
|
int topo_node_sqlite_get_node_type(sqlite3* db, const char* channel_id, uint64_t node_id) {
|
|
if (!db || !channel_id || !node_id) return -1;
|
|
char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl));
|
|
char sql[256];
|
|
snprintf(sql, sizeof(sql), "SELECT node_type FROM \"%s\" WHERE node_id=? AND record_state=0", peers_tbl);
|
|
sqlite3_stmt* st = NULL;
|
|
if (sqlite3_prepare_v2(db, sql, -1, &st, NULL) != SQLITE_OK) return -1;
|
|
sqlite3_bind_int64(st, 1, (sqlite3_int64)node_id);
|
|
int node_type = -1;
|
|
if (sqlite3_step(st) == SQLITE_ROW) node_type = sqlite3_column_int(st, 0);
|
|
sqlite3_finalize(st);
|
|
return node_type;
|
|
}
|