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')
This commit is contained in:
Evgeny committed 2026-07-12 17:39:20 +03:00
1 parent a9a6b6bb10
commit de7841f33a
26 files changed
+761 -265

No files matched your search

+138 -2
View File
@@ -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));
}