db_sync: LMDB→SQLite, multi-instance, etcp_api, chatgui integration

- src/db_sync: LMDB replaced with SQLite (separate tables per instance: db_sync_name_id)
  TTL index (author, timestamp), WAL mode, per-instance peers/timers
  opaque DB_SYNC_INSTANCE* handle, insert callback, select API
  new fields: author_signature, delivered_peers, delivery_chain
  transport: etcp_api direct P2P (was etcp_router)

- src/db_sync.h: user-oriented API docs, multi-instance lifecycle

- lib/Makefile.am: liblmdb sources removed
- lib/sqlite3.c+h: SQLite amalgamation added

- topo_node: LMDB→SQLite (src/topo_node_sqlite.c moved from chatgui)

- chatgui: real db_sync.c replaces db_sync_stub.c
  chat_core: db_sync_insert_signed + dual-write insert callback
  utun_node: db_sync_init/destroy replaces chat_sync_init/destroy
  CMakeLists: stubs removed, real db_sync.c added
This commit is contained in:
Evgeny committed 2026-07-14 14:17:43 +03:00
1 parent 37417ba49d
commit f617dcd68e
21 files changed
+284328 -1174

No files matched your search

+7 -9
View File
@@ -75,16 +75,14 @@ add_executable(chatgui
transport/chat_core.c
transport/chat_sync.c
transport/member_sync.c
transport/db_sync_stub.c
transport/topo_node_sqlite.c
db/db_manager.cpp
db/sqlite3.c
../../lib/sqlite3.c
resources/chatgui.qrc
)
target_include_directories(chatgui PRIVATE ${CMAKE_SOURCE_DIR}/db)
target_include_directories(chatgui PRIVATE ${CMAKE_SOURCE_DIR}/../../lib ${CMAKE_SOURCE_DIR}/../../src ${CMAKE_SOURCE_DIR}/db)
target_compile_definitions(chatgui PRIVATE SQLITE_THREADSAFE=1 USE_SQLITE)
set_source_files_properties(db/sqlite3.c transport/chat_core.c transport/chat_sync.c transport/member_sync.c transport/db_sync_stub.c PROPERTIES LANGUAGE C)
set_source_files_properties(../../lib/sqlite3.c transport/chat_core.c transport/chat_sync.c transport/member_sync.c PROPERTIES LANGUAGE C)
if(WIN32)
target_link_libraries(chatgui PRIVATE ${QT_LIBS} rlottie::rlottie ZLIB::ZLIB OpenSSL::Crypto ZXing::ZXing utun pthread)
else()
@@ -95,16 +93,16 @@ endif()
# Tests
# ============================================================================
add_executable(test_member_sync
db/sqlite3.c
../../lib/sqlite3.c
../../tests/test_member_sync.c
)
target_include_directories(test_member_sync PRIVATE
transport ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/db
${CMAKE_SOURCE_DIR}/../../src ${CMAKE_SOURCE_DIR}/../../lib
transport ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/../../lib
${CMAKE_SOURCE_DIR}/../../src
zxing-cpp/core/src ../../tests
)
target_compile_definitions(test_member_sync PRIVATE USE_SQLITE SQLITE_THREADSAFE=1)
set_source_files_properties(db/sqlite3.c ../../tests/test_member_sync.c PROPERTIES LANGUAGE C)
set_source_files_properties(../../lib/sqlite3.c ../../tests/test_member_sync.c PROPERTIES LANGUAGE C)
if(WIN32)
target_link_libraries(test_member_sync PRIVATE OpenSSL::Crypto pthread)
else()
+3 -2
View File
@@ -23,6 +23,7 @@ set(UASYNC_SOURCES
${LIB_DIR}/radix.c
${LIB_DIR}/serialize.c
${LIB_DIR}/tcp_io.c
${LIB_DIR}/sqlite3.c
${LIB_DIR}/liblmdb/mdb.c
${LIB_DIR}/liblmdb/midl.c
)
@@ -59,10 +60,10 @@ set(UTUN_COMMON_SOURCES
${SRC_DIR}/topo_group.c
${SRC_DIR}/route_ping.c
${SRC_DIR}/topo_node.c
${SRC_DIR}/topo_node_lmdb.c
${SRC_DIR}/topo_node_sqlite.c
${SRC_DIR}/route_connectivity.c
${SRC_DIR}/db_sync.c
${SRC_DIR}/conn_mgr.c
${TRANSPORT_DIR}/db_sync_stub.c
${TRANSPORT_DIR}/chat_sync.c
${TRANSPORT_DIR}/member_sync.c
${SRC_DIR}/routing.c
+100 -113
View File
@@ -6,7 +6,7 @@
*/
#include "chat_core.h"
#include "chat_sync.h"
#include "db_sync.h"
#include "gui_bridge.h"
#include "topo_node_sqlite.h"
#include "member_sync.h"
@@ -41,12 +41,21 @@ static struct chat_core_ctx {
sqlite3* db;
uint64_t my_node_id;
/* db_sync instances per channel */
struct DB_SYNC_INSTANCE** si;
char** si_ch_id;
int si_count, si_capacity;
/* курсоры (макс 16) */
sqlite3_stmt* cursors[16];
uint32_t next_cursor_id;
uint8_t initialized;
} g_cc;
static struct DB_SYNC_INSTANCE* si_find(const char* ch_id);
static void si_register(struct DB_SYNC_INSTANCE* si, const char* ch_id);
static void on_db_sync_insert(struct DB_SYNC_INSTANCE* si, const char* data, size_t len, uint64_t author, void* arg);
/* ─── утилиты ─── */
static void sanitize_ch_id(const char* ch_id, char* out, size_t out_sz) {
@@ -286,96 +295,46 @@ void chat_core_update_my_name_trampoline(void* arg) {
/* ─── отправка сообщения (GUI → uasync) ─── */
void chat_core_submit_message(struct chat_msg_submit* req) {
if (!g_cc.initialized) {
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTIVITY, "%s: submit_message called before init", CC_ID);
return;
}
if (!g_cc.initialized) { DEBUG_ERROR(DEBUG_CATEGORY_CONNECTIVITY, "%s: submit before init", CC_ID); return; }
if (!req) return;
const char* ch_id = req->channel_id;
const uint8_t* data = req->data;
uint32_t data_len = req->data_len;
uint64_t ts = req->timestamp;
uint64_t dh;
compute_datahash(data, data_len, &dh);
struct DB_SYNC_INSTANCE* si = si_find(req->channel_id);
if (!si) { DEBUG_ERROR(DEBUG_CATEGORY_CONNECTIVITY, "%s: no db_sync instance for ch=%s", CC_ID, req->channel_id); return; }
uint8_t prev_ch[32]; get_last_chain_hash(ch_id, prev_ch);
uint8_t chain_hash[32];
compute_chain_hash(prev_ch, (int64_t)ts, dh, chain_hash);
/* Build JSON: {"n":<node_id>,"ch":"<ch_id>","ct":"<>","d":"<>"} */
char json[4096];
snprintf(json, sizeof(json),
"{\"n\":%llu,\"ch\":\"%s\",\"ct\":\"%s\",\"d\":\"%.*s\"}",
(unsigned long long)g_cc.my_node_id, req->channel_id,
req->content_type, (int)req->data_len, (const char*)req->data);
/* FIXME: JSON escaping for d (quotes/backslashes in data) */
/* For now, naive format; will break on special chars. TODO: use base64. */
char tbl[80]; msg_table_name(ch_id, tbl, sizeof(tbl));
char sql[256]; snprintf(sql, sizeof(sql),
"INSERT OR IGNORE INTO \"%s\""
" (node_id, content_type, data, timestamp, datahash, chain_hash,"
" signature, is_outgoing, is_read)"
" VALUES(?,?,?,?,?,?,?,1,1)", tbl);
sqlite3_stmt* stmt = NULL;
if (sqlite3_prepare_v2(g_cc.db, sql, -1, &stmt, NULL) != SQLITE_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTIVITY, "%s: prepare insert failed: %s",
CC_ID, sqlite3_errmsg(g_cc.db));
return;
}
sqlite3_bind_int64(stmt, 1, (sqlite3_int64)g_cc.my_node_id);
sqlite3_bind_text(stmt, 2, req->content_type, -1, SQLITE_STATIC);
sqlite3_bind_blob(stmt, 3, data, (int)data_len, SQLITE_STATIC);
sqlite3_bind_int64(stmt, 4, (sqlite3_int64)ts);
sqlite3_bind_int64(stmt, 5, (sqlite3_int64)dh);
sqlite3_bind_blob(stmt, 6, chain_hash, 32, SQLITE_STATIC);
{
static const char zero64[64] = {0};
sqlite3_bind_blob(stmt, 7, zero64, 64, SQLITE_STATIC);
}
int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
if (rc == SQLITE_DONE) {
DEBUG_INFO(DEBUG_CATEGORY_CONNECTIVITY, "%s: msg inserted ch=%s ts=%llu dh=0x%016llx",
CC_ID, ch_id, (unsigned long long)ts, (unsigned long long)dh);
/* notify GUI to reload messages */
{ uint8_t evt[65]; uint8_t cl = (uint8_t)strlen(ch_id); evt[0] = cl; memcpy(evt + 1, ch_id, cl);
gui_bridge_post(GUI_EVT_MSG_RECEIVED, evt, 1 + cl); }
/* push пирам */
chat_sync_push(g_cc.inst, ch_id, g_cc.my_node_id,
req->content_type, data, data_len, ts, dh);
} else if (rc == SQLITE_CONSTRAINT) {
DEBUG_INFO(DEBUG_CATEGORY_CONNECTIVITY, "%s: msg duplicate ch=%s ts=%llu",
CC_ID, ch_id, (unsigned long long)ts);
} else {
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTIVITY, "%s: insert msg failed: %s",
CC_ID, sqlite3_errmsg(g_cc.db));
int ret = db_sync_insert_signed(si, json, strlen(json), NULL, 0);
if (ret != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTIVITY, "%s: db_sync_insert_signed failed ret=%d", CC_ID, ret);
/* Insert directly to msg_ table as fallback (dual-write not triggered by callback) */
uint64_t dh; compute_datahash((const uint8_t*)req->data, req->data_len, &dh);
char tbl[80]; msg_table_name(req->channel_id, tbl, sizeof(tbl));
char sql[512]; snprintf(sql, sizeof(sql), "INSERT OR IGNORE INTO \"%s\" (node_id,content_type,data,timestamp,datahash,chain_hash,signature,is_outgoing,is_read) VALUES(?,?,?,?,?,?,?,1,1)", tbl);
sqlite3_stmt* st=NULL;
if (sqlite3_prepare_v2(g_cc.db,sql,-1,&st,NULL)==SQLITE_OK) {
sqlite3_bind_int64(st,1,(sqlite3_int64)g_cc.my_node_id);
sqlite3_bind_text(st,2,req->content_type,-1,SQLITE_STATIC);
sqlite3_bind_blob(st,3,req->data,(int)req->data_len,SQLITE_STATIC);
sqlite3_bind_int64(st,4,(sqlite3_int64)req->timestamp);
sqlite3_bind_int64(st,5,(sqlite3_int64)dh);
static const uint8_t ch32[32]={0}; sqlite3_bind_blob(st,6,ch32,32,SQLITE_STATIC);
static const uint8_t sig64[64]={0}; sqlite3_bind_blob(st,7,sig64,64,SQLITE_STATIC);
sqlite3_step(st); sqlite3_finalize(st);
}
/* notify GUI anyway */
uint8_t evt[65]; uint8_t cl=(uint8_t)strlen(req->channel_id); evt[0]=cl; memcpy(evt+1,req->channel_id,cl);
gui_bridge_post(GUI_EVT_MSG_RECEIVED, evt, 1+cl);
}
}
void chat_core_submit_trampoline(void* arg) {
chat_core_submit_message((struct chat_msg_submit*)arg);
u_free(arg);
}
void chat_core_push_message(struct chat_msg_submit* req) {
if (!g_cc.initialized) {
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTIVITY, "%s: push_message called before init", CC_ID);
return;
}
if (!req) return;
uint64_t dh;
compute_datahash(req->data, req->data_len, &dh);
DEBUG_INFO(DEBUG_CATEGORY_CONNECTIVITY, "%s: pushing msg ch=%s ts=%llu dh=0x%016llx",
CC_ID, req->channel_id, (unsigned long long)req->timestamp, (unsigned long long)dh);
chat_sync_push(g_cc.inst, req->channel_id, g_cc.my_node_id,
req->content_type, req->data, req->data_len,
req->timestamp, dh);
}
void chat_core_push_trampoline(void* arg) {
chat_core_push_message((struct chat_msg_submit*)arg);
u_free(arg);
}
void chat_core_submit_trampoline(void* arg) { chat_core_submit_message((struct chat_msg_submit*)arg); u_free(arg); }
/* ─── DB-операции для chat_sync ─── */
@@ -538,35 +497,6 @@ void chat_core_cursor_close(uint32_t cursor_id) {
}
}
void chat_core_mark_sent(const char* ch_id, uint64_t ts, uint64_t dh,
uint64_t node_id) {
if (!g_cc.initialized) return;
char tbl[80]; msg_table_name(ch_id, tbl, sizeof(tbl));
char sql[256]; snprintf(sql, sizeof(sql),
"UPDATE \"%s\" SET sync_flags=sync_flags|1"
" WHERE timestamp=? AND datahash=? AND node_id=? AND is_outgoing=1", tbl);
sqlite3_stmt* stmt = NULL;
if (sqlite3_prepare_v2(g_cc.db, sql, -1, &stmt, NULL) != SQLITE_OK) return;
sqlite3_bind_int64(stmt, 1, (sqlite3_int64)ts);
sqlite3_bind_int64(stmt, 2, (sqlite3_int64)dh);
sqlite3_bind_int64(stmt, 3, (sqlite3_int64)node_id);
sqlite3_step(stmt); sqlite3_finalize(stmt);
}
void chat_core_ttl_delete(const char* ch_id, uint64_t node_id,
uint64_t cutoff_us) {
if (!g_cc.initialized) return;
char tbl[80]; msg_table_name(ch_id, tbl, sizeof(tbl));
char sql[256]; snprintf(sql, sizeof(sql),
"DELETE FROM \"%s\" WHERE node_id=? AND is_outgoing=1 AND (sync_flags&1)=0 AND timestamp<?",
tbl);
sqlite3_stmt* stmt = NULL;
if (sqlite3_prepare_v2(g_cc.db, sql, -1, &stmt, NULL) != SQLITE_OK) return;
sqlite3_bind_int64(stmt, 1, (sqlite3_int64)node_id);
sqlite3_bind_int64(stmt, 2, (sqlite3_int64)cutoff_us);
sqlite3_step(stmt); sqlite3_finalize(stmt);
}
int chat_core_list_channels(uint8_t* buf, size_t buf_size, size_t* out_len) {
if (!g_cc.initialized || !buf || !out_len) return -1;
sqlite3_stmt* stmt = NULL;
@@ -955,6 +885,21 @@ void chat_core_create_channel(struct chat_channel_create* req) {
tbl_msg, tbl_msg);
db_exec(sql);
/* Register with db_sync for message sync */
{
/* Compute a deterministic hash from channel_id for db_sync instance id */
uint64_t ch_hash = 0;
{ const uint8_t* chd = (const uint8_t*)req->channel_id; size_t chl = strlen(req->channel_id); uint8_t sh[32]; SHA256(chd, chl, sh); memcpy(&ch_hash, sh, 8); }
struct DB_SYNC_INSTANCE* si = db_sync_instance_add(g_cc.inst, "chats", ch_hash);
if (si) {
si_register(si, req->channel_id);
db_sync_set_insert_cb(si, on_db_sync_insert, u_strdup(req->channel_id));
DEBUG_INFO(DEBUG_CATEGORY_CONNECTIVITY, "%s: db_sync instance added for ch=%s hash=0x%016llx", CC_ID, req->channel_id, (unsigned long long)ch_hash);
} else {
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTIVITY, "%s: db_sync_instance_add failed for ch=%s", CC_ID, req->channel_id);
}
}
/* записываем канал в БД */
int rc = topo_node_sqlite_channel_put(g_cc.db,
req->channel_id, req->name, req->is_dm, req->owner_node_id,
@@ -1015,3 +960,45 @@ void chat_core_create_channel_trampoline(void* arg) {
chat_core_create_channel(req);
u_free(req);
}
/* ─── db_sync helpers ─── */
static struct DB_SYNC_INSTANCE* si_find(const char* ch_id) {
for (int i=0; i<g_cc.si_count; i++) if (strcmp(g_cc.si_ch_id[i], ch_id)==0) return g_cc.si[i];
return NULL;
}
static void si_register(struct DB_SYNC_INSTANCE* si, const char* ch_id) {
if (g_cc.si_count>=g_cc.si_capacity) { int nc=g_cc.si_capacity?g_cc.si_capacity*2:8; g_cc.si=u_realloc(g_cc.si,nc*sizeof(void*)); g_cc.si_ch_id=u_realloc(g_cc.si_ch_id,nc*sizeof(char*)); g_cc.si_capacity=nc; }
g_cc.si[g_cc.si_count]=si; g_cc.si_ch_id[g_cc.si_count]=u_strdup(ch_id); g_cc.si_count++;
}
static void on_db_sync_insert(struct DB_SYNC_INSTANCE* si, const char* data, size_t len, uint64_t author, void* arg) {
const char* ch_id = (const char*)arg;
uint64_t dh; compute_datahash((const uint8_t*)data, len, &dh);
uint64_t ts = get_time_us();
char tbl[80]; msg_table_name(ch_id, tbl, sizeof(tbl));
char sql[512]; snprintf(sql, sizeof(sql),
"INSERT OR IGNORE INTO \"%s\" (node_id,content_type,data,timestamp,datahash,chain_hash,signature,is_outgoing,is_read,sync_flags)"
" VALUES(?,?,?,?,?,?,?,?,?,?)", tbl);
sqlite3_stmt* st=NULL;
if (sqlite3_prepare_v2(g_cc.db,sql,-1,&st,NULL)!=SQLITE_OK) return;
sqlite3_bind_int64(st,1,(sqlite3_int64)author);
sqlite3_bind_text(st,2,"text/plain",-1,SQLITE_STATIC);
sqlite3_bind_blob(st,3,data,(int)len,SQLITE_STATIC);
sqlite3_bind_int64(st,4,(sqlite3_int64)ts);
sqlite3_bind_int64(st,5,(sqlite3_int64)dh);
{ static const uint8_t z32[32]={0}; sqlite3_bind_blob(st,6,z32,32,SQLITE_STATIC); }
{ static const uint8_t z64[64]={0}; sqlite3_bind_blob(st,7,z64,64,SQLITE_STATIC); }
sqlite3_bind_int(st,8,author==g_cc.my_node_id?1:0);
sqlite3_bind_int(st,9,1); sqlite3_bind_int(st,10,0);
int rc=sqlite3_step(st); sqlite3_finalize(st);
if (rc==SQLITE_DONE) {
uint8_t evt[65]; uint8_t cl=(uint8_t)strlen(ch_id); evt[0]=cl; memcpy(evt+1,ch_id,cl);
gui_bridge_post(GUI_EVT_MSG_RECEIVED, evt, 1+cl);
}
}
/* ─── stubs for chat_sync compatibility (db_sync handles this now) ─── */
void chat_core_mark_sent(const char* ch_id, uint64_t ts, uint64_t dh, uint64_t peer_id) { (void)ch_id; (void)ts; (void)dh; (void)peer_id; }
void chat_core_ttl_delete(const char* ch_id, uint64_t node_id, uint64_t cutoff_us) { (void)ch_id; (void)node_id; (void)cutoff_us; }
+4 -12
View File
@@ -31,20 +31,13 @@ struct chat_msg_submit {
void chat_core_submit_message(struct chat_msg_submit* req);
/* ── Push-only (без вставки в БД, только рассылка пирам) ── */
/* ── Stubs for chat_sync compatibility (todo: remove when chat_sync is fully replaced) ── */
void chat_core_push_message(struct chat_msg_submit* req);
void chat_core_mark_sent(const char* ch_id, uint64_t ts, uint64_t dh, uint64_t peer_id);
void chat_core_ttl_delete(const char* ch_id, uint64_t node_id, uint64_t cutoff_us);
/* ── DB-операции для chat_sync ── */
/* ── DB-операции (оставлены для интроспекции) ── */
uint32_t chat_core_count(const char* ch_id);
int chat_core_chain_hash_at(const char* ch_id, uint32_t pos, uint8_t* hash_out);
int chat_core_insert_record(const char* ch_id, const uint8_t* rec, size_t len);
uint32_t chat_core_cursor_open(const char* ch_id);
int chat_core_cursor_next(uint32_t cursor_id, uint8_t* buf, size_t buf_size, size_t* out_len);
void chat_core_cursor_close(uint32_t cursor_id);
void chat_core_mark_sent(const char* ch_id, uint64_t ts, uint64_t dh, uint64_t node_id);
void chat_core_ttl_delete(const char* ch_id, uint64_t node_id, uint64_t cutoff_us);
int chat_core_list_channels(uint8_t* buf, size_t buf_size, size_t* out_len);
int chat_core_list_peers(const char* ch_id, uint8_t* buf, size_t buf_size, size_t* out_len);
int chat_core_load_nodeinfo(uint64_t node_id, uint8_t* buf, size_t buf_size, size_t* out_len);
@@ -86,6 +79,5 @@ void chat_core_update_my_name_trampoline(void* arg);
/* Трамплин для gui_bridge_post_uasync (GUI → uasync) */
void chat_core_submit_trampoline(void* arg);
void chat_core_push_trampoline(void* arg);
#endif /* CHAT_CORE_H */
-321
View File
@@ -1,321 +0,0 @@
#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,"
" online 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,"
" 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;
}
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;
}
@@ -1,37 +0,0 @@
#ifndef TOPO_NODE_SQLITE_H
#define TOPO_NODE_SQLITE_H
#include <sqlite3.h>
#include <stdint.h>
#include <stddef.h>
struct TOPO_NODEQ;
#define PEERS_JOIN_SIG_SIZE 64
int topo_node_sqlite_init(sqlite3* db);
int topo_node_sqlite_node_put(sqlite3* db, struct TOPO_NODEQ* nq);
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);
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);
int topo_node_sqlite_member_del(sqlite3* db, const char* channel_id, uint64_t node_id);
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);
int topo_node_sqlite_channel_peers_all(sqlite3* db, const char* channel_id,
uint8_t* buf, size_t buf_sz, size_t* out_len);
int topo_node_sqlite_node_set_online(sqlite3* db, uint64_t node_id, int online);
int topo_node_sqlite_node_get_online(sqlite3* db, uint64_t node_id);
#endif
+5 -4
View File
@@ -22,6 +22,7 @@ extern "C" {
#include "../lib/mem.h"
#include "chat_sync.h"
#include "chat_core.h"
#include "db_sync.h"
#include "gui_bridge.h"
}
@@ -237,10 +238,10 @@ void UtunNode::runLoop() {
etcp_router_bind(m_instance, ETCP_RT_ID_CHAT, recvCallback);
/* Initialize chat_core (DB) and chat_sync */
/* Initialize chat_core (DB) and db_sync (message sync) */
chat_core_init(m_instance, m_dbPath.toUtf8().constData());
chat_sync_init(m_instance, NULL);
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "chat_core + chat_sync initialized");
db_sync_init(m_instance);
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "chat_core + db_sync initialized");
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "utun_node: entering poll loop");
while (!m_stop) {
@@ -248,7 +249,7 @@ void UtunNode::runLoop() {
}
etcp_router_bind(m_instance, ETCP_RT_ID_CHAT, nullptr);
chat_sync_destroy(m_instance);
db_sync_destroy(m_instance);
chat_core_destroy(m_instance);
utun_instance_destroy(m_instance);
m_instance = nullptr;