chat_join: модуль join-протокола, invite-ссылки v2, синхронизация мемберов

This commit is contained in:
evgeny committed 2026-09-14 19:57:27 +03:00
1 parent a01a4ddba4
commit 75e39d2fee
41 files changed
+2206 -471

No files matched your search

+320
View File
@@ -0,0 +1,320 @@
/*
* chat_join.c — авторизация входа в канал через connection-узел и инвайтера.
* Маршрутизируемая (etcp_router) часть: регистрация ключа, форвард запроса,
* обработка на инвайтере (верификация + добавление мембера + подпись дерева).
* См. описание flow в chat_join.h.
*/
#include "chat_join.h"
#include "member_sync.h"
#include "../utun_instance.h"
#include "../routing_layer/topo_node_sqlite.h"
#include "../routing_layer/etcp_router.h"
#include "../routing_layer/route_crypto.h"
#include "../transport_layer/etcp_api.h"
#include "../transport_layer/etcp.h"
#include "../transport_layer/secure_channel.h"
#include "../ntp_time.h"
#include "../../lib/mem.h"
#include "../../lib/ll_queue.h"
#include "../../lib/u_async.h"
#include "../../lib/debug_config.h"
#include "../../lib/platform_compat.h"
#include <string.h>
#include <stdlib.h>
#define CJ_ID "chat_join"
/* ── Хранилище ключей на connection-узле ── */
struct join_key_entry {
struct join_key_entry* next;
uint64_t join_key;
uint64_t channel_id;
uint64_t inviter_id; /* кто пригласил (src KEY_REGISTER) */
uint64_t expiry_tb; /* timebase (0.1ms) истечения */
};
static struct join_key_entry* g_keys = NULL;
static uint64_t _now_tb(void) { return get_time_tb(); }
static void _keys_purge_expired(void) {
uint64_t now = _now_tb();
struct join_key_entry** p = &g_keys;
while (*p) {
if ((*p)->expiry_tb <= now) {
struct join_key_entry* r = *p;
*p = r->next;
DEBUG_DEBUG(DEBUG_CATEGORY_CHAT_SYNC, "%s: join-key expired key=%016llx ch=%llu inviter=%016llx",
CJ_ID, (unsigned long long)r->join_key, (unsigned long long)r->channel_id,
(unsigned long long)r->inviter_id);
u_free(r);
} else {
p = &(*p)->next;
}
}
}
static void _keys_put(uint64_t channel_id, uint64_t join_key, uint64_t inviter_id) {
_keys_purge_expired();
struct join_key_entry* e = u_malloc(sizeof(*e));
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: join-key alloc failed", CJ_ID); return; }
e->join_key = join_key; e->channel_id = channel_id; e->inviter_id = inviter_id;
e->expiry_tb = _now_tb() + (uint64_t)JOIN_KEY_TTL_SECONDS * 10000ULL;
e->next = g_keys;
g_keys = e;
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: join-key stored key=%016llx ch=%llu inviter=%016llx ttl=%ds",
CJ_ID, (unsigned long long)join_key, (unsigned long long)channel_id,
(unsigned long long)inviter_id, JOIN_KEY_TTL_SECONDS);
}
static struct join_key_entry* _keys_find(uint64_t channel_id, uint64_t join_key) {
_keys_purge_expired();
for (struct join_key_entry* e = g_keys; e; e = e->next)
if (e->channel_id == channel_id && e->join_key == join_key) return e;
return NULL;
}
static void _keys_free_all(void) {
struct join_key_entry* e = g_keys;
while (e) { struct join_key_entry* n = e->next; u_free(e); e = n; }
g_keys = NULL;
}
/* ── Сериализация member-блока ── */
int chat_join_serialize_member(const struct join_member_data* m,
uint8_t* out, size_t out_sz, size_t* out_len) {
if (!m || !out || !out_len || !m->x25519 || !m->ed25519) return -1;
size_t ul = strnlen(m->userinfo, 255);
size_t need = 8 + 32 + 32 + 64 + 8 + 64 + 8 + 1 + ul;
if (need > out_sz) return -1;
size_t off = 0;
memcpy(out + off, &m->node_id, 8); off += 8;
memcpy(out + off, m->x25519, 32); off += 32;
memcpy(out + off, m->ed25519, 32); off += 32;
if (m->join_sig) memcpy(out + off, m->join_sig, 64); else memset(out + off, 0, 64); off += 64;
memcpy(out + off, &m->join_ts, 8); off += 8;
if (m->update_sig) memcpy(out + off, m->update_sig, 64); else memset(out + off, 0, 64); off += 64;
memcpy(out + off, &m->update_ts, 8); off += 8;
out[off++] = (uint8_t)ul;
if (ul) memcpy(out + off, m->userinfo, ul);
off += ul;
*out_len = off;
return 0;
}
int chat_join_parse_member(const uint8_t* data, size_t len, struct join_member_data* m) {
if (!data || !m || len < 8 + 32 + 32 + 64 + 8 + 64 + 8 + 1) return -1;
memset(m, 0, sizeof(*m));
size_t off = 0;
memcpy(&m->node_id, data + off, 8); off += 8;
m->x25519 = data + off; off += 32;
m->ed25519 = data + off; off += 32;
m->join_sig = data + off; off += 64;
memcpy(&m->join_ts, data + off, 8); off += 8;
m->update_sig = data + off; off += 64;
memcpy(&m->update_ts, data + off, 8); off += 8;
uint8_t ul = data[off++];
if (off + ul > len) return -1;
if (ul > 255) ul = 255;
memcpy(m->userinfo, data + off, ul);
m->userinfo[ul] = '\0';
return 0;
}
/* ── Обработка на инвайтере: верификация + добавление мембера + подпись дерева ── */
void chat_join_process_request(struct UTUN_INSTANCE* inst, uint64_t group_id,
uint64_t from_node, uint64_t join_key,
const uint8_t* member_data, size_t member_len) {
struct join_member_data m;
if (chat_join_parse_member(member_data, member_len, &m) != 0) {
DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "%s: process_request parse failed ch=%llu from=%016llx", CJ_ID, (unsigned long long)group_id, (unsigned long long)from_node);
return;
}
char ch_id[32]; snprintf(ch_id, sizeof(ch_id), "%llu", (unsigned long long)group_id);
sqlite3* db = inst->topo_sqlite_db;
/* 1. привязка идентичности: node_id = derive(x25519) */
if (sc_derive_node_id_from_pubkey(m.x25519) != m.node_id) {
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: process_request node_id/x25519 mismatch (forgery) nid=0x%016llx ch=%s",
CJ_ID, (unsigned long long)m.node_id, ch_id);
return;
}
/* 2. верификация join_sig: sign(ch_x||ch_ed||node_id||x25519||join_ts) ключом ed25519 */
uint8_t ch_x[32], ch_ed[32];
if (!db || topo_node_sqlite_channel_get(db, ch_id, NULL, 0, NULL, ch_x, ch_ed, NULL) != 0) {
DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "%s: process_request no channel ch=%s", CJ_ID, ch_id);
return;
}
uint8_t vmsg[128];
int vlen = member_sync_build_join_msg(ch_x, ch_ed, m.node_id, m.x25519, m.join_ts, vmsg, (int)sizeof(vmsg));
if (vlen < 0 || sc_ed25519_verify(m.ed25519, vmsg, (size_t)vlen, m.join_sig) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: process_request invalid join_sig nid=0x%016llx ch=%s",
CJ_ID, (unsigned long long)m.node_id, ch_id);
return;
}
/* 3. добавить мембера + подписать его pubkey (дерево приглашений) */
uint8_t tree_sig[64];
if (member_sync_sign_pubkey(inst->my_ed25519_privkey, m.x25519, tree_sig) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: process_request sign_pubkey FAILED nid=0x%016llx", CJ_ID, (unsigned long long)m.node_id);
return;
}
int rc = member_sync_put(inst, ch_id, m.node_id, m.x25519, m.ed25519,
m.join_sig, m.join_ts, m.update_sig, m.update_ts,
m.userinfo, NULL, NULL, 0, inst->node_id, tree_sig);
if (rc < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: process_request member_sync_put FAILED ch=%s nid=0x%016llx rc=%d",
CJ_ID, ch_id, (unsigned long long)m.node_id, rc);
return;
}
if (rc & MS_APPLY_CHANGED)
member_sync_broadcast_one(inst, ch_id, m.node_id);
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: process_request accepted joiner=0x%016llx ch=%s via=0x%016llx key=%016llx — signed by me",
CJ_ID, (unsigned long long)m.node_id, ch_id, (unsigned long long)from_node, (unsigned long long)join_key);
}
/* ── etcp_router recv ── */
static void join_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
if (!entry || !conn || !entry->dgram || entry->len < ROUTER_SVC_PAYLOAD_OFF + 1) {
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); }
return;
}
struct UTUN_INSTANCE* inst = conn->instance;
if (!inst) { queue_dgram_free(entry); queue_entry_free(entry); return; }
const uint8_t* d = entry->dgram;
uint64_t from_node; memcpy(&from_node, d + ROUTER_SVC_SRC_OFF, 8);
uint64_t group_id; memcpy(&group_id, d + ROUTER_SVC_GROUP_OFF, 8);
const uint8_t* payload = d + ROUTER_SVC_PAYLOAD_OFF;
size_t plen = entry->len - ROUTER_SVC_PAYLOAD_OFF;
uint8_t subcmd = payload[0];
DEBUG_DEBUG(DEBUG_CATEGORY_CHAT_SYNC, "%s: recv subcmd=%02x from=%016llx ch=%llu len=%zu",
CJ_ID, subcmd, (unsigned long long)from_node, (unsigned long long)group_id, plen);
if (subcmd == JOIN_SUBCMD_KEY_REGISTER) {
/* payload: [subcmd:1][join_key:8] */
if (plen < 1 + 8) { queue_dgram_free(entry); queue_entry_free(entry); return; }
uint64_t join_key; memcpy(&join_key, payload + 1, 8);
_keys_put(group_id, join_key, from_node);
} else if (subcmd == JOIN_SUBCMD_REQUEST_FWD) {
/* payload: [subcmd:1][join_key:8][member...] */
if (plen < 1 + 8) { queue_dgram_free(entry); queue_entry_free(entry); return; }
uint64_t join_key; memcpy(&join_key, payload + 1, 8);
chat_join_process_request(inst, group_id, from_node, join_key, payload + 1 + 8, plen - 1 - 8);
} else {
DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "%s: unknown subcmd=%02x from=%016llx", CJ_ID, subcmd, (unsigned long long)from_node);
}
queue_dgram_free(entry); queue_entry_free(entry);
}
/* ── Жизненный цикл ── */
int chat_join_init(struct UTUN_INSTANCE* inst) {
if (!inst) return -1;
if (etcp_router_bind(inst, ETCP_RT_ID_JOIN, join_recv_cb) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: etcp_router_bind failed", CJ_ID);
return -1;
}
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: initialized", CJ_ID);
return 0;
}
void chat_join_destroy(struct UTUN_INSTANCE* inst) {
if (!inst) return;
etcp_router_unbind(inst, ETCP_RT_ID_JOIN);
_keys_free_all();
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: destroyed", CJ_ID);
}
/* ── Публичные операции ── */
void chat_join_register_key(struct UTUN_INSTANCE* inst, uint64_t channel_id,
uint64_t target_node_id, uint64_t join_key) {
if (!inst || channel_id == 0 || join_key == 0) return;
if (target_node_id == inst->node_id || target_node_id == 0) {
/* инвайтер и есть connection-узел — храним ключ локально */
_keys_put(channel_id, join_key, inst->node_id);
return;
}
uint8_t payload[1 + 8];
payload[0] = JOIN_SUBCMD_KEY_REGISTER;
memcpy(payload + 1, &join_key, 8);
struct ll_entry* entry = queue_entry_new(0);
if (!entry) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: register_key queue_entry_new failed", CJ_ID); return; }
entry->dgram = u_malloc(1 + sizeof(payload));
if (!entry->dgram) { queue_entry_free(entry); return; }
entry->dgram[0] = ETCP_RT_ID_JOIN;
memcpy(entry->dgram + 1, payload, sizeof(payload));
entry->len = 1 + (uint16_t)sizeof(payload);
int rc = etcp_route_send(inst, channel_id, target_node_id, entry, 1, ROUTE_CRYPTO_SIGN | ROUTE_CRYPTO_ENCRYPT);
if (rc != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: register_key etcp_route_send FAILED rc=%d ch=%llu → 0x%016llx",
CJ_ID, rc, (unsigned long long)channel_id, (unsigned long long)target_node_id);
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: join-key registered ch=%llu → connection=0x%016llx key=%016llx",
CJ_ID, (unsigned long long)channel_id, (unsigned long long)target_node_id, (unsigned long long)join_key);
}
uint64_t chat_join_lookup_inviter(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint64_t join_key) {
(void)inst;
struct join_key_entry* e = _keys_find(channel_id, join_key);
return e ? e->inviter_id : 0;
}
int chat_join_forward_request(struct UTUN_INSTANCE* inst, uint64_t channel_id,
uint64_t inviter_id, uint64_t join_key,
const struct join_member_data* m) {
if (!inst || !m || inviter_id == 0) return -1;
uint8_t mbuf[512]; size_t mlen = 0;
if (chat_join_serialize_member(m, mbuf, sizeof(mbuf), &mlen) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: forward serialize failed", CJ_ID);
return -1;
}
size_t plen = 1 + 8 + mlen;
uint8_t* payload = u_malloc(plen);
if (!payload) return -1;
payload[0] = JOIN_SUBCMD_REQUEST_FWD;
memcpy(payload + 1, &join_key, 8);
memcpy(payload + 1 + 8, mbuf, mlen);
struct ll_entry* entry = queue_entry_new(0);
if (!entry) { u_free(payload); return -1; }
entry->dgram = u_malloc(1 + plen);
if (!entry->dgram) { u_free(payload); queue_entry_free(entry); return -1; }
entry->dgram[0] = ETCP_RT_ID_JOIN;
memcpy(entry->dgram + 1, payload, plen);
entry->len = 1 + (uint16_t)plen;
u_free(payload);
int rc = etcp_route_send(inst, channel_id, inviter_id, entry, 1, ROUTE_CRYPTO_SIGN | ROUTE_CRYPTO_ENCRYPT);
if (rc != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: forward etcp_route_send FAILED rc=%d ch=%llu → 0x%016llx",
CJ_ID, rc, (unsigned long long)channel_id, (unsigned long long)inviter_id);
queue_dgram_free(entry); queue_entry_free(entry);
return -1;
}
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: join request forwarded ch=%llu → inviter=0x%016llx joiner=0x%016llx",
CJ_ID, (unsigned long long)channel_id, (unsigned long long)inviter_id, (unsigned long long)m->node_id);
return 0;
}