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.
 
 
 
 
 
 

586 lines
28 KiB

#include "topo_node_sqlite.h"
#include "topo_node.h"
#include "etcp_connections.h"
#include "../lib/debug_config.h"
#include <string.h>
#include <stdio.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);
}
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);
}
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) {
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(?,?,?,?,unixepoch())"
" 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",
-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_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));
char sql[512];
snprintf(sql, sizeof(sql),
"CREATE TABLE IF NOT EXISTS \"%s\" (" // таблица мемберов канала
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
" node_id INTEGER NOT NULL UNIQUE," // ид мембера группы
" x25519_pubkey BLOB NOT NULL," // ключ мембера для шифрования
" ed25519_pubkey BLOB NOT NULL," // ключ мембера для подписи
" join_sig BLOB NOT NULL," // подпись мембера неизменяемая (при присоединении) сh_x25519_pubkey(32) || ch_ed25519_pubkey(32) || node_id(8 LE) || node_x25519_pubkey(32) || join_ts(8 LE)
" join_ts INTEGER NOT NULL," // время присоединения
" update_sig BLOB," // подпись мембера изменяемая join_sig(64) || update_ts(8) || userinfo (NUL-terminated)
" update_ts INTEGER DEFAULT 0,"// время подписи
" userinfo TEXT NOT NULL DEFAULT ''," // имя/метаданные узла в канале (json_flat style)
" creator_sig BLOB," // валидация мембера админом канала
" adm_tags TEXT," // тэги которые может менять админ (json_flat style)
" adm_tags_sig BLOB," // подпись тэгов adm_tags (может быть подписана ключом суперадмина или админов - проверяем перебором)
" node_type INTEGER NOT NULL DEFAULT 0," // ENUM(0-'PRIV',1-'NAT',2-'EIM-NAT',3-'PUBLIC',4-SUPERNODE) тип ноды - private (только нода подключается), nat (односторонние подключения), eim-nat (доступны подключения но надо запрашивать ip), public - на прямых ip (но может быть недоступна в чебурнетах), supernode - на прямых ip, рекомендуемая для подключения
" node_RTT INTEGER" // задержка в ms
")", peers_tbl);
char* err = NULL;
sqlite3_exec(db, sql, NULL, NULL, &err);
if (err) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "topo_node_sqlite_channel_put peers: %s", err); sqlite3_free(err); }
snprintf(sql, sizeof(sql),
"CREATE INDEX IF NOT EXISTS \"idx_%s_type_rtt\" ON \"%s\" (node_type, node_RTT)", peers_tbl, peers_tbl);
sqlite3_exec(db, sql, NULL, NULL, NULL);
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) {
if (!db || !name || !x25519 || !ed25519) return -1;
sqlite3_stmt* stmt = NULL;
if (sqlite3_prepare_v2(db,
"UPDATE nodes SET name=?, x25519_pubkey=?, ed25519_pubkey=?, update_ts=?"
" WHERE node_id=? AND (update_ts IS NULL OR update_ts < ?)", -1, &stmt, NULL) != SQLITE_OK) return -1;
sqlite3_bind_text(stmt, 1, name, -1, SQLITE_STATIC);
sqlite3_bind_blob(stmt, 2, x25519, 32, SQLITE_STATIC);
sqlite3_bind_blob(stmt, 3, ed25519, 32, SQLITE_STATIC);
sqlite3_bind_int64(stmt, 4, (sqlite3_int64)update_ts);
sqlite3_bind_int64(stmt, 5, (sqlite3_int64)node_id);
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;
}