#include "topo_node_sqlite.h" #include "topo_node.h" #include "topo_group.h" #include "etcp_connections.h" #include "../lib/debug_config.h" #include "../lib/mem.h" #include "../lib/memory_pool.h" #include #include #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); } 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," " 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," " 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 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," // для скроллинга чата " 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; } { 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) { char isql[256]; while (sqlite3_step(s) == SQLITE_ROW) { const char* tbl = (const char*)sqlite3_column_text(s, 0); snprintf(isql, sizeof(isql), "CREATE INDEX IF NOT EXISTS \"idx_%s_type_rtt\" ON \"%s\" (node_type, node_RTT)", tbl, tbl); sqlite3_exec(db, isql, NULL, NULL, NULL); snprintf(isql, sizeof(isql), "ALTER TABLE \"%s\" ADD COLUMN connected INTEGER NOT NULL DEFAULT 0", tbl); sqlite3_exec(db, isql, NULL, NULL, NULL); } sqlite3_finalize(s); } } DEBUG_INFO(DEBUG_CATEGORY_BGP, "topo_node_sqlite tables initialized"); return 0; } static uint8_t collect_socket_ids(struct TOPO_NODE* ni, uint8_t* sock_ids) { uint8_t count = 0; struct TOPO_SOCKMETA4* sm4 = ni->v4_sock_meta; while (sm4) { uint8_t i; for (i = 0; i < count; i++) if (sock_ids[i] == sm4->id) break; if (i == count) sock_ids[count++] = sm4->id; if (count < 510) sock_ids[count++] = sm4->id | 1; /* NAT slave slot, LSB=1 */ sm4 = sm4->next; } struct TOPO_SOCKMETA6* sm6 = ni->v6_sock_meta; while (sm6) { uint8_t i; for (i = 0; i < count; i++) if (sock_ids[i] == sm6->id) break; if (i == count) sock_ids[count++] = sm6->id; if (count < 510) sock_ids[count++] = sm6->id | 1; sm6 = sm6->next; } return count; } int topo_node_sqlite_node_put(sqlite3* db, struct TOPO_NODEQ* nq, time_t now_sec) { if (!db || !nq || !nq->node) return -1; struct TOPO_NODE* ni = nq->node; sqlite3_exec(db, "BEGIN", NULL, NULL, NULL); sqlite3_stmt* stmt = NULL; if (sqlite3_prepare_v2(db, "INSERT INTO nodes(node_id, name, x25519_pubkey, ed25519_pubkey, last_seen_at)" " VALUES(?,?,?,?,?)" " ON CONFLICT(node_id) DO UPDATE SET" " name=excluded.name, x25519_pubkey=excluded.x25519_pubkey," " ed25519_pubkey=excluded.ed25519_pubkey, last_seen_at=excluded.last_seen_at,\n" " created_at=COALESCE(nodes.created_at, excluded.created_at)", -1, &stmt, NULL) == SQLITE_OK) { sqlite3_bind_int64(stmt, 1, (sqlite3_int64)ni->node_id); sqlite3_bind_text(stmt, 2, ni->node_name ? ni->node_name : "", -1, SQLITE_STATIC); sqlite3_bind_blob(stmt, 3, ni->public_key, 32, SQLITE_STATIC); sqlite3_bind_blob(stmt, 4, ni->ed25519_public_key, 32, SQLITE_STATIC); sqlite3_bind_int64(stmt, 5, (sqlite3_int64)now_sec); sqlite3_step(stmt); sqlite3_finalize(stmt); } /* collect current socket_ids */ uint8_t sock_ids[510]; /* max: 255 v4 + 255 v6, enough */ uint8_t sock_count = collect_socket_ids(ni, sock_ids); /* delete stale addresses for socket_ids no longer present */ { char del_sql[256]; if (sock_count > 0) { int pos = snprintf(del_sql, sizeof(del_sql), "DELETE FROM node_addresses WHERE node_id=? AND socket_id NOT IN ("); for (uint8_t i = 0; i < sock_count; i++) { if (i > 0) pos += snprintf(del_sql + pos, sizeof(del_sql) - (size_t)pos, ","); pos += snprintf(del_sql + pos, sizeof(del_sql) - (size_t)pos, "%d", (int)sock_ids[i]); } pos += snprintf(del_sql + pos, sizeof(del_sql) - (size_t)pos, ") AND socket_id != 0"); } else { snprintf(del_sql, sizeof(del_sql), "DELETE FROM node_addresses WHERE node_id=? AND socket_id != 0"); } sqlite3_stmt* del_stmt = NULL; if (sqlite3_prepare_v2(db, del_sql, -1, &del_stmt, NULL) == SQLITE_OK) { sqlite3_bind_int64(del_stmt, 1, (sqlite3_int64)ni->node_id); sqlite3_step(del_stmt); int deled = sqlite3_changes(db); if (deled > 0) DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "BGP: [ADDR_SYNC] topo_node_sqlite_node_put DELETE stale sock_ids node=0x%016llx sock_count=%d deleted=%d", (unsigned long long)ni->node_id, sock_count, deled); sqlite3_finalize(del_stmt); } } sqlite3_stmt* addr_stmt = NULL; if (sqlite3_prepare_v2(db, "INSERT INTO node_addresses(node_id, family, protocol, address, port, rtt, addr_type, socket_id)" " VALUES(?,?,?,?,?,?,?,?)", -1, &addr_stmt, NULL) == SQLITE_OK) { struct TOPO_ADDR4* a4 = ni->v4_addrs; while (a4) { int at = ADDR_TYPE_NETIF; if (a4->type == TOPO_ADDR_NAT) { struct TOPO_SOCKMETA4* sm = ni->v4_sock_meta; while (sm) { if (sm->id == (a4->socket_id & ~1)) break; sm = sm->next; } if (sm && (sm->nat_type == NAT_TYPE_STRICT || sm->nat_type == NAT_VERIFIED_STRICT)) at = ADDR_TYPE_NAT_STRICT; else at = ADDR_TYPE_NAT_EIM; } else { struct TOPO_SOCKMETA4* sm = ni->v4_sock_meta; while (sm) { if (sm->id == a4->socket_id) break; sm = sm->next; } if (sm && (sm->nat_type == NAT_TYPE_DIRECT || sm->nat_type == NAT_VERIFIED_DIRECT)) at = ADDR_TYPE_DIRECT; } sqlite3_bind_int64(addr_stmt, 1, (sqlite3_int64)ni->node_id); sqlite3_bind_int(addr_stmt, 2, 4); sqlite3_bind_int(addr_stmt, 3, a4->protocol); sqlite3_bind_blob(addr_stmt, 4, a4->addr, 4, SQLITE_STATIC); sqlite3_bind_int(addr_stmt, 5, a4->port); sqlite3_bind_null(addr_stmt, 6); sqlite3_bind_int(addr_stmt, 7, at); sqlite3_bind_int(addr_stmt, 8, (int)a4->socket_id); sqlite3_step(addr_stmt); sqlite3_reset(addr_stmt); a4 = a4->next; } struct TOPO_ADDR6* a6 = ni->v6_addrs; while (a6) { /* filter link-local IPv6 */ if (a6->addr[0] == 0xFE && (a6->addr[1] & 0xC0) == 0x80) { a6 = a6->next; continue; } int at = ADDR_TYPE_NETIF; if (a6->type == TOPO_ADDR_NAT) { struct TOPO_SOCKMETA6* sm = ni->v6_sock_meta; while (sm) { if (sm->id == (a6->socket_id & ~1)) break; sm = sm->next; } if (sm && (sm->nat_type == NAT_TYPE_STRICT || sm->nat_type == NAT_VERIFIED_STRICT)) at = ADDR_TYPE_NAT_STRICT; else at = ADDR_TYPE_NAT_EIM; } else { struct TOPO_SOCKMETA6* sm = ni->v6_sock_meta; while (sm) { if (sm->id == a6->socket_id) break; sm = sm->next; } if (sm && (sm->nat_type == NAT_TYPE_DIRECT || sm->nat_type == NAT_VERIFIED_DIRECT)) at = ADDR_TYPE_DIRECT; } sqlite3_bind_int64(addr_stmt, 1, (sqlite3_int64)ni->node_id); sqlite3_bind_int(addr_stmt, 2, 6); sqlite3_bind_int(addr_stmt, 3, a6->protocol); sqlite3_bind_blob(addr_stmt, 4, a6->addr, 16, SQLITE_STATIC); sqlite3_bind_int(addr_stmt, 5, a6->port); sqlite3_bind_null(addr_stmt, 6); sqlite3_bind_int(addr_stmt, 7, at); sqlite3_bind_int(addr_stmt, 8, (int)a6->socket_id); sqlite3_step(addr_stmt); sqlite3_reset(addr_stmt); a6 = a6->next; } sqlite3_finalize(addr_stmt); } sqlite3_exec(db, "COMMIT", NULL, NULL, NULL); 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":""} */ " creator_sig BLOB," /* Ed25519 подпись создателя (PEERS_JOIN_SIG_SIZE=64), пока заглушка */ " adm_tags TEXT," /* теги администратора, key=value через пробел/запятую. сейчас: "supernode=yes" → node_type=4 */ " 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+"supernode=yes") */ " node_RTT INTEGER," /* RTT до узла в мс, часть индекса (node_type, node_RTT) */ " connected INTEGER NOT NULL DEFAULT 0" /* 1 = было активное подключение к узлу */ ")"; 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); } 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; } int topo_node_sqlite_member_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[256]; snprintf(sql, sizeof(sql), "INSERT OR REPLACE INTO \"%s\"(node_id, x25519_pubkey, ed25519_pubkey," " join_sig, join_ts, update_sig, update_ts, userinfo, creator_sig)" " VALUES(?,?,?,?,?,?,?,?,?)", 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; } 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)" " VALUES(?,?,?,?,?,?)" " 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_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" " 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); if (!x25519 || !ed_pub) continue; uint8_t nl = puserinfo ? (uint8_t)strnlen(puserinfo, 255) : 0; sqlite3_stmt* ast = NULL; snprintf(sql, sizeof(sql), "SELECT family, socket_id, address, port FROM node_addresses" " WHERE node_id=? AND addr_type=0 LIMIT 16"); if (sqlite3_prepare_v2(db, sql, -1, &ast, NULL) != SQLITE_OK) continue; sqlite3_bind_int64(ast, 1, (sqlite3_int64)node_id); uint8_t addr_cnt = 0; size_t addr_start = 0; while (sqlite3_step(ast) == SQLITE_ROW && addr_cnt < 255) { int family = sqlite3_column_int(ast, 0); int sock_id = sqlite3_column_int(ast, 1); int ip_len = (family == 4) ? 4 : 16; const uint8_t* ip = (const uint8_t*)sqlite3_column_blob(ast, 2); uint16_t port = (uint16_t)sqlite3_column_int(ast, 3); if (!ip) continue; if (off + 1 + 1 + ip_len + 2 > buf_sz) { sqlite3_finalize(ast); sqlite3_finalize(stmt); return -2; } if (addr_cnt == 0) addr_start = off; buf[off++] = (uint8_t)family; buf[off++] = (uint8_t)sock_id; memcpy(buf + off, ip, (size_t)ip_len); off += (size_t)ip_len; buf[off++] = (uint8_t)((port >> 8) & 0xFF); buf[off++] = (uint8_t)(port & 0xFF); addr_cnt++; } sqlite3_finalize(ast); 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 addr_sz = off - addr_start; if (addr_cnt > 0) { if (off + peer_needed > buf_sz) { sqlite3_finalize(stmt); return -2; } memmove(buf + addr_start + peer_needed, buf + addr_start, addr_sz); off = addr_start; } else { if (off + peer_needed + 1 > buf_sz) { sqlite3_finalize(stmt); return -2; } addr_sz = 0; } 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++] = addr_cnt; off += addr_sz; (*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) { 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 }; 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; if (best_at == ADDR_TYPE_DIRECT && base_type == 1) { 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 && strstr(tags, "supernode=yes")) node_type = 4; } sqlite3_finalize(a); } } snprintf(sql, sizeof(sql), "UPDATE \"%s\" SET node_type=? 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_int64(u, 2, (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; int found = 0; { sqlite3_stmt* s = NULL; if (sqlite3_prepare_v2(db, "SELECT name,x25519_pubkey,ed25519_pubkey 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); found = 1; } sqlite3_finalize(s); } } if (!found) { u_free(name); return NULL; } struct TOPO_ADDR4* v4_head = NULL; struct TOPO_ADDR4* v4_tail = NULL; uint8_t sock_id_counter = 0; { sqlite3_stmt* s = NULL; if (sqlite3_prepare_v2(db, "SELECT address,port,addr_type,socket_id FROM node_addresses" " WHERE node_id=? AND family=4 AND protocol=1 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)); memcpy(a4->addr, addr, 4); a4->port = (uint16_t)port; a4->type = type; a4->socket_id = sock_id; a4->protocol = TOPO_PROTO_UDP; if (!v4_head) v4_head = v4_tail = a4; else { v4_tail->next = a4; v4_tail = a4; } } sqlite3_finalize(s); } } if (!v4_head) { u_free(name); DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node_load: node 0x%016llx has no IPv4/UDP addresses", (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->ref_count = 0; memcpy(ni->public_key, pubkey, 32); memcpy(ni->ed25519_public_key, ed_pubkey, 32); ni->node_name = name; ni->v4_addrs = v4_head; DEBUG_INFO(DEBUG_CATEGORY_BGP, "node_load: node 0x%016llx loaded from DB name=%s v4_addrs=%d", (unsigned long long)node_id, name ? name : "", topo_list_count((struct _topo_head*)v4_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[128]; snprintf(sql, sizeof(sql), "SELECT node_id FROM \"%s\" WHERE connected=1", 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[300]; snprintf(sql, sizeof(sql), "SELECT p.node_id FROM \"%s\" p" " WHERE p.node_id!=? AND EXISTS (SELECT 1 FROM node_addresses a" " WHERE a.node_id=p.node_id AND a.family=4 AND a.addr_type IN (1,2)))", 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_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[300]; snprintf(sql, sizeof(sql), "SELECT p.node_id FROM \"%s\" p" " WHERE p.node_id!=? AND EXISTS (SELECT 1 FROM node_addresses a" " WHERE a.node_id=p.node_id AND a.family=4 AND a.addr_type IN (0,3)))", 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; }