chat_join: модуль join-протокола, invite-ссылки v2, синхронизация мемберов
This commit is contained in:
1 parent
a01a4ddba4
commit
75e39d2fee
41 files changed
+2206
-471
No files matched your search
+301
-414
@@ -5,6 +5,7 @@
|
||||
#include "chat_setting.h"
|
||||
#include "chat_event.h"
|
||||
#include "chat_admin.h"
|
||||
#include "chat_join.h"
|
||||
#include "../routing_layer/topo_node_sqlite.h"
|
||||
#include "../routing_layer/topo_group.h"
|
||||
#include "../routing_layer/topo_group_invite.h"
|
||||
@@ -45,6 +46,13 @@ struct channel_cache {
|
||||
uint8_t synced;
|
||||
};
|
||||
|
||||
struct join_pending {
|
||||
uint64_t node_id; /* joiner node_id */
|
||||
char ch_id[64];
|
||||
struct ETCP_CONN* conn; /* прямое соединение к джойнеру */
|
||||
struct join_pending* next;
|
||||
};
|
||||
|
||||
struct chat_sync {
|
||||
struct UTUN_INSTANCE* inst;
|
||||
struct channel_cache* channels;
|
||||
@@ -59,8 +67,10 @@ struct chat_sync {
|
||||
uint8_t sync_scheduled;
|
||||
uint64_t pending_invite_ch_id;
|
||||
uint64_t pending_invite_node_id;
|
||||
uint8_t pending_invite_is_inviter; /* 1 = inviter sends CHANNEL_INVITE on conn_up, 0 = joiner sends CHANNEL_INFO_REQ */
|
||||
char pending_password[128]; /* пароль для CHANNEL_INFO_REQ */
|
||||
uint8_t pending_invite_is_inviter; /* 1 = inviter sends CHANNEL_INVITE on conn_up, 0 = joiner sends JOIN_INFO_REQ */
|
||||
char pending_password[128]; /* пароль для JOIN_INFO_REQ */
|
||||
uint64_t pending_join_key; /* join-ключ из invite-ссылки (джойнер) */
|
||||
struct join_pending* join_pending; /* connection-узел: ожидающие ready джойнеры */
|
||||
/* Pending incoming invites (для ask/deny) */
|
||||
uint64_t pending_inv_in_ch_id[4];
|
||||
uint64_t pending_inv_in_node_id[4];
|
||||
@@ -81,13 +91,12 @@ static const char* cs_msg_name(uint8_t type) {
|
||||
case CS_MSG_PUSH: return "PUSH";
|
||||
case CS_MSG_ACK_PUSH: return "ACK_PUSH";
|
||||
case CS_MSG_SYNC_DONE: return "SYNC_DONE";
|
||||
case CS_MSG_CHANNEL_INFO_REQ: return "CHANNEL_INFO_REQ";
|
||||
case CS_MSG_CHANNEL_INFO_RESP: return "CHANNEL_INFO_RESP";
|
||||
case CS_MSG_CHANNEL_JOIN: return "CHANNEL_JOIN";
|
||||
case CS_MSG_WELCOME: return "WELCOME";
|
||||
case CS_MSG_PEER_UPSERT: return "PEER_UPSERT";
|
||||
case CS_MSG_ERROR: return "ERROR";
|
||||
case CS_MSG_CHANNEL_INVITE: return "CHANNEL_INVITE";
|
||||
case CS_MSG_JOIN_INFO_REQ: return "JOIN_INFO_REQ";
|
||||
case CS_MSG_JOIN_INFO_RESP: return "JOIN_INFO_RESP";
|
||||
case CS_MSG_JOIN_REQUEST: return "JOIN_REQUEST";
|
||||
case CS_MSG_JOIN_READY: return "JOIN_READY";
|
||||
default: return "???";
|
||||
}
|
||||
}
|
||||
@@ -156,15 +165,13 @@ static struct channel_cache* cs_find(struct chat_sync* cs, const char* ch_id) {
|
||||
|
||||
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,
|
||||
static void cs_handle_join_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,
|
||||
static void cs_handle_join_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,
|
||||
static void cs_handle_join_request(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,
|
||||
static void cs_handle_join_ready(struct chat_sync* cs, uint64_t peer,
|
||||
const char* ch_id, const uint8_t* pl, size_t len);
|
||||
static void cs_handle_channel_invite(struct chat_sync* cs, uint64_t peer,
|
||||
const char* ch_id, const uint8_t* pl, size_t len);
|
||||
@@ -173,6 +180,10 @@ static int cs_send_channel_invite(struct chat_sync* cs, struct UTUN_INSTANCE* in
|
||||
const char* ch_name, uint64_t owner,
|
||||
const uint8_t* ch_x25519, const uint8_t* ch_ed,
|
||||
const uint8_t* ch_sig);
|
||||
static void cs_on_member_applied(const char* ch_id, uint64_t node_id,
|
||||
const uint8_t* x25519, uint64_t signed_by,
|
||||
const uint8_t* signature, void* arg);
|
||||
static void cs_pending_free_all(struct chat_sync* cs);
|
||||
/* ── Recv dispatcher ── */
|
||||
|
||||
static void chat_sync_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
@@ -193,9 +204,9 @@ static void chat_sync_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
uint8_t type = d[9];
|
||||
char ch_id[64];
|
||||
if (cs_group_id_to_ch_id(g_cs->inst, group_id, ch_id, sizeof(ch_id)) != 0) {
|
||||
/* CHANNEL_INVITE/INFO_REQ приходят для ещё не созданного у нас канала:
|
||||
/* CHANNEL_INVITE/JOIN_INFO_REQ приходят для ещё не созданного у нас канала:
|
||||
group_id == channel_id (numeric), поэтому выводим ch_id напрямую. */
|
||||
if (type == CS_MSG_CHANNEL_INVITE || type == CS_MSG_CHANNEL_INFO_REQ) {
|
||||
if (type == CS_MSG_CHANNEL_INVITE || type == CS_MSG_JOIN_INFO_REQ || type == CS_MSG_JOIN_REQUEST) {
|
||||
snprintf(ch_id, sizeof(ch_id), "%llu", (unsigned long long)group_id);
|
||||
} else {
|
||||
u_free(entry->dgram); queue_entry_free(entry); return;
|
||||
@@ -210,11 +221,10 @@ static void chat_sync_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
CS_ID, cs_msg_name(type), (unsigned long long)peer, ch_id, plen);
|
||||
|
||||
switch (type) {
|
||||
case CS_MSG_CHANNEL_INFO_REQ: DEBUG_TRACE(DEBUG_CATEGORY_MEMBER_SYNC, "%s: → handle INFO_REQ", CS_ID); cs_handle_channel_info_req(g_cs, peer, ch_id, pl, plen); break;
|
||||
case CS_MSG_CHANNEL_INFO_RESP:DEBUG_TRACE(DEBUG_CATEGORY_MEMBER_SYNC, "%s: → handle INFO_RESP", CS_ID); cs_handle_channel_info_resp(g_cs, peer, ch_id, pl, plen); break;
|
||||
case CS_MSG_CHANNEL_JOIN: DEBUG_TRACE(DEBUG_CATEGORY_MEMBER_SYNC, "%s: → handle JOIN", CS_ID); cs_handle_channel_join(g_cs, peer, ch_id, pl, plen); break;
|
||||
case CS_MSG_WELCOME: DEBUG_TRACE(DEBUG_CATEGORY_MEMBER_SYNC, "%s: → handle WELCOME", CS_ID); cs_handle_welcome(g_cs, peer, ch_id, pl, plen); break;
|
||||
case CS_MSG_PEER_UPSERT: DEBUG_TRACE(DEBUG_CATEGORY_MEMBER_SYNC, "%s: → handle PEER_UPSERT", CS_ID); cs_handle_peer_upsert(g_cs, peer, ch_id, pl, plen); break;
|
||||
case CS_MSG_JOIN_INFO_REQ: DEBUG_TRACE(DEBUG_CATEGORY_MEMBER_SYNC, "%s: → handle JOIN_INFO_REQ", CS_ID); cs_handle_join_info_req(g_cs, peer, ch_id, pl, plen); break;
|
||||
case CS_MSG_JOIN_INFO_RESP: DEBUG_TRACE(DEBUG_CATEGORY_MEMBER_SYNC, "%s: → handle JOIN_INFO_RESP", CS_ID); cs_handle_join_info_resp(g_cs, peer, ch_id, pl, plen); break;
|
||||
case CS_MSG_JOIN_REQUEST: DEBUG_TRACE(DEBUG_CATEGORY_MEMBER_SYNC, "%s: → handle JOIN_REQUEST", CS_ID); cs_handle_join_request(g_cs, peer, ch_id, pl, plen); break;
|
||||
case CS_MSG_JOIN_READY: DEBUG_TRACE(DEBUG_CATEGORY_MEMBER_SYNC, "%s: → handle JOIN_READY", CS_ID); cs_handle_join_ready(g_cs, peer, ch_id, pl, plen); break;
|
||||
case CS_MSG_ERROR: {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: RECV ERROR from=%016llx ch=%s", CS_ID, (unsigned long long)peer, ch_id);
|
||||
if (g_cs->info_req_timer) { uasync_cancel_timeout(g_cs->inst->ua, g_cs->info_req_timer); g_cs->info_req_timer = NULL; }
|
||||
@@ -448,22 +458,25 @@ static void _on_invite_sync_done(uint64_t peer, const char* ns, int result, void
|
||||
u_free(sa);
|
||||
}
|
||||
|
||||
/* ── send CHANNEL_INFO_REQ to start join handshake ── */
|
||||
/* ── send JOIN_INFO_REQ to start join handshake ── */
|
||||
|
||||
static void cs_start_channel_join(struct chat_sync* cs, uint64_t peer) {
|
||||
if (!cs || cs->info_req_timer) return;
|
||||
char ch_id_str[64];
|
||||
snprintf(ch_id_str, sizeof(ch_id_str), "%llu", (unsigned long long)cs->pending_invite_ch_id);
|
||||
/* payload: [join_key:8][pass_len:1][password] */
|
||||
size_t plen = strlen(cs->pending_password);
|
||||
uint8_t req[130];
|
||||
req[0] = CS_MSG_CHANNEL_INFO_REQ;
|
||||
req[1] = (uint8_t)(plen > 127 ? 127 : plen);
|
||||
if (plen > 0) memcpy(req + 2, cs->pending_password, plen > 127 ? 127 : plen);
|
||||
cs_send(cs, ch_id_str, peer, req, 2 + (plen > 127 ? 127 : (int)plen));
|
||||
uint8_t req[140];
|
||||
req[0] = CS_MSG_JOIN_INFO_REQ;
|
||||
memcpy(req + 1, &cs->pending_join_key, 8);
|
||||
req[9] = (uint8_t)(plen > 127 ? 127 : plen);
|
||||
if (plen > 0) memcpy(req + 10, cs->pending_password, plen > 127 ? 127 : plen);
|
||||
cs_send(cs, ch_id_str, peer, req, 10 + (plen > 127 ? 127 : (int)plen));
|
||||
cs->info_req_timer = uasync_set_timeout(cs->inst->ua,
|
||||
CS_INFO_REQ_TIMEOUT_MS * 10, cs, cs_info_req_timeout_cb, "cs_info_req");
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: sent CHANNEL_INFO_REQ ch=%llu peer=0x%016llx pass_len=%zu",
|
||||
CS_ID, (unsigned long long)cs->pending_invite_ch_id, (unsigned long long)peer, plen);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: sent JOIN_INFO_REQ ch=%llu peer=0x%016llx key=%016llx pass_len=%zu",
|
||||
CS_ID, (unsigned long long)cs->pending_invite_ch_id, (unsigned long long)peer,
|
||||
(unsigned long long)cs->pending_join_key, plen);
|
||||
}
|
||||
|
||||
/* ── Connection callbacks ── */
|
||||
@@ -633,7 +646,9 @@ int chat_sync_init(struct UTUN_INSTANCE* inst) {
|
||||
cs->join_timer = NULL;
|
||||
|
||||
member_sync_init(inst);
|
||||
member_sync_add_apply_cbk(cs_on_member_applied, cs);
|
||||
chat_admin_init(inst);
|
||||
chat_join_init(inst);
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: initialized", CS_ID);
|
||||
return 0;
|
||||
@@ -646,8 +661,11 @@ void chat_sync_destroy(struct UTUN_INSTANCE* inst) {
|
||||
if (cs->sync_timer) { uasync_cancel_timeout(inst->ua, cs->sync_timer); cs->sync_timer = NULL; }
|
||||
if (cs->invite_send_timer) { uasync_cancel_timeout(inst->ua, cs->invite_send_timer); cs->invite_send_timer = NULL; }
|
||||
if (cs->sync_scheduled) { cs->sync_scheduled = 0; cs_flush_sync(cs); }
|
||||
member_sync_remove_apply_cbk(cs_on_member_applied, cs);
|
||||
cs_pending_free_all(cs);
|
||||
member_sync_destroy(inst);
|
||||
chat_admin_destroy(inst);
|
||||
chat_join_destroy(inst);
|
||||
cs->initialized = 0; g_cs = NULL;
|
||||
|
||||
etcp_unbind(inst, ETCP_RT_ID_CHAT_SYNC);
|
||||
@@ -704,20 +722,6 @@ struct chat_invite {
|
||||
int addr_count;
|
||||
};
|
||||
|
||||
/* коллбэк invite-подключения (topo_group_invite): nq->handle уже установлен модулем,
|
||||
здесь только лог результата. Реальный join-протокол запускается через cs_on_conn_up
|
||||
(глобальный callback), сохранение в БД — через cs_handle_channel_info_resp. */
|
||||
static void cs_tgi_cb(uint64_t node_id, uint64_t group_id, int event, void* arg) {
|
||||
(void)arg; (void)group_id;
|
||||
if (event == TGI_EVENT_JOIN) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "channel invite: JOINED — connected to 0x%016llx, online now",
|
||||
(unsigned long long)node_id);
|
||||
} else {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "channel invite: TIMEOUT — could not connect to 0x%016llx",
|
||||
(unsigned long long)node_id);
|
||||
}
|
||||
}
|
||||
|
||||
struct cm_invite_wrap { struct chat_invite inv; };
|
||||
|
||||
static void cm_invite_trampoline(void* arg) {
|
||||
@@ -777,13 +781,13 @@ static void cm_invite_trampoline(void* arg) {
|
||||
u_free(ni); u_free(w); return;
|
||||
}
|
||||
|
||||
/* topo_group_invite_join сам создаст TOPO_GROUP, найдёт/создаст соединение (ncd),
|
||||
запустит INVITE_INFO. DB channel + sync создаются позже в cs_handle_channel_info_resp после верификации */
|
||||
/* topo_group_invite_to_channel: прямое ncd-подключение к connection-узлу БЕЗ INVITE_INFO.
|
||||
Реальный join-протокол (JOIN_INFO_REQ → … → JOIN_READY) запускается по cs_on_conn_up. */
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "channel invite: connecting to 0x%016llx via %d addresses",
|
||||
(unsigned long long)node_id, inv->addr_count);
|
||||
int r = topo_group_invite_join(inst, channel_id, ni, node_id, cs_tgi_cb, NULL);
|
||||
int r = topo_group_invite_to_channel(inst, channel_id, ni, node_id);
|
||||
if (r < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "channel invite: FAILED — topo_group_invite_join error=%d", r);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "channel invite: FAILED — topo_group_invite_to_channel error=%d", r);
|
||||
}
|
||||
|
||||
/* free caller-owned ni */
|
||||
@@ -797,7 +801,7 @@ 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,
|
||||
int addrs_data_len,
|
||||
const char* password) {
|
||||
const char* password, uint64_t join_key) {
|
||||
if (!g_cs || !g_cs->inst || !g_cs->inst->ua) {
|
||||
int r = -7;
|
||||
#ifdef __ANDROID__
|
||||
@@ -826,6 +830,7 @@ void chat_sync_connect_from_invite(uint64_t channel_id, uint64_t node_id,
|
||||
|
||||
g_cs->pending_invite_ch_id = channel_id;
|
||||
g_cs->pending_invite_node_id = node_id;
|
||||
g_cs->pending_join_key = join_key;
|
||||
if (password && password[0]) {
|
||||
size_t pl = strlen(password);
|
||||
if (pl >= sizeof(g_cs->pending_password)) pl = sizeof(g_cs->pending_password) - 1;
|
||||
@@ -933,481 +938,363 @@ static int _get_node_name(sqlite3* db, uint64_t node_id, char* out, size_t sz) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* ─── CHANNEL_INFO_REQ (0x09): joiner → inviter ─── */
|
||||
/* ─── JOIN_INFO_REQ (0x10): joiner → connection-узел ─── */
|
||||
|
||||
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) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_MEMBER_SYNC, "%s: INFO_REQ ch=%s from=%016llx len=%zu", CS_ID, ch_id, (unsigned long long)peer, len);
|
||||
static void cs_handle_join_info_req(struct chat_sync* cs, uint64_t peer,
|
||||
const char* ch_id, const uint8_t* pl, size_t len) {
|
||||
/* payload: [join_key:8][pass_len:1][password] */
|
||||
if (len < 9) { DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: JOIN_INFO_REQ too short len=%zu peer=%016llx", CS_ID, len, (unsigned long long)peer); return; }
|
||||
uint64_t join_key; memcpy(&join_key, pl, 8);
|
||||
uint64_t channel_id = strtoull(ch_id, NULL, 10);
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_MEMBER_SYNC, "%s: JOIN_INFO_REQ ch=%s from=%016llx key=%016llx len=%zu",
|
||||
CS_ID, ch_id, (unsigned long long)peer, (unsigned long long)join_key, len);
|
||||
|
||||
/* joiner responded to our CHANNEL_INVITE — cancel invite send timer */
|
||||
if (cs->invite_send_timer) { uasync_cancel_timeout(cs->inst->ua, cs->invite_send_timer); cs->invite_send_timer = NULL; }
|
||||
|
||||
/* Parse password from request (v2 format: type + pass_len + optional_password) */
|
||||
char req_password[128] = "";
|
||||
if (len >= 1) {
|
||||
uint8_t plen2 = pl[0];
|
||||
if (plen2 > 0 && plen2 <= len - 1) {
|
||||
if (plen2 > 127) plen2 = 127;
|
||||
memcpy(req_password, pl + 1, plen2); req_password[plen2] = '\0';
|
||||
}
|
||||
/* 1. валидация join-ключа (зарегистрирован инвайтером на connection-узле, TTL 10 мин) */
|
||||
uint64_t inviter = chat_join_lookup_inviter(cs->inst, channel_id, join_key);
|
||||
if (inviter == 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: JOIN_INFO_REQ invalid/expired join key ch=%s from=%016llx key=%016llx",
|
||||
CS_ID, ch_id, (unsigned long long)peer, (unsigned long long)join_key);
|
||||
uint8_t err[1] = { CS_MSG_ERROR }; cs_send(cs, ch_id, peer, err, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check password if server has one configured */
|
||||
/* 2. пароль (если настроен на сервере) */
|
||||
char req_password[128] = "";
|
||||
if (len >= 10) {
|
||||
uint8_t plen2 = pl[9];
|
||||
if (plen2 > 0 && plen2 <= len - 10) {
|
||||
if (plen2 > 127) plen2 = 127;
|
||||
memcpy(req_password, pl + 10, plen2); req_password[plen2] = '\0';
|
||||
}
|
||||
}
|
||||
{
|
||||
struct global_config *gcfg = &cs->inst->config->global;
|
||||
if (gcfg->chatserver_enabled && gcfg->chatserver_join_password[0]) {
|
||||
if (strcmp(req_password, gcfg->chatserver_join_password) != 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: CHANNEL_INFO_REQ wrong password ch=%s from=%016llx (got '%s', expected '%s')",
|
||||
CS_ID, ch_id, (unsigned long long)peer, req_password, gcfg->chatserver_join_password);
|
||||
uint8_t err[2] = { CS_MSG_ERROR, 0x01 }; /* 0x01 = wrong password */
|
||||
DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: JOIN_INFO_REQ wrong password ch=%s from=%016llx", CS_ID, ch_id, (unsigned long long)peer);
|
||||
uint8_t err[2] = { CS_MSG_ERROR, 0x01 };
|
||||
cs_send(cs, ch_id, peer, err, 2);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char name[128]; uint64_t owner; uint8_t x25519[32], ed_pub[32], ch_sig[64];
|
||||
/* 3. инфо канала */
|
||||
char name[128]; uint64_t owner; uint8_t ch_x25519[32], ch_ed[32], ch_sig[64];
|
||||
if (topo_node_sqlite_channel_get(cs->inst->topo_sqlite_db,
|
||||
ch_id, name, (int)sizeof(name), &owner, x25519, ed_pub, ch_sig) != 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: CHANNEL_INFO_REQ unknown ch=%s from=%016llx", CS_ID, ch_id, (unsigned long long)peer);
|
||||
ch_id, name, (int)sizeof(name), &owner, ch_x25519, ch_ed, ch_sig) != 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: JOIN_INFO_REQ unknown ch=%s from=%016llx", CS_ID, ch_id, (unsigned long long)peer);
|
||||
uint8_t err[1] = { CS_MSG_ERROR }; cs_send(cs, ch_id, peer, err, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
/* 4. мой мембер-рекорд (join_sig/update_sig + дерево приглашений) */
|
||||
uint64_t myid = cs->inst->node_id;
|
||||
if (memcmp(cs->inst->my_keys.public_key, x25519, 32) != 0)
|
||||
DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: CHANNEL_INFO_RESP pubkey MISMATCH: my=%016llx ch=%016llx — channel was created with DIFFERENT keys!",
|
||||
CS_ID, *(const uint64_t*)cs->inst->my_keys.public_key, *(const uint64_t*)x25519);
|
||||
/* load or create join_sig */
|
||||
uint8_t my_join_sig[64] = {0}; uint64_t my_join_ts = 0;
|
||||
{
|
||||
sqlite3* vdb = cs->inst->topo_sqlite_db;
|
||||
if (topo_node_sqlite_member_get_join(vdb, ch_id, myid, my_join_sig, &my_join_ts) != 0) {
|
||||
uint8_t join_msg[128];
|
||||
my_join_ts = (uint64_t)ntp_time_get_seconds(cs->inst);
|
||||
int jmlen = member_sync_build_join_msg(x25519, ed_pub, myid,
|
||||
cs->inst->my_keys.public_key, my_join_ts,
|
||||
int jmlen = member_sync_build_join_msg(ch_x25519, ch_ed, myid, cs->inst->my_keys.public_key, my_join_ts,
|
||||
join_msg, (int)sizeof(join_msg));
|
||||
if (jmlen >= 0) cs_ed25519_sign(cs->inst->my_ed25519_privkey, join_msg, (size_t)jmlen, my_join_sig);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_MEMBER_SYNC, "%s: CHANNEL_INFO_RESP created NEW join_sig: myid=%016llx ts=%llu",
|
||||
CS_ID, (unsigned long long)myid, (unsigned long long)my_join_ts);
|
||||
}
|
||||
}
|
||||
/* generate update_sig */
|
||||
uint8_t my_update_sig[64] = {0}; uint64_t my_update_ts;
|
||||
{
|
||||
char juser[256]; snprintf(juser, sizeof(juser), "{\"name\":\"%s\"}",
|
||||
cs->inst->name[0] ? cs->inst->name : "");
|
||||
char juser[256]; snprintf(juser, sizeof(juser), "{\"name\":\"%s\"}", cs->inst->name[0] ? cs->inst->name : "");
|
||||
my_update_ts = (uint64_t)ntp_time_get_seconds(cs->inst);
|
||||
uint8_t umsg[256];
|
||||
int ulen = member_sync_build_update_msg(my_join_sig, my_update_ts, juser,
|
||||
umsg, (int)sizeof(umsg));
|
||||
int ulen = member_sync_build_update_msg(my_join_sig, my_update_ts, juser, umsg, (int)sizeof(umsg));
|
||||
if (ulen >= 0) cs_ed25519_sign(cs->inst->my_ed25519_privkey, umsg, (size_t)ulen, my_update_sig);
|
||||
}
|
||||
uint64_t my_signed_by = 0; uint8_t my_signature[64] = {0};
|
||||
topo_node_sqlite_member_get_sign(cs->inst->topo_sqlite_db, ch_id, myid, &my_signed_by, my_signature);
|
||||
|
||||
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;
|
||||
/* 5. JOIN_INFO_RESP: инфо канала + мой мембер-рекорд */
|
||||
char juser[256]; snprintf(juser, sizeof(juser), "{\"name\":\"%s\"}", cs->inst->name[0] ? cs->inst->name : "");
|
||||
uint8_t il = (uint8_t)strlen(juser);
|
||||
|
||||
uint8_t buf[1536]; size_t boff = 0;
|
||||
buf[boff++] = CS_MSG_JOIN_INFO_RESP;
|
||||
uint8_t cnl = (uint8_t)strlen(name); buf[boff++] = cnl; memcpy(buf + boff, name, cnl); boff += cnl;
|
||||
memcpy(buf + boff, &owner, 8); boff += 8;
|
||||
memcpy(buf + boff, x25519, 32); boff += 32;
|
||||
memcpy(buf + boff, ed_pub, 32); boff += 32;
|
||||
memcpy(buf + boff, ch_x25519, 32); boff += 32;
|
||||
memcpy(buf + boff, ch_ed, 32); boff += 32;
|
||||
memcpy(buf + boff, ch_sig, 64); boff += 64;
|
||||
uint8_t inv_flags = (my_join_sig[0] || my_join_ts) ? PEERS_FLAG_HAS_JOIN : 0;
|
||||
buf[boff++] = inv_flags;
|
||||
if (inv_flags & PEERS_FLAG_HAS_JOIN) {
|
||||
memcpy(buf + boff, my_join_sig, 64); boff += 64;
|
||||
memcpy(buf + boff, &my_join_ts, 8); boff += 8;
|
||||
}
|
||||
memcpy(buf + boff, &myid, 8); boff += 8;
|
||||
memcpy(buf + boff, cs->inst->my_keys.public_key, 32); boff += 32;
|
||||
memcpy(buf + boff, cs->inst->my_ed25519_pubkey, 32); boff += 32;
|
||||
memcpy(buf + boff, my_join_sig, 64); boff += 64;
|
||||
memcpy(buf + boff, &my_join_ts, 8); boff += 8;
|
||||
memcpy(buf + boff, my_update_sig, 64); boff += 64;
|
||||
memcpy(buf + boff, &my_update_ts, 8); boff += 8;
|
||||
char juser[256]; snprintf(juser, sizeof(juser), "{\"name\":\"%s\"}",
|
||||
cs->inst->name[0] ? cs->inst->name : "");
|
||||
uint8_t il = (uint8_t)strlen(juser);
|
||||
buf[boff++] = il; memcpy(buf + boff, juser, il); boff += il;
|
||||
memcpy(buf + boff, &my_signed_by, 8); boff += 8;
|
||||
memcpy(buf + boff, my_signature, 64); boff += 64;
|
||||
cs_send(cs, ch_id, peer, buf, boff);
|
||||
}
|
||||
|
||||
/* ─── CHANNEL_INFO_RESP (0x0A): inviter → joiner ─── */
|
||||
/* ─── JOIN_INFO_RESP (0x11): connection-узел → 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) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_MEMBER_SYNC, "%s: INFO_RESP ch=%s from=%016llx len=%zu", CS_ID, ch_id, (unsigned long long)peer, len);
|
||||
static void cs_handle_join_info_resp(struct chat_sync* cs, uint64_t peer,
|
||||
const char* ch_id, const uint8_t* pl, size_t len) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_MEMBER_SYNC, "%s: JOIN_INFO_RESP ch=%s from=%016llx len=%zu", CS_ID, ch_id, (unsigned long long)peer, len);
|
||||
if (cs->info_req_timer) { uasync_cancel_timeout(cs->inst->ua, cs->info_req_timer); cs->info_req_timer = NULL; }
|
||||
if (len < 1) { DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: CHANNEL_INFO_RESP too short len=%zu peer=%016llx", CS_ID, len, (unsigned long long)peer); return; }
|
||||
uint8_t nl = pl[0];
|
||||
if (1 + nl + 8 + 32 + 32 + 64 + 1 + 64 + 8 + 1 > len) { DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: CHANNEL_INFO_RESP truncated len=%zu", CS_ID, 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;
|
||||
const uint8_t* x25519 = p; p += 32;
|
||||
const uint8_t* ed_pub = p; p += 32;
|
||||
const uint8_t* ch_sig = p; p += 64;
|
||||
uint8_t inv_flags = *p++;
|
||||
const uint8_t* inviter_join_sig = NULL; uint64_t inviter_join_ts = 0;
|
||||
if (inv_flags & PEERS_FLAG_HAS_JOIN) {
|
||||
if (p + 72 > pl + len) { DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: CHANNEL_INFO_RESP truncated at inviter_join_sig peer=%016llx", CS_ID, (unsigned long long)peer); return; }
|
||||
inviter_join_sig = p; p += 64; memcpy(&inviter_join_ts, p, 8); p += 8;
|
||||
if (len < 1) { DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: JOIN_INFO_RESP too short len=%zu", CS_ID, len); return; }
|
||||
const uint8_t* p = pl;
|
||||
uint8_t cnl = *p++;
|
||||
if (cnl + 8 + 32 + 32 + 64 + 8 + 32 + 32 + 64 + 8 + 64 + 8 + 1 + 8 + 64 > len) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: JOIN_INFO_RESP truncated len=%zu", CS_ID, len); return;
|
||||
}
|
||||
const uint8_t* inviter_update_sig = p; p += 64; uint64_t inviter_update_ts;
|
||||
memcpy(&inviter_update_ts, p, 8); p += 8;
|
||||
uint8_t inv_userinfo_len = *p++;
|
||||
char inv_userinfo[128] = "";
|
||||
if (p + inv_userinfo_len <= pl + len) { memcpy(inv_userinfo, p, inv_userinfo_len); inv_userinfo[inv_userinfo_len] = '\0'; }
|
||||
char name[128]; size_t cn = cnl > 127 ? 127 : cnl; memcpy(name, p, cn); name[cn] = '\0'; p += cnl;
|
||||
uint64_t owner; memcpy(&owner, p, 8); p += 8;
|
||||
const uint8_t* ch_x25519 = p; p += 32;
|
||||
const uint8_t* ch_ed = p; p += 32;
|
||||
const uint8_t* ch_sig = p; p += 64;
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_MEMBER_SYNC, "%s: CHANNEL_INFO_RESP from peer=%016llx owner=%016llx ch_x25519=%016llx ch_ed=%016llx name=%s",
|
||||
uint64_t c_node_id; memcpy(&c_node_id, p, 8); p += 8;
|
||||
const uint8_t* c_x25519 = p; p += 32;
|
||||
const uint8_t* c_ed = p; p += 32;
|
||||
const uint8_t* c_join_sig = p; p += 64;
|
||||
uint64_t c_join_ts; memcpy(&c_join_ts, p, 8); p += 8;
|
||||
const uint8_t* c_update_sig = p; p += 64;
|
||||
uint64_t c_update_ts; memcpy(&c_update_ts, p, 8); p += 8;
|
||||
uint8_t c_uil = *p++;
|
||||
char c_userinfo[256] = "";
|
||||
if (c_uil && p + c_uil <= pl + len) { size_t u = c_uil > 255 ? 255 : c_uil; memcpy(c_userinfo, p, u); c_userinfo[u] = '\0'; p += c_uil; }
|
||||
uint64_t c_signed_by; memcpy(&c_signed_by, p, 8); p += 8;
|
||||
const uint8_t* c_signature = p; p += 64;
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_MEMBER_SYNC, "%s: JOIN_INFO_RESP from=%016llx owner=%016llx ch_x=%016llx ch_ed=%016llx name=%s conn_node=%016llx",
|
||||
CS_ID, (unsigned long long)peer, (unsigned long long)owner,
|
||||
*(const uint64_t*)x25519, *(const uint64_t*)ed_pub, name);
|
||||
*(const uint64_t*)ch_x25519, *(const uint64_t*)ch_ed, name, (unsigned long long)c_node_id);
|
||||
|
||||
/* 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_MEMBER_SYNC, "%s: CHANNEL_INFO_RESP invalid ch_sig ch=%s", CS_ID, ch_id);
|
||||
memcpy(vmsg + vlen, ch_x25519, 32); vlen += 32;
|
||||
memcpy(vmsg + vlen, ch_ed, 32); vlen += 32;
|
||||
if (cs_ed25519_verify(ch_ed, vmsg, vlen, ch_sig) != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: JOIN_INFO_RESP invalid ch_sig ch=%s", CS_ID, ch_id);
|
||||
return;
|
||||
}
|
||||
|
||||
/* save channel to local DB */
|
||||
int cprc = topo_node_sqlite_channel_put(cs->inst->topo_sqlite_db,
|
||||
ch_id, name, owner, x25519, NULL, ed_pub, NULL, ch_sig);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: INFO_RESP channel_put rc=%d ch=%s owner=%016llx ch_x25519=%016llx",
|
||||
CS_ID, cprc, ch_id, (unsigned long long)owner, *(const uint64_t*)x25519);
|
||||
/* save channel */
|
||||
topo_node_sqlite_channel_put(cs->inst->topo_sqlite_db, ch_id, name, owner, ch_x25519, NULL, ch_ed, NULL, ch_sig);
|
||||
chat_core_ensure_channel_ready(ch_id);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: channel ready for sync ch=%s, db_sync will pick up via periodic check or active conn",
|
||||
CS_ID, ch_id);
|
||||
|
||||
/* verify inviter's join_sig AND save node info using ETCP-authenticated keys */
|
||||
{ struct ETCP_CONN* inv_conn = cs_find_conn_for_node(cs->inst, peer);
|
||||
if (!inv_conn) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: CHANNEL_INFO_RESP no ETCP conn for inviter peer=%016llx", CS_ID, (unsigned long long)peer);
|
||||
return;
|
||||
}
|
||||
const uint8_t* inv_x25519 = inv_conn->crypto_ctx.peer_public_key;
|
||||
const uint8_t* inv_ed = inv_conn->peer_ed25519_pubkey;
|
||||
static const uint8_t zero_sig[64] = {0};
|
||||
const uint8_t* jsig = inviter_join_sig ? inviter_join_sig : zero_sig;
|
||||
uint8_t ivmsg[256];
|
||||
int ilen = member_sync_build_update_msg(jsig, inviter_update_ts,
|
||||
inv_userinfo[0] ? inv_userinfo : "",
|
||||
ivmsg, (int)sizeof(ivmsg));
|
||||
if (ilen < 0 || cs_ed25519_verify(inv_ed, ivmsg, (size_t)ilen, inviter_update_sig) != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: CHANNEL_INFO_RESP invalid inviter_update_sig peer=%016llx ts=%llu",
|
||||
CS_ID, (unsigned long long)peer, (unsigned long long)inviter_update_ts);
|
||||
}
|
||||
sqlite3* vdb = cs->inst->topo_sqlite_db;
|
||||
int rc = topo_node_sqlite_member_block_put(vdb, ch_id, peer, inviter_join_sig, inviter_join_ts, inviter_update_sig, inviter_update_ts, inv_x25519, inv_ed, inv_userinfo, inviter_join_sig);
|
||||
if (rc != 0) DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: member_put(inviter) FAILED ch=%s peer=%016llx rc=%d", CS_ID, ch_id, (unsigned long long)peer, rc);
|
||||
char inv_name[128]; json_flat_get(inv_userinfo, "name", inv_name, sizeof(inv_name));
|
||||
topo_node_sqlite_node_update_verified(vdb, peer, inv_name, inv_x25519, inv_ed, inviter_join_ts, ntp_time_get_seconds(cs->inst));
|
||||
}
|
||||
/* добавить connection-узел в мемберы (дерево приглашений из INFO_RESP) */
|
||||
sqlite3* vdb = cs->inst->topo_sqlite_db;
|
||||
topo_node_sqlite_member_block_put(vdb, ch_id, c_node_id, c_join_sig, c_join_ts,
|
||||
c_update_sig, c_update_ts, c_x25519, c_ed,
|
||||
c_userinfo, NULL, c_signed_by, c_signature);
|
||||
{ char cn_name[128]; json_flat_get(c_userinfo, "name", cn_name, sizeof(cn_name));
|
||||
topo_node_sqlite_node_update_verified(vdb, c_node_id, cn_name, c_x25519, c_ed, c_join_ts, ntp_time_get_seconds(cs->inst)); }
|
||||
|
||||
/* generate our own join_sig */
|
||||
/* построить собственный join_sig */
|
||||
uint64_t myid = cs->inst->node_id;
|
||||
uint8_t my_x25519[32];
|
||||
memcpy(my_x25519, cs->inst->my_keys.public_key, 32);
|
||||
const uint8_t* my_x25519 = cs->inst->my_keys.public_key;
|
||||
uint8_t join_sig[64]; uint64_t join_ts;
|
||||
{
|
||||
uint8_t msg[128];
|
||||
join_ts = (uint64_t)ntp_time_get_seconds(cs->inst);
|
||||
int mlen = member_sync_build_join_msg(x25519, ed_pub, myid, my_x25519, join_ts,
|
||||
msg, (int)sizeof(msg));
|
||||
if (mlen >= 0) cs_ed25519_sign(cs->inst->my_ed25519_privkey, msg, (size_t)mlen, join_sig);
|
||||
int mlen = member_sync_build_join_msg(ch_x25519, ch_ed, myid, my_x25519, join_ts, msg, (int)sizeof(msg));
|
||||
if (mlen < 0) { memset(join_sig, 0, 64); DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: JOIN_INFO_RESP build_join_msg failed", CS_ID); return; }
|
||||
cs_ed25519_sign(cs->inst->my_ed25519_privkey, msg, (size_t)mlen, join_sig);
|
||||
}
|
||||
uint8_t my_update_sig[64] = {0}; uint64_t my_update_ts;
|
||||
{
|
||||
char juser[256]; snprintf(juser, sizeof(juser), "{\"name\":\"%s\"}", cs->inst->name[0] ? cs->inst->name : "");
|
||||
my_update_ts = (uint64_t)ntp_time_get_seconds(cs->inst);
|
||||
uint8_t umsg[256];
|
||||
int ulen = member_sync_build_update_msg(join_sig, my_update_ts, juser, umsg, (int)sizeof(umsg));
|
||||
if (ulen >= 0) cs_ed25519_sign(cs->inst->my_ed25519_privkey, umsg, (size_t)ulen, my_update_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;
|
||||
memcpy(jbuf + joff, &join_ts, 8); joff += 8;
|
||||
char juser2[256]; snprintf(juser2, sizeof(juser2), "{\"name\":\"%s\"}",
|
||||
cs->inst->name[0] ? cs->inst->name : "");
|
||||
uint8_t nml = (uint8_t)strlen(juser2);
|
||||
jbuf[joff++] = nml; memcpy(jbuf + joff, juser2, nml); joff += nml;
|
||||
/* send JOIN_REQUEST: [join_key:8][member...] */
|
||||
struct join_member_data jm;
|
||||
memset(&jm, 0, sizeof(jm));
|
||||
jm.node_id = myid; jm.x25519 = my_x25519; jm.ed25519 = cs->inst->my_ed25519_pubkey;
|
||||
jm.join_sig = join_sig; jm.join_ts = join_ts;
|
||||
jm.update_sig = my_update_sig; jm.update_ts = my_update_ts;
|
||||
snprintf(jm.userinfo, sizeof(jm.userinfo), "{\"name\":\"%s\"}", cs->inst->name[0] ? cs->inst->name : "");
|
||||
|
||||
uint8_t jm_buf[512]; size_t jm_len = 0;
|
||||
if (chat_join_serialize_member(&jm, jm_buf, sizeof(jm_buf), &jm_len) != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: JOIN_REQUEST serialize failed", CS_ID);
|
||||
return;
|
||||
}
|
||||
uint8_t jbuf[520]; size_t joff = 0;
|
||||
jbuf[joff++] = CS_MSG_JOIN_REQUEST;
|
||||
memcpy(jbuf + joff, &cs->pending_join_key, 8); joff += 8;
|
||||
memcpy(jbuf + joff, jm_buf, jm_len); joff += jm_len;
|
||||
int jrc = cs_send(cs, ch_id, peer, jbuf, joff);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: JOIN sent myid=%016llx x25519=%016llx join_ts=%llu ch=%s to=0x%016llx rc=%d",
|
||||
CS_ID, (unsigned long long)myid, *(const uint64_t*)my_x25519, (unsigned long long)join_ts, ch_id, (unsigned long long)peer, jrc);
|
||||
cs->join_timer = uasync_set_timeout(cs->inst->ua,
|
||||
CS_JOIN_TIMEOUT_MS * 10, cs, cs_join_timeout_cb, "cs_join");
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: JOIN_REQUEST sent myid=%016llx key=%016llx ch=%s to=0x%016llx rc=%d",
|
||||
CS_ID, (unsigned long long)myid, (unsigned long long)cs->pending_join_key, ch_id, (unsigned long long)peer, jrc);
|
||||
cs->join_timer = uasync_set_timeout(cs->inst->ua, CS_JOIN_TIMEOUT_MS * 10, cs, cs_join_timeout_cb, "cs_join");
|
||||
}
|
||||
|
||||
/* ─── CHANNEL_JOIN (0x0B): joiner → inviter ─── */
|
||||
/* ─── pending-джойнеры (connection-узел): node_id → прямое соединение ─── */
|
||||
|
||||
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 + 8 + 1) { DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: CHANNEL_JOIN too short len=%zu peer=%016llx", CS_ID, len, (unsigned long long)peer); return; }
|
||||
const uint8_t* p = pl;
|
||||
uint64_t node_id; memcpy(&node_id, p, 8); p += 8;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_MEMBER_SYNC, "%s: JOIN ch=%s node=%016llx from=%016llx", CS_ID, ch_id, (unsigned long long)node_id, (unsigned long long)peer);
|
||||
const uint8_t* x25519 = p; p += 32;
|
||||
const uint8_t* ed_pub = p; p += 32;
|
||||
const uint8_t* join_sig = p; p += 64;
|
||||
uint64_t join_ts; memcpy(&join_ts, p, 8); p += 8;
|
||||
uint8_t userinfo_len = *p++;
|
||||
char joiner_userinfo[128] = "";
|
||||
if (p + userinfo_len <= pl + len) { memcpy(joiner_userinfo, p, userinfo_len); joiner_userinfo[userinfo_len] = '\0'; p += userinfo_len; }
|
||||
static struct join_pending* cs_pending_find(struct chat_sync* cs, uint64_t node_id, const char* ch_id) {
|
||||
for (struct join_pending* jp = cs->join_pending; jp; jp = jp->next)
|
||||
if (jp->node_id == node_id && strcmp(jp->ch_id, ch_id) == 0) return jp;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* verify join_sig */
|
||||
uint8_t ch_x25519[32], ch_ed25519[32];
|
||||
if (topo_node_sqlite_channel_get(cs->inst->topo_sqlite_db, ch_id,
|
||||
NULL, 0, NULL, ch_x25519, ch_ed25519, NULL) != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: JOIN channel not in DB ch=%s", CS_ID, ch_id);
|
||||
return;
|
||||
}
|
||||
uint8_t vmsg[128];
|
||||
int vlen = member_sync_build_join_msg(ch_x25519, ch_ed25519, node_id, x25519, join_ts,
|
||||
vmsg, (int)sizeof(vmsg));
|
||||
if (vlen < 0 || cs_ed25519_verify(ed_pub, vmsg, (size_t)vlen, join_sig) != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: JOIN invalid sig node=0x%016llx ch=%s — signed(node=%016llx x25519=%016llx ed=%016llx ts=%llu)", CS_ID,
|
||||
(unsigned long long)node_id, ch_id,
|
||||
(unsigned long long)node_id, *(const uint64_t*)x25519, *(const uint64_t*)ed_pub, (unsigned long long)join_ts);
|
||||
return;
|
||||
}
|
||||
static void cs_pending_add(struct chat_sync* cs, uint64_t node_id, const char* ch_id, struct ETCP_CONN* conn) {
|
||||
if (cs_pending_find(cs, node_id, ch_id)) return;
|
||||
struct join_pending* jp = u_malloc(sizeof(*jp));
|
||||
if (!jp) return;
|
||||
jp->node_id = node_id; snprintf(jp->ch_id, sizeof(jp->ch_id), "%s", ch_id); jp->conn = conn;
|
||||
jp->next = cs->join_pending; cs->join_pending = jp;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_MEMBER_SYNC, "%s: pending join added node=0x%016llx ch=%s", CS_ID, (unsigned long long)node_id, ch_id);
|
||||
}
|
||||
|
||||
sqlite3* db = cs->inst->topo_sqlite_db;
|
||||
int rc = topo_node_sqlite_member_block_put(db, ch_id, node_id, join_sig, join_ts, NULL, 0, x25519, ed_pub, joiner_userinfo, NULL);
|
||||
if (rc != 0) DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: member_put(joiner) FAILED ch=%s node=0x%016llx rc=%d", CS_ID, ch_id, (unsigned long long)node_id, rc);
|
||||
{ char j_name[128]; json_flat_get(joiner_userinfo, "name", j_name, sizeof(j_name));
|
||||
topo_node_sqlite_node_update_verified(db, node_id, j_name, x25519, ed_pub, join_ts, ntp_time_get_seconds(cs->inst)); }
|
||||
|
||||
/* 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);
|
||||
static void cs_pending_remove(struct chat_sync* cs, uint64_t node_id, const char* ch_id) {
|
||||
struct join_pending** p = &cs->join_pending;
|
||||
while (*p) {
|
||||
if ((*p)->node_id == node_id && strcmp((*p)->ch_id, ch_id) == 0) {
|
||||
struct join_pending* r = *p; *p = r->next; u_free(r);
|
||||
return;
|
||||
}
|
||||
p = &(*p)->next;
|
||||
}
|
||||
}
|
||||
|
||||
/* 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;
|
||||
memcpy(ubuf + uoff, &join_ts, 8); uoff += 8;
|
||||
ubuf[uoff++] = userinfo_len;
|
||||
memcpy(ubuf + uoff, joiner_userinfo, userinfo_len); uoff += userinfo_len;
|
||||
cs_propagate(cs, ch_id, peer, ubuf, uoff);
|
||||
}
|
||||
static void cs_pending_free_all(struct chat_sync* cs) {
|
||||
struct join_pending* jp = cs->join_pending;
|
||||
while (jp) { struct join_pending* n = jp->next; u_free(jp); jp = n; }
|
||||
cs->join_pending = NULL;
|
||||
}
|
||||
|
||||
/* add to channel cache — always refresh to include new peer */
|
||||
cs_refresh_channels(cs);
|
||||
/* ─── отправка JOIN_READY джойнеру + снятие pending (connection-узел) ─── */
|
||||
|
||||
{ uint8_t mevt[1 + 64 + 1 + CHAT_MEMBER_DISPLAY_SIZE]; uint8_t cl = (uint8_t)strlen(ch_id);
|
||||
mevt[0] = cl; memcpy(mevt + 1, ch_id, cl); mevt[1 + cl] = 1;
|
||||
if (chat_core_get_single_member(ch_id, node_id, mevt + 1 + cl + 1) == 0)
|
||||
chat_event_post(CHAT_EVT_MEMBER_UPDATED, mevt, 1 + cl + 1 + CHAT_MEMBER_DISPLAY_SIZE); }
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: JOIN calling cs_post_channel_online ch=%s", CS_ID, ch_id);
|
||||
cs_post_channel_online(cs, ch_id);
|
||||
|
||||
/* initiate member sync with the joiner (server side) */
|
||||
struct invite_sync_arg* sa = u_malloc(sizeof(struct invite_sync_arg));
|
||||
if (sa) { sa->node_id = node_id;
|
||||
strncpy(sa->ch_id, ch_id, sizeof(sa->ch_id) - 1); sa->ch_id[sizeof(sa->ch_id) - 1] = '\0';
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_MEMBER_SYNC, "%s: JOIN alloc sa=%p node=0x%016llx ch=%s", CS_ID, (void*)sa, (unsigned long long)sa->node_id, sa->ch_id);
|
||||
member_sync_start(cs->inst, node_id, ch_id, _on_invite_sync_done, sa); }
|
||||
else member_sync_start(cs->inst, node_id, ch_id, NULL, NULL);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: JOIN starting member_sync with joiner node=0x%016llx ch=%s done_cb=%s",
|
||||
CS_ID, (unsigned long long)node_id, ch_id, sa ? "yes" : "no");
|
||||
|
||||
/* if we're the inviter — joiner joined successfully, notify GUI */
|
||||
if (cs->pending_invite_ch_id != 0 && cs->pending_invite_is_inviter) {
|
||||
uint64_t ch_id_num = strtoull(ch_id, NULL, 10);
|
||||
uint8_t evt[20]; memcpy(evt, &node_id, 8); int r = 0; memcpy(evt + 8, &r, 4);
|
||||
memcpy(evt + 12, &ch_id_num, 8);
|
||||
chat_event_post(CHAT_EVT_CONNECT_RESULT, evt, 20);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: JOIN posted CHAT_EVT_CONNECT_RESULT to GUI node=0x%016llx ch=%s",
|
||||
CS_ID, (unsigned long long)node_id, ch_id);
|
||||
cs->pending_invite_node_id = 0; cs->pending_invite_ch_id = 0;
|
||||
cs->pending_invite_is_inviter = 0;
|
||||
if (cs->invite_send_timer) { uasync_cancel_timeout(cs->inst->ua, cs->invite_send_timer); cs->invite_send_timer = NULL; }
|
||||
}
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: JOIN accepted node=0x%016llx ch=%s",
|
||||
static void cs_send_join_ready(struct chat_sync* cs, const char* ch_id, uint64_t node_id) {
|
||||
uint64_t group_id = strtoull(ch_id, NULL, 10);
|
||||
uint8_t rbuf[9];
|
||||
rbuf[0] = CS_MSG_JOIN_READY;
|
||||
memcpy(rbuf + 1, &group_id, 8);
|
||||
cs_send(cs, ch_id, node_id, rbuf, 9);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: JOIN_READY sent to 0x%016llx ch=%s",
|
||||
CS_ID, (unsigned long long)node_id, ch_id);
|
||||
cs_pending_remove(cs, node_id, ch_id);
|
||||
}
|
||||
|
||||
/* ─── WELCOME (0x0C): inviter → joiner with full peer list ─── */
|
||||
/* ─── коллбэк member_sync: пришёл рекорд мембера → отправить JOIN_READY джойнеру ─── */
|
||||
|
||||
static void cs_handle_welcome(struct chat_sync* cs, uint64_t peer,
|
||||
const char* ch_id, const uint8_t* pl, size_t len) {
|
||||
if (cs->join_timer) { uasync_cancel_timeout(cs->inst->ua, cs->join_timer); cs->join_timer = NULL; }
|
||||
(void)peer;
|
||||
if (len < 2) { DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: WELCOME too short len=%zu peer=%016llx", CS_ID, len, (unsigned long long)peer); return; }
|
||||
sqlite3* db = cs->inst->topo_sqlite_db;
|
||||
static void cs_on_member_applied(const char* ch_id, uint64_t node_id,
|
||||
const uint8_t* x25519, uint64_t signed_by,
|
||||
const uint8_t* signature, void* arg) {
|
||||
(void)x25519; (void)signed_by; (void)signature;
|
||||
struct chat_sync* cs = (struct chat_sync*)arg;
|
||||
if (!cs || !cs->initialized) return;
|
||||
struct join_pending* jp = cs_pending_find(cs, node_id, ch_id);
|
||||
if (!jp || !jp->conn) return;
|
||||
cs_send_join_ready(cs, ch_id, node_id);
|
||||
}
|
||||
|
||||
const uint8_t* p = pl;
|
||||
uint16_t pc; memcpy(&pc, p, 2); p += 2;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_MEMBER_SYNC, "%s: WELCOME ch=%s from=%016llx peers=%u", CS_ID, ch_id, (unsigned long long)peer, pc);
|
||||
for (uint16_t i = 0; i < pc; i++) {
|
||||
if ((size_t)(p - pl) + 8 + 32 + 32 + 1 + 64 + 8 + 1 + 1 + 64 > len) { DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: WELCOME peer[%u] truncated at %td len=%zu", CS_ID, i, p - pl, len); break; } /* min: id+x25519+ed+flags+update_sig(64+8)+nl+atl+adm_tags_sig(64) */
|
||||
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;
|
||||
uint8_t flags = *p++;
|
||||
const uint8_t* join_sig = NULL; uint64_t join_ts = 0;
|
||||
if (flags & PEERS_FLAG_HAS_JOIN) {
|
||||
if (p + 64 + 8 > pl + len) { DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: WELCOME join_sig truncated node=0x%016llx i=%u", CS_ID, (unsigned long long)node_id, i); break; }
|
||||
join_sig = p; p += 64;
|
||||
memcpy(&join_ts, p, 8); p += 8;
|
||||
}
|
||||
const uint8_t* update_sig = p; p += 64;
|
||||
uint64_t update_ts; memcpy(&update_ts, p, 8); p += 8;
|
||||
(void)update_sig; (void)update_ts;
|
||||
uint8_t nl = *p++;
|
||||
char peer_userinfo[256] = "";
|
||||
if (nl && p + nl <= pl + len) { memcpy(peer_userinfo, p, nl); peer_userinfo[nl] = '\0'; p += nl; }
|
||||
uint8_t atl = *p++;
|
||||
if (atl > 0 && p + atl <= pl + len) p += atl;
|
||||
p += 64; /* adm_tags_sig */
|
||||
/* ─── JOIN_REQUEST (0x12): joiner → connection-узел ─── */
|
||||
|
||||
static void cs_handle_join_request(struct chat_sync* cs, uint64_t peer,
|
||||
const char* ch_id, const uint8_t* pl, size_t len) {
|
||||
/* payload: [join_key:8][member...] */
|
||||
if (len < 8 + 1) { DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: JOIN_REQUEST too short len=%zu peer=%016llx", CS_ID, len, (unsigned long long)peer); return; }
|
||||
uint64_t join_key; memcpy(&join_key, pl, 8);
|
||||
uint64_t channel_id = strtoull(ch_id, NULL, 10);
|
||||
const uint8_t* member_data = pl + 8; size_t member_len = len - 8;
|
||||
|
||||
/* verify join_sig if present */
|
||||
if (join_sig) {
|
||||
uint8_t ch_x2[32], ch_ed2[32];
|
||||
if (topo_node_sqlite_channel_get(db, ch_id,
|
||||
NULL, 0, NULL, ch_x2, ch_ed2, NULL) != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: WELCOME channel not in DB ch=%s", CS_ID, ch_id);
|
||||
continue;
|
||||
}
|
||||
uint8_t vmsg[128];
|
||||
int vlen = member_sync_build_join_msg(ch_x2, ch_ed2, node_id, x25519, join_ts,
|
||||
vmsg, (int)sizeof(vmsg));
|
||||
if (vlen < 0 || cs_ed25519_verify(ed_pub, vmsg, (size_t)vlen, join_sig) != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: WELCOME invalid join_sig node=0x%016llx — skipping", CS_ID, (unsigned long long)node_id);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
struct join_member_data m;
|
||||
if (chat_join_parse_member(member_data, member_len, &m) != 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: JOIN_REQUEST parse failed ch=%s from=%016llx", CS_ID, ch_id, (unsigned long long)peer);
|
||||
return;
|
||||
}
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_MEMBER_SYNC, "%s: JOIN_REQUEST ch=%s joiner=%016llx from=%016llx key=%016llx",
|
||||
CS_ID, ch_id, (unsigned long long)m.node_id, (unsigned long long)peer, (unsigned long long)join_key);
|
||||
|
||||
int rc = topo_node_sqlite_member_block_put(db, ch_id, node_id, join_sig, join_ts, NULL, 0, x25519, ed_pub, peer_userinfo, NULL);
|
||||
if (rc != 0) DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: member_put(welcome) FAILED ch=%s node=0x%016llx rc=%d", CS_ID, ch_id, (unsigned long long)node_id, rc);
|
||||
{ char pn[256]; json_flat_get(peer_userinfo, "name", pn, sizeof(pn));
|
||||
topo_node_sqlite_node_update_verified(db, node_id, pn, x25519, ed_pub, join_ts, ntp_time_get_seconds(cs->inst)); }
|
||||
/* привязка идентичности: pubkey из ETCP-соединения == заявленный x25519 */
|
||||
struct ETCP_CONN* jconn = cs_find_conn_for_node(cs->inst, peer);
|
||||
if (!jconn || memcmp(jconn->crypto_ctx.peer_public_key, m.x25519, 32) != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: JOIN_REQUEST identity mismatch joiner=0x%016llx peer=0x%016llx — drop",
|
||||
CS_ID, (unsigned long long)m.node_id, (unsigned long long)peer);
|
||||
return;
|
||||
}
|
||||
|
||||
member_sync_cancel(g_cs->inst, node_id, ch_id);
|
||||
/* валидация ключа */
|
||||
uint64_t inviter = chat_join_lookup_inviter(cs->inst, channel_id, join_key);
|
||||
if (inviter == 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: JOIN_REQUEST invalid/expired join key ch=%s joiner=0x%016llx",
|
||||
CS_ID, ch_id, (unsigned long long)m.node_id);
|
||||
uint8_t err[1] = { CS_MSG_ERROR }; cs_send(cs, ch_id, peer, err, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
/* валидный ключ → сразу добавляем узел в группу для BGP sync */
|
||||
{
|
||||
uint64_t gid = strtoull(ch_id, NULL, 10);
|
||||
struct TOPO_GROUP* g = topo_groups_find(cs->inst->topo_groups, gid);
|
||||
if (g && g->group_type == TOPO_GROUP_TYPE_CHAT)
|
||||
topo_group_new_conn(g, jconn);
|
||||
}
|
||||
|
||||
/* добавить в pending (ждут JOIN_READY по приходу рекорда через merkle) */
|
||||
cs_pending_add(cs, m.node_id, ch_id, jconn);
|
||||
|
||||
/* форвард инвайтеру (или локальная обработка, если инвайтер == мы) */
|
||||
if (inviter == cs->inst->node_id) {
|
||||
chat_join_process_request(cs->inst, channel_id, peer, join_key, member_data, member_len);
|
||||
/* мы и есть connection-узел + инвайтер: рекорд добавлен локально,
|
||||
merkle-apply-коллбэк (cs_on_member_applied) здесь не сработает —
|
||||
отправляем JOIN_READY напрямую, если мембер реально добавлен. */
|
||||
if (topo_node_sqlite_member_in_channel(cs->inst->topo_sqlite_db, ch_id, m.node_id))
|
||||
cs_send_join_ready(cs, ch_id, m.node_id);
|
||||
} else {
|
||||
chat_join_forward_request(cs->inst, channel_id, inviter, join_key, &m);
|
||||
}
|
||||
|
||||
cs_refresh_channels(cs);
|
||||
cs_post_channel_online(cs, ch_id);
|
||||
}
|
||||
|
||||
/* add self to local peers table (server added joiner to its DB; client must do the same) */
|
||||
/* ─── JOIN_READY (0x13): connection-узел → joiner (можно запускать BGP) ─── */
|
||||
|
||||
static void cs_handle_join_ready(struct chat_sync* cs, uint64_t peer,
|
||||
const char* ch_id, const uint8_t* pl, size_t len) {
|
||||
if (cs->join_timer) { uasync_cancel_timeout(cs->inst->ua, cs->join_timer); cs->join_timer = NULL; }
|
||||
if (len < 8) { DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: JOIN_READY too short len=%zu peer=%016llx", CS_ID, len, (unsigned long long)peer); return; }
|
||||
uint64_t group_id; memcpy(&group_id, pl, 8);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: JOIN_READY ch=%s group=%016llx from=%016llx — joining complete, starting BGP",
|
||||
CS_ID, ch_id, (unsigned long long)group_id, (unsigned long long)peer);
|
||||
|
||||
/* запуск BGP + полноценное участие в канале */
|
||||
{
|
||||
uint64_t myid = cs->inst->node_id;
|
||||
const uint8_t* my_x25 = cs->inst->my_keys.public_key;
|
||||
const uint8_t* my_ed = cs->inst->my_ed25519_pubkey;
|
||||
const char* my_name = cs->inst->name[0] ? cs->inst->name : "";
|
||||
int src = topo_node_sqlite_member_block_put(db, ch_id, myid, NULL, 0, NULL, 0, my_x25, my_ed, my_name, NULL);
|
||||
if (src != 0)
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: member_put(self) FAILED in WELCOME ch=%s rc=%d", CS_ID, ch_id, src);
|
||||
else
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: WELCOME added self to peers ch=%s node=0x%016llx x25519=%016llx name=%s",
|
||||
CS_ID, ch_id, (unsigned long long)myid, *(const uint64_t*)my_x25, my_name);
|
||||
struct TOPO_GROUP* g = topo_groups_find(cs->inst->topo_groups, group_id);
|
||||
if (g && g->group_type == TOPO_GROUP_TYPE_CHAT) {
|
||||
struct ETCP_CONN* c = cs_find_conn_for_node(cs->inst, peer);
|
||||
if (c) topo_group_new_conn(g, c);
|
||||
}
|
||||
}
|
||||
chat_core_ensure_channel_ready(ch_id);
|
||||
|
||||
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);
|
||||
chat_event_post(CHAT_EVT_CHANNEL_UPDATED, evt, 1 + ch_id_len);
|
||||
|
||||
/* full member list for the newly joined channel */
|
||||
{ uint8_t* members = NULL; int mc = 0;
|
||||
if (chat_core_get_member_list(ch_id, &members, &mc) == 0 && members) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: WELCOME member_list count=%d ch=%s", CS_ID, mc, ch_id);
|
||||
size_t esz = 1 + ch_id_len + 2 + (size_t)mc * CHAT_MEMBER_DISPLAY_SIZE;
|
||||
uint8_t* mevt = u_malloc(esz);
|
||||
if (mevt) {
|
||||
uint16_t u16 = (uint16_t)mc;
|
||||
mevt[0] = ch_id_len; memcpy(mevt + 1, ch_id, ch_id_len);
|
||||
memcpy(mevt + 1 + ch_id_len, &u16, 2);
|
||||
memcpy(mevt + 1 + ch_id_len + 2, members, (size_t)mc * CHAT_MEMBER_DISPLAY_SIZE);
|
||||
chat_event_post(CHAT_EVT_MEMBER_LIST, mevt, (int)esz);
|
||||
u_free(mevt);
|
||||
}
|
||||
u_free(members);
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: WELCOME calling cs_post_channel_online ch=%s", CS_ID, ch_id);
|
||||
cs_post_channel_online(cs, ch_id);
|
||||
cs_resume_db_sync(ch_id);
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: WELCOME processed ch=%s peers=%d — message sync triggered via db_sync (active conn or peer_check timer every 5s)",
|
||||
CS_ID, ch_id, pc);
|
||||
|
||||
if (cs->pending_invite_ch_id != 0) {
|
||||
uint64_t ch_id_num = strtoull(ch_id, NULL, 10);
|
||||
uint8_t cevt[20]; memcpy(cevt, &peer, 8); int r = 0; memcpy(cevt + 8, &r, 4); memcpy(cevt + 12, &ch_id_num, 8);
|
||||
chat_event_post(CHAT_EVT_CONNECT_RESULT, cevt, 20);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: WELCOME invite success ch=%s peer=%016llx — posted CHAT_EVT_CONNECT_RESULT",
|
||||
CS_ID, ch_id, (unsigned long long)peer);
|
||||
cs->pending_invite_node_id = 0; cs->pending_invite_ch_id = 0; if (cs->join_timer) { uasync_cancel_timeout(cs->inst->ua, cs->join_timer); cs->join_timer = NULL; }
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: JOIN_READY invite success ch=%s peer=%016llx", CS_ID, ch_id, (unsigned long long)peer);
|
||||
cs->pending_invite_node_id = 0; cs->pending_invite_ch_id = 0; cs->pending_join_key = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─── 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 + 8 + 1) { DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: PEER_UPSERT too short len=%zu", CS_ID, len); 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;
|
||||
uint64_t join_ts; memcpy(&join_ts, p, 8); p += 8;
|
||||
uint8_t userinfo_len = *p++;
|
||||
char peer_userinfo[128] = "";
|
||||
if (p + userinfo_len <= pl + len) { memcpy(peer_userinfo, p, userinfo_len); peer_userinfo[userinfo_len] = '\0'; p += userinfo_len; }
|
||||
|
||||
/* verify join_sig */
|
||||
uint8_t ch_x3[32], ch_ed3[32];
|
||||
if (topo_node_sqlite_channel_get(cs->inst->topo_sqlite_db, ch_id,
|
||||
NULL, 0, NULL, ch_x3, ch_ed3, NULL) != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: PEER_UPSERT channel not in DB ch=%s", CS_ID, ch_id);
|
||||
return;
|
||||
}
|
||||
uint8_t vmsg[128];
|
||||
int vlen = member_sync_build_join_msg(ch_x3, ch_ed3, node_id, x25519, join_ts,
|
||||
vmsg, (int)sizeof(vmsg));
|
||||
if (vlen < 0 || cs_ed25519_verify(ed_pub, vmsg, (size_t)vlen, join_sig) != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: PEER_UPSERT invalid sig node=0x%016llx", CS_ID,
|
||||
(unsigned long long)node_id);
|
||||
return;
|
||||
}
|
||||
|
||||
sqlite3* db = cs->inst->topo_sqlite_db;
|
||||
|
||||
int rc = topo_node_sqlite_member_block_put(db, ch_id, node_id, join_sig, join_ts, NULL, 0, x25519, ed_pub, peer_userinfo, NULL);
|
||||
if (rc != 0) DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: member_put(peer_upsert) FAILED ch=%s node=0x%016llx rc=%d", CS_ID, ch_id, (unsigned long long)node_id, rc);
|
||||
{ char pn2[128]; json_flat_get(peer_userinfo, "name", pn2, sizeof(pn2));
|
||||
topo_node_sqlite_node_update_verified(db, node_id, pn2, x25519, ed_pub, join_ts, ntp_time_get_seconds(cs->inst)); }
|
||||
|
||||
cs_refresh_channels(cs);
|
||||
|
||||
{ uint8_t mevt[1 + 64 + 1 + CHAT_MEMBER_DISPLAY_SIZE]; uint8_t ml = (uint8_t)strlen(ch_id);
|
||||
mevt[0] = ml; memcpy(mevt + 1, ch_id, ml); mevt[1 + ml] = 1;
|
||||
if (chat_core_get_single_member(ch_id, node_id, mevt + 1 + ml + 1) == 0)
|
||||
chat_event_post(CHAT_EVT_MEMBER_UPDATED, mevt, 1 + ml + 1 + CHAT_MEMBER_DISPLAY_SIZE); }
|
||||
|
||||
/* propagate to others (except sender and the subject node) */
|
||||
cs_propagate(cs, ch_id, peer, pl, len);
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: PEER_UPSERT node=0x%016llx ch=%s",
|
||||
CS_ID, (unsigned long long)node_id, ch_id);
|
||||
}
|
||||
|
||||
/* ─── CHANNEL_INVITE (0x0F): inviter → joiner (push-приглашение в канал) ─── */
|
||||
|
||||
static void cs_handle_channel_invite(struct chat_sync* cs, uint64_t peer,
|
||||
|
||||
Reference in new issue
Block a user