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.
132 lines
7.7 KiB
132 lines
7.7 KiB
/* |
|
* dm_core.h — прямой p2p чат между двумя пользователями (DM) |
|
* |
|
* Отдельная подсистема (НЕ канал): без TOPO_GROUP/member_sync/merkle. |
|
* - 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. */ |
|
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); |
|
|
|
/* Запросить последние 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 */
|
|
|