From 85a91a1320a062570b6b93baf48812e358384874 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 13 Jul 2026 08:06:19 +0300 Subject: [PATCH] chatgui: invite link connect flow + GUI_ERROR always-on diagnostics - debug_ui.h: remove NDEBUG guard so GUI_ERROR works in Release builds - invite_link.h/cpp: add error field to InviteData with specific failure reason - joindialog: show exact decode error in QLabel instead of generic message - chat_sync: implement invite connect flow (CHANNEL_INFO_REQ/RESP, JOIN, WELCOME) - topo_node_sqlite: node/address table init and lookup - utun_node: getInviteAddresses fallback from ETCP sockets - minor fixes in mainwindow, chat_core, gui_bridge --- src/topo_group.c | 2 +- tools/chatgui/src/debug_ui.h | 3 +- tools/chatgui/src/invite_link.cpp | 160 ++++--- tools/chatgui/src/invite_link.h | 5 +- tools/chatgui/src/joindialog.cpp | 29 +- tools/chatgui/src/joindialog.h | 3 +- tools/chatgui/src/mainwindow.cpp | 4 +- tools/chatgui/transport/chat_core.c | 10 +- tools/chatgui/transport/chat_core.h | 3 +- tools/chatgui/transport/chat_sync.c | 451 +++++++++++++++++++- tools/chatgui/transport/chat_sync.h | 24 +- tools/chatgui/transport/gui_bridge.h | 4 +- tools/chatgui/transport/gui_bridge_impl.cpp | 12 +- tools/chatgui/transport/topo_node_sqlite.c | 106 ++++- tools/chatgui/transport/topo_node_sqlite.h | 12 +- tools/chatgui/transport/utun_node.cpp | 42 ++ tools/chatgui/transport/utun_node.h | 4 + 17 files changed, 747 insertions(+), 127 deletions(-) diff --git a/src/topo_group.c b/src/topo_group.c index 0962bdf5..bc2f5c36 100644 --- a/src/topo_group.c +++ b/src/topo_group.c @@ -629,7 +629,7 @@ int topo_group_process_nodeinfo(struct TOPO_GROUP* group, struct ETCP_CONN* from #ifdef USE_SQLITE if (group->group_type == TOPO_GROUP_TYPE_CHAT && group->instance->topo_groups->topo_sqlite_db) { topo_node_sqlite_node_put(group->instance->topo_groups->topo_sqlite_db, nodeinfo1); - if (group->channel_id[0]) topo_node_sqlite_member_put(group->instance->topo_groups->topo_sqlite_db, group->channel_id, node_id); + if (group->channel_id[0]) topo_node_sqlite_member_put(group->instance->topo_groups->topo_sqlite_db, group->channel_id, node_id, NULL, NULL); } else #endif topo_node_lmdb_put(group, nodeinfo1); diff --git a/tools/chatgui/src/debug_ui.h b/tools/chatgui/src/debug_ui.h index 9ec0679b..039c1b8f 100644 --- a/tools/chatgui/src/debug_ui.h +++ b/tools/chatgui/src/debug_ui.h @@ -1,9 +1,10 @@ #pragma once -#if !defined(NDEBUG) && !defined(GUI_DEBUG_DISABLE) +#if !defined(GUI_DEBUG_DISABLE) #include #include +#include namespace GuiDebugPriv { inline FILE *s_file = stderr; diff --git a/tools/chatgui/src/invite_link.cpp b/tools/chatgui/src/invite_link.cpp index ba106313..4a5e1403 100644 --- a/tools/chatgui/src/invite_link.cpp +++ b/tools/chatgui/src/invite_link.cpp @@ -2,62 +2,48 @@ #include "debug_ui.h" #include -static QByteArray encodeAddresses(const QList& addrs) { - QByteArray buf; - for (const auto& a : addrs) { - buf.append(static_cast(a.family)); - buf.append(a.address); - buf.append(static_cast((a.port >> 8) & 0xFF)); - buf.append(static_cast(a.port & 0xFF)); - } - return buf; -} - -static QList decodeAddresses(const uint8_t* data, int len, - int count) { - QList list; - int off = 0; - for (int i = 0; i < count && off < len; i++) { - if (off + 1 > len) break; - InviteAddr a; - a.family = data[off++]; - int alen = (a.family == 4) ? 4 : 16; - if (off + alen + 2 > len) break; - a.address = QByteArray(reinterpret_cast(data + off), alen); - off += alen; - a.port = (static_cast(data[off]) << 8) | data[off + 1]; - off += 2; - list.append(a); - } - return list; -} - -QString encodeInviteLink(const QString& channelId, const QByteArray& pubkey, +QString encodeInviteLink(uint64_t channelId, const QByteArray& pubkey, const QList& addrs) { - QByteArray cid = channelId.toUtf8(); - if (cid.size() > 65535) { - GUI_ERROR("encodeInviteLink: channelId too long (%d bytes)", cid.size()); - return {}; - } if (pubkey.size() != 32) { GUI_ERROR("encodeInviteLink: pubkey must be 32 bytes (got %d)", pubkey.size()); return {}; } - - QByteArray addrsEnc = encodeAddresses(addrs); - int addrCount = addrs.size(); - if (addrCount > 65535) addrCount = 65535; + if (addrs.isEmpty()) { + GUI_ERROR("encodeInviteLink: no addresses"); + return {}; + } QByteArray blob; - blob.reserve(1 + 2 + cid.size() + 32 + 2 + addrsEnc.size()); + blob.reserve(1 + 8 + addrs.size() * (1 + 32 + 18)); + blob.append(static_cast(INVITE_LINK_VERSION)); - blob.append(static_cast((cid.size() >> 8) & 0xFF)); - blob.append(static_cast(cid.size() & 0xFF)); - blob.append(cid); - blob.append(pubkey); - blob.append(static_cast((addrCount >> 8) & 0xFF)); - blob.append(static_cast(addrCount & 0xFF)); - blob.append(addrsEnc); + + /* channel_id: uint64 big-endian */ + for (int i = 7; i >= 0; i--) + blob.append(static_cast((channelId >> (i * 8)) & 0xFF)); + + int i = 0; + while (i < addrs.size()) { + int rem = addrs.size() - i; + int cnt = (rem > 4) ? 4 : rem; + + /* header: bits 1-0 = count-1, bits 5-2 = family bits */ + uint8_t header = (cnt - 1) & 0x03; + for (int j = 0; j < cnt; j++) { + if (addrs[i + j].family == 6) + header |= (1 << (2 + j)); + } + blob.append(static_cast(header)); + blob.append(pubkey); + + for (int j = 0; j < cnt; j++) { + blob.append(addrs[i + j].address); + quint16 p = addrs[i + j].port; + blob.append(static_cast((p >> 8) & 0xFF)); + blob.append(static_cast(p & 0xFF)); + } + i += cnt; + } return QString::fromLatin1("utun://") + QString::fromLatin1(blob.toBase64()); } @@ -65,36 +51,80 @@ QString encodeInviteLink(const QString& channelId, const QByteArray& pubkey, InviteData decodeInviteLink(const QString& link) { InviteData d; if (!link.startsWith(QLatin1String("utun://"))) { + d.error = QStringLiteral("invalid prefix (expected utun://)"); GUI_ERROR("decodeInviteLink: invalid prefix"); return d; } - QByteArray blob = QByteArray::fromBase64( - link.mid(7).toLatin1()); + QByteArray blob = QByteArray::fromBase64(link.mid(7).toLatin1()); + if (blob.isEmpty()) { + d.error = QStringLiteral("base64 decode failed"); + GUI_ERROR("decodeInviteLink: base64 decode failed"); + return d; + } const auto* data = reinterpret_cast(blob.constData()); int len = blob.size(); - if (len < 5) { + if (len < 11) { + d.error = QStringLiteral("blob too short (%1 bytes)").arg(len); GUI_ERROR("decodeInviteLink: blob too short (%d bytes)", len); return d; } int off = 0; - uint8_t ver = data[off++]; - if (ver != INVITE_LINK_VERSION) { - GUI_ERROR("decodeInviteLink: unsupported version %d", ver); + + if (data[off++] != INVITE_LINK_VERSION) { + d.error = QStringLiteral("unsupported version 0x%1").arg(data[off - 1], 2, 16, QLatin1Char('0')); + GUI_ERROR("decodeInviteLink: unsupported version %d", data[off - 1]); return d; } - int cidLen = (static_cast(data[off]) << 8) | data[off + 1]; - off += 2; - if (off + cidLen + 32 + 2 > len) { - GUI_ERROR("decodeInviteLink: truncated blob"); + + /* channel_id: uint64 big-endian */ + if (off + 8 > len) { + d.error = QStringLiteral("truncated at channel_id"); + GUI_ERROR("decodeInviteLink: truncated at channel_id"); return d; } - d.channelId = QString::fromUtf8(reinterpret_cast(data + off), cidLen); - off += cidLen; - d.pubkey = QByteArray(reinterpret_cast(data + off), 32); - off += 32; - int addrCount = (static_cast(data[off]) << 8) | data[off + 1]; - off += 2; - d.addrs = decodeAddresses(data + off, len - off, addrCount); + uint64_t chId = 0; + for (int i = 0; i < 8; i++) + chId = (chId << 8) | data[off++]; + d.channelId = chId; + + bool firstBlock = true; + while (off + 1 <= len) { + uint8_t header = data[off++]; + int cnt = (header & 0x03) + 1; + if (cnt < 1 || cnt > 4) { + d.error = QStringLiteral("invalid addr count %1").arg(cnt); + GUI_ERROR("decodeInviteLink: invalid addr count %d", cnt); + return d; + } + if (off + 32 > len) { + d.error = QStringLiteral("truncated at pubkey"); + GUI_ERROR("decodeInviteLink: truncated at pubkey"); + return d; + } + if (firstBlock) { + d.pubkey = QByteArray(reinterpret_cast(data + off), 32); + firstBlock = false; + } + off += 32; + + for (int j = 0; j < cnt; j++) { + bool isV6 = header & (1 << (2 + j)); + int ipLen = isV6 ? 16 : 4; + if (off + ipLen + 2 > len) { + d.error = QStringLiteral("truncated at addr %1 of block").arg(j); + GUI_ERROR("decodeInviteLink: truncated at addr %d of block", j); + return d; + } + InviteAddr a; + a.family = isV6 ? 6 : 4; + a.address = QByteArray(reinterpret_cast(data + off), ipLen); + off += ipLen; + a.port = (static_cast(data[off]) << 8) | data[off + 1]; + off += 2; + d.addrs.append(a); + } + } + return d; } diff --git a/tools/chatgui/src/invite_link.h b/tools/chatgui/src/invite_link.h index e2893d03..c3a9d4fe 100644 --- a/tools/chatgui/src/invite_link.h +++ b/tools/chatgui/src/invite_link.h @@ -15,12 +15,13 @@ struct InviteAddr { }; struct InviteData { - QString channelId; + uint64_t channelId = 0; QByteArray pubkey; QList addrs; + QString error; }; -QString encodeInviteLink(const QString& channelId, const QByteArray& pubkey, +QString encodeInviteLink(uint64_t channelId, const QByteArray& pubkey, const QList& addrs); InviteData decodeInviteLink(const QString& link); diff --git a/tools/chatgui/src/joindialog.cpp b/tools/chatgui/src/joindialog.cpp index 684652e9..0a2d8da7 100644 --- a/tools/chatgui/src/joindialog.cpp +++ b/tools/chatgui/src/joindialog.cpp @@ -77,9 +77,9 @@ JoinDialog::JoinDialog(UtunNode* node, DbManager* db, QWidget* parent) connect(m_linkEdit, &QLineEdit::returnPressed, this, &JoinDialog::onConnectClicked); - gui_bridge_set_connect_result_cb([](uint64_t nodeId, int result) { + gui_bridge_set_connect_result_cb([](uint64_t nodeId, uint64_t channelId, int result) { JoinDialog* dlg = qobject_cast(QApplication::activeModalWidget()); - if (dlg) dlg->onConnectResult(nodeId, result); + if (dlg) dlg->onConnectResult(nodeId, channelId, result); }); } @@ -105,8 +105,12 @@ void JoinDialog::onConnectClicked() { setStatus("Parsing link..."); InviteData d = decodeInviteLink(link); - if (d.channelId.isEmpty() || d.pubkey.size() != 32) { - setStatus("Invalid invite link.", true); + if (!d.error.isEmpty()) { + setStatus("Invalid invite link: " + d.error, true); + return; + } + if (d.pubkey.size() != 32) { + setStatus(QString("Invalid invite link: pubkey is %1 bytes (need 32)").arg(d.pubkey.size()), true); return; } if (d.addrs.isEmpty()) { @@ -124,6 +128,7 @@ void JoinDialog::onConnectClicked() { nodeId &= 0x7FFFFFFFFFFFFFFFULL; m_pendingNodeId = nodeId; + m_pendingChannelId = d.channelId; m_connecting = true; m_connectBtn->setEnabled(false); m_linkEdit->setReadOnly(true); @@ -138,21 +143,23 @@ void JoinDialog::onConnectClicked() { addrsBuf.append(static_cast(a.port & 0xFF)); } - QByteArray chId = d.channelId.toUtf8(); int addrCount = d.addrs.size(); - chat_sync_connect_from_invite(nodeId, + chat_sync_connect_from_invite(d.channelId, nodeId, (const uint8_t*)d.pubkey.constData(), - (const uint8_t*)addrsBuf.constData(), addrCount, - (const uint8_t*)chId.constData(), chId.size()); + (const uint8_t*)addrsBuf.constData(), addrCount); } -void JoinDialog::onConnectResult(uint64_t nodeId, int result) { +void JoinDialog::onConnectResult(uint64_t nodeId, uint64_t channelId, int result) { if (!m_connecting || nodeId != m_pendingNodeId) return; if (result == 0) { - setStatus("Connected successfully!"); - QTimer::singleShot(800, this, [this] { accept(); }); + setStatus("Connected! Waiting for channel data..."); + QTimer::singleShot(800, this, [this] { + QString cid = QString::number(m_pendingChannelId); + emit joined(cid); + accept(); + }); } else { m_connecting = false; m_connectBtn->setEnabled(true); diff --git a/tools/chatgui/src/joindialog.h b/tools/chatgui/src/joindialog.h index a0979053..717876b0 100644 --- a/tools/chatgui/src/joindialog.h +++ b/tools/chatgui/src/joindialog.h @@ -23,7 +23,7 @@ private slots: private: void setStatus(const QString& text, bool isError = false); - void onConnectResult(uint64_t nodeId, int result); + void onConnectResult(uint64_t nodeId, uint64_t channelId, int result); UtunNode* m_node; DbManager* m_db; @@ -33,4 +33,5 @@ private: QPushButton* m_cancelBtn; bool m_connecting = false; uint64_t m_pendingNodeId = 0; + uint64_t m_pendingChannelId = 0; }; diff --git a/tools/chatgui/src/mainwindow.cpp b/tools/chatgui/src/mainwindow.cpp index e794efa7..521ec157 100644 --- a/tools/chatgui/src/mainwindow.cpp +++ b/tools/chatgui/src/mainwindow.cpp @@ -205,9 +205,9 @@ void MainWindow::showInviteDialog(const QString& channelId) { return; } - QList addrs = m_db->getOnlinePeerAddresses(m_db->myNodeId()); + QList addrs = m_node->getInviteAddresses(m_db); - QString link = encodeInviteLink(channelId, pubkey, addrs); + QString link = encodeInviteLink(channelId.toULongLong(), pubkey, addrs); if (link.isEmpty()) { QMessageBox::warning(this, "Error", "Failed to create invite link."); return; diff --git a/tools/chatgui/transport/chat_core.c b/tools/chatgui/transport/chat_core.c index 9c5b7da0..9da541e7 100644 --- a/tools/chatgui/transport/chat_core.c +++ b/tools/chatgui/transport/chat_core.c @@ -575,10 +575,12 @@ int chat_core_load_nodeinfo(uint64_t node_id, uint8_t* buf, size_t buf_size, /* ─── подключение к пиру из invite-ссылки ─── */ static void connect_result_cb(int result, uint64_t node_id, void* arg) { - uint8_t data[12]; + uint64_t channel_id = arg ? *(uint64_t*)arg : 0; + uint8_t data[20]; memcpy(data, &node_id, 8); memcpy(data + 8, &result, 4); - gui_bridge_post(GUI_EVT_CONNECT_RESULT, data, 12); + memcpy(data + 12, &channel_id, 8); + gui_bridge_post(GUI_EVT_CONNECT_RESULT, data, 20); } void chat_core_connect_from_invite(struct chat_invite* inv) { @@ -598,7 +600,7 @@ void chat_core_connect_from_invite(struct chat_invite* inv) { DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: node 0x%016llx already in BGP, connecting", CC_ID, (unsigned long long)node_id); conn_mgr_connect_node(g_cc.inst->conn_mgr, node_id, 30000, - connect_result_cb, NULL); + connect_result_cb, &inv->channel_id); return; } @@ -651,7 +653,7 @@ void chat_core_connect_from_invite(struct chat_invite* inv) { DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: created NODEINFO for 0x%016llx, %d addrs, connecting", CC_ID, (unsigned long long)node_id, inv->addr_count); conn_mgr_connect_node(g_cc.inst->conn_mgr, node_id, 30000, - connect_result_cb, NULL); + connect_result_cb, &inv->channel_id); } /* ─── создание канала ─── */ diff --git a/tools/chatgui/transport/chat_core.h b/tools/chatgui/transport/chat_core.h index 749acb1c..afe08749 100644 --- a/tools/chatgui/transport/chat_core.h +++ b/tools/chatgui/transport/chat_core.h @@ -52,12 +52,11 @@ int chat_core_load_nodeinfo(uint64_t node_id, uint8_t* buf, size_t buf_size /* ── Подключение к пиру (из invite-ссылки) ── */ struct chat_invite { + uint64_t channel_id; uint64_t node_id; uint8_t pubkey[32]; uint8_t* addrs_data; int addr_count; - uint8_t* ch_id; - int ch_id_len; }; void chat_core_connect_from_invite(struct chat_invite* inv); diff --git a/tools/chatgui/transport/chat_sync.c b/tools/chatgui/transport/chat_sync.c index 9f75e8cc..f3aecb26 100644 --- a/tools/chatgui/transport/chat_sync.c +++ b/tools/chatgui/transport/chat_sync.c @@ -1,12 +1,15 @@ #include "chat_sync.h" #include "chat_core.h" #include "gui_bridge.h" +#include "topo_node_sqlite.h" #include "../../../src/utun_instance.h" #include "../../../src/etcp_router.h" #include "../../../src/etcp_api.h" #include "../../../src/etcp.h" #include "../../../src/conn_mgr.h" +#include "../../../src/topo_group.h" +#include "../../../src/secure_channel.h" #include "../../../lib/u_async.h" #include "../../../lib/ll_queue.h" #include "../../../lib/debug_config.h" @@ -14,6 +17,7 @@ #include "../../../lib/platform_compat.h" #include +#include static struct chat_sync* g_cs = NULL; @@ -33,6 +37,8 @@ struct chat_sync { void* refresh_timer; void* ttl_timer; uint8_t initialized; + uint64_t pending_invite_ch_id; + uint64_t pending_invite_node_id; }; #define CS_ID "chat_sync" @@ -238,6 +244,23 @@ static void cs_handle_sync_done(struct chat_sync* cs, uint64_t peer, if (ch) { ch->msg_count = pc; ch->synced = CS_SYNC_DONE; } } +/* ── Forward declarations for new message handlers ── */ + +static void cs_propagate(struct chat_sync* cs, const char* ch_id, uint64_t exclude_id, + const uint8_t* payload, size_t len); +static void cs_handle_channel_info_req(struct chat_sync* cs, uint64_t peer, + const char* ch_id, const uint8_t* pl, size_t len); +static void cs_handle_channel_info_resp(struct chat_sync* cs, uint64_t peer, + const char* ch_id, const uint8_t* pl, size_t len); +static void cs_handle_channel_join(struct chat_sync* cs, uint64_t peer, + const char* ch_id, const uint8_t* pl, size_t len); +static void cs_handle_welcome(struct chat_sync* cs, uint64_t peer, + const char* ch_id, const uint8_t* pl, size_t len); +static void cs_handle_peer_upsert(struct chat_sync* cs, uint64_t peer, + const char* ch_id, const uint8_t* pl, size_t len); +static void cs_handle_peer_remove(struct chat_sync* cs, uint64_t peer, + const char* ch_id, const uint8_t* pl, size_t len); + /* ── Recv dispatcher ── */ static void chat_sync_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) { @@ -260,12 +283,18 @@ static void chat_sync_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) { size_t plen = dlen - 3 - ch_len; switch (type) { - case CS_MSG_INIT_SYNC: cs_handle_init_sync(g_cs, peer, ch_id, pl, plen); break; - case CS_MSG_INIT_RESP: cs_handle_init_resp(g_cs, peer, ch_id, pl, plen); break; - case CS_MSG_SEND_DATA: cs_handle_send_data(g_cs, peer, ch_id, pl, plen); break; - case CS_MSG_PUSH: cs_handle_push(g_cs, peer, ch_id, pl, plen); break; - case CS_MSG_ACK_PUSH: cs_handle_ack_push(g_cs, peer, ch_id, pl, plen); break; - case CS_MSG_SYNC_DONE: cs_handle_sync_done(g_cs, peer, ch_id, pl, plen); break; + case CS_MSG_INIT_SYNC: cs_handle_init_sync(g_cs, peer, ch_id, pl, plen); break; + case CS_MSG_INIT_RESP: cs_handle_init_resp(g_cs, peer, ch_id, pl, plen); break; + case CS_MSG_SEND_DATA: cs_handle_send_data(g_cs, peer, ch_id, pl, plen); break; + case CS_MSG_PUSH: cs_handle_push(g_cs, peer, ch_id, pl, plen); break; + case CS_MSG_ACK_PUSH: cs_handle_ack_push(g_cs, peer, ch_id, pl, plen); break; + case CS_MSG_SYNC_DONE: cs_handle_sync_done(g_cs, peer, ch_id, pl, plen); break; + case CS_MSG_CHANNEL_INFO_REQ: cs_handle_channel_info_req(g_cs, peer, ch_id, pl, plen); break; + case CS_MSG_CHANNEL_INFO_RESP:cs_handle_channel_info_resp(g_cs, peer, ch_id, pl, plen); break; + case CS_MSG_CHANNEL_JOIN: cs_handle_channel_join(g_cs, peer, ch_id, pl, plen); break; + case CS_MSG_WELCOME: cs_handle_welcome(g_cs, peer, ch_id, pl, plen); break; + case CS_MSG_PEER_UPSERT: cs_handle_peer_upsert(g_cs, peer, ch_id, pl, plen); break; + case CS_MSG_PEER_REMOVE: cs_handle_peer_remove(g_cs, peer, ch_id, pl, plen); break; default: break; } u_free(entry->dgram); queue_entry_free(entry); @@ -278,6 +307,18 @@ static void cs_on_conn_up(struct ETCP_CONN* conn, void* arg) { if (!conn || !g_cs) return; uint64_t peer = conn->peer_node_id; if (peer == 0 || peer == g_cs->inst->node_id) return; + + /* pending invite: send CHANNEL_INFO_REQ */ + if (g_cs->pending_invite_node_id == peer && g_cs->pending_invite_ch_id != 0) { + char ch_id_str[64]; + snprintf(ch_id_str, sizeof(ch_id_str), "%llu", (unsigned long long)g_cs->pending_invite_ch_id); + uint8_t req[1] = { CS_MSG_CHANNEL_INFO_REQ }; + cs_send(g_cs, ch_id_str, peer, req, 1); + g_cs->pending_invite_node_id = 0; + g_cs->pending_invite_ch_id = 0; + return; + } + for (int i = 0; i < g_cs->channel_count; i++) { struct channel_cache* ch = &g_cs->channels[i]; int found = 0; @@ -302,7 +343,12 @@ static void cs_on_conn_down(struct ETCP_CONN* conn, void* arg) { int found = 0; for (int j = 0; j < g_cs->channels[i].peer_count; j++) if (g_cs->channels[i].peer_ids[j] == peer) { found = 1; break; } - if (found) g_cs->channels[i].synced = CS_SYNC_NONE; + if (found) { + g_cs->channels[i].synced = CS_SYNC_NONE; + uint8_t rem[9]; rem[0] = CS_MSG_PEER_REMOVE; + memcpy(rem + 1, &peer, 8); + cs_propagate(g_cs, g_cs->channels[i].channel_id, peer, rem, 9); + } } } @@ -479,9 +525,9 @@ void chat_sync_connect_node(struct UTUN_INSTANCE* inst, uint64_t node_id) { } } -void chat_sync_connect_from_invite(uint64_t node_id, const uint8_t* pubkey_bin, - const uint8_t* addrs_data, int addr_count, - const uint8_t* channel_id, int ch_id_len) { +void chat_sync_connect_from_invite(uint64_t channel_id, uint64_t node_id, + const uint8_t* pubkey_bin, + const uint8_t* addrs_data, int addr_count) { if (!g_cs || !g_cs->inst || !g_cs->inst->ua) { int r = -7; uint8_t err[12]; memcpy(err, &node_id, 8); memcpy(err + 8, &r, 4); @@ -492,6 +538,7 @@ void chat_sync_connect_from_invite(uint64_t node_id, const uint8_t* pubkey_bin, struct chat_invite* inv = u_calloc(1, sizeof(struct chat_invite)); if (!inv) return; + inv->channel_id = channel_id; inv->node_id = node_id; memcpy(inv->pubkey, pubkey_bin, 32); @@ -501,11 +548,387 @@ void chat_sync_connect_from_invite(uint64_t node_id, const uint8_t* pubkey_bin, memcpy(inv->addrs_data, addrs_data, addrs_sz); inv->addr_count = addr_count; - inv->ch_id = u_malloc((size_t)ch_id_len + 1); - if (!inv->ch_id) { u_free(inv->addrs_data); u_free(inv); return; } - memcpy(inv->ch_id, channel_id, (size_t)ch_id_len); - inv->ch_id_len = ch_id_len; + g_cs->pending_invite_ch_id = channel_id; + g_cs->pending_invite_node_id = node_id; gui_bridge_post_uasync_fn( (void(*)(void*))chat_core_connect_from_invite, inv); } + +/* ─── Ed25519 sign / verify helpers ─── */ + +static int cs_ed25519_sign(const uint8_t* privkey, const uint8_t* msg, size_t msg_len, + uint8_t* sig_out) { + EVP_PKEY* pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, privkey, SC_PRIVKEY_SIZE); + if (!pkey) { DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "%s: EVP_PKEY_new failed", CS_ID); return -1; } + EVP_MD_CTX* mdctx = EVP_MD_CTX_new(); + if (!mdctx) { EVP_PKEY_free(pkey); return -1; } + int ok = (EVP_DigestSignInit(mdctx, NULL, NULL, NULL, pkey) == 1) + && (EVP_DigestSign(mdctx, sig_out, &(size_t){64}, msg, msg_len) == 1); + EVP_MD_CTX_free(mdctx); + EVP_PKEY_free(pkey); + return ok ? 0 : -1; +} + +static int cs_ed25519_verify(const uint8_t* pubkey, const uint8_t* msg, size_t msg_len, + const uint8_t* sig) { + EVP_PKEY* pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, pubkey, SC_PUBKEY_SIZE); + if (!pkey) { DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "%s: EVP_PKEY_new pub failed", CS_ID); return -1; } + EVP_MD_CTX* mdctx = EVP_MD_CTX_new(); + if (!mdctx) { EVP_PKEY_free(pkey); return -1; } + int rc = EVP_DigestVerifyInit(mdctx, NULL, NULL, NULL, pkey) + && EVP_DigestVerify(mdctx, sig, 64, msg, msg_len) == 1; + EVP_MD_CTX_free(mdctx); + EVP_PKEY_free(pkey); + return rc ? 0 : -1; +} + +/* ─── Propagation helper ─── */ + +static void cs_propagate(struct chat_sync* cs, const char* ch_id, uint64_t exclude_id, + const uint8_t* payload, size_t len) { + struct channel_cache* ch = cs_find(cs, ch_id); + if (!ch) return; + uint64_t myid = cs->inst->node_id; + for (int i = 0; i < ch->peer_count; i++) { + uint64_t pid = ch->peer_ids[i]; + if (pid == exclude_id || pid == myid) continue; + if (!etcp_router_conn_get(cs->inst, pid, ETCP_RT_ID_CHAT_SYNC)) continue; + cs_send(cs, ch_id, pid, payload, len); + } +} + +/* ─── CHANNEL_INFO_REQ (0x09): joiner → inviter ─── */ + +static void cs_handle_channel_info_req(struct chat_sync* cs, uint64_t peer, + const char* ch_id, const uint8_t* pl, size_t len) { + (void)pl; (void)len; + char name[128]; int is_dm; uint64_t owner; uint8_t x25519[32], ed_pub[32], ch_sig[64]; + if (topo_node_sqlite_channel_get(cs->inst->topo_groups->topo_sqlite_db, + ch_id, name, (int)sizeof(name), &is_dm, &owner, x25519, ed_pub, ch_sig) != 0) { + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: CHANNEL_INFO_REQ unknown ch=%s", CS_ID, ch_id); + return; + } + uint64_t myid = cs->inst->node_id; + uint8_t my_join_sig[64] = {0}; + uint8_t join_msg[256]; size_t mlen = 0; + mlen += snprintf((char*)join_msg + mlen, sizeof(join_msg) - mlen, "%s", ch_id) + 1; + memcpy(join_msg + mlen, &myid, 8); mlen += 8; + memcpy(join_msg + mlen, cs->inst->my_keys.public_key, 32); mlen += 32; + cs_ed25519_sign(cs->inst->my_ed25519_privkey, join_msg, mlen, my_join_sig); + + uint8_t buf[1024]; size_t boff = 0; + buf[boff++] = CS_MSG_CHANNEL_INFO_RESP; + uint8_t nl = (uint8_t)strlen(name); + buf[boff++] = nl; memcpy(buf + boff, name, nl); boff += nl; + memcpy(buf + boff, &owner, 8); boff += 8; + buf[boff++] = (uint8_t)is_dm; + memcpy(buf + boff, x25519, 32); boff += 32; + memcpy(buf + boff, ed_pub, 32); boff += 32; + memcpy(buf + boff, ch_sig, 64); boff += 64; + memcpy(buf + boff, my_join_sig, 64); boff += 64; + cs_send(cs, ch_id, peer, buf, boff); +} + +/* ─── CHANNEL_INFO_RESP (0x0A): inviter → joiner ─── */ + +static void cs_handle_channel_info_resp(struct chat_sync* cs, uint64_t peer, + const char* ch_id, const uint8_t* pl, size_t len) { + if (len < 1) return; + uint8_t nl = pl[0]; if (1 + nl + 8 + 1 + 32 + 32 + 64 + 64 > len) return; + const uint8_t* p = pl + 1; + char name[128]; memcpy(name, p, nl); name[nl] = '\0'; p += nl; + uint64_t owner; memcpy(&owner, p, 8); p += 8; + uint8_t is_dm = *p++; + const uint8_t* x25519 = p; p += 32; + const uint8_t* ed_pub = p; p += 32; + const uint8_t* ch_sig = p; p += 64; + const uint8_t* inviter_join_sig = p; + + /* verify channel signature */ + uint8_t vmsg[1024]; size_t vlen = 0; + vlen += snprintf((char*)vmsg + vlen, sizeof(vmsg) - vlen, "%s", ch_id) + 1; + vlen += snprintf((char*)vmsg + vlen, sizeof(vmsg) - vlen, "%s", name) + 1; + memcpy(vmsg + vlen, &owner, 8); vlen += 8; + memcpy(vmsg + vlen, x25519, 32); vlen += 32; + memcpy(vmsg + vlen, ed_pub, 32); vlen += 32; + if (cs_ed25519_verify(ed_pub, vmsg, vlen, ch_sig) != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "%s: CHANNEL_INFO_RESP invalid ch_sig ch=%s", CS_ID, ch_id); + return; + } + + /* save channel to local DB */ + topo_node_sqlite_channel_put(cs->inst->topo_groups->topo_sqlite_db, + ch_id, name, (int)is_dm, owner, x25519, NULL, ed_pub, NULL, ch_sig); + + /* save inviter as node and member */ + topo_node_sqlite_member_put(cs->inst->topo_groups->topo_sqlite_db, ch_id, peer, + inviter_join_sig, inviter_join_sig); + + /* generate our own join_sig */ + uint64_t myid = cs->inst->node_id; + uint8_t my_x25519[32]; + memcpy(my_x25519, cs->inst->my_keys.public_key, 32); + uint8_t join_sig[64]; + { + uint8_t msg[256]; size_t mlen = 0; + mlen += snprintf((char*)msg + mlen, sizeof(msg) - mlen, "%s", ch_id) + 1; + memcpy(msg + mlen, &myid, 8); mlen += 8; + memcpy(msg + mlen, my_x25519, 32); mlen += 32; + cs_ed25519_sign(cs->inst->my_ed25519_privkey, msg, mlen, join_sig); + } + + /* send JOIN_CHANNEL */ + uint8_t jbuf[512]; size_t joff = 0; + jbuf[joff++] = CS_MSG_CHANNEL_JOIN; + memcpy(jbuf + joff, &myid, 8); joff += 8; + memcpy(jbuf + joff, my_x25519, 32); joff += 32; + memcpy(jbuf + joff, cs->inst->my_ed25519_pubkey, 32); joff += 32; + memcpy(jbuf + joff, join_sig, 64); joff += 64; + /* add local addresses */ + uint8_t addr_cnt = 0; size_t ac_pos = joff; + jbuf[joff++] = 0; + struct ETCP_SOCKET* sock = cs->inst->etcp_sockets; + while (sock && addr_cnt < 255) { + struct sockaddr_storage* sa; + if (sock->nat_addr.ss_family) sa = &sock->nat_addr; + else if (sock->interface_addr.ss_family) sa = &sock->interface_addr; + else { sock = sock->next; continue; } + if (sa->ss_family == AF_INET) { + struct sockaddr_in* sin = (struct sockaddr_in*)sa; + if (joff + 7 > sizeof(jbuf)) break; + jbuf[joff++] = 4; + memcpy(jbuf + joff, &sin->sin_addr, 4); joff += 4; + uint16_t p = ntohs(sin->sin_port); + jbuf[joff++] = (uint8_t)((p >> 8) & 0xFF); + jbuf[joff++] = (uint8_t)(p & 0xFF); + addr_cnt++; + } else if (sa->ss_family == AF_INET6) { + struct sockaddr_in6* sin6 = (struct sockaddr_in6*)sa; + if (joff + 19 > sizeof(jbuf)) break; + jbuf[joff++] = 6; + memcpy(jbuf + joff, &sin6->sin6_addr, 16); joff += 16; + uint16_t p = ntohs(sin6->sin6_port); + jbuf[joff++] = (uint8_t)((p >> 8) & 0xFF); + jbuf[joff++] = (uint8_t)(p & 0xFF); + addr_cnt++; + } + sock = sock->next; + } + jbuf[ac_pos] = addr_cnt; + cs_send(cs, ch_id, peer, jbuf, joff); +} + +/* ─── CHANNEL_JOIN (0x0B): joiner → inviter ─── */ + +static void cs_handle_channel_join(struct chat_sync* cs, uint64_t peer, + const char* ch_id, const uint8_t* pl, size_t len) { + if (len < 8 + 32 + 32 + 64 + 1) return; + const uint8_t* p = pl; + uint64_t node_id; memcpy(&node_id, p, 8); p += 8; + const uint8_t* x25519 = p; p += 32; + const uint8_t* ed_pub = p; p += 32; + const uint8_t* join_sig = p; p += 64; + uint8_t addr_cnt = *p++; + + /* verify join_sig */ + uint8_t vmsg[256]; size_t vlen = 0; + vlen += snprintf((char*)vmsg + vlen, sizeof(vmsg) - vlen, "%s", ch_id) + 1; + memcpy(vmsg + vlen, &node_id, 8); vlen += 8; + memcpy(vmsg + vlen, x25519, 32); vlen += 32; + if (cs_ed25519_verify(ed_pub, vmsg, vlen, join_sig) != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "%s: JOIN invalid sig node=0x%016llx ch=%s", CS_ID, + (unsigned long long)node_id, ch_id); + return; + } + + sqlite3* db = cs->inst->topo_groups->topo_sqlite_db; + topo_node_sqlite_member_put(db, ch_id, node_id, join_sig, NULL); + + /* save/update node addresses */ + for (uint8_t i = 0; i < addr_cnt && p + 1 <= pl + len; i++) { + uint8_t fm = *p++; + int ip_len = (fm == 4) ? 4 : 16; + if (p + ip_len + 2 > pl + len) break; + const uint8_t* ip = p; p += ip_len; + uint16_t port = ((uint16_t)p[0] << 8) | p[1]; p += 2; + + char sql[256]; snprintf(sql, sizeof(sql), + "INSERT OR REPLACE INTO node_addresses(node_id,family,address,port,is_nat)" + " VALUES(?,?,?,?,0)"); + sqlite3_stmt* stmt = NULL; + if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) { + sqlite3_bind_int64(stmt, 1, (sqlite3_int64)node_id); + sqlite3_bind_int(stmt, 2, fm); + sqlite3_bind_blob(stmt, 3, ip, ip_len, SQLITE_STATIC); + sqlite3_bind_int(stmt, 4, (int)port); + sqlite3_step(stmt); sqlite3_finalize(stmt); + } + } + + /* build WELCOME with all current peers */ + uint8_t wbuf[8192]; + { + uint8_t peers_data[8192]; size_t peers_len = 0; + if (topo_node_sqlite_channel_peers_all(db, ch_id, + peers_data, sizeof(peers_data), &peers_len) == 0) { + size_t woff = 0; + wbuf[woff++] = CS_MSG_WELCOME; + if (woff + peers_len <= sizeof(wbuf)) + { memcpy(wbuf + woff, peers_data, peers_len); woff += peers_len; } + cs_send(cs, ch_id, peer, wbuf, woff); + } + } + + /* propagate PEER_UPSERT to other channel members */ + { + uint8_t ubuf[512]; size_t uoff = 0; + ubuf[uoff++] = CS_MSG_PEER_UPSERT; + memcpy(ubuf + uoff, &node_id, 8); uoff += 8; + memcpy(ubuf + uoff, x25519, 32); uoff += 32; + memcpy(ubuf + uoff, ed_pub, 32); uoff += 32; + memcpy(ubuf + uoff, join_sig, 64); uoff += 64; + ubuf[uoff++] = addr_cnt; + size_t addr_data_sz = (size_t)(p - (pl + 8 + 32 + 32 + 64 + 1)); + if (uoff + addr_data_sz <= sizeof(ubuf)) { + memcpy(ubuf + uoff, pl + 8 + 32 + 32 + 64 + 1, addr_data_sz); + uoff += addr_data_sz; + } + cs_propagate(cs, ch_id, peer, ubuf, uoff); + } + + /* add to channel cache */ + struct channel_cache* ch = cs_find(cs, ch_id); + if (!ch) { + cs_refresh_channels(cs); + ch = cs_find(cs, ch_id); + } + + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: JOIN accepted node=0x%016llx ch=%s addrs=%d", + CS_ID, (unsigned long long)node_id, ch_id, addr_cnt); +} + +/* ─── WELCOME (0x0C): inviter → joiner with full peer list ─── */ + +static void cs_handle_welcome(struct chat_sync* cs, uint64_t peer, + const char* ch_id, const uint8_t* pl, size_t len) { + (void)peer; + if (len < 2) return; + sqlite3* db = cs->inst->topo_groups->topo_sqlite_db; + + const uint8_t* p = pl; + uint16_t pc; memcpy(&pc, p, 2); p += 2; + for (uint16_t i = 0; i < pc; i++) { + if ((size_t)(p - pl) + 8 + 32 + 32 + 64 + 1 > len) break; + uint64_t node_id; memcpy(&node_id, p, 8); p += 8; + const uint8_t* x25519 = p; p += 32; + const uint8_t* ed_pub = p; p += 32; + const uint8_t* join_sig = p; p += 64; + uint8_t ac = *p++; + + topo_node_sqlite_member_put(db, ch_id, node_id, join_sig, NULL); + + for (uint8_t j = 0; j < ac; j++) { + if (p + 1 > pl + len) break; + uint8_t fm = *p++; + int ip_len = (fm == 4) ? 4 : 16; + if (p + ip_len + 2 > pl + len) break; + const uint8_t* ip = p; p += ip_len; + uint16_t port = ((uint16_t)p[0] << 8) | p[1]; p += 2; + + char sql[256]; snprintf(sql, sizeof(sql), + "INSERT OR REPLACE INTO node_addresses(node_id,family,address,port,is_nat)" + " VALUES(?,?,?,?,0)"); + sqlite3_stmt* stmt = NULL; + if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) { + sqlite3_bind_int64(stmt, 1, (sqlite3_int64)node_id); + sqlite3_bind_int(stmt, 2, fm); + sqlite3_bind_blob(stmt, 3, ip, ip_len, SQLITE_STATIC); + sqlite3_bind_int(stmt, 4, (int)port); + sqlite3_step(stmt); sqlite3_finalize(stmt); + } + } + } + + cs_refresh_channels(cs); + + uint8_t evt[65]; uint8_t ch_id_len = (uint8_t)strlen(ch_id); + evt[0] = ch_id_len; memcpy(evt + 1, ch_id, ch_id_len); + gui_bridge_post(GUI_EVT_CHANNEL_UPDATED, evt, 1 + ch_id_len); + + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: WELCOME processed ch=%s peers=%d", + CS_ID, ch_id, pc); +} + +/* ─── PEER_UPSERT (0x0D): propagate new peer ─── */ + +static void cs_handle_peer_upsert(struct chat_sync* cs, uint64_t peer, + const char* ch_id, const uint8_t* pl, size_t len) { + if (len < 8 + 32 + 32 + 64 + 1) return; + const uint8_t* p = pl; + uint64_t node_id; memcpy(&node_id, p, 8); p += 8; + const uint8_t* x25519 = p; p += 32; + const uint8_t* ed_pub = p; p += 32; + const uint8_t* join_sig = p; p += 64; + uint8_t ac = *p++; + + /* verify join_sig */ + uint8_t vmsg[256]; size_t vlen = 0; + vlen += snprintf((char*)vmsg + vlen, sizeof(vmsg) - vlen, "%s", ch_id) + 1; + memcpy(vmsg + vlen, &node_id, 8); vlen += 8; + memcpy(vmsg + vlen, x25519, 32); vlen += 32; + if (cs_ed25519_verify(ed_pub, vmsg, vlen, join_sig) != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "%s: PEER_UPSERT invalid sig node=0x%016llx", CS_ID, + (unsigned long long)node_id); + return; + } + + sqlite3* db = cs->inst->topo_groups->topo_sqlite_db; + topo_node_sqlite_member_put(db, ch_id, node_id, join_sig, NULL); + + for (uint8_t i = 0; i < ac; i++) { + if (p + 1 > pl + len) break; + uint8_t fm = *p++; + int ip_len = (fm == 4) ? 4 : 16; + if (p + ip_len + 2 > pl + len) break; + const uint8_t* ip = p; p += ip_len; + uint16_t port = ((uint16_t)p[0] << 8) | p[1]; p += 2; + + char sql[256]; snprintf(sql, sizeof(sql), + "INSERT OR REPLACE INTO node_addresses(node_id,family,address,port,is_nat)" + " VALUES(?,?,?,?,0)"); + sqlite3_stmt* stmt = NULL; + if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) { + sqlite3_bind_int64(stmt, 1, (sqlite3_int64)node_id); + sqlite3_bind_int(stmt, 2, fm); + sqlite3_bind_blob(stmt, 3, ip, ip_len, SQLITE_STATIC); + sqlite3_bind_int(stmt, 4, (int)port); + sqlite3_step(stmt); sqlite3_finalize(stmt); + } + } + + cs_refresh_channels(cs); + + /* propagate to others (except sender and the subject node) */ + cs_propagate(cs, ch_id, peer, pl, len); + + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: PEER_UPSERT node=0x%016llx ch=%s", + CS_ID, (unsigned long long)node_id, ch_id); +} + +/* ─── PEER_REMOVE (0x0E) ─── */ + +static void cs_handle_peer_remove(struct chat_sync* cs, uint64_t peer, + const char* ch_id, const uint8_t* pl, size_t len) { + if (len < 8) return; + uint64_t node_id; memcpy(&node_id, pl, 8); + + sqlite3* db = cs->inst->topo_groups->topo_sqlite_db; + topo_node_sqlite_member_del(db, ch_id, node_id); + cs_refresh_channels(cs); + + cs_propagate(cs, ch_id, peer, pl, len); + + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: PEER_REMOVE node=0x%016llx ch=%s", + CS_ID, (unsigned long long)node_id, ch_id); +} diff --git a/tools/chatgui/transport/chat_sync.h b/tools/chatgui/transport/chat_sync.h index 14b38051..ea511e2f 100644 --- a/tools/chatgui/transport/chat_sync.h +++ b/tools/chatgui/transport/chat_sync.h @@ -15,12 +15,18 @@ struct UASYNC; #define ETCP_RT_ID_CHAT_SYNC 0x30 /* Message types */ -#define CS_MSG_INIT_SYNC 0x01 -#define CS_MSG_INIT_RESP 0x02 -#define CS_MSG_SEND_DATA 0x03 -#define CS_MSG_PUSH 0x04 -#define CS_MSG_ACK_PUSH 0x05 -#define CS_MSG_SYNC_DONE 0x06 +#define CS_MSG_INIT_SYNC 0x01 +#define CS_MSG_INIT_RESP 0x02 +#define CS_MSG_SEND_DATA 0x03 +#define CS_MSG_PUSH 0x04 +#define CS_MSG_ACK_PUSH 0x05 +#define CS_MSG_SYNC_DONE 0x06 +#define CS_MSG_CHANNEL_INFO_REQ 0x09 +#define CS_MSG_CHANNEL_INFO_RESP 0x0A +#define CS_MSG_CHANNEL_JOIN 0x0B +#define CS_MSG_WELCOME 0x0C +#define CS_MSG_PEER_UPSERT 0x0D +#define CS_MSG_PEER_REMOVE 0x0E /* Protocol constants */ #define CS_SEND_DATA_MAX 32 @@ -48,9 +54,9 @@ int chat_sync_push(struct UTUN_INSTANCE* inst, void chat_sync_connect_node(struct UTUN_INSTANCE* inst, uint64_t node_id); /* Подключиться к пиру по данным invite-ссылки (вызывается из GUI-потока) */ -void chat_sync_connect_from_invite(uint64_t node_id, const uint8_t* pubkey_bin, - const uint8_t* addrs_data, int addr_count, - const uint8_t* channel_id, int ch_id_len); +void chat_sync_connect_from_invite(uint64_t channel_id, uint64_t node_id, + const uint8_t* pubkey_bin, + const uint8_t* addrs_data, int addr_count); #ifdef __cplusplus } diff --git a/tools/chatgui/transport/gui_bridge.h b/tools/chatgui/transport/gui_bridge.h index c62101c4..692ca4da 100644 --- a/tools/chatgui/transport/gui_bridge.h +++ b/tools/chatgui/transport/gui_bridge.h @@ -13,7 +13,7 @@ struct UASYNC; /* ── Типы уведомлений uasync→GUI (fire-and-forget) ── */ #define GUI_EVT_MSG_RECEIVED 1 /* data: [ch_id_len:1][ch_id:var] */ -#define GUI_EVT_CONNECT_RESULT 2 /* data: [node_id:8][result:4] */ +#define GUI_EVT_CONNECT_RESULT 2 /* data: [node_id:8][result:4][channel_id:8] */ #define GUI_EVT_NEW_PEER 3 /* data: [node_id:8] */ #define GUI_EVT_CHANNEL_UPDATED 4 /* data: [ch_id_len:1][ch_id:var] */ @@ -35,7 +35,7 @@ void gui_bridge_post_uasync_fn(void (*fn)(void*), void* arg); int gui_bridge_is_uasync_ready(void); /* Callback для результатов подключения (вызывается из GUI-потока) */ -typedef void (*gui_connect_result_fn)(uint64_t node_id, int result); +typedef void (*gui_connect_result_fn)(uint64_t node_id, uint64_t channel_id, int result); void gui_bridge_set_connect_result_cb(gui_connect_result_fn cb); /* Callback для входящих сообщений (вызывается из GUI-потока) */ diff --git a/tools/chatgui/transport/gui_bridge_impl.cpp b/tools/chatgui/transport/gui_bridge_impl.cpp index 4982ad6a..98272695 100644 --- a/tools/chatgui/transport/gui_bridge_impl.cpp +++ b/tools/chatgui/transport/gui_bridge_impl.cpp @@ -51,12 +51,12 @@ void GuiBridgeReceiver::processPost(int eventType, QByteArray data) { } break; case GUI_EVT_CONNECT_RESULT: - if (dlen >= 12) { - uint64_t nodeId; int result; - memcpy(&nodeId, d, 8); memcpy(&result, d + 8, 4); - DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "gui_bridge: CONNECT_RESULT node=0x%016llx result=%d", - (unsigned long long)nodeId, result); - if (g_connect_result_cb) g_connect_result_cb(nodeId, result); + if (dlen >= 20) { + uint64_t nodeId, channelId; int result; + memcpy(&nodeId, d, 8); memcpy(&result, d + 8, 4); memcpy(&channelId, d + 12, 8); + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "gui_bridge: CONNECT_RESULT node=0x%016llx ch=%llu result=%d", + (unsigned long long)nodeId, (unsigned long long)channelId, result); + if (g_connect_result_cb) g_connect_result_cb(nodeId, channelId, result); } else { DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "gui_bridge: CONNECT_RESULT data too short %d", dlen); } diff --git a/tools/chatgui/transport/topo_node_sqlite.c b/tools/chatgui/transport/topo_node_sqlite.c index 26dd8d29..771a8bb4 100644 --- a/tools/chatgui/transport/topo_node_sqlite.c +++ b/tools/chatgui/transport/topo_node_sqlite.c @@ -175,19 +175,22 @@ int topo_node_sqlite_channel_put(sqlite3* db, const char* channel_id, return rc == SQLITE_DONE ? 0 : -1; } -int topo_node_sqlite_member_put(sqlite3* db, const char* channel_id, uint64_t node_id) { +int topo_node_sqlite_member_put(sqlite3* db, const char* channel_id, uint64_t node_id, + const uint8_t* join_sig, const uint8_t* creator_sig) { 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); + "INSERT OR REPLACE INTO \"%s\"(node_id, join_sig, creator_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; + if (join_sig) sqlite3_bind_blob(stmt, 2, join_sig, PEERS_JOIN_SIG_SIZE, SQLITE_STATIC); + else { static const unsigned char zsig[PEERS_JOIN_SIG_SIZE] = {0}; sqlite3_bind_blob(stmt, 2, zsig, PEERS_JOIN_SIG_SIZE, SQLITE_STATIC); } + if (creator_sig) sqlite3_bind_blob(stmt, 3, creator_sig, PEERS_JOIN_SIG_SIZE, SQLITE_STATIC); + else sqlite3_bind_null(stmt, 3); + int rc = sqlite3_step(stmt); sqlite3_finalize(stmt); + return rc == SQLITE_DONE ? 0 : -1; } int topo_node_sqlite_member_del(sqlite3* db, const char* channel_id, uint64_t node_id) { @@ -201,3 +204,94 @@ int topo_node_sqlite_member_del(sqlite3* db, const char* channel_id, uint64_t no sqlite3_step(stmt); sqlite3_finalize(stmt); return 0; } + +int topo_node_sqlite_channel_get(sqlite3* db, const char* channel_id, + char* name_out, int name_sz, int* is_dm, uint64_t* owner_node_id, + uint8_t* x25519_pub, uint8_t* ed25519_pub, uint8_t* signature) { + if (!db || !channel_id) return -1; + sqlite3_stmt* stmt = NULL; + if (sqlite3_prepare_v2(db, + "SELECT name, owner_node_id, is_dm, x25519_pubkey, ed25519_pubkey, signature" + " FROM channels WHERE channel_id=?", -1, &stmt, NULL) != SQLITE_OK) return -1; + sqlite3_bind_text(stmt, 1, channel_id, -1, SQLITE_STATIC); + if (sqlite3_step(stmt) != SQLITE_ROW) { sqlite3_finalize(stmt); return -1; } + if (name_out && name_sz > 0) { + const unsigned char* txt = sqlite3_column_text(stmt, 0); + if (txt) snprintf(name_out, name_sz, "%s", txt); else name_out[0] = '\0'; + } + if (owner_node_id) *owner_node_id = (uint64_t)sqlite3_column_int64(stmt, 1); + if (is_dm) *is_dm = sqlite3_column_int(stmt, 2); + if (x25519_pub) memcpy(x25519_pub, sqlite3_column_blob(stmt, 3), 32); + if (ed25519_pub) memcpy(ed25519_pub, sqlite3_column_blob(stmt, 4), 32); + if (signature) memcpy(signature, sqlite3_column_blob(stmt, 5), 64); + sqlite3_finalize(stmt); + return 0; +} + +int topo_node_sqlite_channel_peers_all(sqlite3* db, const char* channel_id, + uint8_t* buf, size_t buf_sz, size_t* out_len) { + if (!db || !channel_id || !buf || !out_len) return -1; + char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl)); + + sqlite3_stmt* stmt = NULL; + char sql[256]; + snprintf(sql, sizeof(sql), + "SELECT p.node_id, p.join_sig, p.creator_sig," + " n.x25519_pubkey, n.ed25519_pubkey" + " FROM \"%s\" p LEFT JOIN nodes n ON p.node_id = n.node_id", peers_tbl); + if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) return -1; + + size_t off = 0; + if (off + 2 > buf_sz) { sqlite3_finalize(stmt); return -2; } + uint16_t* cnt_ptr = (uint16_t*)(buf + off); off += 2; + *cnt_ptr = 0; + + while (sqlite3_step(stmt) == SQLITE_ROW) { + uint64_t node_id = (uint64_t)sqlite3_column_int64(stmt, 0); + const uint8_t* join_sig = (const uint8_t*)sqlite3_column_blob(stmt, 1); + const uint8_t* x25519 = (const uint8_t*)sqlite3_column_blob(stmt, 3); + const uint8_t* ed_pub = (const uint8_t*)sqlite3_column_blob(stmt, 4); + if (!join_sig || !x25519 || !ed_pub) continue; + + sqlite3_stmt* ast = NULL; + snprintf(sql, sizeof(sql), + "SELECT family, address, port FROM node_addresses" + " WHERE node_id=? AND is_nat=0 LIMIT 16"); + if (sqlite3_prepare_v2(db, sql, -1, &ast, NULL) != SQLITE_OK) continue; + sqlite3_bind_int64(ast, 1, (sqlite3_int64)node_id); + + uint8_t addr_cnt = 0; size_t addr_start = 0; + while (sqlite3_step(ast) == SQLITE_ROW && addr_cnt < 255) { + int family = sqlite3_column_int(ast, 0); + int ip_len = (family == 4) ? 4 : 16; + const uint8_t* ip = (const uint8_t*)sqlite3_column_blob(ast, 1); + uint16_t port = (uint16_t)sqlite3_column_int(ast, 2); + if (!ip) continue; + if (off + 1 + ip_len + 2 > buf_sz) { sqlite3_finalize(ast); sqlite3_finalize(stmt); return -2; } + if (addr_cnt == 0) addr_start = off; + buf[off++] = (uint8_t)family; + memcpy(buf + off, ip, (size_t)ip_len); off += (size_t)ip_len; + buf[off++] = (uint8_t)((port >> 8) & 0xFF); + buf[off++] = (uint8_t)(port & 0xFF); + addr_cnt++; + } + sqlite3_finalize(ast); + if (addr_cnt == 0) continue; + + size_t peer_needed = 8 + 32 + 32 + 64 + 1; + size_t addr_sz = off - addr_start; + if (off + peer_needed > buf_sz) { sqlite3_finalize(stmt); return -2; } + memmove(buf + addr_start + peer_needed, buf + addr_start, addr_sz); + off = addr_start; + memcpy(buf + off, &node_id, 8); off += 8; + memcpy(buf + off, x25519, 32); off += 32; + memcpy(buf + off, ed_pub, 32); off += 32; + memcpy(buf + off, join_sig, 64); off += 64; + buf[off++] = addr_cnt; + off += addr_sz; + (*cnt_ptr)++; + } + sqlite3_finalize(stmt); + *out_len = off; + return 0; +} diff --git a/tools/chatgui/transport/topo_node_sqlite.h b/tools/chatgui/transport/topo_node_sqlite.h index 27217457..8691995b 100644 --- a/tools/chatgui/transport/topo_node_sqlite.h +++ b/tools/chatgui/transport/topo_node_sqlite.h @@ -7,6 +7,8 @@ struct TOPO_NODEQ; +#define PEERS_JOIN_SIG_SIZE 64 + int topo_node_sqlite_init(sqlite3* db); int topo_node_sqlite_node_put(sqlite3* db, struct TOPO_NODEQ* nq); @@ -17,8 +19,16 @@ int topo_node_sqlite_channel_put(sqlite3* db, const char* channel_id, 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_put(sqlite3* db, const char* channel_id, uint64_t node_id, + const uint8_t* join_sig, const uint8_t* creator_sig); int topo_node_sqlite_member_del(sqlite3* db, const char* channel_id, uint64_t node_id); +int topo_node_sqlite_channel_get(sqlite3* db, const char* channel_id, + char* name_out, int name_sz, int* is_dm, uint64_t* owner_node_id, + uint8_t* x25519_pub, uint8_t* ed25519_pub, uint8_t* signature); + +int topo_node_sqlite_channel_peers_all(sqlite3* db, const char* channel_id, + uint8_t* buf, size_t buf_sz, size_t* out_len); + #endif diff --git a/tools/chatgui/transport/utun_node.cpp b/tools/chatgui/transport/utun_node.cpp index 43222a59..62702790 100644 --- a/tools/chatgui/transport/utun_node.cpp +++ b/tools/chatgui/transport/utun_node.cpp @@ -1,6 +1,9 @@ // utun_node.cpp — embedded uTun node running in a dedicated thread #include "utun_node.h" #include +#include + +#include "../db/db_manager.h" // === C headers === extern "C" { @@ -95,6 +98,45 @@ QString UtunNode::pubKeyHex() const { return QString::fromLatin1(m_instance->config->global.my_public_key_hex, 64); } +QList UtunNode::getInviteAddresses(DbManager* db) { + QList addrs; + + if (db && db->isOpen()) { + QList peerAddrs = db->getOnlinePeerAddresses(db->myNodeId()); + for (int i = 0; i < peerAddrs.size() && i < 2; i++) + addrs.append(peerAddrs[i]); + } + + if (addrs.isEmpty() && m_instance) { + struct ETCP_SOCKET* sock = m_instance->etcp_sockets; + while (sock) { + struct sockaddr_storage* sa; + if (sock->nat_addr.ss_family != 0) + sa = &sock->nat_addr; + else if (sock->interface_addr.ss_family != 0) + sa = &sock->interface_addr; + else { sock = sock->next; continue; } + + NodeAddr a; + if (sa->ss_family == AF_INET) { + struct sockaddr_in* sin = (struct sockaddr_in*)sa; + a.family = 4; + a.address = QByteArray((const char*)&sin->sin_addr, 4); + a.port = ntohs(sin->sin_port); + } else { + struct sockaddr_in6* sin6 = (struct sockaddr_in6*)sa; + a.family = 6; + a.address = QByteArray((const char*)&sin6->sin6_addr, 16); + a.port = ntohs(sin6->sin6_port); + } + addrs.append(a); + sock = sock->next; + } + } + + return addrs; +} + 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; diff --git a/tools/chatgui/transport/utun_node.h b/tools/chatgui/transport/utun_node.h index 04c62761..5823a93a 100644 --- a/tools/chatgui/transport/utun_node.h +++ b/tools/chatgui/transport/utun_node.h @@ -5,10 +5,13 @@ #include #include #include +#include #include #include struct UTUN_INSTANCE; +class DbManager; +struct NodeAddr; class UtunNode : public QObject { Q_OBJECT @@ -29,6 +32,7 @@ public: QString nodeIdHex() const; QString pubKeyHex() const; + QList getInviteAddresses(DbManager* db); signals: void started();