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.
461 lines
22 KiB
461 lines
22 KiB
/* |
|
* chat_join.c — авторизация входа в канал через connection-узел и инвайтера. |
|
* Маршрутизируемая (etcp_router) часть: регистрация ключа, форвард запроса, |
|
* обработка на инвайтере (верификация + добавление мембера + подпись дерева). |
|
* См. описание flow в chat_join.h. |
|
*/ |
|
|
|
#include "chat_join.h" |
|
#include "member_sync.h" |
|
#include "chat_event.h" |
|
|
|
#include "../utun_instance.h" |
|
#include "../routing_layer/topo_node_sqlite.h" |
|
#include "../routing_layer/topo_group.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 uint64_t _now_tb(void) { return get_time_tb(); } |
|
|
|
static void _keys_purge_expired(struct UTUN_INSTANCE* inst) { |
|
uint64_t now = _now_tb(); |
|
struct join_key_entry** p = &inst->join_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 int _keys_put(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint64_t join_key, uint64_t inviter_id) { |
|
_keys_purge_expired(inst); |
|
if (!channel_id || !join_key || !inviter_id) { |
|
DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "%s: invalid key registration", CJ_ID); |
|
return -1; |
|
} |
|
for (struct join_key_entry* e = inst->join_keys; e; e = e->next) { |
|
if (e->channel_id != channel_id || e->join_key != join_key) continue; |
|
if (e->inviter_id != inviter_id) { |
|
DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "%s: registration conflicts with existing inviter ch=%llu", CJ_ID, |
|
(unsigned long long)channel_id); |
|
return -1; |
|
} |
|
e->expiry_tb = _now_tb() + (uint64_t)JOIN_KEY_TTL_SECONDS * 10000ULL; |
|
return 0; |
|
} |
|
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 -1; } |
|
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 = inst->join_keys; |
|
inst->join_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); |
|
return 0; |
|
} |
|
|
|
static struct join_key_entry* _keys_find(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint64_t join_key) { |
|
_keys_purge_expired(inst); |
|
for (struct join_key_entry* e = inst->join_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(struct UTUN_INSTANCE* inst) { |
|
struct join_key_entry* e = inst->join_keys; |
|
while (e) { struct join_key_entry* n = e->next; u_free(e); e = n; } |
|
inst->join_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, sizeof(m->userinfo)); |
|
if (ul > MS_USERINFO_MAX) { |
|
DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "chat_join: profile exceeds limit bytes=%zu", ul); return -1; |
|
} |
|
size_t need = 8 + 32 + 32 + 64 + 8 + 64 + 8 + 2 + 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 >> 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 + 2) 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; |
|
size_t ul = ((size_t)data[off] << 8) | data[off + 1]; off += 2; |
|
if (ul > MS_USERINFO_MAX || off + ul != len || memchr(data + off, 0, ul)) { |
|
DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "chat_join: invalid profile length bytes=%zu payload=%zu", ul, len); return -1; |
|
} |
|
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; |
|
|
|
/* 0. верификация join-ключа: принимаем только выданные нами (инвайтером) ключи */ |
|
struct join_key_entry* ke = _keys_find(inst, group_id, join_key); |
|
if (!ke || ke->inviter_id != inst->node_id) { |
|
DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "%s: process_request invalid join key ch=%s from=%016llx key=%016llx — drop", |
|
CJ_ID, ch_id, (unsigned long long)from_node, (unsigned long long)join_key); |
|
return; |
|
} |
|
|
|
/* 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) { |
|
/* джойнер авторизован — добавляем в CHAT-группу сразу, чтобы BGP подключился */ |
|
uint64_t gid = strtoull(ch_id, NULL, 10); |
|
struct TOPO_GROUP* g = topo_groups_find(inst->topo_groups, gid); |
|
if (g && g->group_type == TOPO_GROUP_TYPE_CHAT) { |
|
struct ETCP_CONN* jc = instance_find_conn(inst, m.node_id); |
|
if (jc) { |
|
topo_group_new_conn(g, jc); |
|
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: joiner 0x%016llx added to group ch=%s for BGP", |
|
CJ_ID, (unsigned long long)m.node_id, ch_id); |
|
} |
|
} |
|
/* уведомить GUI инвайтера об успешном присоединении джойнера */ |
|
uint8_t cevt[20]; memcpy(cevt, &m.node_id, 8); |
|
int r = 0; memcpy(cevt + 8, &r, 4); |
|
memcpy(cevt + 12, &group_id, 8); |
|
chat_event_post(inst, CHAT_EVT_CONNECT_RESULT, cevt, 20); |
|
} |
|
|
|
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); |
|
} |
|
|
|
/* Ожидание прикладного ACK: надёжная доставка router сама повторяет потерянные пакеты. */ |
|
struct chat_join_registration { |
|
struct chat_join_registration* next; |
|
struct UTUN_INSTANCE* inst; |
|
uint64_t channel_id, target_node_id, join_key; |
|
void* timer; |
|
chat_join_registered_fn callback; |
|
void* arg; |
|
}; |
|
|
|
static int send_key_control(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint64_t target, |
|
uint64_t key, uint8_t subcmd, uint8_t status) { |
|
struct ll_entry* entry = ll_alloc_lldgram(subcmd == JOIN_SUBCMD_KEY_REGISTER_ACK ? 11 : 10); |
|
if (!entry) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: key control allocation failed", CJ_ID); |
|
return -1; |
|
} |
|
entry->dgram[0] = ETCP_RT_ID_JOIN; |
|
entry->len = entry->memlen; |
|
entry->dgram[1] = subcmd; |
|
memcpy(entry->dgram + 2, &key, 8); |
|
if (subcmd == JOIN_SUBCMD_KEY_REGISTER_ACK) entry->dgram[10] = status; |
|
int rc = etcp_route_send(inst, channel_id, target, entry, 0, ROUTE_CRYPTO_SIGN | ROUTE_CRYPTO_ENCRYPT); |
|
if (rc != 0) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: key control send failed cmd=%02x ch=%llu peer=%016llx rc=%d", |
|
CJ_ID, subcmd, (unsigned long long)channel_id, (unsigned long long)target, rc); |
|
} |
|
return rc; |
|
} |
|
|
|
static void registration_finish(struct chat_join_registration* req, int result) { |
|
struct chat_join_registration** p = &req->inst->join_registrations; |
|
while (*p && *p != req) p = &(*p)->next; |
|
if (*p) *p = req->next; |
|
if (req->timer) uasync_cancel_timeout(req->inst->ua, req->timer); |
|
if (result == CHAT_JOIN_OK) { |
|
if (chat_join_store_local_key(req->inst, req->channel_id, req->join_key) != 0) result = CHAT_JOIN_ERROR; |
|
} |
|
if (result == CHAT_JOIN_OK || result == CHAT_JOIN_CANCELLED) { |
|
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: registration %s ch=%llu peer=%016llx key=%016llx", CJ_ID, |
|
result == CHAT_JOIN_OK ? "confirmed" : "cancelled", (unsigned long long)req->channel_id, |
|
(unsigned long long)req->target_node_id, (unsigned long long)req->join_key); |
|
} else { |
|
DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "%s: registration failed ch=%llu peer=%016llx key=%016llx result=%d", CJ_ID, |
|
(unsigned long long)req->channel_id, (unsigned long long)req->target_node_id, |
|
(unsigned long long)req->join_key, result); |
|
} |
|
chat_join_registered_fn callback = req->callback; |
|
void* arg = req->arg; |
|
u_free(req); |
|
callback(arg, result); |
|
} |
|
|
|
static void registration_timer(void* arg) { |
|
struct chat_join_registration* req = arg; |
|
req->timer = NULL; |
|
registration_finish(req, req->target_node_id == req->inst->node_id ? CHAT_JOIN_OK : CHAT_JOIN_TIMEOUT); |
|
} |
|
|
|
void chat_join_cancel_registration(struct chat_join_registration* req) { |
|
if (req) registration_finish(req, CHAT_JOIN_CANCELLED); |
|
} |
|
|
|
/* ── 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); |
|
uint8_t required = ROUTER_FLAG_SIGNED | ROUTER_FLAG_ENCRYPTED; |
|
if (from_node != inst->node_id && (d[ROUTER_SVC_FLAGS_OFF] & required) != required) { |
|
DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "%s: unprotected join packet rejected from=%016llx ch=%llu", CJ_ID, |
|
(unsigned long long)from_node, (unsigned long long)group_id); |
|
queue_dgram_free(entry); queue_entry_free(entry); return; |
|
} |
|
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 != 9) { |
|
DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "%s: malformed KEY_REGISTER len=%zu", CJ_ID, plen); |
|
} else { |
|
uint64_t key; memcpy(&key, payload + 1, 8); |
|
int rc = _keys_put(inst, group_id, key, from_node); |
|
send_key_control(inst, group_id, from_node, key, JOIN_SUBCMD_KEY_REGISTER_ACK, rc ? 1 : 0); |
|
} |
|
} else if (subcmd == JOIN_SUBCMD_KEY_REGISTER_ACK) { |
|
if (plen != 10 || payload[9] > 1) { |
|
DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "%s: malformed KEY_REGISTER_ACK len=%zu", CJ_ID, plen); |
|
} else { |
|
uint64_t key; memcpy(&key, payload + 1, 8); |
|
struct chat_join_registration* req = inst->join_registrations; |
|
while (req && (req->channel_id != group_id || req->target_node_id != from_node || req->join_key != key)) req = req->next; |
|
/* Освобождаем пакет до callback: владелец может закрыть instance. */ |
|
int result = payload[9] ? CHAT_JOIN_ERROR : CHAT_JOIN_OK; |
|
queue_dgram_free(entry); queue_entry_free(entry); |
|
if (req) registration_finish(req, result); |
|
else DEBUG_DEBUG(DEBUG_CATEGORY_CHAT_SYNC, "%s: late/unmatched registration ACK ch=%llu peer=%016llx", CJ_ID, |
|
(unsigned long long)group_id, (unsigned long long)from_node); |
|
return; |
|
} |
|
} 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; |
|
inst->join_stopping = 0; |
|
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; |
|
inst->join_stopping = 1; |
|
if (inst->router_bindings.callbacks[ETCP_RT_ID_JOIN]) etcp_router_unbind(inst, ETCP_RT_ID_JOIN); |
|
while (inst->join_registrations) chat_join_cancel_registration(inst->join_registrations); |
|
_keys_free_all(inst); |
|
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: destroyed", CJ_ID); |
|
} |
|
|
|
/* ── Публичные операции ── */ |
|
|
|
int chat_join_store_local_key(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint64_t join_key) { |
|
if (!inst) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: local registration without instance", CJ_ID); return -1; } |
|
return _keys_put(inst, channel_id, join_key, inst->node_id); |
|
} |
|
|
|
struct chat_join_registration* chat_join_register_key(struct UTUN_INSTANCE* inst, uint64_t channel_id, |
|
uint64_t target_node_id, uint64_t join_key, chat_join_registered_fn callback, void* arg) { |
|
if (!inst || !inst->ua || inst->join_stopping || !channel_id || !join_key || !callback) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: invalid registration arguments", CJ_ID); |
|
return NULL; |
|
} |
|
for (struct chat_join_registration* r = inst->join_registrations; r; r = r->next) { |
|
if (r->channel_id == channel_id && r->join_key == join_key) { |
|
DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "%s: registration already pending ch=%llu", CJ_ID, (unsigned long long)channel_id); |
|
return NULL; |
|
} |
|
} |
|
struct chat_join_registration* req = u_calloc(1, sizeof(*req)); |
|
if (!req) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: registration allocation failed", CJ_ID); return NULL; } |
|
req->inst = inst; req->channel_id = channel_id; req->join_key = join_key; |
|
req->target_node_id = target_node_id ? target_node_id : inst->node_id; |
|
req->callback = callback; req->arg = arg; |
|
int local = req->target_node_id == inst->node_id; |
|
req->timer = uasync_set_timeout(inst->ua, local ? 0 : JOIN_REGISTER_TIMEOUT_TB, req, registration_timer, "join_register"); |
|
if (!req->timer) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: registration timer allocation failed", CJ_ID); |
|
u_free(req); return NULL; |
|
} |
|
if (!local && send_key_control(inst, channel_id, req->target_node_id, join_key, JOIN_SUBCMD_KEY_REGISTER, 0) != 0) { |
|
uasync_cancel_timeout(inst->ua, req->timer); |
|
u_free(req); return NULL; |
|
} |
|
req->next = inst->join_registrations; |
|
inst->join_registrations = req; |
|
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: registration pending ch=%llu peer=%016llx key=%016llx", CJ_ID, |
|
(unsigned long long)channel_id, (unsigned long long)req->target_node_id, (unsigned long long)join_key); |
|
return req; |
|
} |
|
|
|
uint64_t chat_join_lookup_inviter(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint64_t join_key) { |
|
struct join_key_entry* e = _keys_find(inst, 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[MS_USERINFO_MAX + 256]; 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) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: forward payload allocation failed bytes=%zu", CJ_ID, plen); 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) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: forward entry allocation failed", CJ_ID); u_free(payload); return -1; } |
|
entry->dgram = u_malloc(1 + plen); |
|
if (!entry->dgram) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: forward datagram allocation failed bytes=%zu", CJ_ID, plen + 1); |
|
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); |
|
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; |
|
}
|
|
|