- debug_ui.h: remove NDEBUG guard so GUI_ERROR works in Release builds - invite_link.h/cpp: add error field to InviteData with specific failure reason - joindialog: show exact decode error in QLabel instead of generic message - chat_sync: implement invite connect flow (CHANNEL_INFO_REQ/RESP, JOIN, WELCOME) - topo_node_sqlite: node/address table init and lookup - utun_node: getInviteAddresses fallback from ETCP sockets - minor fixes in mainwindow, chat_core, gui_bridge
298 lines
13 KiB
C
298 lines
13 KiB
C
#include "topo_node_sqlite.h"
|
|
#include "../../../src/topo_node.h"
|
|
#include "../../../lib/debug_config.h"
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
|
|
#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,
|
|
const uint8_t* join_sig, const uint8_t* creator_sig) {
|
|
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, 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);
|
|
if (join_sig) sqlite3_bind_blob(stmt, 2, join_sig, PEERS_JOIN_SIG_SIZE, SQLITE_STATIC);
|
|
else { static const unsigned char zsig[PEERS_JOIN_SIG_SIZE] = {0}; sqlite3_bind_blob(stmt, 2, zsig, PEERS_JOIN_SIG_SIZE, SQLITE_STATIC); }
|
|
if (creator_sig) sqlite3_bind_blob(stmt, 3, creator_sig, PEERS_JOIN_SIG_SIZE, SQLITE_STATIC);
|
|
else sqlite3_bind_null(stmt, 3);
|
|
int rc = sqlite3_step(stmt); sqlite3_finalize(stmt);
|
|
return rc == SQLITE_DONE ? 0 : -1;
|
|
}
|
|
|
|
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, int* is_dm, 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, is_dm, 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 (is_dm) *is_dm = sqlite3_column_int(stmt, 2);
|
|
if (x25519_pub) memcpy(x25519_pub, sqlite3_column_blob(stmt, 3), 32);
|
|
if (ed25519_pub) memcpy(ed25519_pub, sqlite3_column_blob(stmt, 4), 32);
|
|
if (signature) memcpy(signature, sqlite3_column_blob(stmt, 5), 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[256];
|
|
snprintf(sql, sizeof(sql),
|
|
"SELECT p.node_id, p.join_sig, p.creator_sig,"
|
|
" n.x25519_pubkey, n.ed25519_pubkey"
|
|
" FROM \"%s\" p LEFT JOIN nodes n ON p.node_id = n.node_id", 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* join_sig = (const uint8_t*)sqlite3_column_blob(stmt, 1);
|
|
const uint8_t* x25519 = (const uint8_t*)sqlite3_column_blob(stmt, 3);
|
|
const uint8_t* ed_pub = (const uint8_t*)sqlite3_column_blob(stmt, 4);
|
|
if (!join_sig || !x25519 || !ed_pub) continue;
|
|
|
|
sqlite3_stmt* ast = NULL;
|
|
snprintf(sql, sizeof(sql),
|
|
"SELECT family, address, port FROM node_addresses"
|
|
" WHERE node_id=? AND is_nat=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 ip_len = (family == 4) ? 4 : 16;
|
|
const uint8_t* ip = (const uint8_t*)sqlite3_column_blob(ast, 1);
|
|
uint16_t port = (uint16_t)sqlite3_column_int(ast, 2);
|
|
if (!ip) continue;
|
|
if (off + 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;
|
|
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);
|
|
if (addr_cnt == 0) continue;
|
|
|
|
size_t peer_needed = 8 + 32 + 32 + 64 + 1;
|
|
size_t addr_sz = off - addr_start;
|
|
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;
|
|
memcpy(buf + off, &node_id, 8); off += 8;
|
|
memcpy(buf + off, x25519, 32); off += 32;
|
|
memcpy(buf + off, ed_pub, 32); off += 32;
|
|
memcpy(buf + off, join_sig, 64); off += 64;
|
|
buf[off++] = addr_cnt;
|
|
off += addr_sz;
|
|
(*cnt_ptr)++;
|
|
}
|
|
sqlite3_finalize(stmt);
|
|
*out_len = off;
|
|
return 0;
|
|
}
|