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.
257 lines
11 KiB
257 lines
11 KiB
/* |
|
* chat_channel.c — управление каналами: ensure_ready, create, connect |
|
* |
|
* Вынесено из chat_core.c для уменьшения размера модуля. |
|
*/ |
|
|
|
#include "chat_core_priv.h" |
|
#include "gui_bridge.h" |
|
|
|
#include "../../../src/utun_instance.h" |
|
#include "../../../src/ntp_time.h" |
|
#include "topo_node_sqlite.h" |
|
#include "topo_group.h" |
|
#include "topo_group_connect.h" |
|
#include "member_sync.h" |
|
#include "secure_channel.h" |
|
#include "../../../lib/mem.h" |
|
#include "../../../lib/ll_queue.h" |
|
#include "../../../lib/platform_compat.h" |
|
#include "etcp.h" |
|
|
|
#include <openssl/sha.h> |
|
#include <openssl/evp.h> |
|
#include <openssl/rand.h> |
|
|
|
/* ─── 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_addrs) { |
|
int count = 0; |
|
struct ETCP_SOCKET* s = g_cc.inst->etcp_sockets; |
|
while (s && count < max_addrs) { |
|
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; |
|
buf[count * 8] = 4; |
|
buf[count * 8 + 1] = s->sock_id; |
|
memcpy(buf + count * 8 + 2, &sin->sin_addr, 4); |
|
uint16_t port = ntohs(sin->sin_port); |
|
buf[count * 8 + 6] = (uint8_t)(port >> 8); |
|
buf[count * 8 + 7] = (uint8_t)(port & 0xFF); |
|
count++; |
|
} |
|
s = s->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_CONNECTIVITY, "%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_CONNECTIVITY, "%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_CONNECTIVITY, "%s: create_channel NOT INITIALIZED ch=%s", |
|
CC_ID, req ? req->channel_id : "(null)"); |
|
return; |
|
} |
|
if (!req) { DEBUG_ERROR(DEBUG_CATEGORY_CONNECTIVITY, "%s: create_channel req=NULL", CC_ID); return; } |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_CONNECTIVITY, "%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_CONNECTIVITY, "%s: channel_put FAILED ch=%s rc=%d", |
|
CC_ID, req->channel_id, rc); |
|
} else { |
|
DEBUG_INFO(DEBUG_CATEGORY_CONNECTIVITY, "%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, 16); |
|
|
|
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); |
|
if (mrc != 0) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTIVITY, "%s: member_sync_put(self) FAILED ch=%s rc=%d", |
|
CC_ID, req->channel_id, mrc); |
|
} |
|
} |
|
|
|
gui_bridge_post(GUI_EVT_CHANNEL_UPDATED, data, 1 + ch_id_len); |
|
} |
|
} |
|
|
|
void chat_core_create_channel_trampoline(void* arg) { |
|
DEBUG_INFO(DEBUG_CATEGORY_CONNECTIVITY, "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_CONNECTIVITY, "%s: create_channel_auto — not initialized or NULL name", CC_ID); |
|
return; |
|
} |
|
DEBUG_INFO(DEBUG_CATEGORY_CONNECTIVITY, "%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_generate(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_generate(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_CONNECTIVITY, "%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_CONNECTIVITY, "%s: connect_channel — skip (not ready)", CC_ID); return; } |
|
if (!g_cc.inst->topo_groups) { DEBUG_ERROR(DEBUG_CATEGORY_CONNECTIVITY, "%s: connect_channel — no topo_groups", CC_ID); return; } |
|
|
|
uint64_t gid = strtoull(ch_id, NULL, 10); |
|
if (gid == 0) { DEBUG_ERROR(DEBUG_CATEGORY_CONNECTIVITY, "%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_CONNECTIVITY, "%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_INFO(DEBUG_CATEGORY_CONNECTIVITY, "%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_CONNECTIVITY, "%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_CONNECTIVITY, "%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); |
|
}
|
|
|