You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
535 lines
26 KiB
535 lines
26 KiB
/** |
|
* @file topo_group_invite.c |
|
* @brief Invite/join к каналу через прямое (ncd) подключение + проверка членства. |
|
* |
|
* Перенесено из conn_mgr (INVITE_INFO_REQ/RESP) — conn_mgr теперь только медиа. |
|
*/ |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <stdio.h> |
|
#include "../lib/platform_compat.h" |
|
#include "../lib/debug_config.h" |
|
#include "../lib/mem.h" |
|
#include "../lib/memory_pool.h" |
|
#include "../lib/u_async.h" |
|
#include "utun_instance.h" |
|
#include "etcp_api.h" |
|
#include "etcp.h" |
|
#include "etcp_connections.h" |
|
#include "topo_node.h" |
|
#include "topo_node_sqlite.h" |
|
#include "topo_group.h" |
|
#include "topo_group_invite.h" |
|
#include "../transport_layer/node_conn_direct.h" |
|
#include "ntp_time.h" |
|
#include "../chat/chat_core.h" |
|
|
|
#define TGI_ID "topo_group_invite" |
|
#define TGI_TIMEOUT_MS 2000 |
|
|
|
/* wire-формат INVITE_INFO_RESP (devel — без обратной совместимости): |
|
* |
|
* [0..1] cmd, subcmd |
|
* [2..9] group_id |
|
* [10] is_member |
|
* [11..13] reserved |
|
* [14..45] ed25519_pubkey |
|
* [46] node_name_len |
|
* [47..N] node_name |
|
* |
|
* Если is_member == 1 — далее полная информация о канале: |
|
* [N+1] ch_name_len |
|
* [N+2..] ch_name |
|
* +8 owner |
|
* +32 ch_x25519 |
|
* +32 ch_ed_pub |
|
* +64 ch_sig |
|
* +1 inv_flags |
|
* [if PEERS_FLAG_HAS_JOIN: +64 join_sig + 8 join_ts] |
|
* +64 update_sig |
|
* +8 update_ts |
|
* +1 userinfo_len |
|
* +var userinfo |
|
*/ |
|
#pragma pack(push, 1) |
|
struct TGI_INVITE_REQ { uint8_t cmd, subcmd; uint64_t group_id; }; |
|
struct TGI_INVITE_RESP { uint8_t cmd, subcmd; uint64_t group_id; uint8_t is_member; uint8_t reserved[3]; uint8_t ed25519_pubkey[SC_PUBKEY_SIZE]; uint8_t node_name_len; uint8_t node_name[0]; }; |
|
#pragma pack(pop) |
|
|
|
#define TGI_INVITE_RESP_HDR_SIZE (sizeof(struct TGI_INVITE_RESP)) |
|
#define TGI_INVITE_RESP_CH_MIN_SIZE (1 + 8 + 32 + 32 + 64 + 1 + 64 + 8 + 1) |
|
|
|
enum { TGI_CONNECTING = 0, TGI_WAIT_INFO = 1 }; |
|
|
|
struct tgi_pending { |
|
struct tgi_pending* next; |
|
struct UTUN_INSTANCE* inst; |
|
uint64_t node_id, group_id; |
|
uint8_t state; |
|
uint8_t cancelled; |
|
void* overall_timer; |
|
struct NODE_CONN_DIRECT* ncd_handle; |
|
struct TOPO_GROUP_NODE* temp_nq; |
|
tgi_cb_t cb; void* cb_arg; |
|
}; |
|
|
|
/* глобальный список pending-invite (invite — редкая одиночная операция) */ |
|
static struct tgi_pending* g_tgi_list = NULL; |
|
|
|
static void tgi_cancel(struct tgi_pending* p); |
|
static void tgi_fail(struct tgi_pending* p); |
|
static void tgi_handle_info_req(struct ETCP_CONN* conn, const uint8_t* data, size_t len); |
|
static void tgi_handle_info_resp(struct ETCP_CONN* conn, const uint8_t* data, size_t len); |
|
static void tgi_ncd_cb(struct NODE_CONN_DIRECT* h, enum ncd_event ev, void* arg); |
|
|
|
static void tgi_unlink(struct tgi_pending* p) { |
|
struct tgi_pending** pp = &g_tgi_list; |
|
while (*pp) { if (*pp == p) { *pp = p->next; return; } pp = &(*pp)->next; } |
|
} |
|
|
|
static struct tgi_pending* tgi_find_waiting(uint64_t node_id) { |
|
for (struct tgi_pending* p = g_tgi_list; p; p = p->next) |
|
if (p->state == TGI_WAIT_INFO && p->node_id == node_id) return p; |
|
return NULL; |
|
} |
|
|
|
/* ═══════════ приём пакетов ═══════════ */ |
|
|
|
void topo_group_invite_recv_handler(struct ETCP_CONN* conn, struct ll_entry* entry) { |
|
if (!conn || !entry || entry->len < 2) { |
|
if (entry) { if (entry->dgram) queue_dgram_free(entry); queue_entry_free(entry); } |
|
return; |
|
} |
|
uint8_t* d = entry->dgram; |
|
if (d[0] != ETCP_RT_ID_GROUP_INVITE) { queue_dgram_free(entry); queue_entry_free(entry); return; } |
|
uint8_t sub = d[1]; |
|
if (sub == TGI_SUBCMD_INFO_REQ) tgi_handle_info_req(conn, d, entry->len); |
|
else if (sub == TGI_SUBCMD_INFO_RESP) tgi_handle_info_resp(conn, d, entry->len); |
|
queue_dgram_free(entry); queue_entry_free(entry); |
|
} |
|
|
|
/* Инвайтера сторона: INVITE_INFO_REQ → проверка членства → INVITE_INFO_RESP */ |
|
static void tgi_handle_info_req(struct ETCP_CONN* conn, const uint8_t* data, size_t len) { |
|
if (len < sizeof(struct TGI_INVITE_REQ)) { DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "invite: INFO_REQ too short (%zu)", len); return; } |
|
struct TGI_INVITE_REQ* req = (struct TGI_INVITE_REQ*)data; |
|
struct UTUN_INSTANCE* inst = conn->instance; |
|
if (!inst || !inst->topo_groups) return; |
|
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "invite: INFO_REQ from=%016llx group=%016llx", |
|
(unsigned long long)conn->peer_node_id, (unsigned long long)req->group_id); |
|
|
|
char ch_id[32]; snprintf(ch_id, sizeof(ch_id), "%llu", (unsigned long long)req->group_id); |
|
|
|
struct TOPO_GROUP* grp = topo_groups_find(inst->topo_groups, req->group_id); |
|
struct TOPO_GROUP* sg = grp ? grp : topo_groups_get_default(inst->topo_groups); |
|
struct TOPO_NODE* ln = sg && sg->local_node ? topo_node_registry_find(inst->topo_groups, sg->local_node->node_id) : NULL; |
|
const char* nm = ln ? ln->node_name : NULL; size_t nl = nm ? strlen(nm) : 0; if (nl > 255) nl = 255; |
|
|
|
sqlite3* db = inst->topo_sqlite_db; |
|
char ch_name[128] = ""; uint64_t owner = 0; |
|
uint8_t ch_x25519[32] = {0}, ch_ed_pub[32] = {0}, ch_sig[64] = {0}; |
|
int have_channel = db && topo_node_sqlite_channel_get(db, ch_id, ch_name, (int)sizeof(ch_name), |
|
&owner, ch_x25519, ch_ed_pub, ch_sig) == 0; |
|
|
|
if (!have_channel) { |
|
size_t rs = TGI_INVITE_RESP_HDR_SIZE + nl; |
|
uint8_t* rb = u_calloc(1, rs); if (!rb) return; |
|
struct TGI_INVITE_RESP* resp = (struct TGI_INVITE_RESP*)rb; |
|
resp->cmd = ETCP_RT_ID_GROUP_INVITE; resp->subcmd = TGI_SUBCMD_INFO_RESP; |
|
resp->group_id = req->group_id; resp->is_member = 0; |
|
if (sg) memcpy(resp->ed25519_pubkey, sg->ed25519_public_key, SC_PUBKEY_SIZE); |
|
resp->node_name_len = (uint8_t)nl; if (nl) memcpy(resp->node_name, nm, nl); |
|
struct ll_entry* qe = queue_entry_new(0); |
|
if (qe) { qe->dgram = rb; qe->len = (uint16_t)rs; etcp_send(conn, qe); } else u_free(rb); |
|
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "invite: group=%016llx NOT in DB — responding is_member=NO to 0x%016llx", |
|
(unsigned long long)req->group_id, (unsigned long long)conn->peer_node_id); |
|
return; |
|
} |
|
|
|
/* ── участник: генерируем join_sig / update_sig ── */ |
|
uint64_t myid = inst->node_id; |
|
uint8_t my_join_sig[64] = {0}; uint64_t my_join_ts = 0; |
|
{ |
|
if (db && topo_node_sqlite_member_get_join(db, ch_id, myid, my_join_sig, &my_join_ts) != 0) { |
|
uint8_t join_msg[256]; size_t mlen = 0; |
|
memcpy(join_msg + mlen, ch_x25519, 32); mlen += 32; |
|
memcpy(join_msg + mlen, ch_ed_pub, 32); mlen += 32; |
|
memcpy(join_msg + mlen, &myid, 8); mlen += 8; |
|
memcpy(join_msg + mlen, inst->my_keys.public_key, 32); mlen += 32; |
|
my_join_ts = (uint64_t)ntp_time_get_seconds(inst); |
|
memcpy(join_msg + mlen, &my_join_ts, 8); mlen += 8; |
|
if (sc_ed25519_sign(inst->my_ed25519_privkey, join_msg, mlen, my_join_sig) != SC_OK) |
|
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "invite: join_sig sign FAILED myid=%016llx ch=%s", (unsigned long long)myid, ch_id); |
|
else |
|
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "invite: join_sig NEW myid=%016llx ts=%llu ch=%s", |
|
(unsigned long long)myid, (unsigned long long)my_join_ts, ch_id); |
|
} |
|
} |
|
|
|
uint8_t my_update_sig[64] = {0}; uint64_t my_update_ts = 0; |
|
{ |
|
char juser[256]; snprintf(juser, sizeof(juser), "{\"name\":\"%s\"}", |
|
inst->name[0] ? inst->name : ""); |
|
uint8_t umsg[256]; size_t ulen = 0; |
|
memcpy(umsg + ulen, my_join_sig, 64); ulen += 64; |
|
my_update_ts = (uint64_t)ntp_time_get_seconds(inst); |
|
memcpy(umsg + ulen, &my_update_ts, 8); ulen += 8; |
|
size_t jl = strlen(juser); memcpy(umsg + ulen, juser, jl); umsg[ulen + jl] = '\0'; ulen += jl + 1; |
|
if (sc_ed25519_sign(inst->my_ed25519_privkey, umsg, ulen, my_update_sig) != SC_OK) |
|
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "invite: update_sig sign FAILED myid=%016llx", (unsigned long long)myid); |
|
} |
|
|
|
/* ── создаём инфраструктуру группы СРАЗУ ── */ |
|
chat_core_ensure_channel_ready(ch_id); |
|
|
|
/* ── сериализуем ответ ── */ |
|
size_t ch_nl = strlen(ch_name); if (ch_nl > 255) ch_nl = 255; |
|
uint8_t inv_flags = (my_join_sig[0] || my_join_ts) ? PEERS_FLAG_HAS_JOIN : 0; |
|
char userinfo[256]; size_t userinfo_len = (size_t)snprintf(userinfo, sizeof(userinfo), |
|
"{\"name\":\"%s\"}", inst->name[0] ? inst->name : ""); |
|
|
|
size_t ch_info_len = 1 + ch_nl + 8 + 32 + 32 + 64 + 1 |
|
+ (inv_flags & PEERS_FLAG_HAS_JOIN ? 72 : 0) |
|
+ 64 + 8 + 1 + userinfo_len; |
|
size_t rs = TGI_INVITE_RESP_HDR_SIZE + nl + ch_info_len; |
|
uint8_t* rb = u_malloc(rs); if (!rb) return; |
|
memset(rb, 0, rs); |
|
|
|
{ struct TGI_INVITE_RESP* resp = (struct TGI_INVITE_RESP*)rb; |
|
resp->cmd = ETCP_RT_ID_GROUP_INVITE; resp->subcmd = TGI_SUBCMD_INFO_RESP; |
|
resp->group_id = req->group_id; resp->is_member = 1; |
|
if (sg) memcpy(resp->ed25519_pubkey, sg->ed25519_public_key, SC_PUBKEY_SIZE); |
|
resp->node_name_len = (uint8_t)nl; if (nl) memcpy(resp->node_name, nm, nl); } |
|
|
|
uint8_t* ch = rb + TGI_INVITE_RESP_HDR_SIZE + nl; |
|
*ch++ = (uint8_t)ch_nl; memcpy(ch, ch_name, ch_nl); ch += ch_nl; |
|
memcpy(ch, &owner, 8); ch += 8; |
|
memcpy(ch, ch_x25519, 32); ch += 32; |
|
memcpy(ch, ch_ed_pub, 32); ch += 32; |
|
memcpy(ch, ch_sig, 64); ch += 64; |
|
*ch++ = inv_flags; |
|
if (inv_flags & PEERS_FLAG_HAS_JOIN) { memcpy(ch, my_join_sig, 64); ch += 64; memcpy(ch, &my_join_ts, 8); ch += 8; } |
|
memcpy(ch, my_update_sig, 64); ch += 64; |
|
memcpy(ch, &my_update_ts, 8); ch += 8; |
|
*ch++ = (uint8_t)userinfo_len; memcpy(ch, userinfo, userinfo_len); |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, |
|
"invite: membership CHECK OK from=0x%016llx group=%016llx ch_name=%.*s owner=%016llx inv_flags=%u → is_member=YES (%zuB)", |
|
(unsigned long long)conn->peer_node_id, (unsigned long long)req->group_id, |
|
(int)ch_nl, ch_name, (unsigned long long)owner, (unsigned)inv_flags, rs); |
|
|
|
{ struct ll_entry* qe = queue_entry_new(0); |
|
if (qe) { qe->dgram = rb; qe->len = (uint16_t)rs; etcp_send(conn, qe); } else u_free(rb); |
|
} |
|
} |
|
|
|
/* Джойнер: INVITE_INFO_RESP → верификация ch_sig → создать канал → TGI_EVENT_JOIN */ |
|
static void tgi_handle_info_resp(struct ETCP_CONN* conn, const uint8_t* data, size_t len) { |
|
if (len < TGI_INVITE_RESP_HDR_SIZE) { DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "invite: INFO_RESP too short (%zu < %zu)", len, TGI_INVITE_RESP_HDR_SIZE); return; } |
|
struct TGI_INVITE_RESP* resp = (struct TGI_INVITE_RESP*)data; |
|
uint8_t nnl = resp->node_name_len; |
|
if (len < TGI_INVITE_RESP_HDR_SIZE + (size_t)nnl) { DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "invite: INFO_RESP truncated"); return; } |
|
|
|
struct tgi_pending* inv = tgi_find_waiting(conn->peer_node_id); |
|
if (!inv) { DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "invite: INFO_RESP — no pending invite in WAIT_INFO state"); return; } |
|
|
|
if (!resp->is_member) { |
|
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "invite: membership DENIED by 0x%016llx — not in channel", (unsigned long long)inv->node_id); |
|
tgi_fail(inv); return; |
|
} |
|
|
|
/* ── сохраняем Ed25519 pubkey ответчика для NODEINFO-верификации ── */ |
|
if (inv->temp_nq) { |
|
struct TOPO_NODE* ni2 = topo_node_registry_find(inv->inst->topo_groups, inv->temp_nq->node_id); |
|
if (ni2) memcpy(ni2->ed25519_public_key, resp->ed25519_pubkey, SC_PUBKEY_SIZE); |
|
} |
|
|
|
/* ── парсим channel_info ── */ |
|
const uint8_t* ch = data + TGI_INVITE_RESP_HDR_SIZE + nnl; |
|
size_t ch_remain = len - (TGI_INVITE_RESP_HDR_SIZE + nnl); |
|
if (ch_remain < TGI_INVITE_RESP_CH_MIN_SIZE) { DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "invite: INFO_RESP channel info too short"); tgi_fail(inv); return; } |
|
|
|
uint8_t c_nl = *ch++; ch_remain--; |
|
if (ch_remain < (size_t)c_nl) { DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "invite: ch_name truncated"); tgi_fail(inv); return; } |
|
char ch_name[128]; size_t cn = c_nl > 127 ? 127 : c_nl; memcpy(ch_name, ch, cn); ch_name[cn] = '\0'; ch += c_nl; ch_remain -= c_nl; |
|
|
|
uint64_t ch_owner; memcpy(&ch_owner, ch, 8); ch += 8; ch_remain -= 8; |
|
const uint8_t* ch_x25519 = ch; ch += 32; ch_remain -= 32; |
|
const uint8_t* ch_ed_pub = ch; ch += 32; ch_remain -= 32; |
|
const uint8_t* ch_sig = ch; ch += 64; ch_remain -= 64; |
|
|
|
if (ch_remain < 1) { DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "invite: ch_info truncated at inv_flags"); tgi_fail(inv); return; } |
|
uint8_t inv_flags = *ch++; ch_remain--; |
|
|
|
const uint8_t* join_sig = NULL; uint64_t join_ts = 0; |
|
if (inv_flags & PEERS_FLAG_HAS_JOIN) { |
|
if (ch_remain < 72) { DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "invite: ch_info truncated at join_sig"); tgi_fail(inv); return; } |
|
join_sig = ch; ch += 64; memcpy(&join_ts, ch, 8); ch += 8; ch_remain -= 72; |
|
} |
|
|
|
if (ch_remain < 73) { DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "invite: ch_info truncated at update"); tgi_fail(inv); return; } |
|
const uint8_t* upd_sig = ch; ch += 64; uint64_t upd_ts; memcpy(&upd_ts, ch, 8); ch += 8; ch_remain -= 72; |
|
|
|
uint8_t ui_len = *ch++; ch_remain--; |
|
char ch_userinfo[256] = ""; |
|
if (ui_len > 0 && ch_remain >= ui_len) { size_t ul = ui_len > 255 ? 255 : ui_len; memcpy(ch_userinfo, ch, ul); ch_userinfo[ul] = '\0'; } |
|
|
|
char ch_id[32]; snprintf(ch_id, sizeof(ch_id), "%llu", (unsigned long long)resp->group_id); |
|
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, |
|
"invite: INFO_RESP from=0x%016llx group=%016llx ch_name=%s ch_owner=%016llx inv_flags=%u join_ts=%llu upd_ts=%llu userinfo=%s", |
|
(unsigned long long)conn->peer_node_id, (unsigned long long)resp->group_id, |
|
ch_name, (unsigned long long)ch_owner, (unsigned)inv_flags, |
|
(unsigned long long)join_ts, (unsigned long long)upd_ts, |
|
ch_userinfo[0] ? ch_userinfo : "?"); |
|
|
|
/* ── верифицируем ch_sig ── */ |
|
{ |
|
uint8_t vmsg[1024]; size_t vlen = 0; |
|
vlen += snprintf((char*)vmsg + vlen, sizeof(vmsg) - vlen, "%s", ch_id) + 1; |
|
vlen += (size_t)snprintf((char*)vmsg + vlen, sizeof(vmsg) - vlen, "%s", ch_name) + 1; |
|
memcpy(vmsg + vlen, &ch_owner, 8); vlen += 8; |
|
memcpy(vmsg + vlen, ch_x25519, 32); vlen += 32; |
|
memcpy(vmsg + vlen, ch_ed_pub, 32); vlen += 32; |
|
if (sc_ed25519_verify(ch_ed_pub, vmsg, vlen, ch_sig) != SC_OK) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "invite: ch_sig VERIFY FAIL — rejecting group=%016llx from=0x%016llx", |
|
(unsigned long long)resp->group_id, (unsigned long long)conn->peer_node_id); |
|
tgi_fail(inv); return; |
|
} |
|
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "invite: ch_sig VERIFY OK"); |
|
} |
|
|
|
/* ── сохраняем канал и создаём группу ── */ |
|
{ sqlite3* ldb = inv->inst->topo_sqlite_db; |
|
if (ldb) topo_node_sqlite_channel_put(ldb, ch_id, ch_name, ch_owner, |
|
ch_x25519, NULL, ch_ed_pub, NULL, ch_sig); |
|
} |
|
chat_core_ensure_channel_ready(ch_id); |
|
|
|
/* ── верифицируем update_sig приглашающего ── */ |
|
{ |
|
const uint8_t* inv_ed = conn->peer_ed25519_pubkey; |
|
uint8_t ivmsg[256]; size_t ilen = 0; |
|
if (join_sig && (inv_flags & PEERS_FLAG_HAS_JOIN)) { memcpy(ivmsg + ilen, join_sig, 64); ilen += 64; } |
|
else { memset(ivmsg + ilen, 0, 64); ilen += 64; } |
|
memcpy(ivmsg + ilen, &upd_ts, 8); ilen += 8; |
|
{ const char* nm2 = ch_userinfo[0] ? ch_userinfo : ""; |
|
size_t nl2 = strlen(nm2); memcpy(ivmsg + ilen, nm2, nl2 + 1); ilen += nl2 + 1; } |
|
if (sc_ed25519_verify(inv_ed, ivmsg, ilen, upd_sig) != SC_OK) { |
|
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "invite: inviter_update_sig VERIFY FAIL node=0x%016llx", (unsigned long long)conn->peer_node_id); |
|
} else { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "invite: inviter_update_sig VERIFY OK"); |
|
} |
|
} |
|
|
|
if (inv->overall_timer) { uasync_cancel_timeout(inv->inst->ua, inv->overall_timer); inv->overall_timer = NULL; } |
|
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "invite: membership CONFIRMED by 0x%016llx name=\"%.*s\" ch=%s — full channel ready", |
|
(unsigned long long)inv->node_id, (int)nnl, nnl ? (const char*)resp->node_name : "?", ch_id); |
|
|
|
/* ── успех: переводим ncd-handle во владение узла, доставляем JOIN ── */ |
|
{ |
|
uint64_t node_id = inv->node_id, group_id = inv->group_id; |
|
tgi_cb_t cb = inv->cb; void* cb_arg = inv->cb_arg; |
|
if (inv->ncd_handle) node_conn_direct_set_callback(inv->ncd_handle, NULL, NULL); |
|
struct TOPO_GROUP* g = topo_groups_find(inv->inst->topo_groups, group_id); |
|
struct TOPO_GROUP_NODE* nq = g ? topo_node_find_by_id(g, node_id) : NULL; |
|
if (nq) nq->handle = inv->ncd_handle; |
|
inv->ncd_handle = NULL; /* не закрываем — соединение переходит узлу */ |
|
inv->temp_nq = NULL; /* не удаляем узел из группы */ |
|
tgi_cancel(inv); |
|
if (cb) cb(node_id, group_id, TGI_EVENT_JOIN, cb_arg); |
|
} |
|
} |
|
|
|
/* ═══════════ ncd-коллбэк (джойнер) ═══════════ */ |
|
|
|
static void tgi_ncd_cb(struct NODE_CONN_DIRECT* h, enum ncd_event ev, void* arg) { |
|
struct tgi_pending* p = (struct tgi_pending*)arg; |
|
if (!p || p->cancelled) return; |
|
if (ev == NCD_EVENT_UP) { |
|
if (p->state != TGI_CONNECTING) return; |
|
struct ETCP_CONN* conn = node_conn_direct_get_conn(h); |
|
if (!conn) { tgi_fail(p); return; } |
|
p->state = TGI_WAIT_INFO; |
|
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "invite: connected to 0x%016llx — sending membership check", (unsigned long long)p->node_id); |
|
struct TGI_INVITE_REQ req; |
|
req.cmd = ETCP_RT_ID_GROUP_INVITE; req.subcmd = TGI_SUBCMD_INFO_REQ; req.group_id = p->group_id; |
|
struct ll_entry* qe = queue_entry_new(0); |
|
if (!qe) { tgi_fail(p); return; } |
|
qe->dgram = u_malloc(sizeof(req)); |
|
if (!qe->dgram) { queue_entry_free(qe); tgi_fail(p); return; } |
|
memcpy(qe->dgram, &req, sizeof(req)); qe->len = sizeof(req); |
|
if (etcp_send(conn, qe) != 0) { u_free(qe->dgram); queue_entry_free(qe); tgi_fail(p); } |
|
} else if (ev == NCD_EVENT_TIMEOUT) { |
|
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "invite: ncd TIMEOUT connecting to 0x%016llx", (unsigned long long)p->node_id); |
|
tgi_fail(p); |
|
} |
|
} |
|
|
|
static void tgi_overall_timeout(void* arg) { |
|
struct tgi_pending* p = (struct tgi_pending*)arg; |
|
if (p->cancelled) return; |
|
p->overall_timer = NULL; |
|
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "invite: overall TIMEOUT to 0x%016llx — no response within %dms", |
|
(unsigned long long)p->node_id, TGI_TIMEOUT_MS); |
|
tgi_fail(p); |
|
} |
|
|
|
/* ═══════════ отмена / fail ═══════════ */ |
|
|
|
static void tgi_cancel(struct tgi_pending* p) { |
|
if (!p) return; |
|
p->cancelled = 1; |
|
if (p->overall_timer) { uasync_cancel_timeout(p->inst->ua, p->overall_timer); p->overall_timer = NULL; } |
|
if (p->ncd_handle) { node_conn_direct_force_close(p->ncd_handle); p->ncd_handle = NULL; } |
|
if (p->temp_nq) { |
|
struct TOPO_GROUP* g = topo_groups_find(p->inst->topo_groups, p->group_id); |
|
if (g) { queue_remove_data(g->nodes, &p->temp_nq->ll); topo_nodeq_free_group_fields(p->inst->topo_groups, p->temp_nq); queue_entry_free(&p->temp_nq->ll); } |
|
p->temp_nq = NULL; |
|
} |
|
tgi_unlink(p); |
|
u_free(p); |
|
} |
|
|
|
static void tgi_fail(struct tgi_pending* p) { |
|
if (!p || p->cancelled) return; |
|
uint64_t node_id = p->node_id, group_id = p->group_id; |
|
tgi_cb_t cb = p->cb; void* cb_arg = p->cb_arg; |
|
tgi_cancel(p); |
|
if (cb) cb(node_id, group_id, TGI_EVENT_TIMEOUT, cb_arg); |
|
} |
|
|
|
void topo_group_invite_cancel_all(struct UTUN_INSTANCE* inst) { |
|
struct tgi_pending* p = g_tgi_list; |
|
while (p) { |
|
struct tgi_pending* next = p->next; |
|
if (!inst || p->inst == inst) tgi_cancel(p); |
|
p = next; |
|
} |
|
} |
|
|
|
/* ═══════════ инициализация ═══════════ */ |
|
|
|
void topo_group_invite_init(struct UTUN_INSTANCE* inst) { |
|
if (!inst) return; |
|
etcp_bind(inst, ETCP_RT_ID_GROUP_INVITE, topo_group_invite_recv_handler); |
|
} |
|
|
|
/* ═══════════ входная точка джойнера ═══════════ */ |
|
|
|
int topo_group_invite_join(struct UTUN_INSTANCE* inst, uint64_t group_id, |
|
struct TOPO_NODE* ni, uint64_t node_id, |
|
tgi_cb_t cb, void* cb_arg) { |
|
if (!inst || !inst->topo_groups || !cb || !ni) return -1; |
|
|
|
/* 1. Найти или создать группу */ |
|
struct TOPO_GROUP* group = topo_groups_find(inst->topo_groups, group_id); |
|
if (!group) { |
|
char ch[32]; snprintf(ch, sizeof(ch), "%llu", (unsigned long long)group_id); |
|
group = topo_groups_create_group(inst->topo_groups, group_id, TOPO_GROUP_TYPE_CHAT, ch); |
|
if (!group) return -1; |
|
} |
|
|
|
uint64_t nid = node_id; |
|
if (!nid || nid == inst->node_id) return -1; |
|
if (!ni->v4_addrs && !ni->v6_addrs) return -1; |
|
if (!ni->public_key[0]) return -1; |
|
|
|
/* 2. Копировать ni в group_ni */ |
|
struct TOPO_GROUPS* groups = inst->topo_groups; |
|
struct TOPO_NODE* gni = u_calloc(1, sizeof(*gni)); if (!gni) return -1; |
|
gni->node_id = nid; gni->ver = 1; memcpy(gni->public_key, ni->public_key, SC_PUBKEY_SIZE); |
|
for (const struct TOPO_ADDR4* src = ni->v4_addrs; src; src = src->next) { |
|
struct TOPO_ADDR4* a4 = memory_pool_alloc(groups->v4_addr_pool); if (!a4) continue; |
|
memset(a4, 0, sizeof(*a4)); memcpy(a4->addr, src->addr, 4); a4->port = src->port; |
|
a4->type = TOPO_ADDR_NAT; a4->protocol = src->protocol; a4->next = gni->v4_addrs; gni->v4_addrs = a4; |
|
} |
|
for (const struct TOPO_ADDR6* src6 = ni->v6_addrs; src6; src6 = src6->next) { |
|
struct TOPO_ADDR6* a6 = memory_pool_alloc(groups->v6_addr_pool); if (!a6) continue; |
|
memset(a6, 0, sizeof(*a6)); memcpy(a6->addr, src6->addr, 16); a6->port = src6->port; |
|
a6->type = TOPO_ADDR_NAT; a6->protocol = src6->protocol; a6->next = gni->v6_addrs; gni->v6_addrs = a6; |
|
} |
|
if (!gni->v4_addrs && !gni->v6_addrs) { u_free(gni); return -1; } |
|
gni = topo_node_registry_store(groups, gni); if (!gni) return -1; |
|
|
|
/* 3. TOPO_GROUP_NODE + pending */ |
|
struct ll_entry* qe = queue_entry_new(sizeof(struct TOPO_GROUP_NODE)); |
|
if (!qe) { topo_node_registry_unref(groups, gni->node_id); return -1; } |
|
struct TOPO_GROUP_NODE* nq = (struct TOPO_GROUP_NODE*)qe; |
|
memset((uint8_t*)nq + sizeof(struct ll_entry), 0, sizeof(*nq) - sizeof(struct ll_entry)); nq->node_id = nid; |
|
|
|
struct tgi_pending* p = u_calloc(1, sizeof(*p)); |
|
if (!p) { queue_entry_free(qe); topo_node_registry_unref(groups, gni->node_id); return -1; } |
|
p->inst = inst; p->node_id = nid; p->group_id = group_id; p->state = TGI_CONNECTING; |
|
p->temp_nq = nq; p->cb = cb; p->cb_arg = cb_arg; |
|
p->next = g_tgi_list; g_tgi_list = p; |
|
|
|
queue_data_put_with_index(group->nodes, &nq->ll); |
|
|
|
/* 4. прямое подключение (ncd) */ |
|
if (node_conn_direct_open_node(inst, nid, tgi_ncd_cb, p, &p->ncd_handle, gni, NULL) == NCD_ERR) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "invite: ncd open failed for 0x%016llx", (unsigned long long)nid); |
|
tgi_fail(p); return -1; |
|
} |
|
|
|
/* 5. лог адресов */ |
|
{ |
|
char addrs[512] = ""; int aoff = 0; |
|
for (const struct TOPO_ADDR4* a = gni->v4_addrs; a; a = a->next) |
|
aoff += snprintf(addrs + aoff, sizeof(addrs) - (size_t)aoff, " %d.%d.%d.%d:%d(%s)", |
|
a->addr[0], a->addr[1], a->addr[2], a->addr[3], (int)a->port, |
|
a->protocol == TOPO_PROTO_UDP ? "UDP" : a->protocol == TOPO_PROTO_TCP ? "TCP" : "?"); |
|
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "invite: starting to 0x%016llx via%s timeout=%dms", |
|
(unsigned long long)nid, addrs, TGI_TIMEOUT_MS); |
|
} |
|
|
|
p->overall_timer = uasync_set_timeout(inst->ua, TGI_TIMEOUT_MS * 10, p, tgi_overall_timeout, "tgi_overall"); |
|
return 0; |
|
} |
|
|
|
/* ═══════════ инвайтера сторона (direct, без INVITE_INFO) ═══════════ */ |
|
|
|
struct tgi_conn_ctx { struct UTUN_INSTANCE* inst; uint64_t node_id, group_id; }; |
|
|
|
static void tgi_to_channel_cb(struct NODE_CONN_DIRECT* h, enum ncd_event ev, void* arg) { |
|
struct tgi_conn_ctx* c = (struct tgi_conn_ctx*)arg; |
|
if (ev == NCD_EVENT_UP) { |
|
struct TOPO_GROUP* g = topo_groups_find(c->inst->topo_groups, c->group_id); |
|
struct TOPO_GROUP_NODE* nq = g ? topo_node_find_by_id(g, c->node_id) : NULL; |
|
if (nq) nq->handle = h; |
|
node_conn_direct_set_callback(h, NULL, NULL); |
|
u_free(c); |
|
} else if (ev == NCD_EVENT_TIMEOUT) { |
|
u_free(c); |
|
} |
|
} |
|
|
|
int topo_group_invite_to_channel(struct UTUN_INSTANCE* inst, uint64_t group_id, |
|
struct TOPO_NODE* ni, uint64_t node_id) { |
|
if (!inst || !inst->topo_groups) return -1; |
|
struct TOPO_GROUP* group = topo_groups_find(inst->topo_groups, group_id); |
|
if (!group) { |
|
char ch[32]; snprintf(ch, sizeof(ch), "%llu", (unsigned long long)group_id); |
|
group = topo_groups_create_group(inst->topo_groups, group_id, TOPO_GROUP_TYPE_CHAT, ch); |
|
if (!group) return -1; |
|
} |
|
if (!node_id || node_id == inst->node_id) return -1; |
|
|
|
struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(group, node_id); |
|
if (!nq) { |
|
struct ll_entry* qe = queue_entry_new(sizeof(struct TOPO_GROUP_NODE)); |
|
if (!qe) return -1; |
|
nq = (struct TOPO_GROUP_NODE*)qe; |
|
memset((uint8_t*)nq + sizeof(struct ll_entry), 0, sizeof(*nq) - sizeof(struct ll_entry)); |
|
nq->node_id = node_id; |
|
queue_data_put_with_index(group->nodes, &nq->ll); |
|
} |
|
|
|
struct tgi_conn_ctx* c = u_calloc(1, sizeof(*c)); |
|
if (!c) return -1; |
|
c->inst = inst; c->node_id = node_id; c->group_id = group_id; |
|
|
|
struct NODE_CONN_DIRECT* h = NULL; |
|
int r = ni ? node_conn_direct_open_node(inst, node_id, tgi_to_channel_cb, c, &h, ni, NULL) |
|
: node_conn_direct_open(inst, node_id, tgi_to_channel_cb, c, &h, NULL); |
|
if (r == NCD_ERR || !h) { u_free(c); return -1; } |
|
return 0; |
|
}
|
|
|