From 7628f77b930fb26e45f8088dab51d44de49743de Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 6 Jul 2026 11:59:01 +0300 Subject: [PATCH] chatgui: integrate UtunNode + NodeConfig, replace MsgClient - Add NodeConfig: simplified INI config manager for embedded uTun node - X25519 key generation via OpenSSL EVP - Random node_id generation - addServer/removeServer, addClient/removeClient API - Auto-creates config with default server on first run - Integrate UtunNode into MainWindow: - Creates NodeConfig in ~/.config/chatgui/node.conf - Starts embedded uTun node (no TUN, no msg_transport) - In separate thread with uasync_poll event loop - Replace MsgClient (TCP IPC) with UtunNode (direct calls): - ChatPropagator now uses UtunNode for send/recv - send(uint64_t, QByteArray) replaces sendTo - messageReceived signal replaces received signal - MainWindow cleanup: remove MsgClient, add UtunNode+NodeConfig --- tools/chatgui/CMakeLists.txt | 1 + tools/chatgui/src/mainwindow.cpp | 69 +++++-- tools/chatgui/src/mainwindow.h | 12 +- tools/chatgui/transport/chat_propagator.cpp | 12 +- tools/chatgui/transport/chat_propagator.h | 6 +- tools/chatgui/transport/node_config.cpp | 206 ++++++++++++++++++++ tools/chatgui/transport/node_config.h | 60 ++++++ 7 files changed, 340 insertions(+), 26 deletions(-) create mode 100644 tools/chatgui/transport/node_config.cpp create mode 100644 tools/chatgui/transport/node_config.h diff --git a/tools/chatgui/CMakeLists.txt b/tools/chatgui/CMakeLists.txt index 6b55060f..53484fac 100644 --- a/tools/chatgui/CMakeLists.txt +++ b/tools/chatgui/CMakeLists.txt @@ -65,6 +65,7 @@ add_executable(chatgui transport/crypto.cpp transport/chat_propagator.cpp transport/utun_node.cpp + transport/node_config.cpp db/db_manager.cpp db/sqlite3.c resources/chatgui.qrc diff --git a/tools/chatgui/src/mainwindow.cpp b/tools/chatgui/src/mainwindow.cpp index 6fa002e7..f8dc6483 100644 --- a/tools/chatgui/src/mainwindow.cpp +++ b/tools/chatgui/src/mainwindow.cpp @@ -5,14 +5,16 @@ #include "invitedialog.h" #include "invite_link.h" #include "../db/db_manager.h" -#include "../transport/msg_client.h" +#include "../transport/utun_node.h" +#include "../transport/node_config.h" #include "../transport/chat_propagator.h" -#include "../transport/crypto.h" #include #include #include #include #include +#include +#include MainWindow::MainWindow(QWidget *parent, DbManager* db) : QMainWindow(parent) @@ -22,8 +24,6 @@ MainWindow::MainWindow(QWidget *parent, DbManager* db) , m_trayIcon(nullptr) , m_trayMenu(nullptr) , m_db(db) - , m_client(nullptr) - , m_propagator(nullptr) { setWindowTitle("Chat"); resize(900, 600); @@ -31,12 +31,8 @@ MainWindow::MainWindow(QWidget *parent, DbManager* db) setupTray(); if (m_db && m_db->isOpen()) { - m_client = new MsgClient(this); - m_propagator = new ChatPropagator(m_client, m_db, this); - m_messageList->setPropagator(m_propagator); - - connect(m_propagator, &ChatPropagator::messageReceived, - m_messageList, &MessageList::addMessage); + setupNode(); + setupMessaging(); connect(m_channelList, &ChannelList::channelSelected, this, [this](const QString& channelId) { @@ -56,6 +52,11 @@ MainWindow::MainWindow(QWidget *parent, DbManager* db) } } +MainWindow::~MainWindow() { + if (m_node) { m_node->stop(); delete m_node; m_node = nullptr; } + delete m_config; +} + void MainWindow::setupUi() { auto *splitter = new QSplitter(Qt::Horizontal, this); @@ -76,6 +77,45 @@ void MainWindow::setupUi() { setCentralWidget(splitter); } +void MainWindow::setupNode() { + QString configDir = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation); + QDir().mkpath(configDir); + QString configPath = configDir + "/node.conf"; + + m_config = new NodeConfig(configPath); + if (!m_config->exists()) { + if (!m_config->create("chatgui", 1333)) { + qWarning("Failed to create node config"); + return; + } + } else { + m_config->load(); + } + + m_node = new UtunNode(this); + connect(m_node, &UtunNode::error, this, [](const QString& msg) { + qWarning("UtunNode error: %s", qPrintable(msg)); + }); + connect(m_node, &UtunNode::started, this, []() { + qDebug("UtunNode started"); + }); + connect(m_node, &UtunNode::stopped, this, []() { + qDebug("UtunNode stopped"); + }); + + if (!m_node->start(configPath)) + qWarning("Failed to start UtunNode"); +} + +void MainWindow::setupMessaging() { + if (!m_node || !m_node->isRunning()) return; + m_propagator = new ChatPropagator(m_node, m_db, this); + m_messageList->setPropagator(m_propagator); + + connect(m_propagator, &ChatPropagator::messageReceived, + m_messageList, &MessageList::addMessage); +} + void MainWindow::setupTray() { m_trayIcon = new QSystemTrayIcon(this); m_trayIcon->setIcon(style()->standardIcon(QStyle::SP_ComputerIcon)); @@ -119,9 +159,12 @@ void MainWindow::onTrayActivated(QSystemTrayIcon::ActivationReason reason) { void MainWindow::showInviteDialog(const QString& channelId) { if (!m_db || !m_db->isOpen()) return; - QByteArray pubkey = m_db->getLocalPubkey(); + QByteArray pubkey; + if (m_config && !m_config->pubKey().isEmpty()) + pubkey = QByteArray::fromHex(m_config->pubKey().toLatin1()); + if (pubkey.size() != 32) { - QMessageBox::warning(this, "Ошибка", "Не удалось получить публичный ключ узла."); + QMessageBox::warning(this, "Error", "Failed to get node public key."); return; } @@ -129,7 +172,7 @@ void MainWindow::showInviteDialog(const QString& channelId) { QString link = encodeInviteLink(channelId, pubkey, addrs); if (link.isEmpty()) { - QMessageBox::warning(this, "Ошибка", "Не удалось сформировать пригласительную ссылку."); + QMessageBox::warning(this, "Error", "Failed to create invite link."); return; } diff --git a/tools/chatgui/src/mainwindow.h b/tools/chatgui/src/mainwindow.h index 9483a886..b2b8e7d1 100644 --- a/tools/chatgui/src/mainwindow.h +++ b/tools/chatgui/src/mainwindow.h @@ -8,14 +8,15 @@ class ChannelList; class MessageList; class AccountList; class DbManager; -class MsgClient; +class UtunNode; +class NodeConfig; class ChatPropagator; class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = nullptr, DbManager* db = nullptr); - ~MainWindow() override = default; + ~MainWindow() override; protected: void closeEvent(QCloseEvent *event) override; @@ -27,6 +28,8 @@ private slots: private: void setupUi(); void setupTray(); + void setupNode(); + void setupMessaging(); ChannelList *m_channelList; MessageList *m_messageList; @@ -34,6 +37,7 @@ private: QSystemTrayIcon *m_trayIcon; QMenu *m_trayMenu; DbManager *m_db; - MsgClient *m_client; - ChatPropagator *m_propagator; + UtunNode *m_node = nullptr; + NodeConfig *m_config = nullptr; + ChatPropagator *m_propagator = nullptr; }; diff --git a/tools/chatgui/transport/chat_propagator.cpp b/tools/chatgui/transport/chat_propagator.cpp index 54974a40..99bc1ba4 100644 --- a/tools/chatgui/transport/chat_propagator.cpp +++ b/tools/chatgui/transport/chat_propagator.cpp @@ -1,6 +1,6 @@ #include "chat_propagator.h" #include "chat_proto.h" -#include "msg_client.h" +#include "utun_node.h" #include "crypto.h" #include "db_manager.h" @@ -21,13 +21,13 @@ static uint16_t read16(const uint8_t* p) { } static void write16(uint8_t* p, uint16_t v) { memcpy(p, &v, 2); } -ChatPropagator::ChatPropagator(MsgClient* client, DbManager* db, QObject* parent) +ChatPropagator::ChatPropagator(UtunNode* node, DbManager* db, QObject* parent) : QObject(parent) - , m_client(client) + , m_node(node) , m_db(db) , m_ackTimer(new QTimer(this)) { - connect(m_client, &MsgClient::received, + connect(m_node, &UtunNode::messageReceived, this, &ChatPropagator::onReceived); connect(m_ackTimer, &QTimer::timeout, this, &ChatPropagator::onAckTimeout); @@ -107,7 +107,7 @@ void ChatPropagator::fanoutGossip(const QString& channelId, const QByteArray& pa } void ChatPropagator::sendGossip(quint64 peer, const QByteArray& packet) { - m_client->sendTo(peer, packet); + m_node->send(peer, packet); } /* ======================================================================== */ @@ -149,7 +149,7 @@ void ChatPropagator::handleNewMsg(quint64 fromPeer, const QByteArray& payload) { fanoutGossip(channelId, payload, sig, ChatProp::kDefaultFanout, exclude); /* ack to sender */ - m_client->sendTo(fromPeer, buildAckPacket(sig)); + m_node->send(fromPeer, buildAckPacket(sig)); } void ChatPropagator::handleNewAck(const QByteArray& payload) { diff --git a/tools/chatgui/transport/chat_propagator.h b/tools/chatgui/transport/chat_propagator.h index 8645c214..beb7d5fc 100644 --- a/tools/chatgui/transport/chat_propagator.h +++ b/tools/chatgui/transport/chat_propagator.h @@ -7,14 +7,14 @@ #include #include -class MsgClient; +class UtunNode; class DbManager; struct MessageRow; class ChatPropagator : public QObject { Q_OBJECT public: - ChatPropagator(MsgClient* client, DbManager* db, QObject* parent = nullptr); + ChatPropagator(UtunNode* node, DbManager* db, QObject* parent = nullptr); void setKeys(const QByteArray& edPriv, const QByteArray& edPub, quint64 nodeId); void start(); @@ -26,7 +26,7 @@ signals: const QByteArray& content, qint64 timestamp); private: - MsgClient* m_client; + UtunNode* m_node; DbManager* m_db; QTimer* m_ackTimer; diff --git a/tools/chatgui/transport/node_config.cpp b/tools/chatgui/transport/node_config.cpp new file mode 100644 index 00000000..f25ea6a8 --- /dev/null +++ b/tools/chatgui/transport/node_config.cpp @@ -0,0 +1,206 @@ +// node_config.cpp — Simplified INI config manager for embedded uTun node +#include "node_config.h" + +extern "C" { +#include +#include +} +#include +#include +#include + +static QString formatKeyHex(const uint8_t* bin, size_t len) { + return QByteArray(reinterpret_cast(bin), len).toHex(); +} + +static bool parseKeyHex(const QString& hex, uint8_t* bin, size_t len) { + QByteArray ba = QByteArray::fromHex(hex.toLatin1()); + if (ba.size() != (int)len) return false; + memcpy(bin, ba.constData(), len); + return true; +} + +// ===================================================================== +// NodeConfig +// ===================================================================== + +NodeConfig::NodeConfig(const QString& path) : m_path(path) {} + +bool NodeConfig::exists() const { + return QFileInfo::exists(m_path); +} + +void NodeConfig::generateIdentity() { + // X25519 keypair + 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; + uint8_t pk[32], sk[32]; + EVP_PKEY_get_raw_public_key(pkey, pk, &pkLen); + EVP_PKEY_get_raw_private_key(pkey, sk, &skLen); + m_pubKey = formatKeyHex(pk, 32); + m_privKey = formatKeyHex(sk, 32); + EVP_PKEY_free(pkey); + } + + // Random 63-bit node_id + uint64_t id = 0; + RAND_bytes(reinterpret_cast(&id), sizeof(id)); + id &= 0x7FFFFFFFFFFFFFFFULL; + m_nodeId = QString("%1").arg(id, 16, 16, QChar('0')).right(16); +} + +bool NodeConfig::create(const QString& nodeName, quint16 listenPort) { + m_nodeName = nodeName.isEmpty() ? "chatgui" : nodeName; + if (m_pubKey.isEmpty()) generateIdentity(); + if (!m_controlAddr.isEmpty() && m_controlAddr.indexOf(':') < 0) + m_controlAddr = "127.0.0.1:9999"; + m_servers.clear(); + m_clients.clear(); + if (listenPort > 0) + m_servers.append({"lan", QString("0.0.0.0:%1").arg(listenPort)}); + return save(); +} + +bool NodeConfig::load() { + QFile f(m_path); + if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) return false; + + m_servers.clear(); + m_clients.clear(); + + QString section; + QTextStream in(&f); + while (!in.atEnd()) { + QString line = in.readLine().trimmed(); + if (line.isEmpty() || line.startsWith('#')) continue; + + if (line.startsWith('[') && line.endsWith(']')) { + section = line.mid(1, line.length() - 2); + continue; + } + + int eq = line.indexOf('='); + if (eq < 0) continue; + QString key = line.left(eq).trimmed(); + QString val = line.mid(eq + 1).trimmed(); + + if (section == "global") { + if (key == "my_node_name") m_nodeName = val; + else if (key == "my_node_id") m_nodeId = val; + else if (key == "my_public_key") m_pubKey = val; + else if (key == "my_private_key") m_privKey = val; + } else if (section.startsWith("server:")) { + QString srvName = section.mid(7); + if (key == "addr") m_servers.append({srvName, val}); + } else if (section.startsWith("client:")) { + QString cliName = section.mid(7); + if (key == "peer_public_key") { + NodeClient nc; + nc.name = cliName; + nc.pubKeyHex = val; + m_clients.append(nc); + } else if (key == "link") { + if (!m_clients.isEmpty() && m_clients.last().name == cliName) { + // link=server:addr:port + int sep1 = val.indexOf(':'); + int sep2 = val.lastIndexOf(':'); + if (sep1 > 0 && sep2 > sep1) { + m_clients.last().serverName = val.left(sep1); + m_clients.last().remoteAddr = val.mid(sep1 + 1); + } + } + } + } else if (section == "control") { + if (key == "addr") m_controlAddr = val; + } + } + f.close(); + return !m_nodeId.isEmpty(); +} + +bool NodeConfig::save() { + QFile f(m_path); + if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) return false; + + QTextStream out(&f); + out << "[global]\n"; + if (!m_nodeName.isEmpty()) out << "my_node_name=" << m_nodeName << "\n"; + if (!m_nodeId.isEmpty()) out << "my_node_id=" << m_nodeId << "\n"; + if (!m_privKey.isEmpty()) out << "my_private_key=" << m_privKey << "\n"; + if (!m_pubKey.isEmpty()) out << "my_public_key=" << m_pubKey << "\n"; + out << "tun_enabled=no\n"; + out << "\n"; + + for (auto& srv : m_servers) { + out << "[server:" << srv.first << "]\n"; + out << "addr=" << srv.second << "\n\n"; + } + + for (auto& cli : m_clients) { + out << "[client:" << cli.name << "]\n"; + out << "peer_public_key=" << cli.pubKeyHex << "\n"; + out << "link=" << cli.serverName << ":" << cli.remoteAddr << "\n\n"; + } + + if (!m_controlAddr.isEmpty()) { + out << "[control]\n"; + out << "addr=" << m_controlAddr << "\n\n"; + } + + f.close(); + return true; +} + +uint64_t NodeConfig::nodeIdU64() const { + if (m_nodeId.length() != 16) return 0; + return m_nodeId.toULongLong(nullptr, 16); +} + +quint16 NodeConfig::controlPort() const { + if (m_controlAddr.isEmpty()) return 0; + int sep = m_controlAddr.lastIndexOf(':'); + if (sep < 0) return 0; + return static_cast(m_controlAddr.midRef(sep + 1).toUInt()); +} + +void NodeConfig::setControlPort(quint16 p) { + int sep = m_controlAddr.lastIndexOf(':'); + if (sep >= 0) + m_controlAddr = m_controlAddr.left(sep) + ":" + QString::number(p); + else + m_controlAddr = "127.0.0.1:" + QString::number(p); +} + +bool NodeConfig::addServer(const QString& name, const QString& addr) { + for (auto& s : m_servers) { if (s.first == name) return false; } + m_servers.append({name, addr}); + return save(); +} + +bool NodeConfig::removeServer(const QString& name) { + for (int i = 0; i < m_servers.size(); ++i) { + if (m_servers[i].first == name) { m_servers.removeAt(i); return save(); } + } + return false; +} + +bool NodeConfig::addClient(const QString& name, const QString& pubKeyHex, + const QString& serverName, const QString& remoteAddr) { + for (auto& c : m_clients) { if (c.name == name) return false; } + m_clients.append({name, pubKeyHex, serverName, remoteAddr}); + return save(); +} + +bool NodeConfig::removeClient(const QString& name) { + for (int i = 0; i < m_clients.size(); ++i) { + if (m_clients[i].name == name) { m_clients.removeAt(i); return save(); } + } + return false; +} diff --git a/tools/chatgui/transport/node_config.h b/tools/chatgui/transport/node_config.h new file mode 100644 index 00000000..c8a652b7 --- /dev/null +++ b/tools/chatgui/transport/node_config.h @@ -0,0 +1,60 @@ +// node_config.h — Simplified INI config manager for embedded uTun node +#ifndef NODE_CONFIG_H +#define NODE_CONFIG_H + +#include +#include +#include + +struct NodeClient { + QString name; + QString pubKeyHex; + QString serverName; + QString remoteAddr; +}; + +class NodeConfig { +public: + explicit NodeConfig(const QString& path); + + bool exists() const; + bool create(const QString& nodeName, quint16 listenPort); + bool load(); + bool save(); + + QString nodeName() const { return m_nodeName; } + void setNodeName(const QString& n) { m_nodeName = n; } + QString nodeId() const { return m_nodeId; } + QString pubKey() const { return m_pubKey; } + QString privKey() const { return m_privKey; } + uint64_t nodeIdU64() const; + + QList> servers() const { return m_servers; } + bool addServer(const QString& name, const QString& addr); + bool removeServer(const QString& name); + + QList clients() const { return m_clients; } + bool addClient(const QString& name, const QString& pubKeyHex, + const QString& serverName, const QString& remoteAddr); + bool removeClient(const QString& name); + + QString controlAddr() const { return m_controlAddr; } + void setControlAddr(const QString& a) { m_controlAddr = a; } + quint16 controlPort() const; + void setControlPort(quint16 p); + + QString filePath() const { return m_path; } + +private: + void generateIdentity(); + + QString m_path; + QString m_nodeName; + QString m_nodeId; + QString m_pubKey; + QString m_privKey; + QList> m_servers; + QList m_clients; + QString m_controlAddr; +}; +#endif // NODE_CONFIG_H