|
|
|
|
@ -14,6 +14,8 @@
|
|
|
|
|
#include "dm_mailbox.h" |
|
|
|
|
#include "../chat/chat_core.h" |
|
|
|
|
#include "../chat/chat_event.h" |
|
|
|
|
#include "../chat/invite_build.h" |
|
|
|
|
#include "../chat/chat_join.h" |
|
|
|
|
#include "../utun_instance.h" |
|
|
|
|
#include "../ntp_time.h" |
|
|
|
|
#include "../routing_layer/etcp_router.h" |
|
|
|
|
@ -45,6 +47,7 @@ struct dm_state {
|
|
|
|
|
sqlite3* db; |
|
|
|
|
int initialized; |
|
|
|
|
void* timer; |
|
|
|
|
struct dm_invite_req* invite; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
/* ── вспомогательные ── */ |
|
|
|
|
@ -833,6 +836,7 @@ fail:
|
|
|
|
|
void dm_core_destroy(struct UTUN_INSTANCE* inst) { |
|
|
|
|
struct dm_state* dm = dm_of(inst); |
|
|
|
|
if (!dm) return; |
|
|
|
|
if (dm->invite) chat_invite_build_cancel(dm->invite->pending); |
|
|
|
|
if (dm->timer) uasync_cancel_timeout(inst->ua, dm->timer); |
|
|
|
|
dm->timer = NULL; |
|
|
|
|
dm->initialized = 0; |
|
|
|
|
@ -1032,6 +1036,93 @@ void dm_send_trampoline(void* arg) {
|
|
|
|
|
DM_ID, conv); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Завершить операцию и вернуть результат с неизменяемым адресатом. */ |
|
|
|
|
static void dm_invite_done(struct dm_invite_req* req, int result) { |
|
|
|
|
struct dm_state* dm = dm_of(req->inst); |
|
|
|
|
if (dm && dm->invite == req) dm->invite = NULL; |
|
|
|
|
uint64_t conv = dm_derive_conv_id(req->inst->node_id, req->peer_node_id); |
|
|
|
|
int32_t status = result; |
|
|
|
|
uint8_t event[28]; |
|
|
|
|
memcpy(event, &req->request_id, 8); memcpy(event + 8, &status, 4); |
|
|
|
|
memcpy(event + 12, &req->peer_node_id, 8); memcpy(event + 20, &conv, 8); |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_DM, "invite PM: finished request=%llu peer=%llu ch=%llu result=%d", |
|
|
|
|
(unsigned long long)req->request_id, (unsigned long long)req->peer_node_id, |
|
|
|
|
(unsigned long long)req->channel_id, result); |
|
|
|
|
chat_event_post(req->inst, CHAT_EVT_DM_INVITE_RESULT, event, sizeof(event)); |
|
|
|
|
u_free(req); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* ACK регистрации разрешает отправку ссылки; ошибка/отмена не создаёт сообщение. */ |
|
|
|
|
static void dm_invite_ready(void* arg, int result, const char* link) { |
|
|
|
|
struct dm_invite_req* req = arg; |
|
|
|
|
req->pending = NULL; |
|
|
|
|
if (result == CHAT_JOIN_CANCELLED) { dm_invite_done(req, DM_INVITE_CANCELLED); return; } |
|
|
|
|
if (result != CHAT_JOIN_OK || !link || !link[0]) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_DM, "invite PM: registration failed request=%llu ch=%llu result=%d", |
|
|
|
|
(unsigned long long)req->request_id, (unsigned long long)req->channel_id, result); |
|
|
|
|
dm_invite_done(req, DM_INVITE_BUILD_ERROR); return; |
|
|
|
|
} |
|
|
|
|
char conv[64]; |
|
|
|
|
snprintf(conv, sizeof(conv), "%llu", (unsigned long long)dm_derive_conv_id(req->inst->node_id, req->peer_node_id)); |
|
|
|
|
size_t bytes = strlen(link); |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_DM, "invite PM: registered request=%llu peer=%llu ch=%llu bytes=%zu ttl=%ds", |
|
|
|
|
(unsigned long long)req->request_id, (unsigned long long)req->peer_node_id, |
|
|
|
|
(unsigned long long)req->channel_id, bytes, JOIN_KEY_TTL_SECONDS); |
|
|
|
|
int rc = dm_send(req->inst, conv, "text", (const uint8_t*)link, (uint32_t)bytes); |
|
|
|
|
if (rc) DEBUG_ERROR(DEBUG_CATEGORY_DM, "invite PM: send failed request=%llu conv=%s rc=%d", |
|
|
|
|
(unsigned long long)req->request_id, conv, rc); |
|
|
|
|
dm_invite_done(req, rc ? DM_INVITE_SEND_ERROR : DM_INVITE_OK); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Создать/найти PM, затем зарегистрировать свежий invite; все данные принадлежат запросу. */ |
|
|
|
|
void dm_invite_trampoline(void* arg) { |
|
|
|
|
struct dm_invite_req* req = arg; |
|
|
|
|
if (!req) { DEBUG_ERROR(DEBUG_CATEGORY_DM, "invite PM: missing request"); return; } |
|
|
|
|
struct dm_state* dm = dm_of(req->inst); |
|
|
|
|
if (req->cancel) { |
|
|
|
|
if (dm && dm->invite && dm->invite->request_id == req->request_id) { |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_DM, "invite PM: cancelling request=%llu", (unsigned long long)req->request_id); |
|
|
|
|
chat_invite_build_cancel(dm->invite->pending); |
|
|
|
|
} else DEBUG_DEBUG(DEBUG_CATEGORY_DM, "invite PM: cancel already completed request=%llu", |
|
|
|
|
(unsigned long long)req->request_id); |
|
|
|
|
u_free(req); return; |
|
|
|
|
} |
|
|
|
|
if (!req->inst) { DEBUG_ERROR(DEBUG_CATEGORY_DM, "invite PM: missing instance"); u_free(req); return; } |
|
|
|
|
if (!dm || !dm->initialized || !req->request_id || !req->peer_node_id || req->peer_node_id == req->inst->node_id) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_DM, "invite PM: invalid state/recipient request=%llu peer=%llu", |
|
|
|
|
(unsigned long long)req->request_id, (unsigned long long)req->peer_node_id); |
|
|
|
|
dm_invite_done(req, DM_INVITE_START_ERROR); return; |
|
|
|
|
} |
|
|
|
|
if (dm->invite) { |
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_DM, "invite PM: busy request=%llu pending=%llu", |
|
|
|
|
(unsigned long long)req->request_id, (unsigned long long)dm->invite->request_id); |
|
|
|
|
dm_invite_done(req, DM_INVITE_BUSY); return; |
|
|
|
|
} |
|
|
|
|
char channel[64], conv[64]; |
|
|
|
|
snprintf(channel, sizeof(channel), "%llu", (unsigned long long)req->channel_id); |
|
|
|
|
struct TOPO_GROUP* group = topo_groups_find(req->inst->topo_groups, req->channel_id); |
|
|
|
|
if (!group || group->group_type != TOPO_GROUP_TYPE_CHAT || |
|
|
|
|
!topo_node_sqlite_member_in_channel(dm->db, channel, req->inst->node_id)) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_DM, "invite PM: no active membership ch=%s", channel); |
|
|
|
|
dm_invite_done(req, DM_INVITE_BUILD_ERROR); return; |
|
|
|
|
} |
|
|
|
|
snprintf(conv, sizeof(conv), "%llu", (unsigned long long)dm_derive_conv_id(req->inst->node_id, req->peer_node_id)); |
|
|
|
|
struct dm_conv c; |
|
|
|
|
if (dm_conv_load(dm, conv, &c) != 0) { |
|
|
|
|
uint8_t x25519[32], ed25519[32]; |
|
|
|
|
if (dm_node_pubkeys(dm, req->peer_node_id, x25519, ed25519) != 0 || |
|
|
|
|
dm_start(req->inst, req->peer_node_id, x25519, ed25519, req->peer_name, req->source_ch_id) != 0) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_DM, "invite PM: conversation failed peer=%llu", (unsigned long long)req->peer_node_id); |
|
|
|
|
dm_invite_done(req, DM_INVITE_START_ERROR); return; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_DM, "invite PM: building request=%llu peer=%llu source=%s ch=%s conv=%s", |
|
|
|
|
(unsigned long long)req->request_id, (unsigned long long)req->peer_node_id, req->source_ch_id, channel, conv); |
|
|
|
|
dm->invite = req; |
|
|
|
|
req->pending = chat_invite_build_link(req->inst, req->channel_id, 0, NULL, dm_invite_ready, req); |
|
|
|
|
if (!req->pending) dm_invite_ready(req, CHAT_JOIN_ERROR, NULL); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Единая BGP-проверка для кнопки PM и запуска звонка; только uasync-поток. */ |
|
|
|
|
int dm_call_route(struct UTUN_INSTANCE* inst, const char* conv_id, uint64_t* peer, uint64_t* group_id) { |
|
|
|
|
struct dm_state* dm = dm_of(inst); |
|
|
|
|
|