Browse Source

chatgui: create group, unified logging, fix uasync_post epoll bug

- Add CreateGroupDialog with group name input
- Generate X25519+Ed25519 keypairs, random uint64 channel_id,
  Ed25519 signature, write to DB via uasync thread
- chat_core_create_channel(): create msg table, topo_node_sqlite_channel_put()
- GUI_EVT_CHANNEL_UPDATED notification to reload channel list
- Per-category debug config: debug_level= + debug_categories= in [gui]
- Unified logging via debug_config: all threads write to same file/console
- debug_enable_console(1) to mirror file output to stderr
- DEBUG_LEVEL_DISABLED=-1: truly disable category, NONE=0 fallback to global
- Fix uasync_post under epoll: process_posted_tasks was never called
- Fix MainWindow constructor parameter ordering (dbPath vs debugFile)
- debug_ui.h: append mode ('a' instead of 'w')
chatgui
Evgeny 3 months ago
parent
commit
de7841f33a
  1. 20
      lib/debug_config.c
  2. 13
      lib/debug_config.h
  3. 1
      lib/u_async.c
  4. 35
      src/topo_group.c
  5. 14
      src/topo_group.h
  6. 4
      tools/chatgui/CMakeLists.txt
  7. 164
      tools/chatgui/db/db_manager.cpp
  8. 2
      tools/chatgui/libutun/CMakeLists.txt
  9. 9
      tools/chatgui/src/channellist.cpp
  10. 1
      tools/chatgui/src/channellist.h
  11. 51
      tools/chatgui/src/creategroupdialog.cpp
  12. 15
      tools/chatgui/src/creategroupdialog.h
  13. 2
      tools/chatgui/src/debug_ui.h
  14. 7
      tools/chatgui/src/main.cpp
  15. 140
      tools/chatgui/src/mainwindow.cpp
  16. 8
      tools/chatgui/src/mainwindow.h
  17. 167
      tools/chatgui/transport/chat_core.c
  18. 17
      tools/chatgui/transport/chat_core.h
  19. 7
      tools/chatgui/transport/gui_bridge.h
  20. 42
      tools/chatgui/transport/gui_bridge_impl.cpp
  21. 6
      tools/chatgui/transport/node_config.cpp
  22. 4
      tools/chatgui/transport/node_config.h
  23. 203
      tools/chatgui/transport/topo_node_sqlite.c
  24. 24
      tools/chatgui/transport/topo_node_sqlite.h
  25. 62
      tools/chatgui/transport/utun_node.cpp
  26. 4
      tools/chatgui/transport/utun_node.h

20
lib/debug_config.c

@ -137,13 +137,14 @@ static const struct {
const char* name;
debug_level_t level;
} g_level_table[] = {
{"none", DEBUG_LEVEL_NONE},
{"error", DEBUG_LEVEL_ERROR},
{"warn", DEBUG_LEVEL_WARN},
{"info", DEBUG_LEVEL_INFO},
{"debug", DEBUG_LEVEL_DEBUG},
{"trace", DEBUG_LEVEL_TRACE},
{NULL, DEBUG_LEVEL_NONE}
{"disabled", DEBUG_LEVEL_DISABLED},
{"none", DEBUG_LEVEL_NONE},
{"error", DEBUG_LEVEL_ERROR},
{"warn", DEBUG_LEVEL_WARN},
{"info", DEBUG_LEVEL_INFO},
{"debug", DEBUG_LEVEL_DEBUG},
{"trace", DEBUG_LEVEL_TRACE},
{NULL, DEBUG_LEVEL_NONE}
};
debug_category_t get_category_by_name(const char* name) {
@ -163,7 +164,7 @@ debug_level_t debug_level_from_name(const char* name) {
return g_level_table[i].level;
}
}
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid debug level '%s' (valid: none,error,warn,info,debug,trace)", name);
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid debug level '%s' (valid: disabled,none,error,warn,info,debug,trace)", name);
return DEBUG_LEVEL_INFO; /* fallback */
}
@ -345,6 +346,9 @@ int debug_should_output(debug_level_t level, debug_category_t category_idx) {
}
debug_level_t cat_level = g_debug_config.category_levels[category_idx];
if (cat_level == DEBUG_LEVEL_DISABLED) {
return 0;
}
if (cat_level == DEBUG_LEVEL_NONE) {
cat_level = g_debug_config.level;
}

13
lib/debug_config.h

@ -26,12 +26,13 @@ extern "C" {
/* Debug levels */
typedef enum {
DEBUG_LEVEL_NONE = 0, // No debug output
DEBUG_LEVEL_ERROR = 1, // Errors only
DEBUG_LEVEL_WARN = 2, // Warnings and errors
DEBUG_LEVEL_INFO = 3, // Info, warnings, errors
DEBUG_LEVEL_DEBUG = 4, // Full debug output
DEBUG_LEVEL_TRACE = 5 // Trace everything
DEBUG_LEVEL_DISABLED = -1, // Category completely disabled, never outputs
DEBUG_LEVEL_NONE = 0, // Fallback to global level
DEBUG_LEVEL_ERROR = 1, // Errors only
DEBUG_LEVEL_WARN = 2, // Warnings and errors
DEBUG_LEVEL_INFO = 3, // Info, warnings, errors
DEBUG_LEVEL_DEBUG = 4, // Full debug output
DEBUG_LEVEL_TRACE = 5 // Trace everything
} debug_level_t;
/* Debug categories as indices (0-based) */

1
lib/u_async.c

@ -924,6 +924,7 @@ static void process_epoll_events(struct UASYNC* ua, struct epoll_event* events,
// Wakeup fd detected by data.fd < 0 (lower 32 bits of data.u64 = -1)
if (events[i].data.fd < 0) {
if (events[i].events & EPOLLIN) { drain_wakeup_pipe(ua); }
process_posted_tasks(ua);
continue;
}

35
src/topo_group.c

@ -22,6 +22,9 @@
#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
@ -221,7 +224,8 @@ 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 (instance->config && instance->config->global.db_path[0]) topo_node_lmdb_init(group, instance->config->global.db_path);
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; }
@ -246,7 +250,7 @@ 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); }
topo_node_lmdb_destroy(group);
if (group->group_type == TOPO_GROUP_TYPE_UTUN) topo_node_lmdb_destroy(group);
if (group->nodes) queue_free(group->nodes);
}
@ -322,16 +326,25 @@ struct TOPO_GROUP* topo_groups_find(struct TOPO_GROUPS* g, uint64_t group_id) {
return e ? (struct TOPO_GROUP*)e : NULL;
}
struct TOPO_GROUP* topo_groups_create_group(struct TOPO_GROUPS* g, uint64_t group_id, uint8_t group_type) {
struct TOPO_GROUP* topo_groups_create_group(struct TOPO_GROUPS* g, uint64_t group_id, uint8_t group_type, const char* channel_id) {
if (!g || !g->instance) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid args"); return NULL; }
if (topo_groups_find(g, group_id)) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "group_id=%016llx already exists", (unsigned long long)group_id); return NULL; }
struct TOPO_GROUP* group = topo_group_create(g->instance, group_id, group_type);
if (!group) return NULL;
if (channel_id) { strncpy(group->channel_id, channel_id, sizeof(group->channel_id) - 1); group->channel_id[sizeof(group->channel_id) - 1] = '\0'; }
queue_data_put_with_index(g->group_list, &group->ll);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Group created: group_id=%016llx type=%u", (unsigned long long)group_id, group_type);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Group created: group_id=%016llx type=%u ch_id=%s", (unsigned long long)group_id, group_type, group->channel_id);
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; }
void topo_group_new_conn(struct TOPO_GROUP* group, struct ETCP_CONN* conn) {
@ -612,7 +625,15 @@ int topo_group_process_nodeinfo(struct TOPO_GROUP* group, struct ETCP_CONN* from
topo_group_add_path(nodeinfo1, from, hop_list, nodeinfo1->hop_count);
if (group->group_type != TOPO_GROUP_TYPE_CHAT && group->instance->rt) route_insert(group->instance->rt, nodeinfo1);
if (node_id != group->instance->node_id) topo_node_lmdb_put(group, 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);
} else
#endif
topo_node_lmdb_put(group, nodeinfo1);
}
if (group->instance->control_srv) control_server_notify_node_change(group->instance->control_srv, nodeinfo1);
int hop_count = nodeinfo1->hop_count;
@ -659,6 +680,10 @@ 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;
}

14
src/topo_group.h

@ -14,6 +14,9 @@ extern "C" {
#include "route_lib.h"
#include "topo_node.h"
#include "secure_channel.h"
#ifdef USE_SQLITE
#include <sqlite3.h>
#endif
// ETCP ID для пакетов топологии
@ -114,6 +117,7 @@ struct TOPO_GROUP {
uint32_t next_ping_req_id;
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;
};
@ -130,6 +134,9 @@ 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
};
/**
@ -163,9 +170,10 @@ struct TOPO_GROUP* topo_groups_find(struct TOPO_GROUPS* g, uint64_t group_id);
* @param g контейнер групп
* @param group_id уникальный идентификатор
* @param group_type TOPO_GROUP_TYPE_UTUN или TOPO_GROUP_TYPE_CHAT
* @param channel_id идентификатор канала для групп типа CHAT (может быть NULL)
* @return TOPO_GROUP или NULL при ошибке
*/
struct TOPO_GROUP* topo_groups_create_group(struct TOPO_GROUPS* g, uint64_t group_id, uint8_t group_type);
struct TOPO_GROUP* topo_groups_create_group(struct TOPO_GROUPS* g, uint64_t group_id, uint8_t group_type, const char* channel_id);
/**
* @brief Добавляет conn в senders_list (если нет), отправляет запрос таблицы (nodeinfo).
@ -276,6 +284,10 @@ 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
}

4
tools/chatgui/CMakeLists.txt

@ -49,6 +49,7 @@ add_executable(chatgui
src/main.cpp
src/mainwindow.cpp
src/channellist.cpp
src/creategroupdialog.cpp
src/channeldelegate.cpp
src/chatview.cpp
src/messagedelegate.cpp
@ -73,13 +74,14 @@ add_executable(chatgui
transport/chat_core.c
transport/chat_sync.c
transport/db_sync_stub.c
transport/topo_node_sqlite.c
db/db_manager.cpp
db/sqlite3.c
resources/chatgui.qrc
)
target_include_directories(chatgui PRIVATE ${CMAKE_SOURCE_DIR}/db)
target_compile_definitions(chatgui PRIVATE SQLITE_THREADSAFE=1)
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/db_sync_stub.c PROPERTIES LANGUAGE C)
if(WIN32)
target_link_libraries(chatgui PRIVATE ${QT_LIBS} rlottie::rlottie ZLIB::ZLIB OpenSSL::Crypto ZXing::ZXing utun pthread)

164
tools/chatgui/db/db_manager.cpp

@ -8,7 +8,6 @@
/* ── helpers ── */
static const char kZero64[64] = {};
static const char kZero32[32] = {};
QString DbManager::sanitizeTableName(const QString& chId) {
QString s;
@ -119,28 +118,6 @@ void DbManager::initTables() {
" updated_at INTEGER DEFAULT (unixepoch())"
");"
"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 accounts ("
" node_id INTEGER PRIMARY KEY REFERENCES nodes(node_id),"
" display_name TEXT NOT NULL,"
@ -150,21 +127,6 @@ void DbManager::initTables() {
" created_at INTEGER DEFAULT (unixepoch())"
");"
"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())"
");"
"CREATE TABLE IF NOT EXISTS ui_state ("
" key TEXT PRIMARY KEY,"
" value TEXT"
@ -213,12 +175,11 @@ bool DbManager::createChannel(const QString& chId, const QString& name,
quint64 ownerNodeId,
const QByteArray& x25519pub, const QByteArray& x25519priv,
const QByteArray& ed25519pub, const QByteArray& ed25519priv) {
GUI_INFO("createChannel: chId='%s' name='%s'", chId.toUtf8().constData(), name.toUtf8().constData());
QByteArray sig = kZero64; Q_UNUSED(sig);
GUI_INFO("createChannel (msg table only): chId='%s' name='%s'", chId.toUtf8().constData(), name.toUtf8().constData());
(void)ownerNodeId; (void)x25519pub; (void)x25519priv; (void)ed25519pub; (void)ed25519priv;
QString msgt = msgTableName(chId), pt = peersTableName(chId);
QString msgt = msgTableName(chId);
/* per-channel message table (DDL — до INSERT, чтобы не ломать транзакцию) */
QString csql = QStringLiteral(
"CREATE TABLE IF NOT EXISTS \"%1\" ("
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
@ -237,41 +198,7 @@ bool DbManager::createChannel(const QString& chId, const QString& name,
csql = QStringLiteral("CREATE INDEX IF NOT EXISTS idx_%1_ts ON \"%1\"(timestamp, datahash)").arg(msgt);
sqlite3_exec(m_db, csql.toUtf8().constData(), nullptr, nullptr, nullptr);
/* per-channel peers table */
csql = QStringLiteral(
"CREATE TABLE IF NOT EXISTS \"%1\" ("
" node_id INTEGER NOT NULL,"
" join_sig BLOB NOT NULL,"
" creator_sig BLOB,"
" comment TEXT,"
" joined_at INTEGER DEFAULT (unixepoch()),"
" PRIMARY KEY (node_id))").arg(pt);
sqlite3_exec(m_db, csql.toUtf8().constData(), nullptr, nullptr, nullptr);
/* вставка записи channels (после DDL, корректно внутри транзакции) */
sqlite3_stmt* stmt = prepareOrNull(
"INSERT INTO channels(channel_id,name,owner_node_id,is_dm,"
" x25519_pubkey,x25519_privkey,ed25519_pubkey,ed25519_privkey,signature)"
" VALUES(?,?,?,0,?,?,?,?,?)");
if (!stmt) return false;
sqlite3_bind_text(stmt, 1, chId.toUtf8().constData(), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 2, name.toUtf8().constData(), -1, SQLITE_TRANSIENT);
sqlite3_bind_int64(stmt, 3, (sqlite3_int64)ownerNodeId);
sqlite3_bind_blob(stmt, 4, x25519pub.isEmpty() ? kZero32 : x25519pub.constData(),
x25519pub.isEmpty() ? 32 : x25519pub.size(), SQLITE_TRANSIENT);
sqlite3_bind_blob(stmt, 5, x25519priv.isEmpty() ? nullptr : x25519priv.constData(),
x25519priv.size(), SQLITE_TRANSIENT);
sqlite3_bind_blob(stmt, 6, ed25519pub.isEmpty() ? kZero32 : ed25519pub.constData(),
ed25519pub.isEmpty() ? 32 : ed25519pub.size(), SQLITE_TRANSIENT);
sqlite3_bind_blob(stmt, 7, ed25519priv.isEmpty() ? nullptr : ed25519priv.constData(),
ed25519priv.size(), SQLITE_TRANSIENT);
sqlite3_bind_blob(stmt, 8, sig.constData(), sig.size(), SQLITE_TRANSIENT);
bool ok = sqlite3_step(stmt) == SQLITE_DONE;
sqlite3_finalize(stmt);
if (!ok) { GUI_ERROR("createChannel %s failed: %s", chId.toUtf8().constData(), sqlite3_errmsg(m_db)); return false; }
GUI_INFO("Channel created: %s (msg=%s, peers=%s)",
chId.toUtf8().constData(), msgt.toUtf8().constData(), pt.toUtf8().constData());
GUI_INFO("Message table ensured: %s", msgt.toUtf8().constData());
return true;
}
@ -284,6 +211,7 @@ bool DbManager::deleteChannel(const QString& chId) {
sqlite3_bind_text(stmt, 1, chId.toUtf8().constData(), -1, SQLITE_TRANSIENT);
sqlite3_step(stmt);
sqlite3_finalize(stmt);
GUI_INFO("Channel deleted: %s", chId.toUtf8().constData());
return true;
}
@ -349,73 +277,32 @@ bool DbManager::insertMessage(const QString& chId, quint64 nodeId,
return ok;
}
/* ── peers ── */
/* ── peers (запись теперь через topo_node_sqlite) ── */
bool DbManager::addPeer(const QString& chId, quint64 nodeId,
const QByteArray& joinSig, const QByteArray& creatorSig,
const QString& comment) {
QString sql = QStringLiteral(
"INSERT OR REPLACE INTO \"%1\"(node_id, join_sig, creator_sig, comment)"
" VALUES(?,?,?,?)").arg(peersTableName(chId));
sqlite3_stmt* stmt = prepareOrNull(sql.toUtf8().constData());
if (!stmt) return false;
sqlite3_bind_int64(stmt, 1, (sqlite3_int64)nodeId);
sqlite3_bind_blob(stmt, 2, joinSig.constData(), joinSig.size(), SQLITE_TRANSIENT);
sqlite3_bind_blob(stmt, 3, creatorSig.isEmpty() ? nullptr : creatorSig.constData(),
creatorSig.size(), SQLITE_TRANSIENT);
if (!comment.isEmpty())
sqlite3_bind_text(stmt, 4, comment.toUtf8().constData(), -1, SQLITE_TRANSIENT);
else
sqlite3_bind_null(stmt, 4);
bool ok = sqlite3_step(stmt) == SQLITE_DONE;
sqlite3_finalize(stmt);
return ok;
(void)chId; (void)nodeId; (void)joinSig; (void)creatorSig; (void)comment;
return true;
}
bool DbManager::removePeer(const QString& chId, quint64 nodeId) {
QString sql = QStringLiteral(
"DELETE FROM \"%1\" WHERE node_id=?").arg(peersTableName(chId));
sqlite3_stmt* stmt = prepareOrNull(sql.toUtf8().constData());
if (!stmt) return false;
sqlite3_bind_int64(stmt, 1, (sqlite3_int64)nodeId);
sqlite3_step(stmt);
sqlite3_finalize(stmt);
(void)chId; (void)nodeId;
return true;
}
/* ── nodes ── */
/* ── nodes (запись теперь через topo_node_sqlite) ── */
bool DbManager::upsertNode(quint64 nodeId, const QString& name,
const QByteArray& x25519pub, const QByteArray& ed25519pub) {
sqlite3_stmt* stmt = prepareOrNull(
"INSERT OR REPLACE INTO nodes(node_id, name, x25519_pubkey, ed25519_pubkey, last_seen_at)"
" VALUES(?,?,?,?,unixepoch())");
if (!stmt) return false;
sqlite3_bind_int64(stmt, 1, (sqlite3_int64)nodeId);
sqlite3_bind_text(stmt, 2, name.toUtf8().constData(), -1, SQLITE_TRANSIENT);
sqlite3_bind_blob(stmt, 3, x25519pub.constData(), x25519pub.size(), SQLITE_TRANSIENT);
sqlite3_bind_blob(stmt, 4, ed25519pub.isEmpty() ? nullptr : ed25519pub.constData(),
ed25519pub.size(), SQLITE_TRANSIENT);
bool ok = sqlite3_step(stmt) == SQLITE_DONE;
sqlite3_finalize(stmt);
return ok;
(void)nodeId; (void)name; (void)x25519pub; (void)ed25519pub;
return true;
}
bool DbManager::addNodeAddress(quint64 nodeId, int family, int protocol,
const QByteArray& addr, quint16 port, bool isNat) {
sqlite3_stmt* stmt = prepareOrNull(
"INSERT OR IGNORE INTO node_addresses(node_id, family, protocol, address, port, is_nat)"
" VALUES(?,?,?,?,?,?)");
if (!stmt) return false;
sqlite3_bind_int64(stmt, 1, (sqlite3_int64)nodeId);
sqlite3_bind_int(stmt, 2, family);
sqlite3_bind_int(stmt, 3, protocol);
sqlite3_bind_blob(stmt, 4, addr.constData(), addr.size(), SQLITE_TRANSIENT);
sqlite3_bind_int(stmt, 5, port);
sqlite3_bind_int(stmt, 6, isNat ? 1 : 0);
bool ok = sqlite3_step(stmt) == SQLITE_DONE;
sqlite3_finalize(stmt);
return ok;
(void)nodeId; (void)family; (void)protocol; (void)addr; (void)port; (void)isNat;
return true;
}
QByteArray DbManager::loadNodeInfo(quint64 nodeId) const {
@ -755,7 +642,7 @@ void DbManager::setNodeOnline(quint64 nodeId, bool online) {
/* ── seed stub data ── */
void DbManager::seedStubData() {
GUI_INFO("Seeding stub data...");
GUI_INFO("Seeding stub data (accounts only)...");
sqlite3_exec(m_db, "BEGIN", nullptr, nullptr, nullptr);
unsigned char pub_dummy[32] = {};
@ -773,14 +660,6 @@ void DbManager::seedStubData() {
{5, "Eve", "E", "#00B894"},
};
for (auto& a : accs) {
ins = prepareOrNull("INSERT OR IGNORE INTO nodes(node_id,name,x25519_pubkey,ed25519_pubkey,last_seen_at)"
" VALUES(?,?,?,?,unixepoch())");
sqlite3_bind_int64(ins, 1, (sqlite3_int64)a.id);
sqlite3_bind_text(ins, 2, a.name, -1, SQLITE_TRANSIENT);
sqlite3_bind_blob(ins, 3, pub_dummy, 32, SQLITE_TRANSIENT);
sqlite3_bind_blob(ins, 4, pub_dummy, 32, SQLITE_TRANSIENT);
sqlite3_step(ins); sqlite3_finalize(ins);
ins = prepareOrNull("INSERT OR IGNORE INTO accounts(node_id,display_name,avatar_color,avatar_letter)"
" VALUES(?,?,?,?)");
sqlite3_bind_int64(ins, 1, (sqlite3_int64)a.id);
@ -790,17 +669,6 @@ void DbManager::seedStubData() {
sqlite3_step(ins); sqlite3_finalize(ins);
}
/* channels (1 демо-канал) */
QByteArray dummyPub(32, '\0'), dummySig(64, '\0'), dummyCH(32, '\0');
createChannel("ch:general", "#general", 0, dummyPub, QByteArray(), dummyPub, QByteArray());
/* peers */
for (auto& a : accs) {
addPeer("ch:general", a.id, dummySig, dummySig, a.name);
}
/* messages — 0 для простоты, добавим позже в seed v2 */
sqlite3_exec(m_db, "COMMIT", nullptr, nullptr, nullptr);
GUI_INFO("Seed data inserted (5 accounts, 1 channel)");
GUI_INFO("Seed data inserted (5 accounts)");
}

2
tools/chatgui/libutun/CMakeLists.txt

@ -119,10 +119,12 @@ target_include_directories(utun PUBLIC
${LIB_DIR}
${TRANSPORT_DIR}
${SRC_DIR}/uip
${CMAKE_SOURCE_DIR}/db
)
target_compile_definitions(utun PRIVATE
USE_OPENSSL
USE_SQLITE
)
set_target_properties(utun PROPERTIES

9
tools/chatgui/src/channellist.cpp

@ -67,6 +67,15 @@ ChannelList::ChannelList(DbManager* db, QWidget *parent)
fl->addStretch();
auto *createBtn = new QPushButton("GRP", footer);
createBtn->setFixedSize(36, 28);
createBtn->setStyleSheet(
"QPushButton { border: 1.5px solid #27AE60; border-radius: 6px;"
" font-size: 11px; font-weight: bold; color: #27AE60; background: transparent; }"
"QPushButton:hover { background: #27AE60; color: white; }");
connect(createBtn, &QPushButton::clicked, this, [this] { emit createGroupRequested(); });
fl->addWidget(createBtn);
auto *plusBtn = new QPushButton("+", footer);
plusBtn->setFixedSize(28, 28);
plusBtn->setStyleSheet(

1
tools/chatgui/src/channellist.h

@ -19,6 +19,7 @@ signals:
void channelSelected(const QString& channelId);
void inviteRequested(const QString& channelId);
void joinRequested();
void createGroupRequested();
void settingsRequested();
private slots:

51
tools/chatgui/src/creategroupdialog.cpp

@ -0,0 +1,51 @@
#include "creategroupdialog.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QPushButton>
CreateGroupDialog::CreateGroupDialog(QWidget* parent)
: QDialog(parent)
, m_nameEdit(new QLineEdit(this))
{
setWindowTitle("Создать группу");
setMinimumWidth(350);
setModal(true);
auto* layout = new QVBoxLayout(this);
auto* nameLabel = new QLabel("Название группы:", this);
layout->addWidget(nameLabel);
m_nameEdit->setPlaceholderText("Введите название...");
m_nameEdit->setMaxLength(128);
layout->addWidget(m_nameEdit);
layout->addSpacing(12);
auto* btnLayout = new QHBoxLayout();
btnLayout->addStretch();
auto* cancelBtn = new QPushButton("Отмена", this);
connect(cancelBtn, &QPushButton::clicked, this, &QDialog::reject);
btnLayout->addWidget(cancelBtn);
auto* createBtn = new QPushButton("Создать", this);
createBtn->setDefault(true);
connect(createBtn, &QPushButton::clicked, this, [this]() {
if (m_nameEdit->text().trimmed().isEmpty()) {
m_nameEdit->setFocus();
return;
}
accept();
});
btnLayout->addWidget(createBtn);
layout->addLayout(btnLayout);
m_nameEdit->setFocus();
}
QString CreateGroupDialog::groupName() const {
return m_nameEdit->text().trimmed();
}

15
tools/chatgui/src/creategroupdialog.h

@ -0,0 +1,15 @@
#pragma once
#include <QDialog>
#include <QLineEdit>
class CreateGroupDialog : public QDialog {
Q_OBJECT
public:
explicit CreateGroupDialog(QWidget* parent = nullptr);
QString groupName() const;
private:
QLineEdit* m_nameEdit;
};

2
tools/chatgui/src/debug_ui.h

@ -10,7 +10,7 @@ namespace GuiDebugPriv {
inline void setFile(const char *path) {
if (s_file && s_file != stderr) fclose(s_file);
s_file = path ? fopen(path, "w") : nullptr;
s_file = path ? fopen(path, "a") : nullptr;
if (!s_file) s_file = stderr;
}
}

7
tools/chatgui/src/main.cpp

@ -5,7 +5,6 @@
#include "mainwindow.h"
#include "../db/db_manager.h"
#include "../transport/node_config.h"
#include "debug_ui.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
@ -55,16 +54,14 @@ int main(int argc, char *argv[]) {
cfg.save();
}
if (!cfg.debugFile().isEmpty())
guiDebugFile(cfg.debugFile().toUtf8().constData());
QString dbPath = cfg.dbPath();
if (dbPath.isEmpty()) dbPath = "chats.db";
if (!QDir::isAbsolutePath(dbPath))
dbPath = QCoreApplication::applicationDirPath() + "/" + dbPath;
DbManager db(dbPath);
MainWindow w(nullptr, &db, cfgPath, cfg.debugFile());
MainWindow w(nullptr, &db, cfgPath, dbPath, cfg.debugFile(),
cfg.debugLevel(), cfg.debugCategories());
w.show();
return app.exec();

140
tools/chatgui/src/mainwindow.cpp

@ -5,6 +5,7 @@
#include "invitedialog.h"
#include "joindialog.h"
#include "settingsdialog.h"
#include "creategroupdialog.h"
#include "invite_link.h"
#include "../db/db_manager.h"
#include "../transport/utun_node.h"
@ -16,6 +17,14 @@
#include <QMessageBox>
#include <QDebug>
extern "C" {
#include "../transport/chat_core.h"
#include "../../lib/mem.h"
#include "../../lib/debug_config.h"
#include <openssl/evp.h>
#include <openssl/rand.h>
}
static MainWindow* s_mainWindow = nullptr;
static void onMsgReceivedCallback(const char* ch_id, int ch_id_len) {
@ -24,8 +33,16 @@ static void onMsgReceivedCallback(const char* ch_id, int ch_id_len) {
s_mainWindow->onMessageReceived(ch_id);
}
static void onChannelUpdatedCallback(const char* ch_id, int ch_id_len) {
Q_UNUSED(ch_id_len);
Q_UNUSED(ch_id);
if (s_mainWindow)
s_mainWindow->reloadChannels();
}
MainWindow::MainWindow(QWidget *parent, DbManager* db, const QString& cfgPath,
const QString& dbPath, const QString& debugFile)
const QString& dbPath, const QString& debugFile,
const QString& debugLevel, const QString& debugCategories)
: QMainWindow(parent)
, m_channelList(nullptr)
, m_messageList(nullptr)
@ -36,6 +53,8 @@ MainWindow::MainWindow(QWidget *parent, DbManager* db, const QString& cfgPath,
, m_cfgPath(cfgPath)
, m_cfgDbPath(dbPath)
, m_cfgDebugFile(debugFile)
, m_cfgDebugLevel(debugLevel)
, m_cfgDebugCategories(debugCategories)
{
setWindowTitle("Chat");
resize(900, 600);
@ -45,6 +64,7 @@ MainWindow::MainWindow(QWidget *parent, DbManager* db, const QString& cfgPath,
if (m_db && m_db->isOpen()) {
s_mainWindow = this;
gui_bridge_set_msg_received_cb(onMsgReceivedCallback);
gui_bridge_set_channel_updated_cb(onChannelUpdatedCallback);
setupNode();
setupMessaging();
@ -59,6 +79,8 @@ MainWindow::MainWindow(QWidget *parent, DbManager* db, const QString& cfgPath,
this, &MainWindow::showInviteDialog);
connect(m_channelList, &ChannelList::joinRequested,
this, &MainWindow::showJoinDialog);
connect(m_channelList, &ChannelList::createGroupRequested,
this, &MainWindow::onCreateGroupRequested);
connect(m_channelList, &ChannelList::settingsRequested,
this, &MainWindow::showSettings);
@ -109,6 +131,10 @@ void MainWindow::setupNode() {
if (!m_cfgDebugFile.isEmpty())
m_node->setDebugFile(m_cfgDebugFile);
if (!m_cfgDebugLevel.isEmpty())
m_node->setDebugLevel(m_cfgDebugLevel);
if (!m_cfgDebugCategories.isEmpty())
m_node->setDebugCategories(m_cfgDebugCategories);
if (!m_cfgDbPath.isEmpty())
m_node->setDbPath(m_cfgDbPath);
@ -208,7 +234,117 @@ void MainWindow::showSettings() {
}
void MainWindow::onMessageReceived(const char* ch_id) {
qDebug("MainWindow: msg received ch=%s", ch_id);
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "MainWindow: msg received ch=%s", ch_id);
if (m_messageList)
m_messageList->refresh();
}
void MainWindow::reloadChannels() {
if (m_channelList)
m_channelList->loadChannels();
}
void MainWindow::onCreateGroupRequested() {
if (!m_node || !m_node->isRunning()) {
QMessageBox::warning(this, "Error", "Node is not running.");
return;
}
if (!gui_bridge_is_uasync_ready()) {
QMessageBox::warning(this, "Error", "uTun thread is not ready yet. Please wait and try again.");
return;
}
CreateGroupDialog dlg(this);
if (dlg.exec() != QDialog::Accepted) return;
QString name = dlg.groupName();
if (name.isEmpty()) return;
/* генерация X25519 ключей */
uint8_t x25519_pub[32] = {0}, x25519_priv[32] = {0};
{
EVP_PKEY* pkey = nullptr;
EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_X25519, nullptr);
if (ctx) {
EVP_PKEY_keygen_init(ctx);
EVP_PKEY_generate(ctx, &pkey);
EVP_PKEY_CTX_free(ctx);
}
if (pkey) {
size_t pkLen = 32, skLen = 32;
EVP_PKEY_get_raw_public_key(pkey, x25519_pub, &pkLen);
EVP_PKEY_get_raw_private_key(pkey, x25519_priv, &skLen);
EVP_PKEY_free(pkey);
}
}
/* генерация Ed25519 ключей */
uint8_t ed25519_pub[32] = {0}, ed25519_priv[32] = {0};
EVP_PKEY* ed_pkey = nullptr;
{
EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, nullptr);
if (ctx) {
EVP_PKEY_keygen_init(ctx);
EVP_PKEY_generate(ctx, &ed_pkey);
EVP_PKEY_CTX_free(ctx);
}
if (ed_pkey) {
size_t pkLen = 32, skLen = 32;
EVP_PKEY_get_raw_public_key(ed_pkey, ed25519_pub, &pkLen);
EVP_PKEY_get_raw_private_key(ed_pkey, ed25519_priv, &skLen);
}
}
/* случайный channel_id (uint64 → decimal string) */
uint64_t chId = 0;
RAND_bytes((uint8_t*)&chId, sizeof(chId));
QString channelId = QString::number(chId);
/* owner_node_id из instance */
uint64_t ownerNodeId = m_node->nodeIdHex().toULongLong(nullptr, 16);
/* Ed25519 подпись: channel_id || name || owner_node_id(8 LE) || x25519_pub(32) || ed25519_pub(32) */
uint8_t sig[64] = {0};
if (ed_pkey) {
QByteArray nameUtf8 = name.toUtf8();
QByteArray chIdUtf8 = channelId.toUtf8();
size_t msgLen = chIdUtf8.size() + 1 + nameUtf8.size() + 1 + 8 + 32 + 32;
uint8_t* msg = (uint8_t*)malloc(msgLen);
if (msg) {
uint8_t* p = msg;
memcpy(p, chIdUtf8.constData(), chIdUtf8.size()); p += chIdUtf8.size(); *p++ = '\0';
memcpy(p, nameUtf8.constData(), nameUtf8.size()); p += nameUtf8.size(); *p++ = '\0';
memcpy(p, &ownerNodeId, 8); p += 8;
memcpy(p, x25519_pub, 32); p += 32;
memcpy(p, ed25519_pub, 32); p += 32;
EVP_MD_CTX* mdctx = EVP_MD_CTX_new();
if (mdctx) {
EVP_DigestSignInit(mdctx, nullptr, nullptr, nullptr, ed_pkey);
size_t siglen = 64;
EVP_DigestSign(mdctx, sig, &siglen, msg, msgLen);
EVP_MD_CTX_free(mdctx);
}
free(msg);
}
EVP_PKEY_free(ed_pkey);
}
/* отправляем в uasync-поток */
struct chat_channel_create* req = (struct chat_channel_create*)u_malloc(sizeof(*req));
memset(req, 0, sizeof(*req));
snprintf(req->channel_id, sizeof(req->channel_id), "%s", qPrintable(channelId));
snprintf(req->name, sizeof(req->name), "%s", qPrintable(name));
req->owner_node_id = ownerNodeId;
req->is_dm = 0;
memcpy(req->x25519_pubkey, x25519_pub, 32);
memcpy(req->x25519_privkey, x25519_priv, 32);
memcpy(req->ed25519_pubkey, ed25519_pub, 32);
memcpy(req->ed25519_privkey, ed25519_priv, 32);
memcpy(req->signature, sig, 64);
gui_bridge_post_uasync_fn(chat_core_create_channel_trampoline, req);
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "MainWindow: create group request posted ch=%s name=%s",
qPrintable(channelId), qPrintable(name));
}

8
tools/chatgui/src/mainwindow.h

@ -15,7 +15,9 @@ class MainWindow : public QMainWindow {
public:
explicit MainWindow(QWidget *parent, DbManager* db, const QString& cfgPath,
const QString& dbPath = QString(),
const QString& debugFile = QString());
const QString& debugFile = QString(),
const QString& debugLevel = QString(),
const QString& debugCategories = QString());
~MainWindow() override;
protected:
@ -25,10 +27,12 @@ private slots:
void onTrayActivated(QSystemTrayIcon::ActivationReason reason);
void showInviteDialog(const QString& channelId);
void showJoinDialog();
void onCreateGroupRequested();
void showSettings();
public:
void onMessageReceived(const char* ch_id);
void reloadChannels();
private:
void setupUi();
@ -46,4 +50,6 @@ private:
QString m_cfgPath;
QString m_cfgDbPath;
QString m_cfgDebugFile;
QString m_cfgDebugLevel;
QString m_cfgDebugCategories;
};

167
tools/chatgui/transport/chat_core.c

@ -8,6 +8,7 @@
#include "chat_core.h"
#include "chat_sync.h"
#include "gui_bridge.h"
#include "topo_node_sqlite.h"
#include "../../../src/utun_instance.h"
#include "../../../src/etcp_router.h"
@ -128,6 +129,9 @@ int chat_core_init(struct UTUN_INSTANCE* inst, const char* db_path) {
g_cc.inst = inst;
g_cc.my_node_id = inst->node_id;
topo_node_sqlite_init(g_cc.db);
topo_groups_set_sqlite_db(inst->topo_groups, g_cc.db);
db_exec(
"CREATE TABLE IF NOT EXISTS local_identity ("
" id INTEGER PRIMARY KEY CHECK (id = 1),"
@ -140,28 +144,6 @@ int chat_core_init(struct UTUN_INSTANCE* inst, const char* db_path) {
" updated_at INTEGER DEFAULT (unixepoch())"
");"
"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 accounts ("
" node_id INTEGER PRIMARY KEY REFERENCES nodes(node_id),"
" display_name TEXT NOT NULL,"
@ -171,21 +153,6 @@ int chat_core_init(struct UTUN_INSTANCE* inst, const char* db_path) {
" created_at INTEGER DEFAULT (unixepoch())"
");"
"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())"
");"
"CREATE TABLE IF NOT EXISTS ui_state ("
" key TEXT PRIMARY KEY,"
" value TEXT"
@ -627,7 +594,7 @@ void chat_core_connect_from_invite(struct chat_invite* inv) {
uint64_t node_id = inv->node_id;
if (nodeinfo_find_by_id(group, node_id)) {
if (topo_node_find_by_id(group, node_id)) {
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: node 0x%016llx already in BGP, connecting",
CC_ID, (unsigned long long)node_id);
conn_mgr_connect_node(g_cc.inst->conn_mgr, node_id, 30000,
@ -635,56 +602,47 @@ void chat_core_connect_from_invite(struct chat_invite* inv) {
return;
}
size_t dyn_sz = (size_t)inv->addr_count * sizeof(struct NODEINFO_IPV4_ADDR);
size_t data_sz = sizeof(struct NODEINFO_Q) - sizeof(struct ll_entry) + dyn_sz + 8;
struct NODEINFO_Q* nq = (struct NODEINFO_Q*)queue_entry_new(data_sz);
if (!nq) {
DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "%s: failed to alloc NODEINFO_Q", CC_ID);
struct ll_entry* qe = queue_entry_new(sizeof(struct TOPO_NODEQ));
if (!qe) {
DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "%s: failed to alloc TOPO_NODEQ", CC_ID);
uint8_t err[12]; int r = -7;
memcpy(err, &node_id, 8); memcpy(err + 8, &r, 4);
gui_bridge_post(GUI_EVT_CONNECT_RESULT, err, 12);
return;
}
struct TOPO_NODEQ* nq = (struct TOPO_NODEQ*)qe;
memset((uint8_t*)nq + sizeof(struct ll_entry), 0, sizeof(*nq) - sizeof(struct ll_entry));
struct NODEINFO* ni = &nq->node;
struct TOPO_NODE* ni = u_calloc(1, sizeof(struct TOPO_NODE));
if (!ni) { queue_entry_free(qe); return; }
ni->node_id = node_id;
ni->group_id = group->group_id;
ni->ver = 0;
memcpy(ni->public_key, inv->pubkey, SC_PUBKEY_SIZE);
memset(ni->ed25519_public_key, 0, SC_PUBKEY_SIZE);
ni->node_name_len = 0;
ni->local_v4_sockets = 0;
ni->local_v4_addrs = (uint8_t)inv->addr_count;
ni->local_v6_sockets = 0;
ni->local_v6_addrs = 0;
ni->local_v4_subnets = 0;
ni->local_v6_subnets = 0;
ni->tranzit_nodes = 0;
ni->hop_count = 0;
uint8_t* dyn = (uint8_t*)ni + sizeof(struct NODEINFO);
ni->node_name = u_strdup("");
struct TOPO_ADDR4* addrs_head = NULL;
const uint8_t* src = inv->addrs_data;
for (int i = 0; i < inv->addr_count; i++) {
struct NODEINFO_IPV4_ADDR* addr =
(struct NODEINFO_IPV4_ADDR*)(dyn + i * sizeof(struct NODEINFO_IPV4_ADDR));
memset(addr, 0, sizeof(*addr));
uint8_t family = *src++;
if (family == 4) {
memcpy(addr->addr, src, 4); src += 4;
uint16_t port_be = ((uint16_t)src[0] << 8) | src[1]; src += 2;
addr->port = port_be;
addr->type = ADDR_TYPE_REAL;
addr->socket_id = 0;
addr->protocol = NODE_PROTO_UDP;
struct TOPO_ADDR4* a4 = memory_pool_alloc(group->instance->topo_groups->v4_addr_pool);
if (!a4) continue;
memcpy(a4->addr, src, 4); src += 4;
a4->port = ((uint16_t)src[0] << 8) | src[1]; src += 2;
a4->type = TOPO_ADDR_REAL; a4->socket_id = 0; a4->protocol = TOPO_PROTO_UDP;
a4->next = addrs_head; addrs_head = a4;
} else {
src += 18; continue;
src += 18; /* skip v6 */
}
}
ni->v4_addrs = addrs_head;
nq->alien = 0;
nq->dirty = 0;
nq->last_ver = 0;
nq->conn_mgr_type = CONN_TYPE_NONE;
nq->conn_mgr_intermediariy_count = 0;
nq->node = ni; topo_node_ref(ni);
nq->hash_node_id = node_id; nq->alien = 0;
nq->dirty = 0; nq->last_ver = 0;
nq->conn_mgr_type = CONN_TYPE_NONE; nq->conn_mgr_intermediariy_count = 0;
memset(&nq->connectivity, 0, sizeof(nq->connectivity));
nq->best_socket = NULL;
@ -695,3 +653,72 @@ void chat_core_connect_from_invite(struct chat_invite* inv) {
conn_mgr_connect_node(g_cc.inst->conn_mgr, node_id, 30000,
connect_result_cb, NULL);
}
/* ─── создание канала ─── */
void chat_core_create_channel(struct chat_channel_create* req) {
if (!g_cc.initialized) {
DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "%s: create_channel NOT INITIALIZED ch=%s",
CC_ID, req ? req->channel_id : "(null)");
return;
}
if (!req) { DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "%s: create_channel req=NULL", CC_ID); return; }
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: create_channel BEGIN ch=%s name=%s",
CC_ID, req->channel_id, req->name);
char tbl_msg[80]; msg_table_name(req->channel_id, tbl_msg, sizeof(tbl_msg));
/* создаём таблицу сообщений */
char sql[512]; snprintf(sql, sizeof(sql),
"CREATE TABLE IF NOT EXISTS \"%s\" ("
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
" node_id INTEGER NOT NULL,"
" content_type TEXT NOT NULL,"
" data BLOB NOT NULL,"
" timestamp INTEGER NOT NULL,"
" datahash INTEGER NOT NULL,"
" chain_hash BLOB NOT NULL,"
" signature BLOB,"
" is_outgoing INTEGER DEFAULT 0,"
" is_read INTEGER DEFAULT 0,"
" sync_flags INTEGER DEFAULT 0,"
" UNIQUE(timestamp, datahash))", tbl_msg);
db_exec(sql);
snprintf(sql, sizeof(sql),
"CREATE INDEX IF NOT EXISTS \"idx_%s_ts_dh\" ON \"%s\"(timestamp, datahash)",
tbl_msg, tbl_msg);
db_exec(sql);
/* записываем канал в БД */
int rc = topo_node_sqlite_channel_put(g_cc.db,
req->channel_id, req->name, req->is_dm, req->owner_node_id,
req->x25519_pubkey, req->x25519_privkey,
req->ed25519_pubkey, req->ed25519_privkey,
req->signature);
if (rc != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "%s: channel_put FAILED ch=%s rc=%d",
CC_ID, req->channel_id, rc);
} else {
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: channel_put OK ch=%s name=%s owner=0x%016llx",
CC_ID, req->channel_id, req->name, (unsigned long long)req->owner_node_id);
/* уведомляем GUI */
uint8_t ch_id_len = (uint8_t)strlen(req->channel_id);
uint8_t data[65];
data[0] = ch_id_len;
memcpy(data + 1, req->channel_id, ch_id_len);
gui_bridge_post(GUI_EVT_CHANNEL_UPDATED, data, 1 + ch_id_len);
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: gui_bridge_post sent GUI_EVT_CHANNEL_UPDATED ch=%s",
CC_ID, req->channel_id);
}
}
void chat_core_create_channel_trampoline(void* arg) {
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "chat_core: TRAMPOLINE invoked arg=%p", arg);
struct chat_channel_create* req = (struct chat_channel_create*)arg;
chat_core_create_channel(req);
u_free(req);
}

17
tools/chatgui/transport/chat_core.h

@ -62,6 +62,23 @@ struct chat_invite {
void chat_core_connect_from_invite(struct chat_invite* inv);
/* ── Создание канала (GUI → uasync) ── */
struct chat_channel_create {
char channel_id[64];
char name[128];
uint64_t owner_node_id;
int is_dm;
uint8_t x25519_pubkey[32];
uint8_t x25519_privkey[32];
uint8_t ed25519_pubkey[32];
uint8_t ed25519_privkey[32];
uint8_t signature[64];
};
void chat_core_create_channel(struct chat_channel_create* req);
void chat_core_create_channel_trampoline(void* arg);
/* ── Утилиты ── */
void chat_core_set_my_node_id(uint64_t node_id);

7
tools/chatgui/transport/gui_bridge.h

@ -31,6 +31,9 @@ void gui_bridge_post(int event_type, const uint8_t* data, int data_len);
/* GUI → uasync: выполнить функцию в uasync-потоке */
void gui_bridge_post_uasync_fn(void (*fn)(void*), void* arg);
/* Проверить готовность uasync (g_ua != NULL) */
int gui_bridge_is_uasync_ready(void);
/* Callback для результатов подключения (вызывается из GUI-потока) */
typedef void (*gui_connect_result_fn)(uint64_t node_id, int result);
void gui_bridge_set_connect_result_cb(gui_connect_result_fn cb);
@ -39,6 +42,10 @@ void gui_bridge_set_connect_result_cb(gui_connect_result_fn cb);
typedef void (*gui_msg_received_fn)(const char* ch_id, int ch_id_len);
void gui_bridge_set_msg_received_cb(gui_msg_received_fn cb);
/* Callback для обновления канала (вызывается из GUI-потока) */
typedef void (*gui_channel_updated_fn)(const char* ch_id, int ch_id_len);
void gui_bridge_set_channel_updated_cb(gui_channel_updated_fn cb);
#ifdef __cplusplus
}
#endif

42
tools/chatgui/transport/gui_bridge_impl.cpp

@ -3,12 +3,12 @@
#include <QObject>
#include <QMetaObject>
#include <QByteArray>
#include <QDebug>
#include <cstring>
extern "C" {
#include "../../../lib/u_async.h"
#include "../../../lib/mem.h"
#include "../../../lib/debug_config.h"
}
/* ── Внутренний объект-приёмник в GUI-потоке ── */
@ -26,6 +26,7 @@ public:
static GuiBridgeReceiver* g_receiver = nullptr;
static gui_connect_result_fn g_connect_result_cb = nullptr;
static gui_msg_received_fn g_msg_received_cb = nullptr;
static gui_channel_updated_fn g_channel_updated_cb = nullptr;
static struct UASYNC* g_ua = nullptr;
/* ── GuiBridgeReceiver implementation ── */
@ -40,32 +41,32 @@ void GuiBridgeReceiver::processPost(int eventType, QByteArray data) {
uint8_t chLen = d[0];
if (dlen >= 1 + chLen) {
QString chId = QString::fromUtf8((const char*)d + 1, chLen);
qDebug("gui_bridge: MSG_RECEIVED ch=%s", qPrintable(chId));
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "gui_bridge: MSG_RECEIVED ch=%s", qPrintable(chId));
if (g_msg_received_cb) g_msg_received_cb((const char*)d + 1, chLen);
} else {
qWarning("gui_bridge: MSG_RECEIVED invalid data length %d", dlen);
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "gui_bridge: MSG_RECEIVED invalid data length %d", dlen);
}
} else {
qWarning("gui_bridge: MSG_RECEIVED data too short %d", dlen);
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "gui_bridge: MSG_RECEIVED data too short %d", dlen);
}
break;
case GUI_EVT_CONNECT_RESULT:
if (dlen >= 12) {
uint64_t nodeId; int result;
memcpy(&nodeId, d, 8); memcpy(&result, d + 8, 4);
qDebug("gui_bridge: CONNECT_RESULT node=0x%016llx result=%d",
(unsigned long long)nodeId, result);
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "gui_bridge: CONNECT_RESULT node=0x%016llx result=%d",
(unsigned long long)nodeId, result);
if (g_connect_result_cb) g_connect_result_cb(nodeId, result);
} else {
qWarning("gui_bridge: CONNECT_RESULT data too short %d", dlen);
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "gui_bridge: CONNECT_RESULT data too short %d", dlen);
}
break;
case GUI_EVT_NEW_PEER:
if (dlen >= 8) {
uint64_t nodeId; memcpy(&nodeId, d, 8);
qDebug("gui_bridge: NEW_PEER node=0x%016llx", (unsigned long long)nodeId);
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "gui_bridge: NEW_PEER node=0x%016llx", (unsigned long long)nodeId);
} else {
qWarning("gui_bridge: NEW_PEER data too short %d", dlen);
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "gui_bridge: NEW_PEER data too short %d", dlen);
}
break;
case GUI_EVT_CHANNEL_UPDATED:
@ -73,14 +74,15 @@ void GuiBridgeReceiver::processPost(int eventType, QByteArray data) {
uint8_t chLen = d[0];
if (dlen >= 1 + chLen) {
QString chId = QString::fromUtf8((const char*)d + 1, chLen);
qDebug("gui_bridge: CHANNEL_UPDATED ch=%s", qPrintable(chId));
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "gui_bridge: CHANNEL_UPDATED ch=%s", qPrintable(chId));
if (g_channel_updated_cb) g_channel_updated_cb((const char*)d + 1, chLen);
}
} else {
qWarning("gui_bridge: CHANNEL_UPDATED data too short %d", dlen);
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "gui_bridge: CHANNEL_UPDATED data too short %d", dlen);
}
break;
default:
qWarning("gui_bridge: unknown event type %d", eventType);
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "gui_bridge: unknown event type %d", eventType);
break;
}
}
@ -112,7 +114,17 @@ void gui_bridge_post(int event_type, const uint8_t* data, int data_len) {
}
void gui_bridge_post_uasync_fn(void (*fn)(void*), void* arg) {
if (g_ua && fn) uasync_post(g_ua, fn, arg);
if (!g_ua) {
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "gui_bridge_post_uasync_fn: g_ua=NULL, post dropped");
return;
}
if (!fn) return;
uasync_post(g_ua, fn, arg);
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "gui_bridge_post_uasync_fn: posted fn=%p arg=%p", (void*)fn, arg);
}
int gui_bridge_is_uasync_ready(void) {
return g_ua != NULL ? 1 : 0;
}
void gui_bridge_set_connect_result_cb(gui_connect_result_fn cb) {
@ -123,6 +135,10 @@ void gui_bridge_set_msg_received_cb(gui_msg_received_fn cb) {
g_msg_received_cb = cb;
}
void gui_bridge_set_channel_updated_cb(gui_channel_updated_fn cb) {
g_channel_updated_cb = cb;
}
} /* extern "C" */
#include "gui_bridge_impl.moc"

6
tools/chatgui/transport/node_config.cpp

@ -125,6 +125,8 @@ bool NodeConfig::load() {
} else if (section == "gui") {
if (key == "db_path") m_dbPath = val;
else if (key == "debug_file") m_debugFile = val;
else if (key == "debug_level") m_debugLevel = val;
else if (key == "debug_categories") m_debugCategories = val;
}
}
f.close();
@ -169,7 +171,9 @@ bool NodeConfig::save() {
out << "[gui]\n";
out << "db_path=" << (m_dbPath.isEmpty() ? "chats.db" : m_dbPath) << "\n";
out << "debug_file=" << m_debugFile << "\n\n";
out << "debug_file=" << m_debugFile << "\n";
out << "debug_level=" << (m_debugLevel.isEmpty() ? "info" : m_debugLevel) << "\n";
out << "debug_categories=" << m_debugCategories << "\n\n";
f.close();
return true;

4
tools/chatgui/transport/node_config.h

@ -48,6 +48,8 @@ public:
/* GUI settings (from [gui] section) */
QString dbPath() const { return m_dbPath; }
QString debugFile() const { return m_debugFile; }
QString debugLevel() const { return m_debugLevel; }
QString debugCategories() const { return m_debugCategories; }
/* Identity generation (public for fixing invalid keys) */
void generateIdentity();
@ -64,5 +66,7 @@ private:
QString m_controlAddr;
QString m_dbPath;
QString m_debugFile;
QString m_debugLevel;
QString m_debugCategories;
};
#endif // NODE_CONFIG_H

203
tools/chatgui/transport/topo_node_sqlite.c

@ -0,0 +1,203 @@
#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) {
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;
}

24
tools/chatgui/transport/topo_node_sqlite.h

@ -0,0 +1,24 @@
#ifndef TOPO_NODE_SQLITE_H
#define TOPO_NODE_SQLITE_H
#include <sqlite3.h>
#include <stdint.h>
#include <stddef.h>
struct TOPO_NODEQ;
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);
int topo_node_sqlite_member_del(sqlite3* db, const char* channel_id, uint64_t node_id);
#endif

62
tools/chatgui/transport/utun_node.cpp

@ -60,6 +60,14 @@ void UtunNode::setDbPath(const QString& path) {
m_dbPath = path;
}
void UtunNode::setDebugLevel(const QString& level) {
m_debugLevel = level;
}
void UtunNode::setDebugCategories(const QString& categories) {
m_debugCategories = categories;
}
void UtunNode::stop() {
if (!m_running) return;
m_stop = true;
@ -100,11 +108,62 @@ void UtunNode::runLoop() {
g_currentNode = this;
debug_config_init();
debug_set_level(DEBUG_LEVEL_ERROR);
debug_enable_function_name(0);
/* ── debug level from config ── */
if (!m_debugLevel.isEmpty()) {
QString lvl = m_debugLevel.toLower();
debug_level_t dl = DEBUG_LEVEL_INFO;
if (lvl == "none") dl = DEBUG_LEVEL_NONE;
else if (lvl == "error") dl = DEBUG_LEVEL_ERROR;
else if (lvl == "warn") dl = DEBUG_LEVEL_WARN;
else if (lvl == "info") dl = DEBUG_LEVEL_INFO;
else if (lvl == "debug") dl = DEBUG_LEVEL_DEBUG;
else if (lvl == "trace") dl = DEBUG_LEVEL_TRACE;
g_debug_config.level = dl;
g_debug_config.console_level = dl;
g_debug_config.file_level = dl;
} else {
g_debug_config.level = DEBUG_LEVEL_INFO;
g_debug_config.console_level = DEBUG_LEVEL_INFO;
g_debug_config.file_level = DEBUG_LEVEL_INFO;
}
/* chatgui defaults: suppress spam, verbose for our categories */
g_debug_config.category_levels[DEBUG_CATEGORY_MEMORY] = DEBUG_LEVEL_DISABLED;
g_debug_config.category_levels[DEBUG_CATEGORY_TIMING] = DEBUG_LEVEL_DISABLED;
g_debug_config.category_levels[DEBUG_CATEGORY_DEBUG] = DEBUG_LEVEL_TRACE;
g_debug_config.category_levels[DEBUG_CATEGORY_BGP] = DEBUG_LEVEL_INFO;
/* per-category overrides from config: cat=level,cat=level,... */
if (!m_debugCategories.isEmpty()) {
QByteArray cats = m_debugCategories.toUtf8();
char* token = strtok(cats.data(), ",");
while (token) {
while (*token == ' ') token++;
char* eq = strchr(token, '=');
if (eq) {
*eq = '\0';
char* cat_name = token;
char* lvl_name = eq + 1;
while (*lvl_name == ' ') lvl_name++;
debug_category_t cat = get_category_by_name(cat_name);
debug_level_t lvl = debug_level_from_name(lvl_name);
if (cat != DEBUG_CATEGORY_NONE && lvl != DEBUG_LEVEL_NONE) {
g_debug_config.category_levels[cat] = lvl;
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "debug category: %s=%s",
debug_get_category_name(cat),
lvl_name);
}
}
token = strtok(NULL, ",");
}
}
/* file output (shared with GUI via debug_config) */
if (!m_debugFile.isEmpty()) {
debug_enable_file_output(m_debugFile.toUtf8().constData(), 0);
debug_enable_console(1);
g_debug_config.thread_marker = 2;
}
@ -145,6 +204,7 @@ void UtunNode::runLoop() {
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");
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "utun_node: entering poll loop");
while (!m_stop) {
uasync_poll(ua, 100);

4
tools/chatgui/transport/utun_node.h

@ -22,6 +22,8 @@ public:
void setDebugFile(const QString& path);
void setDbPath(const QString& path);
void setDebugLevel(const QString& level);
void setDebugCategories(const QString& categories);
void send(uint64_t dstNodeId, const QByteArray& data);
@ -45,5 +47,7 @@ private:
QString m_configPath;
QString m_debugFile;
QString m_dbPath;
QString m_debugLevel;
QString m_debugCategories;
};
#endif // UTUN_NODE_H

Loading…
Cancel
Save