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
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); |
|
}
|
|
|