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

+4 -2
View File
@@ -12,7 +12,7 @@ utun_CORE_SOURCES = \
topo_group.c \
route_ping.c \
topo_node.c \
topo_node_lmdb.c \
topo_node_sqlite.c \
route_connectivity.c \
conn_mgr.c \
db_sync.c \
@@ -65,7 +65,7 @@ libutun_a_SOURCES = \
topo_group.c \
route_ping.c \
topo_node.c \
topo_node_lmdb.c \
topo_node_sqlite.c \
route_connectivity.c \
conn_mgr.c \
db_sync.c \
@@ -119,6 +119,8 @@ utun_CFLAGS = \
-I$(top_srcdir)/lib \
-I$(top_srcdir)/src/uip \
-g \
-DUSE_SQLITE \
-DSQLITE_THREADSAFE=1 \
$(DEBUG_FLAGS)
# Libraries
+1 -1
View File
@@ -114,7 +114,7 @@ struct global_config {
// Debug and logging configuration
char log_file[256]; // Path to log file (empty = stdout)
char db_path[256]; // Path to LMDB nodeinfo database (empty = disabled)
char db_path[256]; // Path to SQLite database (empty = disabled)
int db_sync_enabled; // 1 = enable distributed DB sync (default: 0)
uint32_t db_sync_ttl; // TTL for unsent records in seconds (default: 86400)
char debug_level[16]; // debug level: error, warn, info, debug, trace
+309 -870
View File
File diff suppressed because it is too large. Load diff
+63 -5
View File
@@ -1,4 +1,33 @@
// db_sync.h — Distributed content-addressed table with LMDB storage and peer sync via etcp_router
// db_sync.h — Distributed content-addressed table with SQLite + peer sync via etcp_api (direct P2P)
//
// Назначение: децентрализованная реплицируемая таблица JSON-записей между всеми узлами сети.
// Модуль поддерживает несколько независимых инстансов (таблиц), каждый идентифицируется парой (name, id).
//
// Использование:
// 1. Включить в конфиге: db_sync_enabled = 1
// Опционально: db_sync_ttl = 86400 (по умолчанию)
// 2. db_sync_init() — вызывается автоматически при старте utun_instance
// 3. struct DB_SYNC_INSTANCE* si = db_sync_instance_add(inst, "chats", 1);
// Создаёт/регистрирует инстанс. При поднятии соединений автоматически запускает sync.
// 4. db_sync_insert(si, json_data) — вставить запись. Автоматически PUSH всем synced-пирам.
// 5. db_sync_count(si) — количество записей в локальной БД для этого инстанса
// 6. db_sync_instance_remove(si) — удалить инстанс (таблица БД не удаляется)
// 7. db_sync_destroy() — вызывается автоматически при завершении
//
// Синхронизация:
// - При поднятии ETCP-соединения с пиром для каждого инстанса запускается полная синхронизация
// - Каждое сообщение содержит instance_hash (первые 64 бита SHA256(name||id_be)),
// что позволяет маршрутизировать сообщения к нужному инстансу на приёмной стороне
// - Если пир не имеет инстанса с таким хешем — возвращает DB_MSG_ERROR(0x01)
// - Если пир добавляет инстанс позже — сам инициирует sync в нашу сторону
// - Протокол: сравнение chain_hash для поиска расхождений (7 типов сообщений)
// - Новые записи немедленно рассылаются подключённым пирам через PUSH
//
// Нюансы:
// - Записи не редактируются и не удаляются явно — только TTL-очистка (per-instance)
// - Дубликаты определяются по (instance_hash, timestamp, datahash)
// - БД хранится в SQLite, путь: <db_path>/sync
// - Синхронизация идёт через etcp_api (service ID 0x20), только прямые P2P соединения
#ifndef DB_SYNC_H
#define DB_SYNC_H
@@ -6,11 +35,11 @@
extern "C" {
#endif
#include <stdint.h>
#include <stddef.h>
struct UTUN_INSTANCE;
struct DB_SYNC_INSTANCE;
// etcp_router service ID
#define ETCP_RT_ID_DB_SYNC 0x20
@@ -23,6 +52,11 @@ struct UTUN_INSTANCE;
#define DB_MSG_PUSH 0x05
#define DB_MSG_ACK_PUSH 0x06
#define DB_MSG_SYNC_DONE 0x07
#define DB_MSG_ERROR 0x08
// Error codes for DB_MSG_ERROR
#define DB_ERR_NOT_FOUND 0x01 // instance not found
#define DB_ERR_DISABLED 0x02 // instance exists but disabled
// Defaults
#define DB_SYNC_DEFAULT_TTL 86400
@@ -37,12 +71,36 @@ struct UTUN_INSTANCE;
#define DB_REFINE_HASHES 16
#define DB_SEND_DATA_MAX 32
// Global lifecycle
int db_sync_init(struct UTUN_INSTANCE* inst);
void db_sync_destroy(struct UTUN_INSTANCE* inst);
int db_sync_insert(struct UTUN_INSTANCE* inst, const char* json_data);
int db_sync_insert_len(struct UTUN_INSTANCE* inst, const char* json_data, size_t len);
uint32_t db_sync_count(struct UTUN_INSTANCE* inst);
// Instance management
struct DB_SYNC_INSTANCE* db_sync_instance_add(struct UTUN_INSTANCE* inst, const char* name, uint64_t id);
void db_sync_instance_remove(struct DB_SYNC_INSTANCE* si);
// Data operations (per-instance)
int db_sync_insert_len(struct DB_SYNC_INSTANCE* si, const char* json_data, size_t len);
int db_sync_insert(struct DB_SYNC_INSTANCE* si, const char* json_data);
int db_sync_insert_signed(struct DB_SYNC_INSTANCE* si, const char* json_data, size_t len,
const uint8_t* sig, size_t sig_len);
uint32_t db_sync_count(struct DB_SYNC_INSTANCE* si);
// Select: iterate records ordered by (timestamp, datahash), starting at offset, max limit (0=unlimited).
// Returns number of records passed to callback.
typedef void (*db_sync_select_cb)(void* arg, uint64_t id, uint64_t timestamp,
const char* data, size_t data_len, uint64_t author,
const uint8_t* author_sig, size_t sig_len,
int delivered_peers, const char* delivery_chain);
int db_sync_select(struct DB_SYNC_INSTANCE* si, uint32_t offset, uint32_t limit,
db_sync_select_cb cb, void* arg);
// Insert callback: fired after local or peer insert succeeds.
// author_node_id = self for local inserts, peer node_id for remote.
typedef void (*db_sync_insert_cb)(struct DB_SYNC_INSTANCE* si,
const char* json_data, size_t len,
uint64_t author_node_id, void* arg);
void db_sync_set_insert_cb(struct DB_SYNC_INSTANCE* si, db_sync_insert_cb cb, void* arg);
#ifdef __cplusplus
}
+23 -20
View File
@@ -16,15 +16,12 @@
#include "etcp_debug.h"
#include "config_parser.h"
#include "topo_node.h"
#include "topo_node_lmdb.h"
#include "topo_node_sqlite.h"
#include "route_lib.h"
#include "topo_group.h"
#include "route_ping.h"
#include "route_connectivity.h"
#include "control_server.h"
#ifdef USE_SQLITE
#include "../../tools/chatgui/transport/topo_node_sqlite.h"
#endif
#define TOPO_GROUP_UTUN 0x8000000000000000ULL
@@ -224,9 +221,6 @@ static struct TOPO_GROUP* topo_group_create(struct UTUN_INSTANCE* instance, uint
sc_derive_ed25519_pubkey(instance->my_keys.private_key, group->ed25519_public_key);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Ed25519 pubkey derived from X25519 private key");
if (group_type == TOPO_GROUP_TYPE_UTUN && instance->config && instance->config->global.db_path[0])
topo_node_lmdb_init(group, instance->config->global.db_path);
group->nodes = queue_new(instance->ua, BGP_NODES_HASH_SIZE, offsetof(struct TOPO_NODEQ, hash_node_id) - sizeof(struct ll_entry), 8, "group_nodes");
if (!group->nodes) { queue_entry_free(qe); DEBUG_ERROR(DEBUG_CATEGORY_BGP, "nodes queue creation failed"); return NULL; }
@@ -250,8 +244,6 @@ static void topo_group_destroy(struct TOPO_GROUP* group) {
if (group->local_node) { topo_node_free_lists(group, group->local_node); u_free(group->local_node); }
if (group->group_type == TOPO_GROUP_TYPE_UTUN) topo_node_lmdb_destroy(group);
if (group->nodes) queue_free(group->nodes);
}
@@ -273,6 +265,20 @@ struct TOPO_GROUPS* topo_groups_init(struct UTUN_INSTANCE* instance) {
g->v4_subnet_pool = memory_pool_init(sizeof(struct TOPO_SUBNET4), "to_v4sub");
g->v6_subnet_pool = memory_pool_init(sizeof(struct TOPO_SUBNET6), "to_v6sub");
if (instance->config && instance->config->global.db_path[0]) {
int rc = sqlite3_open(instance->config->global.db_path, &g->topo_sqlite_db);
if (rc == SQLITE_OK && g->topo_sqlite_db) {
sqlite3_exec(g->topo_sqlite_db, "PRAGMA journal_mode=WAL", NULL, NULL, NULL);
sqlite3_exec(g->topo_sqlite_db, "PRAGMA foreign_keys=ON", NULL, NULL, NULL);
topo_node_sqlite_init(g->topo_sqlite_db);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "SQLite opened: %s", instance->config->global.db_path);
} else {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "sqlite3_open(%s) failed: %s",
instance->config->global.db_path, g->topo_sqlite_db ? sqlite3_errmsg(g->topo_sqlite_db) : "null db");
if (g->topo_sqlite_db) { sqlite3_close(g->topo_sqlite_db); g->topo_sqlite_db = NULL; }
}
}
instance->topo_groups = g;
struct TOPO_GROUP* default_group = topo_group_create(instance, TOPO_GROUP_UTUN, TOPO_GROUP_TYPE_UTUN);
@@ -313,6 +319,8 @@ void topo_groups_destroy(struct UTUN_INSTANCE* instance) {
memory_pool_destroy(g->v4_subnet_pool);
memory_pool_destroy(g->v6_subnet_pool);
if (g->topo_sqlite_db) { sqlite3_close(g->topo_sqlite_db); g->topo_sqlite_db = NULL; }
u_free(g); instance->topo_groups = NULL;
}
@@ -337,13 +345,11 @@ struct TOPO_GROUP* topo_groups_create_group(struct TOPO_GROUPS* g, uint64_t grou
return group;
}
#ifdef USE_SQLITE
void topo_groups_set_sqlite_db(struct TOPO_GROUPS* g, sqlite3* db) {
if (!g) return;
g->topo_sqlite_db = db;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "topo_sqlite_db set");
}
#endif
void topo_group_set_nat_check_local(struct TOPO_GROUP* group, int allow) { if (!group) return; group->allow_nat_check_local = allow ? 1 : 0; }
@@ -630,13 +636,12 @@ int topo_group_process_nodeinfo(struct TOPO_GROUP* group, struct ETCP_CONN* from
if (group->group_type != TOPO_GROUP_TYPE_CHAT && group->instance->rt) route_insert(group->instance->rt, nodeinfo1);
if (node_id != group->instance->node_id) {
#ifdef USE_SQLITE
if (group->group_type == TOPO_GROUP_TYPE_CHAT && group->instance->topo_groups->topo_sqlite_db) {
topo_node_sqlite_node_put(group->instance->topo_groups->topo_sqlite_db, nodeinfo1);
if (group->channel_id[0]) topo_node_sqlite_member_put(group->instance->topo_groups->topo_sqlite_db, group->channel_id, node_id, NULL, NULL);
} else
#endif
topo_node_lmdb_put(group, nodeinfo1);
sqlite3* sdb = group->instance->topo_groups->topo_sqlite_db;
if (sdb) {
topo_node_sqlite_node_put(sdb, nodeinfo1);
if (group->group_type == TOPO_GROUP_TYPE_CHAT && group->channel_id[0])
topo_node_sqlite_member_put(sdb, group->channel_id, node_id, NULL, NULL);
}
}
if (group->instance->control_srv) control_server_notify_node_change(group->instance->control_srv, nodeinfo1);
@@ -687,10 +692,8 @@ int topo_group_process_withdraw(struct TOPO_GROUP* group, struct ETCP_CONN* send
struct ll_entry* entry = queue_find_data_by_index(group->nodes, &node_id);
if (entry) { queue_remove_data(group->nodes, entry); queue_entry_free(entry); }
topo_group_broadcast_withdraw(group, node_id, wd_source, sender);
#ifdef USE_SQLITE
if (group->group_type == TOPO_GROUP_TYPE_CHAT && group->instance->topo_groups->topo_sqlite_db)
topo_node_sqlite_member_del(group->instance->topo_groups->topo_sqlite_db, group->channel_id, node_id);
#endif
}
return 0;
}
+25 -10
View File
@@ -1,3 +1,27 @@
/**
* @file topo_group.h
* @brief BGP-подобный обмен топологией узлов между пирами через ETCP.
*
* Механика:
* - Узлы обмениваются информацией друг о друге (pubkey, адреса, подсети)
* через NODEINFO/WITHDRAW сообщения, маршрутизируемые по ETCP.
* - Каждый узел хранит полную таблицу известных узлов (структура TOPO_NODEQ).
* - При подключении нового пира — полная синхронизация таблицы.
* - При отключении пира — withdraw всех узлов, достижимых только через него.
*
* Группы (TOPO_GROUP):
* - TOPO_GROUP_TYPE_UTUN — основная VPN-сеть (обмен маршрутами, подсетями)
* - TOPO_GROUP_TYPE_CHAT — чат-группа (только узлы, без подсетей)
* - Каждая группа изолирована: узлы из utun-группы не видны в чат-группе и наоборот
*
* Дополнительные функции:
* - NAT-детекция и проверка связности (NAT_INFO, NAT_CHECK_REQ, PING)
* - Поиск оптимального маршрута до узла (topo_group_find_conn_for_node)
*
* Хранение: все узлы сохраняются в SQLite (таблицы nodes, node_addresses,
* channels, peers_<channel_id>). База открывается в topo_groups_init()
* если в конфиге задан db_path.
*/
#ifndef TOPO_GROUP_H
#define TOPO_GROUP_H
@@ -10,13 +34,10 @@ extern "C" {
#include <stddef.h>
#include "../lib/ll_queue.h"
#include "../lib/memory_pool.h"
#include "../lib/liblmdb/lmdb.h"
#include "../lib/sqlite3.h"
#include "route_lib.h"
#include "topo_node.h"
#include "secure_channel.h"
#ifdef USE_SQLITE
#include <sqlite3.h>
#endif
struct UTUN_INSTANCE;
@@ -125,8 +146,6 @@ struct TOPO_GROUP {
uint8_t allow_nat_check_local;
uint8_t ed25519_public_key[SC_PUBKEY_SIZE];
char channel_id[64]; // channel_id для групп типа CHAT
MDB_env* nodeinfo_env;
MDB_dbi nodeinfo_dbi;
};
/**
@@ -141,9 +160,7 @@ struct TOPO_GROUPS {
struct memory_pool* v6_addr_pool;
struct memory_pool* v4_subnet_pool;
struct memory_pool* v6_subnet_pool;
#ifdef USE_SQLITE
sqlite3* topo_sqlite_db;
#endif
topo_node_updated_fn node_updated_cb; /* optional — set by chatgui's member_sync */
};
@@ -294,9 +311,7 @@ struct nat_check_arg {
uint16_t nat_port;
};
#ifdef USE_SQLITE
void topo_groups_set_sqlite_db(struct TOPO_GROUPS* g, sqlite3* db);
#endif
#ifdef __cplusplus
+21 -1
View File
@@ -1,4 +1,24 @@
#ifndef TOPO_NODE_H
/**
* @file topo_node.h
* @brief Модель данных узла сети — структуры, сериализация, wire-формат.
*
* Этот модуль определяет:
* - Как узел представлен в памяти (TOPO_NODE, TOPO_NODEQ, адреса, подсети)
* - Как узел передаётся по сети (packed-структуры TOPOMSG_*)
* - Как узел сериализуется/десериализуется (topo_node_serialize/deserialize)
*
* Модуль не содержит сетевой логики и BGP-обмена — этим занимается topo_group.
*
* Хранение: данные узлов сохраняются в SQLite (таблицы nodes, node_addresses)
* через модуль topo_node_sqlite.
*
* Основные понятия:
* TOPO_NODE — идентичность узла (pubkeys, имя, версия, списки адресов)
* TOPO_NODEQ — запись в очереди группы (плюс пути, hop_list, связность)
* TOPO_NODEPATH — путь до узла через конкретное ETCP-соединение
* TOPOMSG_NODE — wire-формат, передаваемый по NODEINFO
*/
#ifndef TOPO_NODE_H
#define TOPO_NODE_H
#ifdef __cplusplus
-66
View File
@@ -1,66 +0,0 @@
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <sys/stat.h>
#include "topo_node_lmdb.h"
#include "topo_group.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#define NODEINFO_DB_MAPSIZE (10UL * 1024 * 1024)
int topo_node_lmdb_init(struct TOPO_GROUP* bgp, const char* db_path) {
if (!bgp || !db_path || !db_path[0]) return -1;
int rc = mdb_env_create(&bgp->nodeinfo_env);
if (rc != MDB_SUCCESS) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB mdb_env_create failed: %s", mdb_strerror(rc)); return -1; }
rc = mdb_env_set_mapsize(bgp->nodeinfo_env, NODEINFO_DB_MAPSIZE);
if (rc != MDB_SUCCESS) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB mdb_env_set_mapsize failed: %s", mdb_strerror(rc)); mdb_env_close(bgp->nodeinfo_env); bgp->nodeinfo_env = NULL; return -1; }
rc = mdb_env_set_maxdbs(bgp->nodeinfo_env, 2);
if (rc != MDB_SUCCESS) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB mdb_env_set_maxdbs failed: %s", mdb_strerror(rc)); mdb_env_close(bgp->nodeinfo_env); bgp->nodeinfo_env = NULL; return -1; }
rc = mdb_env_open(bgp->nodeinfo_env, db_path, 0, 0644);
if (rc != MDB_SUCCESS) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB mdb_env_open(%s) failed: %s", db_path, mdb_strerror(rc)); mdb_env_close(bgp->nodeinfo_env); bgp->nodeinfo_env = NULL; return -1; }
MDB_txn* txn = NULL; rc = mdb_txn_begin(bgp->nodeinfo_env, NULL, 0, &txn);
if (rc != MDB_SUCCESS) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB mdb_txn_begin failed: %s", mdb_strerror(rc)); mdb_env_close(bgp->nodeinfo_env); bgp->nodeinfo_env = NULL; return -1; }
rc = mdb_dbi_open(txn, "nodeinfo", MDB_CREATE, &bgp->nodeinfo_dbi);
if (rc != MDB_SUCCESS) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB mdb_dbi_open(nodeinfo) failed: %s", mdb_strerror(rc)); mdb_txn_abort(txn); mdb_env_close(bgp->nodeinfo_env); bgp->nodeinfo_env = NULL; return -1; }
rc = mdb_txn_commit(txn);
if (rc != MDB_SUCCESS) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB mdb_txn_commit failed: %s", mdb_strerror(rc)); mdb_env_close(bgp->nodeinfo_env); bgp->nodeinfo_env = NULL; return -1; }
DEBUG_INFO(DEBUG_CATEGORY_BGP, "LMDB nodeinfo database opened: %s", db_path);
return 0;
}
void topo_node_lmdb_destroy(struct TOPO_GROUP* bgp) {
if (!bgp) return;
if (bgp->nodeinfo_env) { mdb_env_close(bgp->nodeinfo_env); bgp->nodeinfo_env = NULL; DEBUG_INFO(DEBUG_CATEGORY_BGP, "LMDB nodeinfo database closed"); }
}
int topo_node_lmdb_put(struct TOPO_GROUP* bgp, struct TOPO_NODEQ* nq) {
if (!bgp || !bgp->nodeinfo_env || !nq || !nq->node) return -1;
uint8_t buf[8192];
int ser_len = topo_node_serialize(bgp, nq, buf, sizeof(buf));
if (ser_len < 0) return -1;
MDB_val key, data;
key.mv_size = 8; key.mv_data = &nq->node->node_id;
data.mv_size = (size_t)ser_len; data.mv_data = buf;
MDB_txn* txn = NULL; int rc = mdb_txn_begin(bgp->nodeinfo_env, NULL, 0, &txn);
if (rc != MDB_SUCCESS) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB put mdb_txn_begin failed: %s", mdb_strerror(rc)); return -1; }
rc = mdb_put(txn, bgp->nodeinfo_dbi, &key, &data, 0);
if (rc != MDB_SUCCESS) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB mdb_put node %016llx failed: %s", (unsigned long long)nq->node->node_id, mdb_strerror(rc)); mdb_txn_abort(txn); return -1; }
rc = mdb_txn_commit(txn);
if (rc != MDB_SUCCESS) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB put mdb_txn_commit failed: %s", mdb_strerror(rc)); return -1; }
return 0;
}
int topo_node_lmdb_del(struct TOPO_GROUP* bgp, uint64_t node_id) {
if (!bgp || !bgp->nodeinfo_env) return -1;
MDB_val key; key.mv_size = 8; key.mv_data = &node_id;
MDB_txn* txn = NULL; int rc = mdb_txn_begin(bgp->nodeinfo_env, NULL, 0, &txn);
if (rc != MDB_SUCCESS) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB del mdb_txn_begin failed: %s", mdb_strerror(rc)); return -1; }
rc = mdb_del(txn, bgp->nodeinfo_dbi, &key, NULL);
if (rc != MDB_SUCCESS && rc != MDB_NOTFOUND) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB mdb_del node %016llx failed: %s", (unsigned long long)node_id, mdb_strerror(rc)); mdb_txn_abort(txn); return -1; }
rc = mdb_txn_commit(txn);
if (rc != MDB_SUCCESS) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB del mdb_txn_commit failed: %s", mdb_strerror(rc)); return -1; }
return 0;
}
-23
View File
@@ -1,23 +0,0 @@
#ifndef TOPO_NODE_LMDB_H
#define TOPO_NODE_LMDB_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include "topo_node.h"
struct TOPO_GROUP;
int topo_node_lmdb_init(struct TOPO_GROUP* group, const char* db_path);
void topo_node_lmdb_destroy(struct TOPO_GROUP* group);
int topo_node_lmdb_put(struct TOPO_GROUP* group, struct TOPO_NODEQ* nq);
int topo_node_lmdb_del(struct TOPO_GROUP* group, uint64_t node_id);
#ifdef __cplusplus
}
#endif
#endif
+321
View File
@@ -0,0 +1,321 @@
#include "topo_node_sqlite.h"
#include "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;
}
+37
View File
@@ -0,0 +1,37 @@
#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