#include "invite_link.h" #include "debug_ui.h" #include QString encodeInviteLink(uint64_t channelId, const QByteArray& pubkey, const QList& addrs) { 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; blob.reserve(1 + 8 + addrs.size() * (1 + 32 + 19)); blob.append(static_cast(INVITE_LINK_VERSION)); /* 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(static_cast(addrs[i + j].socketId)); 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()); } 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(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; 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; } /* 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(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 + 1 + 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.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; }