/* * chat_channel.c — управление каналами: ensure_ready, create, connect * * Вынесено из chat_core.c для уменьшения размера модуля. */ #include "chat_core_priv.h" #include "chat_event.h" #include "../utun_instance.h" #include "../ntp_time.h" #include "../routing_layer/topo_node_sqlite.h" #include "../routing_layer/topo_group.h" #include "../routing_layer/topo_group_connect.h" #include "member_sync.h" #include "../transport_layer/secure_channel.h" #include "../../lib/mem.h" #include "../../lib/ll_queue.h" #include "../../lib/platform_compat.h" #include "../transport_layer/etcp.h" #include "../transport_layer/etcp_connections.h" #define OPENSSL_API_COMPAT 0x10100000L #include #include #include /* ─── Ed25519 подпись join-сообщения ─── */ static int channel_sign_join(const uint8_t* ch_x25519, const uint8_t* ch_ed25519, uint64_t node_id, const uint8_t* node_pubkey, uint64_t join_ts, const uint8_t* ed25519_privkey, uint8_t sig_out[64]) { uint8_t msg[256]; size_t mlen = 0; memcpy(msg + mlen, ch_x25519, 32); mlen += 32; memcpy(msg + mlen, ch_ed25519, 32); mlen += 32; memcpy(msg + mlen, &node_id, 8); mlen += 8; memcpy(msg + mlen, node_pubkey, 32); mlen += 32; memcpy(msg + mlen, &join_ts, 8); mlen += 8; EVP_PKEY* pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, ed25519_privkey, 32); if (!pkey) { memset(sig_out, 0, 64); return -1; } EVP_MD_CTX* mdctx = EVP_MD_CTX_new(); if (!mdctx) { EVP_PKEY_free(pkey); memset(sig_out, 0, 64); return -1; } int ok = EVP_DigestSignInit(mdctx, NULL, NULL, NULL, pkey) == 1 && EVP_DigestSign(mdctx, sig_out, &(size_t){64}, msg, mlen) == 1; EVP_MD_CTX_free(mdctx); EVP_PKEY_free(pkey); if (!ok) memset(sig_out, 0, 64); return ok ? 0 : -1; } /* ─── сбор адресов текущего узла (для member_sync_put) ─── */ static int collect_my_addrs(uint8_t* buf, int max_buf_sz) { int pos = 0, count = 0; struct ETCP_SOCKET* s = g_cc.inst->etcp_sockets; while (s) { struct sockaddr_storage* sa = s->interface_addr.ss_family ? &s->interface_addr : NULL; if (!sa) sa = s->local_addr.ss_family ? &s->local_addr : NULL; if (sa && sa->ss_family == AF_INET) { struct sockaddr_in* sin = (struct sockaddr_in*)sa; if (pos + 9 > max_buf_sz) break; buf[pos++] = 4; buf[pos++] = s->sock_id; buf[pos++] = 1; // proto=1 (UDP) memcpy(buf + pos, &sin->sin_addr, 4); pos += 4; uint16_t port = ntohs(sin->sin_port); buf[pos++] = (uint8_t)(port >> 8); buf[pos++] = (uint8_t)(port & 0xFF); count++; uint8_t* ip = (uint8_t*)&sin->sin_addr; DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: [ADDR_SYNC] collect_my_addrs UDP sock=%d %d.%d.%d.%d:%d", CC_ID, s->sock_id, ip[0], ip[1], ip[2], ip[3], port); } else if (sa && sa->ss_family == AF_INET6) { struct sockaddr_in6* sin6 = (struct sockaddr_in6*)sa; if (pos + 21 > max_buf_sz) break; buf[pos++] = 6; buf[pos++] = s->sock_id; buf[pos++] = 1; // proto=1 (UDP) memcpy(buf + pos, &sin6->sin6_addr, 16); pos += 16; uint16_t port = ntohs(sin6->sin6_port); buf[pos++] = (uint8_t)(port >> 8); buf[pos++] = (uint8_t)(port & 0xFF); count++; DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: [ADDR_SYNC] collect_my_addrs UDP v6 sock=%d port=%d", CC_ID, s->sock_id, port); } s = s->next; } { struct TCP_SOCKET* ts = g_cc.inst->tcp_sockets; while (ts) { struct sockaddr_storage* addr = ts->interface_addr.ss_family ? &ts->interface_addr : &ts->local_addr; if (addr && addr->ss_family == AF_INET) { struct sockaddr_in* sin = (struct sockaddr_in*)addr; if (pos + 9 > max_buf_sz) break; buf[pos++] = 4; buf[pos++] = ts->sock_id; buf[pos++] = 2; // proto=2 (TCP) memcpy(buf + pos, &sin->sin_addr, 4); pos += 4; uint16_t port = ntohs(sin->sin_port); buf[pos++] = (uint8_t)(port >> 8); buf[pos++] = (uint8_t)(port & 0xFF); count++; uint8_t* ip = (uint8_t*)&sin->sin_addr; DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: [ADDR_SYNC] collect_my_addrs TCP sock=%d %d.%d.%d.%d:%d", CC_ID, ts->sock_id, ip[0], ip[1], ip[2], ip[3], port); } else if (addr && addr->ss_family == AF_INET6) { struct sockaddr_in6* sin6 = (struct sockaddr_in6*)addr; if (pos + 21 > max_buf_sz) break; buf[pos++] = 6; buf[pos++] = ts->sock_id; buf[pos++] = 2; // proto=2 (TCP) memcpy(buf + pos, &sin6->sin6_addr, 16); pos += 16; uint16_t port = ntohs(sin6->sin6_port); buf[pos++] = (uint8_t)(port >> 8); buf[pos++] = (uint8_t)(port & 0xFF); count++; DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: [ADDR_SYNC] collect_my_addrs TCP v6 sock=%d port=%d", CC_ID, ts->sock_id, port); } ts = ts->next; } } return count; } /* ─── подготовка инфраструктуры канала (db_sync instance) ─── */ void chat_core_ensure_channel_ready(const char* ch_id) { if (!g_cc.initialized || !ch_id || !ch_id[0]) return; if (si_find(ch_id)) return; char tbl_msg[80]; msg_table_name(ch_id, tbl_msg, sizeof(tbl_msg)); uint64_t ch_hash = 0; { const uint8_t* chd = (const uint8_t*)ch_id; size_t chl = strlen(ch_id); uint8_t sh[32]; SHA256(chd, chl, sh); memcpy(&ch_hash, sh, 8); } struct DB_SYNC_INSTANCE* si = db_sync_instance_add(g_cc.inst, tbl_msg, ch_hash, 1); uint64_t gid = strtoull(ch_id, NULL, 10); if (gid != 0 && g_cc.inst->topo_groups && !topo_groups_find(g_cc.inst->topo_groups, gid)) topo_groups_create_group(g_cc.inst->topo_groups, gid, TOPO_GROUP_TYPE_CHAT, ch_id); if (si) { si_register(si, ch_id); db_sync_set_insert_cb(si, on_msg_inserted, u_strdup(ch_id)); DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: channel ready ch=%s tbl=%s hash=0x%016llx", CC_ID, ch_id, tbl_msg, (unsigned long long)ch_hash); } else { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: db_sync_instance_add failed for ch=%s", CC_ID, ch_id); } } /* ─── создание канала ─── */ void chat_core_create_channel(struct chat_channel_create* req) { if (!g_cc.initialized) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: create_channel NOT INITIALIZED ch=%s", CC_ID, req ? req->channel_id : "(null)"); return; } if (!req) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: create_channel req=NULL", CC_ID); return; } DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: create_channel BEGIN ch=%s name=%s", CC_ID, req->channel_id, req->name); int rc = topo_node_sqlite_channel_put(g_cc.db, req->channel_id, req->name, req->owner_node_id, req->x25519_pubkey, req->x25519_privkey, req->ed25519_pubkey, req->ed25519_privkey, req->signature); if (rc != 0) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: channel_put FAILED ch=%s rc=%d", CC_ID, req->channel_id, rc); } else { DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: channel_put OK ch=%s name=%s owner=0x%016llx", CC_ID, req->channel_id, req->name, (unsigned long long)req->owner_node_id); chat_core_ensure_channel_ready(req->channel_id); uint8_t ch_id_len = (uint8_t)strlen(req->channel_id); uint8_t data[65]; data[0] = ch_id_len; memcpy(data + 1, req->channel_id, ch_id_len); { uint64_t myid = g_cc.inst->node_id; uint64_t join_ts = (uint64_t)ntp_time_get_seconds(g_cc.inst); uint8_t join_sig[64]; channel_sign_join(req->x25519_pubkey, req->ed25519_pubkey, myid, g_cc.inst->my_keys.public_key, join_ts, g_cc.inst->my_ed25519_privkey, join_sig); uint8_t my_addrs[256]; int my_addr_cnt = collect_my_addrs(my_addrs, (int)sizeof(my_addrs)); char juser3[256]; snprintf(juser3, sizeof(juser3), "{\"name\":\"%s\"}", g_cc.inst->name[0] ? g_cc.inst->name : ""); int mrc = member_sync_put(g_cc.inst, req->channel_id, myid, g_cc.inst->my_keys.public_key, g_cc.inst->my_ed25519_pubkey, join_sig, join_ts, NULL, 0, juser3, my_addrs, my_addr_cnt, NULL, NULL, 0); if (mrc != 0) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: member_sync_put(self) FAILED ch=%s rc=%d", CC_ID, req->channel_id, mrc); } chat_core_sync_my_addresses(); } chat_event_post(CHAT_EVT_CHANNEL_UPDATED, data, 1 + ch_id_len); } } void chat_core_create_channel_trampoline(void* arg) { DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "chat_core: TRAMPOLINE invoked arg=%p", arg); struct chat_channel_create* req = (struct chat_channel_create*)arg; chat_core_create_channel(req); u_free(req); } void chat_core_create_channel_auto_trampoline(void* arg) { chat_core_create_channel_auto((const char*)arg); u_free(arg); } void chat_core_create_channel_auto(const char* name) { if (!g_cc.initialized || !name) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: create_channel_auto — not initialized or NULL name", CC_ID); return; } DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: create_channel_auto BEGIN name=%s", CC_ID, name); uint8_t x25519_pub[32] = {0}, x25519_priv[32] = {0}; { EVP_PKEY* pkey = NULL; EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_X25519, NULL); if (ctx) { EVP_PKEY_keygen_init(ctx); EVP_PKEY_keygen(ctx, &pkey); EVP_PKEY_CTX_free(ctx); } if (pkey) { size_t pkLen = 32, skLen = 32; EVP_PKEY_get_raw_public_key(pkey, x25519_pub, &pkLen); EVP_PKEY_get_raw_private_key(pkey, x25519_priv, &skLen); EVP_PKEY_free(pkey); } } uint8_t ed25519_pub[32] = {0}, ed25519_priv[32] = {0}; EVP_PKEY* ed_pkey = NULL; { EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, NULL); if (ctx) { EVP_PKEY_keygen_init(ctx); EVP_PKEY_keygen(ctx, &ed_pkey); EVP_PKEY_CTX_free(ctx); } if (ed_pkey) { size_t pkLen = 32, skLen = 32; EVP_PKEY_get_raw_public_key(ed_pkey, ed25519_pub, &pkLen); EVP_PKEY_get_raw_private_key(ed_pkey, ed25519_priv, &skLen); } } uint64_t chId = 0; RAND_bytes((uint8_t*)&chId, sizeof(chId)); char channelId[64]; snprintf(channelId, sizeof(channelId), "%llu", (unsigned long long)chId); uint8_t sig[64] = {0}; if (ed_pkey) { size_t nameLen = strlen(name); size_t chIdLen = strlen(channelId); size_t msgLen = chIdLen + 1 + nameLen + 1 + 8 + 32 + 32; uint8_t* msg = u_malloc(msgLen); if (msg) { uint8_t* p = msg; memcpy(p, channelId, chIdLen); p += chIdLen; *p++ = '\0'; memcpy(p, name, nameLen); p += nameLen; *p++ = '\0'; memcpy(p, &g_cc.my_node_id, 8); p += 8; memcpy(p, x25519_pub, 32); p += 32; memcpy(p, ed25519_pub, 32); EVP_MD_CTX* mdctx = EVP_MD_CTX_new(); if (mdctx) { EVP_DigestSignInit(mdctx, NULL, NULL, NULL, ed_pkey); size_t sl = 64; EVP_DigestSign(mdctx, sig, &sl, msg, msgLen); EVP_MD_CTX_free(mdctx); } u_free(msg); } EVP_PKEY_free(ed_pkey); } struct chat_channel_create* req = u_calloc(1, sizeof(*req)); if (!req) return; snprintf(req->channel_id, sizeof(req->channel_id), "%s", channelId); snprintf(req->name, sizeof(req->name), "%s", name); req->owner_node_id = g_cc.my_node_id; memcpy(req->x25519_pubkey, x25519_pub, 32); memcpy(req->x25519_privkey, x25519_priv, 32); memcpy(req->ed25519_pubkey, ed25519_pub, 32); memcpy(req->ed25519_privkey, ed25519_priv, 32); memcpy(req->signature, sig, 64); chat_core_create_channel(req); u_free(req); } /* ─── управление подключением к каналу (выбор в GUI) ─── */ void chat_core_connect_channel(const char* ch_id) { DEBUG_TRACE(DEBUG_CATEGORY_CHAT_SYNC, "%s: connect_channel called ch_id=%s initialized=%d", CC_ID, ch_id ? ch_id : "(null)", g_cc.initialized); if (!g_cc.initialized || !ch_id || !ch_id[0]) { DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "%s: connect_channel — skip (not ready)", CC_ID); return; } if (!g_cc.inst->topo_groups) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: connect_channel — no topo_groups", CC_ID); return; } uint64_t gid = strtoull(ch_id, NULL, 10); if (gid == 0) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: connect_channel — invalid ch_id=%s", CC_ID, ch_id); return; } chat_core_ensure_channel_ready(ch_id); struct TOPO_GROUP* group = topo_groups_find(g_cc.inst->topo_groups, gid); if (!group) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: connect_channel — group not found ch=%s gid=%016llx total_groups=%d", CC_ID, ch_id, (unsigned long long)gid, queue_entry_count(g_cc.inst->topo_groups->group_list)); return; } if (group->connect) { DEBUG_DEBUG(DEBUG_CATEGORY_CHAT_SYNC, "%s: connect_channel — already in progress ch=%s active=%d", CC_ID, ch_id, topo_group_connect_active_count(group)); return; } DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: connect_channel ch=%s gid=%016llx group_type=%d — starting connect_init", CC_ID, ch_id, (unsigned long long)gid, group->group_type); topo_group_connect_init(group); } void chat_core_connect_channel_trampoline(void* arg) { char* ch_id = (char*)arg; DEBUG_TRACE(DEBUG_CATEGORY_CHAT_SYNC, "%s: connect_channel_trampoline ch_id=%s", CC_ID, ch_id ? ch_id : "(null)"); if (!ch_id) return; chat_core_connect_channel(ch_id); u_free(ch_id); }