Files
utun2/src/dm/dm_core.h
T

155 lines
9.3 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* dm_core.h — прямой p2p чат между двумя пользователями (DM)
*
* Личная беседа не создаёт отдельную TOPO_GROUP и не синхронизируется через member_sync/Merkle.
* Для доставки использует маршрут из общей CHAT-группы участников.
* - conv_id и content_key детерминированно выводятся обеими сторонами
* из своих ключей и pubkey пира (см. dm_crypto.h) — без переговоров.
* - сообщения идут через etcp_router (прямое соединение или релей),
* при недоступности пира — через dm_mailbox (суперузлы).
* - содержимое шифруется E2E (AES-256-CCM), запись подписывается Ed25519.
*
* Два независимых направленных потока (я→пир и пир→я) с монотонным seq.
*/
#ifndef DM_CORE_H
#define DM_CORE_H
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include "dm_media.h"
struct UTUN_INSTANCE;
#ifdef __cplusplus
extern "C" {
#endif
/* ── Каноническое тело DM-сообщения (общий wire-формат dm_core и dm_mailbox) ──
* [conv_id:8][seq:8][ts:8][author:8][ct_len:1][ct:ct_len][data_len:2][data_enc:data_len][media?:56][sig:64]
* media присутствует только для ct="file", подписан вместе с сообщением.
* data_enc — AES-256-CCM (шифротекст + tag), sig — Ed25519 автора по всем полям до sig. */
#define DM_MSG_FIXED_HDR 33 /* conv_id+seq+ts+author(32) + ct_len(1) — до ct */
#define DM_MSG_SIG_SIZE 64
#define DM_DATA_MAX 1024
#define DM_RECEIPT_SIZE 136
/* Вычислить полную длину канонического тела из его заголовка.
* Возвращает 1 и *out_len, либо 0 если буфер мал/повреждён. */
static inline int dm_msg_len(const uint8_t* msg, size_t avail, size_t* out_len) {
if (!msg || !out_len || avail < DM_MSG_FIXED_HDR) return 0;
uint8_t ct_len = msg[32];
if (avail < (size_t)DM_MSG_FIXED_HDR + ct_len + 2) return 0;
uint16_t data_len;
memcpy(&data_len, msg + DM_MSG_FIXED_HDR + ct_len, 2);
if (data_len < 16 || data_len > DM_DATA_MAX + 16) return 0;
size_t media_len = ct_len == 4 && !memcmp(msg + DM_MSG_FIXED_HDR, "file", 4) ? DM_MEDIA_DESC_SIZE : 0;
size_t total = (size_t)DM_MSG_FIXED_HDR + ct_len + 2 + data_len + media_len + DM_MSG_SIG_SIZE;
if (avail < total) return 0;
*out_len = total;
return 1;
}
/* Общие операции протокола DM; все вызовы в uasync-потоке.
* group_id — только область маршрутизации, идентичность беседы от неё не зависит.
* route_group выбирает общую группу с живым путём, возвращает 1/0.
* verify_message проверяет каноническое тело и подпись автора для recipient.
* accept_message возвращает 0 только после commit (включая точный повтор).
* Квитанция: ["DMACK001":8][conv:8][seq:8][author:8][recipient:8][SHA256(msg):32][sig:64].
* verify_receipt проверяет подпись получателя и при msg!=NULL точное соответствие телу.
* accept_receipt удаляет только соответствующее исходящее pending-сообщение. */
int dm_route_group(struct UTUN_INSTANCE* inst, uint64_t peer, uint64_t preferred, uint64_t* group_id);
int dm_verify_message(struct UTUN_INSTANCE* inst, uint64_t recipient, const uint8_t* msg, size_t len);
int dm_accept_message(struct UTUN_INSTANCE* inst, const uint8_t* msg, size_t len, uint8_t receipt[DM_RECEIPT_SIZE]);
int dm_verify_receipt(struct UTUN_INSTANCE* inst, const uint8_t receipt[DM_RECEIPT_SIZE], const uint8_t* msg, size_t len);
int dm_accept_receipt(struct UTUN_INSTANCE* inst, const uint8_t receipt[DM_RECEIPT_SIZE]);
/* Жизненный цикл: инициализация после chat_core_init, destroy перед chat_core_destroy. */
int dm_core_init(struct UTUN_INSTANCE* inst);
void dm_core_destroy(struct UTUN_INSTANCE* inst);
/* Начать беседу с пользователем (peer). source_ch_id — канал, где нашли target
* (общая группа; нужен для маршрутизации и подключения). Создаёт беседу если её
* ещё нет, выводит conv_id/ключи. Подключение для передачи медиа принадлежит media.
* Возвращает 0 при успехе, <0 при ошибке. */
int dm_start(struct UTUN_INSTANCE* inst, uint64_t peer_node_id,
const uint8_t peer_x25519[32], const uint8_t peer_ed25519[32],
const char* peer_name, const char* source_ch_id);
/* Отправить сообщение в беседу. Шифрует, подписывает, сохраняет локально и
* доставляет (прямое соединение при доступности пира, иначе mailbox).
* conv_id — строковое представление (как в dm_conversations.conv_id).
* Для ct="file" data=[descriptor:56][basename:utf8], только после подготовки dm_media.
* Для ct="invite" data=[group_name:utf8][NUL][полная ссылка:utf8], без завершающего NUL.
* Имя и ссылка шифруются и подписываются вместе; GUI получает data=ссылка, tags.group_name=имя. */
int dm_send(struct UTUN_INSTANCE* inst, const char* conv_id, const char* content_type,
const uint8_t* data, uint32_t data_len);
/* Список бесед в JSON: [{conv_id,peer_node_id,peer_name,last_ts,out_seq,in_seq}] */
int dm_list_conversations_json(struct UTUN_INSTANCE* inst, char* buf, size_t buf_size, size_t* out_len);
/* Сообщения беседы в JSON (расшифрованные): [{dir,seq,ts,author,ct,data}]
* count=0 → все, offset=0 → с начала. */
int dm_list_messages_json(struct UTUN_INSTANCE* inst, const char* conv_id, int count, int offset,
char* buf, size_t buf_size, size_t* out_len);
/* ── Трамплины для gui_bridge (GUI → uasync) ── */
/* Начать беседу с узлом из общей группы. Трамплин сам находит x25519/ed25519
* пира в таблице nodes (как headless hc_handle_dm_start), после чего вызывает
* dm_start(). Результат — событие CHAT_EVT_DM_CONV_UPDATED. */
struct dm_start_req {
struct UTUN_INSTANCE* inst;
uint64_t peer_node_id;
char source_ch_id[64];
char peer_name[128];
};
void dm_start_trampoline(void* arg);
/* Отправить текстовое сообщение в беседу (content_type="text"). */
struct dm_send_req {
struct UTUN_INSTANCE* inst;
char conv_id[64];
uint32_t data_len;
uint8_t* data; /* data_len байт (выделяется в хвосте структуры) */
};
void dm_send_trampoline(void* arg);
/* Приглашение в выбранный канал через PM: свежая ссылка после ACK регистрации,
* затем dm_send("invite") с названием группы. Получатель фиксирован независимо от текущего 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 {
struct UTUN_INSTANCE* inst;
char conv_id[64];
};
void dm_messages_trampoline(void* arg);
/* Живой BGP-маршрут PM; group_id=0 в снимке означает недоступность. Все вызовы в uasync. */
int dm_call_route(struct UTUN_INSTANCE* inst, const char* conv_id, uint64_t* peer, uint64_t* group_id);
void dm_call_route_trampoline(void* arg); /* аргумент: dm_messages_req */
struct dm_conversations_req { struct UTUN_INSTANCE* inst; };
void dm_conversations_trampoline(void* arg);
#ifdef __cplusplus
}
#endif
#endif /* DM_CORE_H */