Browse Source

Add cancellable PM invitation operation with correlated results

master
evgeny 1 week ago
parent
commit
6f99ad3103
  1. 1
      src/chat/chat_event.h
  2. 91
      src/dm/dm_core.c
  3. 19
      src/dm/dm_core.h

1
src/chat/chat_event.h

@ -69,6 +69,7 @@ extern "C" {
#define CHAT_EVT_ATTACHMENT_PREPARATION 46 /* [target_len:1][target:var][job_uuid:16][state:1], 0=start 1=ready 2=failed */
#define CHAT_EVT_DM_CALL_ROUTE 47 /* [conv_id_len:1][conv_id:var][peer_node_id:8][group_id:8], 0 = нет BGP-пути */
#define CHAT_EVT_DM_INVITE_RESULT 48 /* [request_id:8][result:4][peer_node_id:8][conv_id:8], dm_invite_result */
typedef void (*chat_event_handler_fn)(struct UTUN_INSTANCE* inst, int type, const uint8_t* data, int len);

91
src/dm/dm_core.c

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

19
src/dm/dm_core.h

@ -114,6 +114,25 @@ struct dm_send_req {
};
void dm_send_trampoline(void* arg);
/* Приглашение в выбранный канал через PM: свежая ссылка после ACK регистрации,
* затем обычный dm_send("text"). Получатель фиксирован независимо от текущего UI.
* Один ожидающий запрос на instance; результат CHAT_EVT_DM_INVITE_RESULT.
* cancel=1 отменяет только ожидающий request_id; отправленное PM не отзывается.
* arg=u_calloc(dm_invite_req); обработчик/финальный callback освобождает arg. */
enum dm_invite_result {
DM_INVITE_OK = 0, DM_INVITE_START_ERROR = -1, DM_INVITE_BUILD_ERROR = -2,
DM_INVITE_SEND_ERROR = -3, DM_INVITE_CANCELLED = -4, DM_INVITE_BUSY = -5
};
struct dm_invite_req {
struct UTUN_INSTANCE* inst;
uint64_t request_id, peer_node_id, channel_id;
char source_ch_id[64]; /* общая группа для новой PM; из шапки PM может быть пустой */
char peer_name[128];
int cancel;
struct chat_invite_build* pending; /* только uasync */
};
void dm_invite_trampoline(void* arg);
/* Запросить последние 200 сообщений со статусами. CHAT_EVT_DM_MESSAGES:
* [conv_id_len:1][conv_id:var][JSON:utf8]. Все чтения в uasync. */
struct dm_messages_req {

Loading…
Cancel
Save