diff --git a/tools/chatgui/CMakeLists.txt b/tools/chatgui/CMakeLists.txt index 600433fd..6b55060f 100644 --- a/tools/chatgui/CMakeLists.txt +++ b/tools/chatgui/CMakeLists.txt @@ -64,6 +64,7 @@ add_executable(chatgui transport/msg_client.cpp transport/crypto.cpp transport/chat_propagator.cpp + transport/utun_node.cpp db/db_manager.cpp db/sqlite3.c resources/chatgui.qrc diff --git a/tools/chatgui/transport/utun_node.cpp b/tools/chatgui/transport/utun_node.cpp new file mode 100644 index 00000000..f7043a15 --- /dev/null +++ b/tools/chatgui/transport/utun_node.cpp @@ -0,0 +1,135 @@ +// utun_node.cpp — embedded uTun node running in a dedicated thread +#include "utun_node.h" +#include + +// === C headers === +extern "C" { +#include "utun_instance.h" +#include "etcp_router.h" +#include "etcp_api.h" +#include "etcp.h" +#include "config_parser.h" +#include "config_updater.h" +#include "control_server.h" +#include "secure_channel.h" +#include "../lib/u_async.h" +#include "../lib/ll_queue.h" +#include "../lib/memory_pool.h" +#include "../lib/debug_config.h" +#include "../lib/mem.h" +} + +#define ETCP_RT_ID_CHAT 0x11 + +static thread_local UtunNode* g_currentNode = nullptr; + +UtunNode::UtunNode(QObject* parent) : QObject(parent) {} + +UtunNode::~UtunNode() { + stop(); +} + +bool UtunNode::start(const QString& configPath) { + if (m_running) return false; + m_configPath = configPath; + m_stop = false; + m_running = true; + m_thread = std::thread(&UtunNode::runLoop, this); + + for (int i = 0; i < 50 && m_running && !m_instance; ++i) + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + + if (!m_instance) { + m_stop = true; + if (m_thread.joinable()) m_thread.join(); + m_running = false; + return false; + } + emit started(); + return true; +} + +void UtunNode::stop() { + if (!m_running) return; + m_stop = true; + if (m_thread.joinable()) m_thread.join(); + m_running = false; +} + +void UtunNode::send(uint64_t dstNodeId, const QByteArray& data) { + if (!m_instance) return; + struct ll_entry* entry = queue_entry_new(data.size()); + if (!entry) return; + memcpy(entry->data, data.constData(), data.size()); + etcp_route_send(m_instance, dstNodeId, entry, 0); +} + +QString UtunNode::nodeIdHex() const { + if (!m_instance) return {}; + char buf[32]; + snprintf(buf, sizeof(buf), "%016" PRIx64, m_instance->node_id); + return QString(buf); +} + +QString UtunNode::pubKeyHex() const { + if (!m_instance || !m_instance->config) return {}; + return QString::fromLatin1(m_instance->config->global.my_public_key_hex, 64); +} + +void UtunNode::recvCallback(struct ETCP_CONN* conn, struct ll_entry* entry) { + if (!g_currentNode || !entry) return; + uint64_t src = conn ? conn->peer_node_id : 0; + QByteArray data((const char*)entry->data, (int)(entry->len ? entry->len : 0)); + QMetaObject::invokeMethod(g_currentNode, [=] { + emit g_currentNode->messageReceived(src, data); + }, Qt::QueuedConnection); +} + +void UtunNode::runLoop() { + g_currentNode = this; + + debug_config_init(); + debug_set_level(DEBUG_LEVEL_ERROR); + debug_enable_function_name(0); + + utun_instance_set_tun_init_enabled(0); + + struct UASYNC* ua = uasync_create(); + if (!ua) { + QMetaObject::invokeMethod(this, [this] { emit error("uasync_create failed"); }); + g_currentNode = nullptr; + return; + } + + m_instance = utun_instance_create(ua, m_configPath.toUtf8().constData()); + if (!m_instance) { + QMetaObject::invokeMethod(this, [this] { emit error("utun_instance_create failed"); }); + uasync_destroy(ua, 0); + g_currentNode = nullptr; + return; + } + + m_instance->test_user_ptr = this; + + if (utun_instance_init(m_instance) != 0) { + QMetaObject::invokeMethod(this, [this] { emit error("utun_instance_init failed"); }); + utun_instance_destroy(m_instance); + m_instance = nullptr; + uasync_destroy(ua, 0); + g_currentNode = nullptr; + return; + } + + etcp_router_bind(m_instance, ETCP_RT_ID_CHAT, recvCallback); + + while (!m_stop) { + uasync_poll(ua, 100); + } + + etcp_router_bind(m_instance, ETCP_RT_ID_CHAT, nullptr); + utun_instance_destroy(m_instance); + m_instance = nullptr; + uasync_destroy(ua, 0); + g_currentNode = nullptr; + QMetaObject::invokeMethod(this, [this] { emit stopped(); }); +} diff --git a/tools/chatgui/transport/utun_node.h b/tools/chatgui/transport/utun_node.h new file mode 100644 index 00000000..08e4b51f --- /dev/null +++ b/tools/chatgui/transport/utun_node.h @@ -0,0 +1,44 @@ +// utun_node.h — C++ wrapper for embedded uTun node (runs in dedicated thread) +#ifndef UTUN_NODE_H +#define UTUN_NODE_H + +#include +#include +#include +#include +#include + +struct UTUN_INSTANCE; + +class UtunNode : public QObject { + Q_OBJECT +public: + explicit UtunNode(QObject* parent = nullptr); + ~UtunNode(); + + bool start(const QString& configPath); + void stop(); + bool isRunning() const { return m_running; } + + void send(uint64_t dstNodeId, const QByteArray& data); + + QString nodeIdHex() const; + QString pubKeyHex() const; + +signals: + void started(); + void stopped(); + void error(const QString& msg); + void messageReceived(uint64_t src, const QByteArray& data); + +private: + void runLoop(); + static void recvCallback(struct ETCP_CONN* conn, struct ll_entry* entry); + + UTUN_INSTANCE* m_instance = nullptr; + std::thread m_thread; + std::atomic m_running{false}; + std::atomic m_stop{false}; + QString m_configPath; +}; +#endif // UTUN_NODE_H