You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1083 lines
54 KiB
1083 lines
54 KiB
#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> |
|
|
|
#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 config_type, int nat_type) { |
|
if (config_type == CFG_SERVER_TYPE_PUBLIC) return ADDR_TYPE_DIRECT; |
|
if (config_type == CFG_SERVER_TYPE_PRIVATE || config_type == CFG_SERVER_TYPE_LOCAL) return ADDR_TYPE_NETIF; |
|
if (nat_type == NAT_TYPE_EIM) return ADDR_TYPE_NAT_EIM; |
|
if (nat_type == NAT_TYPE_STRICT) return ADDR_TYPE_NAT_STRICT; |
|
if (nat_type == NAT_TYPE_DIRECT) return ADDR_TYPE_DIRECT; |
|
if (nat_type == NAT_VERIFIED_EIM) return ADDR_TYPE_NAT_EIM; |
|
if (nat_type == NAT_VERIFIED_STRICT) return ADDR_TYPE_NAT_STRICT; |
|
if (nat_type == NAT_VERIFIED_DIRECT) return ADDR_TYPE_DIRECT; |
|
return ADDR_TYPE_NAT; /* за NAT, тип неизвестен */ |
|
} |
|
|
|
static void smeta4_lookup(struct TOPO_SOCKMETA4* head, uint8_t sid, uint8_t* cfg, uint8_t* nat) { |
|
*cfg = CFG_SERVER_TYPE_UNKNOWN; *nat = NAT_TYPE_UNKNOWN; |
|
for (const struct TOPO_SOCKMETA4* m = head; m; m = m->next) |
|
if (m->id == sid) { *cfg = m->config_type; *nat = m->nat_type; return; } |
|
} |
|
|
|
static void smeta6_lookup(struct TOPO_SOCKMETA6* head, uint8_t sid, uint8_t* cfg, uint8_t* nat) { |
|
*cfg = CFG_SERVER_TYPE_UNKNOWN; *nat = NAT_TYPE_UNKNOWN; |
|
for (const struct TOPO_SOCKMETA6* m = head; m; m = m->next) |
|
if (m->id == sid) { *cfg = m->config_type; *nat = m->nat_type; return; } |
|
} |
|
|
|
/* ── Персистенция адресов узла (источник истины — BGP/topo_group) ── */ |
|
|
|
int topo_node_sqlite_addrs_put(sqlite3* db, uint64_t node_id, struct TOPO_NODE* ni) { |
|
if (!db || !ni) return -1; |
|
sqlite3_exec(db, "BEGIN", NULL, NULL, NULL); |
|
|
|
sqlite3_stmt* ds = NULL; |
|
int deleted = 0; |
|
if (sqlite3_prepare_v2(db, "DELETE FROM node_addresses WHERE node_id=?", -1, &ds, NULL) == SQLITE_OK) { |
|
sqlite3_bind_int64(ds, 1, (sqlite3_int64)node_id); |
|
int drc = sqlite3_step(ds); |
|
deleted = sqlite3_changes(db); |
|
if (drc != SQLITE_DONE) |
|
DEBUG_WARN(DEBUG_CATEGORY_BGP, "addrs_put: DELETE rc=%d (not DONE) node=%016llx: %s", |
|
drc, (unsigned long long)node_id, sqlite3_errmsg(db)); |
|
sqlite3_finalize(ds); |
|
} else { |
|
DEBUG_WARN(DEBUG_CATEGORY_BGP, "addrs_put: DELETE prepare failed node=%016llx: %s", |
|
(unsigned long long)node_id, sqlite3_errmsg(db)); |
|
} |
|
|
|
sqlite3_stmt* is = NULL; |
|
if (sqlite3_prepare_v2(db, |
|
"INSERT INTO node_addresses(node_id,family,protocol,address,port,addr_type,config_type,nat_type,socket_id)" |
|
" VALUES(?,?,?,?,?,?,?,?,?)", -1, &is, NULL) != SQLITE_OK) { |
|
sqlite3_exec(db, "COMMIT", NULL, NULL, NULL); |
|
return -1; |
|
} |
|
|
|
int written = 0; |
|
for (const struct TOPO_ADDR4* a = ni->v4_addrs; a; a = a->next) { |
|
uint8_t cfg, nat; smeta4_lookup(ni->v4_sock_meta, a->socket_id, &cfg, &nat); |
|
int at = (a->type == TOPO_ADDR_INTERFACE) ? addr_type_from_ip(4, a->addr) |
|
: addr_type_classify(cfg, nat); |
|
sqlite3_bind_int64(is, 1, (sqlite3_int64)node_id); |
|
sqlite3_bind_int(is, 2, 4); |
|
sqlite3_bind_int(is, 3, (int)a->protocol); |
|
sqlite3_bind_blob(is, 4, a->addr, 4, SQLITE_STATIC); |
|
sqlite3_bind_int(is, 5, (int)a->port); |
|
sqlite3_bind_int(is, 6, at); |
|
sqlite3_bind_int(is, 7, (int)cfg); |
|
sqlite3_bind_int(is, 8, (int)nat); |
|
sqlite3_bind_int(is, 9, (int)a->socket_id); |
|
if (sqlite3_step(is) != SQLITE_DONE) |
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "addrs_put: INSERT failed node=%016llx fam=4 sock=%d at=%d cfg=%d nat=%d addr=%d.%d.%d.%d:%d: %s", |
|
(unsigned long long)node_id, a->socket_id, at, cfg, nat, |
|
a->addr[0], a->addr[1], a->addr[2], a->addr[3], a->port, sqlite3_errmsg(db)); |
|
sqlite3_reset(is); |
|
written++; |
|
} |
|
for (const struct TOPO_ADDR6* a = ni->v6_addrs; a; a = a->next) { |
|
uint8_t cfg, nat; smeta6_lookup(ni->v6_sock_meta, a->socket_id, &cfg, &nat); |
|
int at = (a->type == TOPO_ADDR_INTERFACE) ? ADDR_TYPE_NETIF |
|
: addr_type_classify(cfg, nat); |
|
sqlite3_bind_int64(is, 1, (sqlite3_int64)node_id); |
|
sqlite3_bind_int(is, 2, 6); |
|
sqlite3_bind_int(is, 3, (int)a->protocol); |
|
sqlite3_bind_blob(is, 4, a->addr, 16, SQLITE_STATIC); |
|
sqlite3_bind_int(is, 5, (int)a->port); |
|
sqlite3_bind_int(is, 6, at); |
|
sqlite3_bind_int(is, 7, (int)cfg); |
|
sqlite3_bind_int(is, 8, (int)nat); |
|
sqlite3_bind_int(is, 9, (int)a->socket_id); |
|
if (sqlite3_step(is) != SQLITE_DONE) |
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "addrs_put: INSERT failed node=%016llx fam=6 sock=%d at=%d cfg=%d nat=%d: %s", |
|
(unsigned long long)node_id, a->socket_id, at, cfg, nat, sqlite3_errmsg(db)); |
|
sqlite3_reset(is); |
|
written++; |
|
} |
|
sqlite3_finalize(is); |
|
sqlite3_exec(db, "COMMIT", NULL, NULL, NULL); |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "addrs_put: node=%016llx deleted=%d v4=%d v6=%d written=%d", |
|
(unsigned long long)node_id, deleted, |
|
topo_list_count((struct _topo_head*)ni->v4_addrs), |
|
topo_list_count((struct _topo_head*)ni->v6_addrs), written); |
|
return 0; |
|
} |
|
|
|
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=?", 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 (!db || !out_ch_ids || !out_count) return -1; |
|
*out_ch_ids = NULL; *out_count = 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 -1; |
|
|
|
uint64_t* ids = NULL; int count = 0, cap = 0; |
|
char sql[256]; |
|
while (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=?", 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); |
|
int is_member = (sqlite3_step(m) == 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) { u_free(ids); sqlite3_finalize(s); return -1; } |
|
ids = tmp; |
|
} |
|
ids[count++] = ch; |
|
} |
|
} |
|
} |
|
} |
|
sqlite3_finalize(s); |
|
*out_ch_ids = ids; *out_count = count; |
|
return 0; |
|
} |
|
|
|
int topo_node_sqlite_init(sqlite3* db) { |
|
if (!db) return -1; |
|
|
|
const char* sql = |
|
"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_type INTEGER DEFAULT 0," |
|
" socket_id INTEGER NOT NULL DEFAULT 0," |
|
" 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, addr_type);" |
|
"CREATE INDEX IF NOT EXISTS idx_na_addr_lookup ON node_addresses(family, address, port, socket_id);" |
|
|
|
"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 байта) |
|
" 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; |
|
} |
|
|
|
/* 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\"", 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=?", 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_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) return -1; |
|
|
|
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 UNIQUE," /* уникальный ID узла (64-bit) */ |
|
" 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), пока заглушка */ |
|
" 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=суперузел(DIRECT+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, не синхронизируется) */ |
|
" source INTEGER NOT NULL DEFAULT 0" /* 0=полная merkle-запись 1=упрощённый плейсхолдер topo_group |
|
(не участвует в merkle-хеше, дополняется полной записью) */ |
|
")"; |
|
|
|
int ddl_sz = snprintf(NULL, 0, ddl_tmpl, peers_tbl); |
|
char* ddl = u_malloc(ddl_sz + 1); |
|
if (ddl) { |
|
snprintf(ddl, ddl_sz + 1, ddl_tmpl, peers_tbl); |
|
char* err = NULL; |
|
sqlite3_exec(db, ddl, NULL, NULL, &err); |
|
if (err) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "topo_node_sqlite_channel_put peers: %s", err); sqlite3_free(err); } |
|
u_free(ddl); |
|
} |
|
|
|
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); |
|
if (idx_sql) { |
|
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); |
|
sqlite3_exec(db, idx_sql, NULL, NULL, NULL); |
|
u_free(idx_sql); |
|
} |
|
|
|
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); |
|
if (sidx_sql) { |
|
snprintf(sidx_sql, sidx_sz + 1, |
|
"CREATE INDEX IF NOT EXISTS \"idx_%s_storage\" ON \"%s\" (storage)", peers_tbl, peers_tbl); |
|
sqlite3_exec(db, sidx_sql, NULL, NULL, NULL); |
|
u_free(sidx_sql); |
|
} |
|
|
|
sqlite3_stmt* stmt = NULL; |
|
if (sqlite3_prepare_v2(db, |
|
"INSERT OR REPLACE INTO channels(channel_id, name, owner_node_id," |
|
" x25519_pubkey, x25519_privkey, ed25519_pubkey, ed25519_privkey, signature)" |
|
" VALUES(?,?,?,?,?,?,?,?)", -1, &stmt, NULL) != SQLITE_OK) return -1; |
|
sqlite3_bind_text(stmt, 1, channel_id, -1, SQLITE_STATIC); |
|
sqlite3_bind_text(stmt, 2, name, -1, SQLITE_STATIC); |
|
sqlite3_bind_int64(stmt, 3, (sqlite3_int64)owner_node_id); |
|
sqlite3_bind_blob(stmt, 4, x25519_pub, 32, SQLITE_STATIC); |
|
if (x25519_priv) sqlite3_bind_blob(stmt, 5, x25519_priv, 32, SQLITE_STATIC); |
|
else sqlite3_bind_null(stmt, 5); |
|
sqlite3_bind_blob(stmt, 6, ed25519_pub, 32, SQLITE_STATIC); |
|
if (ed25519_priv) sqlite3_bind_blob(stmt, 7, ed25519_priv, 32, SQLITE_STATIC); |
|
else sqlite3_bind_null(stmt, 7); |
|
sqlite3_bind_blob(stmt, 8, signature, 64, SQLITE_STATIC); |
|
int rc = sqlite3_step(stmt); sqlite3_finalize(stmt); |
|
return rc == SQLITE_DONE ? 0 : -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) 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) { |
|
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[640]; |
|
snprintf(sql, sizeof(sql), |
|
"INSERT INTO \"%s\"(node_id, x25519_pubkey, ed25519_pubkey," |
|
" join_sig, join_ts, update_sig, update_ts, userinfo, creator_sig)" |
|
" VALUES(?,?,?,?,?,?,?,?,?)" |
|
" ON CONFLICT(node_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," |
|
" source=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); |
|
sqlite3_bind_blob(stmt, 2, x25519_pubkey, 32, SQLITE_STATIC); |
|
sqlite3_bind_blob(stmt, 3, ed25519_pubkey, 32, SQLITE_STATIC); |
|
if (join_sig) sqlite3_bind_blob(stmt, 4, join_sig, PEERS_JOIN_SIG_SIZE, SQLITE_STATIC); |
|
else { static const unsigned char zsig[PEERS_JOIN_SIG_SIZE] = {0}; sqlite3_bind_blob(stmt, 4, zsig, PEERS_JOIN_SIG_SIZE, SQLITE_STATIC); } |
|
sqlite3_bind_int64(stmt, 5, (sqlite3_int64)join_ts); |
|
if (update_sig) sqlite3_bind_blob(stmt, 6, update_sig, PEERS_UPDATE_SIG_SIZE, SQLITE_STATIC); |
|
else sqlite3_bind_null(stmt, 6); |
|
sqlite3_bind_int64(stmt, 7, (sqlite3_int64)update_ts); |
|
sqlite3_bind_text(stmt, 8, userinfo ? userinfo : "", -1, SQLITE_STATIC); |
|
if (creator_sig) sqlite3_bind_blob(stmt, 9, creator_sig, PEERS_JOIN_SIG_SIZE, SQLITE_STATIC); |
|
else sqlite3_bind_null(stmt, 9); |
|
int rc = sqlite3_step(stmt); sqlite3_finalize(stmt); |
|
return rc == SQLITE_DONE ? 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) 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=?", peers_tbl); |
|
sqlite3_stmt* u = NULL; |
|
if (sqlite3_prepare_v2(db, sql, -1, &u, NULL) != SQLITE_OK) return -1; |
|
if (adm_tags && adm_tags[0]) sqlite3_bind_text(u, 1, adm_tags, -1, SQLITE_STATIC); |
|
else sqlite3_bind_null(u, 1); |
|
if (adm_tags_sig) sqlite3_bind_blob(u, 2, adm_tags_sig, 64, SQLITE_STATIC); |
|
else sqlite3_bind_null(u, 2); |
|
sqlite3_bind_int(u, 3, storage); |
|
sqlite3_bind_int(u, 4, adm_tags_deleted(adm_tags)); |
|
sqlite3_bind_int64(u, 5, (sqlite3_int64)node_id); |
|
sqlite3_step(u); sqlite3_finalize(u); |
|
return 0; |
|
} |
|
|
|
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) return -1; |
|
sqlite3_stmt* stmt = NULL; |
|
if (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) != SQLITE_OK) return -1; |
|
sqlite3_bind_int64(stmt, 1, (sqlite3_int64)node_id); |
|
sqlite3_bind_text(stmt, 2, name && name[0] ? name : "", -1, SQLITE_STATIC); |
|
sqlite3_bind_blob(stmt, 3, x25519, 32, SQLITE_STATIC); |
|
sqlite3_bind_blob(stmt, 4, ed25519, 32, SQLITE_STATIC); |
|
sqlite3_bind_int64(stmt, 5, (sqlite3_int64)created_at); |
|
sqlite3_bind_int64(stmt, 6, (sqlite3_int64)update_ts); |
|
sqlite3_step(stmt); |
|
sqlite3_finalize(stmt); |
|
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=?", 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) { memcpy(join_sig_out, sig, PEERS_JOIN_SIG_SIZE); *join_ts_out = (uint64_t)sqlite3_column_int64(stmt, 1); rc = 0; } |
|
} |
|
sqlite3_finalize(stmt); |
|
return rc; |
|
} |
|
|
|
int topo_node_sqlite_member_del(sqlite3* db, const char* channel_id, uint64_t node_id) { |
|
if (!db || !channel_id) 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=?", 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_step(stmt); sqlite3_finalize(stmt); |
|
return 0; |
|
} |
|
|
|
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) return -1; |
|
sqlite3_stmt* stmt = NULL; |
|
if (sqlite3_prepare_v2(db, |
|
"SELECT name, owner_node_id, x25519_pubkey, ed25519_pubkey, signature" |
|
" FROM channels WHERE channel_id=?", -1, &stmt, NULL) != SQLITE_OK) return -1; |
|
sqlite3_bind_text(stmt, 1, channel_id, -1, SQLITE_STATIC); |
|
if (sqlite3_step(stmt) != SQLITE_ROW) { sqlite3_finalize(stmt); return -1; } |
|
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); |
|
sqlite3_finalize(stmt); |
|
return 0; |
|
} |
|
|
|
int topo_node_sqlite_channel_get_priv(sqlite3* db, const char* channel_id, |
|
uint8_t* ed25519_priv_out) { |
|
if (!db || !channel_id || !ed25519_priv_out) return -1; |
|
sqlite3_stmt* stmt = NULL; |
|
if (sqlite3_prepare_v2(db, |
|
"SELECT ed25519_privkey FROM channels WHERE channel_id=?", -1, &stmt, NULL) != SQLITE_OK) return -1; |
|
sqlite3_bind_text(stmt, 1, channel_id, -1, SQLITE_STATIC); |
|
if (sqlite3_step(stmt) != SQLITE_ROW) { sqlite3_finalize(stmt); return -1; } |
|
const uint8_t* blob = (const uint8_t*)sqlite3_column_blob(stmt, 0); |
|
if (!blob || sqlite3_column_bytes(stmt, 0) < 32) { sqlite3_finalize(stmt); return -1; } |
|
memcpy(ed25519_priv_out, blob, 32); |
|
sqlite3_finalize(stmt); |
|
return 0; |
|
} |
|
|
|
int topo_node_sqlite_channel_get_privs(sqlite3* db, const char* channel_id, |
|
uint8_t* x25519_priv_out, uint8_t* ed25519_priv_out) { |
|
if (!db || !channel_id || !x25519_priv_out || !ed25519_priv_out) return -1; |
|
sqlite3_stmt* stmt = NULL; |
|
if (sqlite3_prepare_v2(db, |
|
"SELECT x25519_privkey, ed25519_privkey FROM channels WHERE channel_id=?", -1, &stmt, NULL) != SQLITE_OK) return -1; |
|
sqlite3_bind_text(stmt, 1, channel_id, -1, SQLITE_STATIC); |
|
if (sqlite3_step(stmt) != SQLITE_ROW) { sqlite3_finalize(stmt); return -1; } |
|
const uint8_t* x = (const uint8_t*)sqlite3_column_blob(stmt, 0); |
|
const uint8_t* e = (const uint8_t*)sqlite3_column_blob(stmt, 1); |
|
if (!x || sqlite3_column_bytes(stmt, 0) < 32 || !e || sqlite3_column_bytes(stmt, 1) < 32) { |
|
sqlite3_finalize(stmt); return -1; |
|
} |
|
memcpy(x25519_priv_out, x, 32); |
|
memcpy(ed25519_priv_out, e, 32); |
|
sqlite3_finalize(stmt); |
|
return 0; |
|
} |
|
|
|
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 || !x25519_priv || !ed25519_priv) return -1; |
|
sqlite3_stmt* stmt = NULL; |
|
if (sqlite3_prepare_v2(db, |
|
"UPDATE channels SET x25519_privkey=?, ed25519_privkey=? WHERE channel_id=?", -1, &stmt, NULL) != SQLITE_OK) return -1; |
|
sqlite3_bind_blob(stmt, 1, x25519_priv, 32, SQLITE_STATIC); |
|
sqlite3_bind_blob(stmt, 2, ed25519_priv, 32, SQLITE_STATIC); |
|
sqlite3_bind_text(stmt, 3, channel_id, -1, SQLITE_STATIC); |
|
int rc = sqlite3_step(stmt) == SQLITE_DONE ? 0 : -1; |
|
sqlite3_finalize(stmt); |
|
return rc; |
|
} |
|
|
|
int topo_node_sqlite_channel_peers_all(sqlite3* db, const char* channel_id, |
|
uint8_t* buf, size_t buf_sz, size_t* out_len) { |
|
if (!db || !channel_id || !buf || !out_len) return -1; |
|
char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl)); |
|
|
|
sqlite3_stmt* stmt = NULL; |
|
char sql[512]; |
|
snprintf(sql, sizeof(sql), |
|
"SELECT node_id, x25519_pubkey, ed25519_pubkey," |
|
" join_sig, join_ts, update_sig, update_ts, userinfo, adm_tags, adm_tags_sig" |
|
" FROM \"%s\" ORDER BY node_id ASC", peers_tbl); |
|
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) return -1; |
|
|
|
size_t off = 0; |
|
if (off + 2 > buf_sz) { sqlite3_finalize(stmt); return -2; } |
|
uint16_t* cnt_ptr = (uint16_t*)(buf + off); off += 2; |
|
*cnt_ptr = 0; |
|
|
|
while (sqlite3_step(stmt) == SQLITE_ROW) { |
|
uint64_t node_id = (uint64_t)sqlite3_column_int64(stmt, 0); |
|
const uint8_t* x25519 = (const uint8_t*)sqlite3_column_blob(stmt, 1); |
|
const uint8_t* ed_pub = (const uint8_t*)sqlite3_column_blob(stmt, 2); |
|
const uint8_t* join_sig = (const uint8_t*)sqlite3_column_blob(stmt, 3); |
|
uint64_t join_ts = (uint64_t)sqlite3_column_int64(stmt, 4); |
|
const uint8_t* update_sig = (const uint8_t*)sqlite3_column_blob(stmt, 5); |
|
uint64_t update_ts = (uint64_t)sqlite3_column_int64(stmt, 6); |
|
const char* puserinfo = (const char*)sqlite3_column_text(stmt, 7); |
|
const char* adm_tags_s = (const char*)sqlite3_column_text(stmt, 8); |
|
const uint8_t* adm_tags_sig_b = (const uint8_t*)sqlite3_column_blob(stmt, 9); |
|
if (!x25519 || !ed_pub) continue; |
|
uint8_t nl = puserinfo ? (uint8_t)strnlen(puserinfo, 255) : 0; |
|
uint8_t atl = adm_tags_s ? (uint8_t)strnlen(adm_tags_s, 255) : 0; |
|
|
|
uint8_t flags = (join_sig && join_ts) ? PEERS_FLAG_HAS_JOIN : 0; |
|
size_t peer_needed = 8 + 32 + 32 + 1 + (flags & PEERS_FLAG_HAS_JOIN ? 64+8 : 0) + 64 + 8 + 1 + (size_t)nl + 1 + (size_t)atl + 64; |
|
if (off + peer_needed > buf_sz) { sqlite3_finalize(stmt); return -2; } |
|
memcpy(buf + off, &node_id, 8); off += 8; |
|
memcpy(buf + off, x25519, 32); off += 32; |
|
memcpy(buf + off, ed_pub, 32); off += 32; |
|
buf[off++] = flags; |
|
if (flags & PEERS_FLAG_HAS_JOIN) { |
|
memcpy(buf + off, join_sig, 64); off += 64; |
|
memcpy(buf + off, &join_ts, 8); off += 8; |
|
} |
|
if (update_sig && update_ts) { |
|
memcpy(buf + off, update_sig, 64); off += 64; |
|
memcpy(buf + off, &update_ts, 8); off += 8; |
|
} else { |
|
memset(buf + off, 0, 64); off += 64; |
|
uint64_t z = 0; memcpy(buf + off, &z, 8); off += 8; |
|
} |
|
buf[off++] = nl; |
|
if (nl) { memcpy(buf + off, puserinfo, nl); off += nl; } |
|
buf[off++] = atl; |
|
if (atl) { memcpy(buf + off, adm_tags_s, atl); off += atl; } |
|
if (adm_tags_sig_b && sqlite3_column_bytes(stmt, 9) >= 64) { memcpy(buf + off, adm_tags_sig_b, 64); off += 64; } |
|
else { memset(buf + off, 0, 64); off += 64; } |
|
(*cnt_ptr)++; |
|
} |
|
sqlite3_finalize(stmt); |
|
*out_len = off; |
|
return 0; |
|
} |
|
|
|
int topo_node_sqlite_node_set_online(sqlite3* db, uint64_t node_id, int online) { |
|
if (!db) return -1; |
|
sqlite3_stmt* stmt = NULL; |
|
if (sqlite3_prepare_v2(db, "UPDATE nodes SET online=? WHERE node_id=?", -1, &stmt, NULL) != SQLITE_OK) return -1; |
|
sqlite3_bind_int(stmt, 1, online ? 1 : 0); |
|
sqlite3_bind_int64(stmt, 2, (sqlite3_int64)node_id); |
|
sqlite3_step(stmt); |
|
sqlite3_finalize(stmt); |
|
return 0; |
|
} |
|
|
|
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) return -1; |
|
sqlite3_stmt* stmt = NULL; |
|
if (sqlite3_prepare_v2(db, "SELECT ed25519_pubkey FROM nodes WHERE node_id=?", |
|
-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 void* b = sqlite3_column_blob(stmt, 0); |
|
int bytes = sqlite3_column_bytes(stmt, 0); |
|
if (b && bytes >= 32) { uint64_t chk; memcpy(&chk, b, 8); if (chk != 0) { memcpy(pubkey_out, b, 32); rc = 0; } } |
|
} |
|
sqlite3_finalize(stmt); |
|
return rc; |
|
} |
|
|
|
int topo_node_sqlite_nodeinfo_updated(sqlite3* db, uint64_t node_id) { |
|
if (!db) return -1; |
|
|
|
int best_at = -1; |
|
{ |
|
sqlite3_stmt* s = NULL; |
|
if (sqlite3_prepare_v2(db, |
|
"SELECT addr_type FROM node_addresses WHERE node_id=?", -1, &s, NULL) == SQLITE_OK) { |
|
sqlite3_bind_int64(s, 1, (sqlite3_int64)node_id); |
|
while (sqlite3_step(s) == SQLITE_ROW) { |
|
int at = sqlite3_column_int(s, 0); |
|
if (at == ADDR_TYPE_DIRECT) { best_at = ADDR_TYPE_DIRECT; break; } |
|
if (best_at < 0 || at < best_at) best_at = at; |
|
} |
|
sqlite3_finalize(s); |
|
} |
|
} |
|
|
|
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 }; |
|
int base_type = (best_at < 0) ? 0 : at2type[best_at]; |
|
|
|
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 -1; |
|
|
|
char sql[256]; |
|
while (sqlite3_step(s) == SQLITE_ROW) { |
|
const char* tbl = (const char*)sqlite3_column_text(s, 0); |
|
|
|
int node_type = base_type; int is_storage = 0; int is_deleted = 0; |
|
snprintf(sql, sizeof(sql), |
|
"SELECT adm_tags FROM \"%s\" WHERE node_id=?", tbl); |
|
sqlite3_stmt* a = NULL; |
|
if (sqlite3_prepare_v2(db, sql, -1, &a, NULL) == SQLITE_OK) { |
|
sqlite3_bind_int64(a, 1, (sqlite3_int64)node_id); |
|
if (sqlite3_step(a) == SQLITE_ROW) { |
|
const char* tags = (const char*)sqlite3_column_text(a, 0); |
|
if (tags) { |
|
char buf[64]; |
|
if (best_at == ADDR_TYPE_DIRECT && base_type == 1) { |
|
if (json_flat_get(tags, "supernode", buf, sizeof(buf)) == 0 && strcmp(buf, "yes") == 0) node_type = 4; |
|
if (json_flat_get(tags, "storage", buf, sizeof(buf)) == 0 && strcmp(buf, "yes") == 0) is_storage = 1; |
|
} |
|
is_deleted = adm_tags_deleted(tags); |
|
} |
|
} |
|
sqlite3_finalize(a); |
|
} |
|
|
|
snprintf(sql, sizeof(sql), |
|
"UPDATE \"%s\" SET node_type=?, storage=?, deleted=? WHERE node_id=?", tbl); |
|
sqlite3_stmt* u = NULL; |
|
if (sqlite3_prepare_v2(db, sql, -1, &u, NULL) == SQLITE_OK) { |
|
sqlite3_bind_int(u, 1, node_type); |
|
sqlite3_bind_int(u, 2, is_storage); |
|
sqlite3_bind_int(u, 3, is_deleted); |
|
sqlite3_bind_int64(u, 4, (sqlite3_int64)node_id); |
|
sqlite3_step(u); sqlite3_finalize(u); |
|
} |
|
} |
|
sqlite3_finalize(s); |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "nodeinfo_updated: node=%016llx best_at=%d base_type=%d", |
|
(unsigned long long)node_id, best_at, base_type); |
|
return 0; |
|
} |
|
|
|
void topo_node_sqlite_update_rtt(sqlite3* db, uint64_t node_id, uint16_t rtt) { |
|
if (!db) return; |
|
|
|
{ |
|
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); |
|
sqlite3_step(s); |
|
sqlite3_finalize(s); |
|
} |
|
} |
|
|
|
{ |
|
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; |
|
|
|
char sql[256]; |
|
while (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=?", 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); |
|
sqlite3_step(u); sqlite3_finalize(u); |
|
} |
|
} |
|
sqlite3_finalize(s); |
|
} |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "sqlite_update_rtt: node=%016llx rtt=%u", |
|
(unsigned long long)node_id, (unsigned)rtt); |
|
} |
|
|
|
struct TOPO_NODE* topo_node_sqlite_node_load(sqlite3* db, struct TOPO_GROUPS* groups, uint64_t node_id) { |
|
if (!db || !groups) return NULL; |
|
|
|
uint8_t pubkey[32] = {0}, ed_pubkey[32] = {0}; |
|
char* name = NULL; |
|
uint8_t client_type = 0; |
|
int found = 0; |
|
{ |
|
sqlite3_stmt* s = NULL; |
|
if (sqlite3_prepare_v2(db, "SELECT name,x25519_pubkey,ed25519_pubkey,client_type FROM nodes WHERE node_id=?", |
|
-1, &s, NULL) == SQLITE_OK) { |
|
sqlite3_bind_int64(s, 1, (sqlite3_int64)node_id); |
|
if (sqlite3_step(s) == SQLITE_ROW) { |
|
const char* n = (const char*)sqlite3_column_text(s, 0); |
|
if (n) name = u_strdup(n); |
|
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; |
|
} |
|
sqlite3_finalize(s); |
|
} |
|
} |
|
if (!found) { u_free(name); return NULL; } |
|
|
|
struct TOPO_ADDR4* v4_head = NULL; struct TOPO_ADDR4* v4_tail = NULL; |
|
struct TOPO_SOCKMETA4* m4_head = NULL; struct TOPO_SOCKMETA4* m4_tail = NULL; |
|
uint8_t sock_id_counter = 0; |
|
{ |
|
sqlite3_stmt* s = NULL; |
|
if (sqlite3_prepare_v2(db, |
|
"SELECT address,port,addr_type,socket_id,protocol,config_type,nat_type FROM node_addresses" |
|
" WHERE node_id=? AND family=4 ORDER BY addr_type", |
|
-1, &s, NULL) == SQLITE_OK) { |
|
sqlite3_bind_int64(s, 1, (sqlite3_int64)node_id); |
|
while (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 at = sqlite3_column_int(s, 2); |
|
int sock_id_raw = sqlite3_column_int(s, 3); |
|
uint8_t sock_id = (uint8_t)(sock_id_raw ? sock_id_raw : ++sock_id_counter); |
|
uint8_t type = (at == ADDR_TYPE_NETIF) ? TOPO_ADDR_INTERFACE : TOPO_ADDR_NAT; |
|
struct TOPO_ADDR4* a4 = memory_pool_alloc(groups->v4_addr_pool); |
|
if (!a4) continue; |
|
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) { |
|
m4->id = sock_id; |
|
m4->config_type = (uint8_t)sqlite3_column_int(s, 5); |
|
m4->nat_type = (uint8_t)sqlite3_column_int(s, 6); |
|
m4->next = NULL; |
|
if (!m4_head) m4_head = m4_tail = m4; |
|
else { m4_tail->next = m4; m4_tail = m4; } |
|
} |
|
} |
|
} |
|
sqlite3_finalize(s); |
|
} |
|
} |
|
/* IPv6 addresses */ |
|
struct TOPO_ADDR6* v6_head = NULL; struct TOPO_ADDR6* v6_tail = NULL; |
|
struct TOPO_SOCKMETA6* m6_head = NULL; struct TOPO_SOCKMETA6* m6_tail = NULL; |
|
{ |
|
sqlite3_stmt* s = NULL; |
|
if (sqlite3_prepare_v2(db, |
|
"SELECT address,port,addr_type,socket_id,protocol,config_type,nat_type FROM node_addresses" |
|
" WHERE node_id=? AND family=6 ORDER BY addr_type", |
|
-1, &s, NULL) == SQLITE_OK) { |
|
sqlite3_bind_int64(s, 1, (sqlite3_int64)node_id); |
|
while (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 at = sqlite3_column_int(s, 2); |
|
int sock_id_raw = sqlite3_column_int(s, 3); |
|
uint8_t sock_id = (uint8_t)(sock_id_raw ? sock_id_raw : ++sock_id_counter); |
|
uint8_t type = (at == ADDR_TYPE_NETIF) ? TOPO_ADDR_INTERFACE : TOPO_ADDR_NAT; |
|
struct TOPO_ADDR6* a6 = memory_pool_alloc(groups->v6_addr_pool); |
|
if (!a6) continue; |
|
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) { |
|
m6->id = sock_id; |
|
m6->config_type = (uint8_t)sqlite3_column_int(s, 5); |
|
m6->nat_type = (uint8_t)sqlite3_column_int(s, 6); |
|
m6->next = NULL; |
|
if (!m6_head) m6_head = m6_tail = m6; |
|
else { m6_tail->next = m6; m6_tail = m6; } |
|
} |
|
} |
|
} |
|
sqlite3_finalize(s); |
|
} |
|
} |
|
if (!v4_head && !v6_head) { u_free(name); DEBUG_WARN(DEBUG_CATEGORY_BGP, "node_load: node 0x%016llx has no addresses (may be stub/seed — will retry when addresses arrive)", (unsigned long long)node_id); return NULL; } |
|
|
|
struct TOPO_NODE* ni = u_calloc(1, sizeof(struct TOPO_NODE)); |
|
if (!ni) { u_free(name); return NULL; } |
|
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; |
|
|
|
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; |
|
} |
|
|
|
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[128]; snprintf(sql, sizeof(sql), "UPDATE \"%s\" SET connected=? WHERE node_id=?", 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; |
|
} |
|
|
|
int topo_node_sqlite_get_connected_peers(sqlite3* db, const char* channel_id, |
|
uint64_t** out_ids, int* out_count) { |
|
if (!db || !channel_id || !out_ids || !out_count) return -1; |
|
*out_ids = NULL; *out_count = 0; |
|
char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl)); |
|
char sql[192]; snprintf(sql, sizeof(sql), "SELECT node_id FROM \"%s\" WHERE connected=1 AND deleted=0", peers_tbl); |
|
sqlite3_stmt* st = NULL; |
|
if (sqlite3_prepare_v2(db, sql, -1, &st, NULL) != SQLITE_OK) return -1; |
|
int cnt = 0; |
|
while (sqlite3_step(st) == SQLITE_ROW) cnt++; |
|
if (cnt == 0) { sqlite3_finalize(st); return 0; } |
|
uint64_t* ids = u_malloc((size_t)cnt * sizeof(uint64_t)); |
|
if (!ids) { sqlite3_finalize(st); return -1; } |
|
sqlite3_reset(st); int i = 0; |
|
while (sqlite3_step(st) == SQLITE_ROW) ids[i++] = (uint64_t)sqlite3_column_int64(st, 0); |
|
sqlite3_finalize(st); |
|
*out_ids = ids; *out_count = cnt; |
|
return 0; |
|
} |
|
|
|
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 (!db || !channel_id || !out_ids || !out_count) return -1; |
|
*out_ids = NULL; *out_count = 0; |
|
char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl)); |
|
char sql[384]; 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.deleted=1)", |
|
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)self_node_id); |
|
int cnt = 0; |
|
while (sqlite3_step(st) == SQLITE_ROW) cnt++; |
|
if (cnt == 0) { sqlite3_finalize(st); return 0; } |
|
uint64_t* ids = u_malloc((size_t)cnt * sizeof(uint64_t)); |
|
if (!ids) { sqlite3_finalize(st); return -1; } |
|
sqlite3_reset(st); int i = 0; |
|
while (sqlite3_step(st) == SQLITE_ROW) ids[i++] = (uint64_t)sqlite3_column_int64(st, 0); |
|
sqlite3_finalize(st); |
|
*out_ids = ids; *out_count = cnt; |
|
return 0; |
|
} |
|
|
|
int topo_node_sqlite_get_supernode_peers(sqlite3* db, const char* channel_id, |
|
uint64_t** out_ids, int* out_count) { |
|
if (!db || !channel_id || !out_ids || !out_count) return -1; |
|
*out_ids = NULL; *out_count = 0; |
|
char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl)); |
|
char sql[256]; snprintf(sql, sizeof(sql), |
|
"SELECT p.node_id FROM \"%s\" p WHERE p.node_type=4 AND p.deleted=0" |
|
" AND EXISTS (SELECT 1 FROM node_addresses na WHERE na.node_id=p.node_id AND na.family IN (4,6))", |
|
peers_tbl); |
|
sqlite3_stmt* st = NULL; |
|
if (sqlite3_prepare_v2(db, sql, -1, &st, NULL) != SQLITE_OK) return -1; |
|
int cnt = 0; |
|
while (sqlite3_step(st) == SQLITE_ROW) cnt++; |
|
if (cnt == 0) { sqlite3_finalize(st); return 0; } |
|
uint64_t* ids = u_malloc((size_t)cnt * sizeof(uint64_t)); |
|
if (!ids) { sqlite3_finalize(st); return -1; } |
|
sqlite3_reset(st); int i = 0; |
|
while (sqlite3_step(st) == SQLITE_ROW) ids[i++] = (uint64_t)sqlite3_column_int64(st, 0); |
|
sqlite3_finalize(st); |
|
*out_ids = ids; *out_count = cnt; |
|
return 0; |
|
} |
|
|
|
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 (!db || !channel_id || !out_ids || !out_count) return -1; |
|
*out_ids = NULL; *out_count = 0; |
|
char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl)); |
|
char sql[384]; 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.deleted=1)", |
|
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)self_node_id); |
|
int cnt = 0; |
|
while (sqlite3_step(st) == SQLITE_ROW) cnt++; |
|
if (cnt == 0) { sqlite3_finalize(st); return 0; } |
|
uint64_t* ids = u_malloc((size_t)cnt * sizeof(uint64_t)); |
|
if (!ids) { sqlite3_finalize(st); return -1; } |
|
sqlite3_reset(st); int i = 0; |
|
while (sqlite3_step(st) == SQLITE_ROW) ids[i++] = (uint64_t)sqlite3_column_int64(st, 0); |
|
sqlite3_finalize(st); |
|
*out_ids = ids; *out_count = cnt; |
|
return 0; |
|
}
|
|
|