stcp: ed25519 pubkey exchange in handshake, fix instance destroy segfault, fix chat_tcp_test

- u_async.c: process immediate_queue BEFORE epoll_wait in uasync_poll
- stcp_link.c: defensive NULL etcp_conn/etcp_link in stcp_link_close
- utun_instance.c: move stcp_server_list_destroy_all to Phase J (before ETCP),
  add deferred drain in both Phase J and L
- stcp.h/c: STCP_HS_ENC_CLIENT 6→38, STCP_HS_ENC_SERVER 7→39,
  exchange ed25519_pubkey in both handshake directions
- etcp_connections.c: copy peer_ed25519 from STCP to ETCP_CONN in
  etcp_link_enter_ready_tcp; guard link->etcp in tcp_link_close_cb
- chat_sync.c: fix lk->conn NULL dereference for TCP links
- test_stcp.c: update stcp_server_create/stcp_client_connect callsites
- tools/chat_tcp_test: change transport udp→tcp
This commit is contained in:
evgeny committed 2026-08-07 00:48:27 +03:00
1 parent a3783b5f55
commit 38c9f229bc
1272 files changed
+968388 -1

No files matched your search

+160
View File
@@ -0,0 +1,160 @@
#include "invite_link.h"
#include "debug_ui.h"
#include <QByteArray>
QString encodeInviteLink(uint64_t channelId, const QByteArray& pubkey,
const QList<NodeAddr>& addrs,
const QString& password) {
if (pubkey.size() != 32) {
GUI_ERROR("encodeInviteLink: pubkey must be 32 bytes (got %d)", pubkey.size());
return {};
}
if (addrs.isEmpty()) {
GUI_ERROR("encodeInviteLink: no addresses");
return {};
}
QByteArray blob;
int hasPass = !password.isEmpty() ? 1 : 0;
blob.reserve(1 + (hasPass ? 1 + password.size() : 0) + 8 + addrs.size() * (1 + 32 + 20));
blob.append(static_cast<char>(hasPass ? 0x02 : INVITE_LINK_VERSION));
if (hasPass) {
QByteArray passBytes = password.toUtf8();
blob.append(static_cast<char>(passBytes.size()));
blob.append(passBytes);
}
/* 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(static_cast<char>(addrs[i + j].socketId));
blob.append(static_cast<char>(addrs[i + j].protocol));
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());
}
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());
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 < 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 && ver != 0x02) {
d.error = QStringLiteral("unsupported version 0x%1").arg(ver, 2, 16, QLatin1Char('0'));
GUI_ERROR("decodeInviteLink: unsupported version %d", ver);
return d;
}
/* v2: password field before channel_id */
if (ver == 0x02) {
if (off >= len) {
d.error = QStringLiteral("truncated at pass_len");
return d;
}
uint8_t plen = data[off++];
if (plen > 0) {
if (off + plen > len) {
d.error = QStringLiteral("truncated at password");
return d;
}
d.password = QString::fromUtf8(reinterpret_cast<const char*>(data + off), plen);
off += plen;
}
}
/* 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;
}
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 + 2 + 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.socketId = data[off++];
a.proto = data[off++];
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;
}