Browse Source
- 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
26 changed files with 759 additions and 263 deletions
@ -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(); |
||||||
|
} |
||||||
@ -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; |
||||||
|
}; |
||||||
@ -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; |
||||||
|
} |
||||||
@ -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 |
||||||
Loading…
Reference in new issue