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.
 
 
 
 
 
 

385 lines
17 KiB

/*
* chat_channel.c — управление каналами: ensure_ready, create, connect
*
* Вынесено из chat_core.c для уменьшения размера модуля.
*/
#include "chat_core_priv.h"
#include "chat_event.h"
#include "chat_setting.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 "chat_sync.h"
#include "../transport_layer/secure_channel.h"
#include "../media_delivery/media_delivery.h"
#include "../media_delivery/media_index.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 <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[128];
int mlen = member_sync_build_join_msg(ch_x25519, ch_ed25519, node_id, node_pubkey, join_ts,
msg, (int)sizeof(msg));
if (mlen < 0) { memset(sig_out, 0, 64); return -1; }
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, (size_t)mlen) == 1;
EVP_MD_CTX_free(mdctx);
EVP_PKEY_free(pkey);
if (!ok) memset(sig_out, 0, 64);
return ok ? 0 : -1;
}
/* ─── подготовка инфраструктуры канала (db_sync instance) ─── */
void chat_core_ensure_channel_ready(struct UTUN_INSTANCE* inst, const char* ch_id) {
struct chat_core_ctx* cc = CC(inst);
if (!cc || !cc->initialized || !ch_id || !ch_id[0]) return;
if (si_find(inst, ch_id)) return;
char tbl_msg[80]; msg_table_name(ch_id, tbl_msg, sizeof(tbl_msg));
uint64_t gid = strtoull(ch_id, NULL, 10);
struct DB_SYNC_INSTANCE* si = db_sync_instance_add(inst, tbl_msg, gid, 1);
if (gid != 0 && inst->topo_groups && !topo_groups_find(inst->topo_groups, gid)) {
struct TOPO_GROUP* g = topo_groups_create_group(inst->topo_groups, gid, TOPO_GROUP_TYPE_CHAT, ch_id);
if (g) member_sync_subscribe_group(inst, g);
}
if (si) {
si_register(inst, si, ch_id);
struct msg_insert_arg* ia = u_malloc(sizeof(*ia));
if (ia) { ia->inst = inst; snprintf(ia->ch_id, sizeof(ia->ch_id), "%s", ch_id); }
db_sync_set_insert_cb(si, on_msg_inserted, ia);
db_sync_instance_set_gated(si, 1);
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: channel ready ch=%s tbl=%s group=0x%016llx",
CC_ID, ch_id, tbl_msg, (unsigned long long)gid);
} 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 UTUN_INSTANCE* inst, struct chat_channel_create* req) {
struct chat_core_ctx* cc = CC(inst);
if (!cc || !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(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(inst, 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 = inst->node_id;
uint64_t join_ts = (uint64_t)ntp_time_get_seconds(inst);
uint8_t join_sig[64];
channel_sign_join(req->x25519_pubkey, req->ed25519_pubkey,
myid, inst->my_keys.public_key,
join_ts, inst->my_ed25519_privkey, join_sig);
char juser3[256]; snprintf(juser3, sizeof(juser3), "{\"name\":\"%s\"}",
inst->name[0] ? inst->name : "");
/* корень дерева приглашений: подписан ключом группы (канальный ed25519) */
uint8_t root_sig[64]; memset(root_sig, 0, 64);
member_sync_sign_pubkey(req->ed25519_privkey, inst->my_keys.public_key, root_sig);
int mrc = member_sync_put(inst, req->channel_id, myid,
inst->my_keys.public_key, inst->my_ed25519_pubkey,
join_sig, join_ts, NULL, 0, juser3, NULL, NULL, 0, 0, root_sig);
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);
} else if (mrc & MS_APPLY_CHANGED) {
member_sync_broadcast_one(inst, req->channel_id, myid);
}
/* адреса + update_ts + подпись + синк — единой функцией */
chat_core_update_my_member(inst);
}
chat_event_post(inst, 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;
if (!req) return;
chat_core_create_channel(req->inst, req);
u_free(req);
}
void chat_core_create_channel_auto_trampoline(void* arg) {
struct chat_create_auto_arg* a = (struct chat_create_auto_arg*)arg;
if (!a) return;
chat_core_create_channel_auto(a->inst, a->name);
u_free(arg);
}
void chat_core_create_channel_auto(struct UTUN_INSTANCE* inst, const char* name) {
struct chat_core_ctx* cc = CC(inst);
if (!cc || !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));
chId &= 0x7FFFFFFFFFFFFFFFULL; /* как node_id — всегда < 2^63 (помещается в signed Long) */
if (chId == 0) chId = 1;
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, &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;
req->inst = inst;
snprintf(req->channel_id, sizeof(req->channel_id), "%s", channelId);
snprintf(req->name, sizeof(req->name), "%s", name);
req->owner_node_id = 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(inst, req);
u_free(req);
}
/* ─── управление подключением к каналу (выбор в GUI) ─── */
void chat_core_connect_channel(struct UTUN_INSTANCE* inst, const char* ch_id) {
struct chat_core_ctx* cc = CC(inst);
DEBUG_TRACE(DEBUG_CATEGORY_CHAT_SYNC, "%s: connect_channel called ch_id=%s initialized=%d", CC_ID, ch_id ? ch_id : "(null)", cc ? cc->initialized : 0);
if (!cc || !cc->initialized || !ch_id || !ch_id[0]) { DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "%s: connect_channel — skip (not ready)", CC_ID); return; }
if (!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(inst, ch_id);
struct TOPO_GROUP* group = topo_groups_find(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(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;
}
if (!chat_setting_get_int(inst, "group_autoconnect", 1)) {
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: connect_channel ch=%s — group_autoconnect disabled, skip auto-connect loop",
CC_ID, ch_id);
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) {
struct chat_create_auto_arg* a = (struct chat_create_auto_arg*)arg;
DEBUG_TRACE(DEBUG_CATEGORY_CHAT_SYNC, "%s: connect_channel_trampoline ch_id=%s", CC_ID, a ? a->name : "(null)");
if (!a) return;
chat_core_connect_channel(a->inst, a->name);
u_free(a);
}
/* ─── однократное подключение к конкретному узлу канала (GUI → uasync) ─── */
void chat_core_connect_node(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t node_id) {
struct chat_core_ctx* cc = CC(inst);
if (!cc || !cc->initialized || !inst || !ch_id || !ch_id[0] || !node_id) {
DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "%s: connect_node — skip (not ready or invalid args)", CC_ID);
return;
}
if (node_id == inst->node_id) {
DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "%s: connect_node — skip self node=0x%016llx", CC_ID, (unsigned long long)node_id);
return;
}
chat_core_ensure_channel_ready(inst, ch_id);
uint64_t gid = strtoull(ch_id, NULL, 10);
struct TOPO_GROUP* group = topo_groups_find(inst->topo_groups, gid);
if (!group) {
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: connect_node — group not found ch=%s", CC_ID, ch_id);
return;
}
topo_group_connect_node_once(group, node_id);
}
void chat_core_connect_node_trampoline(void* arg) {
struct chat_connect_node_req* r = (struct chat_connect_node_req*)arg;
if (!r) return;
chat_core_connect_node(r->inst, r->ch_id, r->node_id);
u_free(r);
}
/* ─── применение настройки group_autoconnect ко всем CHAT-группам ─── */
void chat_core_apply_group_autoconnect(struct UTUN_INSTANCE* inst) {
int enabled = chat_setting_get_int(inst, "group_autoconnect", 1);
if (!inst || !inst->topo_groups || !inst->topo_groups->group_list) return;
struct ll_entry* e = inst->topo_groups->group_list->head;
while (e) {
struct TOPO_GROUP* g = (struct TOPO_GROUP*)e;
if (g->group_type == TOPO_GROUP_TYPE_CHAT) {
if (enabled && !g->connect) {
topo_group_connect_init(g);
} else if (!enabled && g->connect) {
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: group_autoconnect OFF — stop auto-connect ch=%s", CC_ID, g->channel_id);
topo_group_connect_destroy(g);
}
}
e = e->next;
}
}
/* ─── локальное удаление канала ─── */
static const char* cc_ui_state_get(struct chat_core_ctx* cc, const char* key) {
static char val[512];
val[0] = '\0';
sqlite3_stmt* st = NULL;
if (sqlite3_prepare_v2(cc->db, "SELECT value FROM ui_state WHERE key=?", -1, &st, NULL) == SQLITE_OK) {
sqlite3_bind_text(st, 1, key, -1, SQLITE_STATIC);
if (sqlite3_step(st) == SQLITE_ROW) {
const char* v = (const char*)sqlite3_column_text(st, 0);
if (v) snprintf(val, sizeof(val), "%s", v);
}
sqlite3_finalize(st);
}
return val;
}
void chat_core_delete_channel(struct UTUN_INSTANCE* inst, const char* ch_id) {
struct chat_core_ctx* cc = CC(inst);
if (!cc || !cc->initialized || !cc->db || !inst) {
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: delete_channel — not initialized", CC_ID);
return;
}
if (!ch_id || !ch_id[0]) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: delete_channel — empty ch_id", CC_ID); return; }
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: delete_channel BEGIN ch=%s", CC_ID, ch_id);
uint64_t gid = strtoull(ch_id, NULL, 10);
/* 1. отмена медиа-загрузок/релэев/суперузлов канала (держат conn_mgr handle группы) */
media_delivery_cancel_channel(inst, gid);
/* 2. снять merkle-сессии этого namespace */
member_sync_cancel_channel(inst, ch_id);
/* 3. удалить группу (conn_mgr → закрытие соединений, connect-цикл, broadcast, recovery) */
if (inst->topo_groups)
topo_groups_remove_group(inst->topo_groups, gid);
/* 4. удалить db_sync инстанс (освобождает память, отменяет таймеры) */
{
struct DB_SYNC_INSTANCE* si = si_find(inst, ch_id);
if (si) db_sync_instance_remove(si);
}
/* 5. немедленно убрать канал из кеша chat_sync */
chat_sync_on_channel_deleted(inst, ch_id);
/* 6. удалить медиафайлы канала на диске + строки media_files */
media_index_delete_channel(cc->db, ch_id, cc_ui_state_get(cc, "media_base"));
/* 7. удалить таблицы и метаданные канала */
topo_node_sqlite_channel_delete(cc->db, ch_id);
/* 8. уведомить GUI */
{
uint8_t evt[65]; uint8_t cl = (uint8_t)strlen(ch_id);
evt[0] = cl; memcpy(evt + 1, ch_id, cl);
chat_event_post(inst, CHAT_EVT_CHANNEL_DELETED, evt, 1 + cl);
}
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: delete_channel DONE ch=%s gid=0x%016llx", CC_ID, ch_id, (unsigned long long)gid);
}
void chat_core_delete_channel_trampoline(void* arg) {
struct chat_delete_channel_req* req = (struct chat_delete_channel_req*)arg;
if (!req) return;
chat_core_delete_channel(req->inst, req->ch_id);
u_free(req);
}