#include "topo_node_sqlite.h" #include "../../../src/topo_node.h" #include "../../../lib/debug_config.h" #include #include #define PEERS_JOIN_SIG_SIZE 64 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," " 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," " is_nat INTEGER DEFAULT 0," " created_at INTEGER DEFAULT (unixepoch())" ");" "CREATE INDEX IF NOT EXISTS idx_na_node ON node_addresses(node_id);" "CREATE TABLE IF NOT EXISTS channels (" " channel_id TEXT PRIMARY KEY," " name TEXT NOT NULL," " owner_node_id INTEGER," " is_dm INTEGER DEFAULT 0," " x25519_pubkey BLOB NOT NULL," " x25519_privkey BLOB," " ed25519_pubkey BLOB NOT NULL," " ed25519_privkey BLOB," " signature BLOB NOT NULL," " 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; } DEBUG_INFO(DEBUG_CATEGORY_BGP, "topo_node_sqlite tables initialized"); return 0; } 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 OR REPLACE INTO nodes(node_id, name, x25519_pubkey, ed25519_pubkey, last_seen_at)" " VALUES(?,?,?,?,unixepoch())", -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); } sqlite3_stmt* del_stmt = NULL; if (sqlite3_prepare_v2(db, "DELETE FROM node_addresses WHERE node_id=?", -1, &del_stmt, NULL) == SQLITE_OK) { sqlite3_bind_int64(del_stmt, 1, (sqlite3_int64)ni->node_id); sqlite3_step(del_stmt); 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, is_nat)" " VALUES(?,?,?,?,?,?,?)", -1, &addr_stmt, NULL) == SQLITE_OK) { struct TOPO_ADDR4* a4 = ni->v4_addrs; while (a4) { 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, a4->type == 1 ? 1 : 0); sqlite3_step(addr_stmt); sqlite3_reset(addr_stmt); a4 = a4->next; } struct TOPO_ADDR6* a6 = ni->v6_addrs; while (a6) { 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, a6->type == 1 ? 1 : 0); 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, int is_dm, 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\" (" " node_id INTEGER NOT NULL," " join_sig BLOB NOT NULL," " creator_sig BLOB," " comment TEXT," " joined_at INTEGER DEFAULT (unixepoch())," " PRIMARY KEY (node_id))", 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); } sqlite3_stmt* stmt = NULL; if (sqlite3_prepare_v2(db, "INSERT OR REPLACE INTO channels(channel_id, name, owner_node_id, is_dm," " 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_int(stmt, 4, is_dm); sqlite3_bind_blob(stmt, 5, x25519_pub, 32, SQLITE_STATIC); if (x25519_priv) sqlite3_bind_blob(stmt, 6, x25519_priv, 32, SQLITE_STATIC); else sqlite3_bind_null(stmt, 6); sqlite3_bind_blob(stmt, 7, ed25519_pub, 32, SQLITE_STATIC); if (ed25519_priv) sqlite3_bind_blob(stmt, 8, ed25519_priv, 32, SQLITE_STATIC); else sqlite3_bind_null(stmt, 8); sqlite3_bind_blob(stmt, 9, 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) { 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), "INSERT OR REPLACE INTO \"%s\"(node_id, join_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); static const unsigned char zero_sig[PEERS_JOIN_SIG_SIZE] = {0}; sqlite3_bind_blob(stmt, 2, zero_sig, PEERS_JOIN_SIG_SIZE, SQLITE_STATIC); sqlite3_step(stmt); sqlite3_finalize(stmt); return 0; } 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; }