Browse Source

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
chatgui
Evgeny 3 months ago
parent
commit
85a91a1320
  1. 2
      src/topo_group.c
  2. 3
      tools/chatgui/src/debug_ui.h
  3. 160
      tools/chatgui/src/invite_link.cpp
  4. 5
      tools/chatgui/src/invite_link.h
  5. 29
      tools/chatgui/src/joindialog.cpp
  6. 3
      tools/chatgui/src/joindialog.h
  7. 4
      tools/chatgui/src/mainwindow.cpp
  8. 10
      tools/chatgui/transport/chat_core.c
  9. 3
      tools/chatgui/transport/chat_core.h
  10. 451
      tools/chatgui/transport/chat_sync.c
  11. 24
      tools/chatgui/transport/chat_sync.h
  12. 4
      tools/chatgui/transport/gui_bridge.h
  13. 12
      tools/chatgui/transport/gui_bridge_impl.cpp
  14. 106
      tools/chatgui/transport/topo_node_sqlite.c
  15. 12
      tools/chatgui/transport/topo_node_sqlite.h
  16. 42
      tools/chatgui/transport/utun_node.cpp
  17. 4
      tools/chatgui/transport/utun_node.h

2
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);

3
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 <cstdio>
#include <cstdarg>
#include <cstring>
namespace GuiDebugPriv {
inline FILE *s_file = stderr;

160
tools/chatgui/src/invite_link.cpp

@ -2,62 +2,48 @@
#include "debug_ui.h"
#include <QByteArray>
static QByteArray encodeAddresses(const QList<NodeAddr>& addrs) {
QByteArray buf;
for (const auto& a : addrs) {
buf.append(static_cast<char>(a.family));
buf.append(a.address);
buf.append(static_cast<char>((a.port >> 8) & 0xFF));
buf.append(static_cast<char>(a.port & 0xFF));
}
return buf;
}
static QList<InviteAddr> decodeAddresses(const uint8_t* data, int len,
int count) {
QList<InviteAddr> 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<const char*>(data + off), alen);
off += alen;
a.port = (static_cast<quint16>(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<NodeAddr>& 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<char>(INVITE_LINK_VERSION));
blob.append(static_cast<char>((cid.size() >> 8) & 0xFF));
blob.append(static_cast<char>(cid.size() & 0xFF));
blob.append(cid);
blob.append(pubkey);
blob.append(static_cast<char>((addrCount >> 8) & 0xFF));
blob.append(static_cast<char>(addrCount & 0xFF));
blob.append(addrsEnc);
/* channel_id: uint64 big-endian */
for (int i = 7; i >= 0; i--)
blob.append(static_cast<char>((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<char>(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<char>((p >> 8) & 0xFF));
blob.append(static_cast<char>(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<const uint8_t*>(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<int>(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<const char*>(data + off), cidLen);
off += cidLen;
d.pubkey = QByteArray(reinterpret_cast<const char*>(data + off), 32);
off += 32;
int addrCount = (static_cast<int>(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<const char*>(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<const char*>(data + off), ipLen);
off += ipLen;
a.port = (static_cast<quint16>(data[off]) << 8) | data[off + 1];
off += 2;
d.addrs.append(a);
}
}
return d;
}

5
tools/chatgui/src/invite_link.h

@ -15,12 +15,13 @@ struct InviteAddr {
};
struct InviteData {
QString channelId;
uint64_t channelId = 0;
QByteArray pubkey;
QList<InviteAddr> addrs;
QString error;
};
QString encodeInviteLink(const QString& channelId, const QByteArray& pubkey,
QString encodeInviteLink(uint64_t channelId, const QByteArray& pubkey,
const QList<NodeAddr>& addrs);
InviteData decodeInviteLink(const QString& link);

29
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<JoinDialog*>(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<char>(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);

3
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;
};

4
tools/chatgui/src/mainwindow.cpp

@ -205,9 +205,9 @@ void MainWindow::showInviteDialog(const QString& channelId) {
return;
}
QList<NodeAddr> addrs = m_db->getOnlinePeerAddresses(m_db->myNodeId());
QList<NodeAddr> 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;

10
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);
}
/* ─── создание канала ─── */

3
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);

451
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 <string.h>
#include <openssl/evp.h>
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);
}

24
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
}

4
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-потока) */

12
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);
}

106
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;
}

12
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

42
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 <cinttypes>
#include <netinet/in.h>
#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<NodeAddr> UtunNode::getInviteAddresses(DbManager* db) {
QList<NodeAddr> addrs;
if (db && db->isOpen()) {
QList<NodeAddr> 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;

4
tools/chatgui/transport/utun_node.h

@ -5,10 +5,13 @@
#include <QObject>
#include <QByteArray>
#include <QString>
#include <QList>
#include <atomic>
#include <thread>
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<NodeAddr> getInviteAddresses(DbManager* db);
signals:
void started();

Loading…
Cancel
Save