Browse Source

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
chatgui
Evgeny 3 months ago
parent
commit
7628f77b93
  1. 1
      tools/chatgui/CMakeLists.txt
  2. 69
      tools/chatgui/src/mainwindow.cpp
  3. 12
      tools/chatgui/src/mainwindow.h
  4. 12
      tools/chatgui/transport/chat_propagator.cpp
  5. 6
      tools/chatgui/transport/chat_propagator.h
  6. 206
      tools/chatgui/transport/node_config.cpp
  7. 60
      tools/chatgui/transport/node_config.h

1
tools/chatgui/CMakeLists.txt

@ -65,6 +65,7 @@ add_executable(chatgui
transport/crypto.cpp transport/crypto.cpp
transport/chat_propagator.cpp transport/chat_propagator.cpp
transport/utun_node.cpp transport/utun_node.cpp
transport/node_config.cpp
db/db_manager.cpp db/db_manager.cpp
db/sqlite3.c db/sqlite3.c
resources/chatgui.qrc resources/chatgui.qrc

69
tools/chatgui/src/mainwindow.cpp

@ -5,14 +5,16 @@
#include "invitedialog.h" #include "invitedialog.h"
#include "invite_link.h" #include "invite_link.h"
#include "../db/db_manager.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/chat_propagator.h"
#include "../transport/crypto.h"
#include <QSplitter> #include <QSplitter>
#include <QCloseEvent> #include <QCloseEvent>
#include <QApplication> #include <QApplication>
#include <QStyle> #include <QStyle>
#include <QMessageBox> #include <QMessageBox>
#include <QStandardPaths>
#include <QDir>
MainWindow::MainWindow(QWidget *parent, DbManager* db) MainWindow::MainWindow(QWidget *parent, DbManager* db)
: QMainWindow(parent) : QMainWindow(parent)
@ -22,8 +24,6 @@ MainWindow::MainWindow(QWidget *parent, DbManager* db)
, m_trayIcon(nullptr) , m_trayIcon(nullptr)
, m_trayMenu(nullptr) , m_trayMenu(nullptr)
, m_db(db) , m_db(db)
, m_client(nullptr)
, m_propagator(nullptr)
{ {
setWindowTitle("Chat"); setWindowTitle("Chat");
resize(900, 600); resize(900, 600);
@ -31,12 +31,8 @@ MainWindow::MainWindow(QWidget *parent, DbManager* db)
setupTray(); setupTray();
if (m_db && m_db->isOpen()) { if (m_db && m_db->isOpen()) {
m_client = new MsgClient(this); setupNode();
m_propagator = new ChatPropagator(m_client, m_db, this); setupMessaging();
m_messageList->setPropagator(m_propagator);
connect(m_propagator, &ChatPropagator::messageReceived,
m_messageList, &MessageList::addMessage);
connect(m_channelList, &ChannelList::channelSelected, connect(m_channelList, &ChannelList::channelSelected,
this, [this](const QString& channelId) { 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() { void MainWindow::setupUi() {
auto *splitter = new QSplitter(Qt::Horizontal, this); auto *splitter = new QSplitter(Qt::Horizontal, this);
@ -76,6 +77,45 @@ void MainWindow::setupUi() {
setCentralWidget(splitter); 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() { void MainWindow::setupTray() {
m_trayIcon = new QSystemTrayIcon(this); m_trayIcon = new QSystemTrayIcon(this);
m_trayIcon->setIcon(style()->standardIcon(QStyle::SP_ComputerIcon)); m_trayIcon->setIcon(style()->standardIcon(QStyle::SP_ComputerIcon));
@ -119,9 +159,12 @@ void MainWindow::onTrayActivated(QSystemTrayIcon::ActivationReason reason) {
void MainWindow::showInviteDialog(const QString& channelId) { void MainWindow::showInviteDialog(const QString& channelId) {
if (!m_db || !m_db->isOpen()) return; 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) { if (pubkey.size() != 32) {
QMessageBox::warning(this, "Ошибка", "Не удалось получить публичный ключ узла."); QMessageBox::warning(this, "Error", "Failed to get node public key.");
return; return;
} }
@ -129,7 +172,7 @@ void MainWindow::showInviteDialog(const QString& channelId) {
QString link = encodeInviteLink(channelId, pubkey, addrs); QString link = encodeInviteLink(channelId, pubkey, addrs);
if (link.isEmpty()) { if (link.isEmpty()) {
QMessageBox::warning(this, "Ошибка", "Не удалось сформировать пригласительную ссылку."); QMessageBox::warning(this, "Error", "Failed to create invite link.");
return; return;
} }

12
tools/chatgui/src/mainwindow.h

@ -8,14 +8,15 @@ class ChannelList;
class MessageList; class MessageList;
class AccountList; class AccountList;
class DbManager; class DbManager;
class MsgClient; class UtunNode;
class NodeConfig;
class ChatPropagator; class ChatPropagator;
class MainWindow : public QMainWindow { class MainWindow : public QMainWindow {
Q_OBJECT Q_OBJECT
public: public:
explicit MainWindow(QWidget *parent = nullptr, DbManager* db = nullptr); explicit MainWindow(QWidget *parent = nullptr, DbManager* db = nullptr);
~MainWindow() override = default; ~MainWindow() override;
protected: protected:
void closeEvent(QCloseEvent *event) override; void closeEvent(QCloseEvent *event) override;
@ -27,6 +28,8 @@ private slots:
private: private:
void setupUi(); void setupUi();
void setupTray(); void setupTray();
void setupNode();
void setupMessaging();
ChannelList *m_channelList; ChannelList *m_channelList;
MessageList *m_messageList; MessageList *m_messageList;
@ -34,6 +37,7 @@ private:
QSystemTrayIcon *m_trayIcon; QSystemTrayIcon *m_trayIcon;
QMenu *m_trayMenu; QMenu *m_trayMenu;
DbManager *m_db; DbManager *m_db;
MsgClient *m_client; UtunNode *m_node = nullptr;
ChatPropagator *m_propagator; NodeConfig *m_config = nullptr;
ChatPropagator *m_propagator = nullptr;
}; };

12
tools/chatgui/transport/chat_propagator.cpp

@ -1,6 +1,6 @@
#include "chat_propagator.h" #include "chat_propagator.h"
#include "chat_proto.h" #include "chat_proto.h"
#include "msg_client.h" #include "utun_node.h"
#include "crypto.h" #include "crypto.h"
#include "db_manager.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); } 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) : QObject(parent)
, m_client(client) , m_node(node)
, m_db(db) , m_db(db)
, m_ackTimer(new QTimer(this)) , m_ackTimer(new QTimer(this))
{ {
connect(m_client, &MsgClient::received, connect(m_node, &UtunNode::messageReceived,
this, &ChatPropagator::onReceived); this, &ChatPropagator::onReceived);
connect(m_ackTimer, &QTimer::timeout, connect(m_ackTimer, &QTimer::timeout,
this, &ChatPropagator::onAckTimeout); this, &ChatPropagator::onAckTimeout);
@ -107,7 +107,7 @@ void ChatPropagator::fanoutGossip(const QString& channelId, const QByteArray& pa
} }
void ChatPropagator::sendGossip(quint64 peer, const QByteArray& packet) { 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); fanoutGossip(channelId, payload, sig, ChatProp::kDefaultFanout, exclude);
/* ack to sender */ /* ack to sender */
m_client->sendTo(fromPeer, buildAckPacket(sig)); m_node->send(fromPeer, buildAckPacket(sig));
} }
void ChatPropagator::handleNewAck(const QByteArray& payload) { void ChatPropagator::handleNewAck(const QByteArray& payload) {

6
tools/chatgui/transport/chat_propagator.h

@ -7,14 +7,14 @@
#include <QTimer> #include <QTimer>
#include <cstdint> #include <cstdint>
class MsgClient; class UtunNode;
class DbManager; class DbManager;
struct MessageRow; struct MessageRow;
class ChatPropagator : public QObject { class ChatPropagator : public QObject {
Q_OBJECT Q_OBJECT
public: 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 setKeys(const QByteArray& edPriv, const QByteArray& edPub, quint64 nodeId);
void start(); void start();
@ -26,7 +26,7 @@ signals:
const QByteArray& content, qint64 timestamp); const QByteArray& content, qint64 timestamp);
private: private:
MsgClient* m_client; UtunNode* m_node;
DbManager* m_db; DbManager* m_db;
QTimer* m_ackTimer; QTimer* m_ackTimer;

206
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 <openssl/evp.h>
#include <openssl/rand.h>
}
#include <QFile>
#include <QTextStream>
#include <QFileInfo>
static QString formatKeyHex(const uint8_t* bin, size_t len) {
return QByteArray(reinterpret_cast<const char*>(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<uint8_t*>(&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<quint16>(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;
}

60
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 <QString>
#include <QList>
#include <QPair>
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<QPair<QString,QString>> servers() const { return m_servers; }
bool addServer(const QString& name, const QString& addr);
bool removeServer(const QString& name);
QList<NodeClient> 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<QPair<QString,QString>> m_servers;
QList<NodeClient> m_clients;
QString m_controlAddr;
};
#endif // NODE_CONFIG_H
Loading…
Cancel
Save