docs(dm): clarify core and history ownership contracts
This commit is contained in:
1 parent
492f6adc99
commit
2fe024fbbb
2 files changed
+69
-35
No files matched your search
+59
-31
@@ -9,7 +9,11 @@
|
||||
* при недоступности пира — через dm_mailbox (суперузлы).
|
||||
* - содержимое шифруется E2E (AES-256-CCM), запись подписывается Ed25519.
|
||||
*
|
||||
* Два независимых направленных потока (я→пир и пир→я) с монотонным seq.
|
||||
* Два независимых направленных потока (я→пир и пир→я) с монотонным исходящим seq.
|
||||
* Все API и трамплины выполняются в uasync-потоке с живым instance/SQLite/router.
|
||||
* Входные строки/тела заимствуются на время вызова; функции не забирают их владение.
|
||||
* Запросы трамплинов — один блок u_malloc/u_calloc с NUL-строками; обработчик освобождает блок.
|
||||
* Instance должен пережить запрос и его отложенное завершение; остановка сериализуется через uasync.
|
||||
*/
|
||||
|
||||
#ifndef DM_CORE_H
|
||||
@@ -29,7 +33,9 @@ extern "C" {
|
||||
/* ── Каноническое тело 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. */
|
||||
* data_enc — AES-256-CCM (шифротекст + tag), sig — Ed25519 автора по всем полям до sig.
|
||||
* Числа native-endian; seq=1..INT64_MAX, ts — миллисекунды NTP-времени instance.
|
||||
* ETCP_RT_ID_DM=0x34: [MSG=1][тело] либо [ACK=2][квитанция:136]; router-пакет подписан. */
|
||||
#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
|
||||
@@ -54,22 +60,29 @@ static inline int dm_msg_len(const uint8_t* msg, size_t avail, size_t* out_len)
|
||||
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-сообщение. */
|
||||
/* Выбрать общую CHAT-группу с путём router к peer, сначала preferred (0=без предпочтения).
|
||||
* 1 и *group_id при успехе; 0 при отсутствии пути/ошибке, выход не меняется.
|
||||
* group_id обязателен; наличие пути не гарантирует успешной последующей отправки. */
|
||||
int dm_route_group(struct UTUN_INSTANCE* inst, uint64_t peer, uint64_t preferred, uint64_t* group_id);
|
||||
/* Проверить точную длину msg/len, conv/author/recipient/seq, descriptor и подпись автора из nodes.
|
||||
* 0=корректно, -1=отказ. Нужна SQLite, владелец dm_core не требуется.
|
||||
* Не расшифровывает тело и не проверяет членство/роль router-отправителя: это обязанность вызывающего. */
|
||||
int dm_verify_message(struct UTUN_INSTANCE* inst, uint64_t recipient, const uint8_t* msg, size_t len);
|
||||
/* Проверить и расшифровать входящее тело для локального узла, атомарно сохранить историю/беседу/media.
|
||||
* 0 и receipt после commit либо точного повтора; -1 при отказе. Для file нужен живой dm_media.
|
||||
* Точный удалённый повтор подтверждается по отпечатку без восстановления истории; конфликт отклоняется.
|
||||
* receipt — обязательные 136 байт; при -1 буфер не использовать. Ошибка подписи квитанции возможна после commit. */
|
||||
int dm_accept_message(struct UTUN_INSTANCE* inst, const uint8_t* msg, size_t len, uint8_t receipt[DM_RECEIPT_SIZE]);
|
||||
/* receipt — ровно 136 доступных байт: ["DMACK001":8][conv:8][seq:8][author:8][recipient:8][SHA256(msg):32][sig:64].
|
||||
* Проверяет идентичность и подпись B по первым 72 байтам; при msg!=NULL также точное тело msg/len.
|
||||
* 0=корректно, -1=отказ. Не проверяет наличие outbox и ничего не записывает. */
|
||||
int dm_verify_receipt(struct UTUN_INSTANCE* inst, const uint8_t receipt[DM_RECEIPT_SIZE], const uint8_t* msg, size_t len);
|
||||
/* Для локального автора проверить receipt и удалить только точный outbox; история/file receipt сохраняются.
|
||||
* 0=удалено либо outbox уже отсутствует, -1=ошибка/конфликт. Требуется запущенный dm_core. */
|
||||
int dm_accept_receipt(struct UTUN_INSTANCE* inst, const uint8_t receipt[DM_RECEIPT_SIZE]);
|
||||
|
||||
/* Жизненный цикл в uasync: init после chat_core_init с SQLite/router/ua ядра; 0=успех, -1=ошибка.
|
||||
* Повторный init запущенного владельца возвращает 0. Для send/accept нужен также dm_media_init.
|
||||
* Повторный init запущенного владельца возвращает 0. Для file-send/accept нужен также dm_media_init.
|
||||
* destroy отменяет invite/таймер и снимает bind; БД/outbox сохраняются. Вызывать до chat_core_destroy. */
|
||||
int dm_core_init(struct UTUN_INSTANCE* inst);
|
||||
void dm_core_destroy(struct UTUN_INSTANCE* inst);
|
||||
@@ -99,9 +112,16 @@ int dm_start(struct UTUN_INSTANCE* inst, uint64_t peer_node_id,
|
||||
int dm_send(struct UTUN_INSTANCE* inst, const char* conv_id, const char* content_type,
|
||||
const uint8_t* data, uint32_t data_len);
|
||||
|
||||
/* Локальное удаление; generation защищает повторно открытую беседу от старого запроса. */
|
||||
struct dm_delete_req { struct UTUN_INSTANCE* inst; char conv_id[64]; uint64_t generation, request_id; };
|
||||
/* Локальное удаление по dm_history_delete; generation защищает повторно открытую беседу.
|
||||
* CHAT_EVT_DM_DELETE_RESULT: [request_id:8][conv:u64][generation:8][success:u8], всего 25 байт native-endian. */
|
||||
struct dm_delete_req {
|
||||
struct UTUN_INSTANCE* inst; /* Заимствованное ядро до завершения запроса. */
|
||||
char conv_id[64]; /* Десятичный ID беседы с NUL. */
|
||||
uint64_t generation; /* Ненулевое ожидаемое поколение. */
|
||||
uint64_t request_id; /* Корреляция результата с запросом UI. */
|
||||
};
|
||||
void dm_delete_trampoline(void* arg);
|
||||
/* После commit удаления отменить ожидающий invite этой беседы; квитанции/другие беседы не меняет. */
|
||||
void dm_cancel_pending_history(struct UTUN_INSTANCE* inst, const char* conv);
|
||||
|
||||
/* До 200 видимых бесед по last_ts DESC: [{conv_id,peer_node_id,peer_name,last_ts,out_seq,in_seq,generation}].
|
||||
@@ -113,7 +133,9 @@ int dm_list_conversations_json(struct UTUN_INSTANCE* inst, char* buf, size_t buf
|
||||
* count=1..200 задаёт предел; count<=0 или count>200 означает 200.
|
||||
* offset>=0 отсчитывается от новых сообщений; offset=0 выбирает последние count.
|
||||
* Выбранная страница возвращается по возрастанию ts, seq, dir.
|
||||
* buf — выходной буфер с NUL; 0 и *out_len байт без NUL при успехе, -1 при ошибке. */
|
||||
* buf — выходной буфер с NUL; 0 и *out_len байт без NUL при успехе, -1 при ошибке.
|
||||
* pending относится к квитанции сообщения. media_status: delivered/ready/downloading/expired/pending.
|
||||
* hidden здесь не фильтруется; интерфейс выбирает видимую беседу по списку/поколению. */
|
||||
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);
|
||||
|
||||
@@ -123,20 +145,20 @@ int dm_list_messages_json(struct UTUN_INSTANCE* inst, const char* conv_id, int c
|
||||
* пира в таблице 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];
|
||||
struct UTUN_INSTANCE* inst; /* Заимствованное ядро. */
|
||||
uint64_t peer_node_id; /* Другой участник общей группы; ключи читаются из nodes. */
|
||||
char source_ch_id[64]; /* Десятичный ID общей группы либо пустая строка. */
|
||||
char peer_name[128]; /* UTF-8 имя с NUL; dm_start ограничит до 63 байт. */
|
||||
};
|
||||
void dm_start_trampoline(void* arg);
|
||||
|
||||
/* Отправить текстовое сообщение в беседу (content_type="text"). */
|
||||
struct dm_send_req {
|
||||
struct UTUN_INSTANCE* inst;
|
||||
char conv_id[64];
|
||||
struct UTUN_INSTANCE* inst; /* Заимствованное ядро. */
|
||||
char conv_id[64]; /* Десятичный ID беседы с NUL. */
|
||||
uint64_t generation; /* 0: захватить при входе в uasync, до любой фоновой работы. */
|
||||
uint32_t data_len;
|
||||
uint8_t* data; /* data_len байт (выделяется в хвосте структуры) */
|
||||
uint32_t data_len; /* Число байт текста, до DM_DATA_MAX. */
|
||||
uint8_t* data; /* Указывает на data_len байт в том же блоке после структуры. */
|
||||
};
|
||||
void dm_send_trampoline(void* arg);
|
||||
|
||||
@@ -151,12 +173,15 @@ enum dm_invite_result {
|
||||
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, generation;
|
||||
struct UTUN_INSTANCE* inst; /* Заимствованное ядро до итогового callback. */
|
||||
uint64_t request_id; /* Ненулевой ID; cancel использует тот же ID. */
|
||||
uint64_t peer_node_id; /* Фиксированный получатель, не локальный узел. */
|
||||
uint64_t channel_id; /* Выбранная активная CHAT-группа отправителя. */
|
||||
uint64_t generation; /* Заполняет uasync до регистрации; проверяет callback. */
|
||||
char source_ch_id[64]; /* общая группа для новой PM; из шапки PM может быть пустой */
|
||||
char peer_name[128];
|
||||
int cancel;
|
||||
struct chat_invite_build* pending; /* только uasync */
|
||||
char peer_name[128]; /* UTF-8 имя для новой беседы с NUL. */
|
||||
int cancel; /* 0=создать, 1=отменить ожидающий request_id. */
|
||||
struct chat_invite_build* pending; /* Внутренний handle; запрос создаётся обнулённым. */
|
||||
};
|
||||
void dm_invite_trampoline(void* arg);
|
||||
|
||||
@@ -164,15 +189,18 @@ void dm_invite_trampoline(void* arg);
|
||||
* [conv_id_len:1][conv_id:var][generation:8][JSON:utf8]. Все чтения в uasync.
|
||||
* Ненулевое generation проверяется до чтения; 0 захватывает текущее поколение. GUI отбрасывает старый снимок. */
|
||||
struct dm_messages_req {
|
||||
struct UTUN_INSTANCE* inst;
|
||||
char conv_id[64];
|
||||
struct UTUN_INSTANCE* inst; /* Заимствованное ядро. */
|
||||
char conv_id[64]; /* Десятичный ID с NUL, также для запроса маршрута звонка. */
|
||||
uint64_t generation; /* Поколение выбранного PM; для запроса маршрута не используется. */
|
||||
};
|
||||
void dm_messages_trampoline(void* arg);
|
||||
/* Живой BGP-маршрут PM; group_id=0 в снимке означает недоступность. Все вызовы в uasync. */
|
||||
/* 1 при живом BGP-маршруте общей группы, 0 при недоступности/ошибке. peer/group_id обязательны.
|
||||
* При корректных аргументах выходы сначала обнуляются; peer может быть известен и без маршрута.
|
||||
* Трамплин выдаёт CHAT_EVT_DM_CALL_ROUTE: [conv_len:1][conv][peer:8][group:8]; group=0 без маршрута. */
|
||||
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; };
|
||||
/* Снимок бесед публикуется как CHAT_EVT_DM_CONVERSATIONS с JSON без NUL. */
|
||||
struct dm_conversations_req { struct UTUN_INSTANCE* inst; /* Заимствованное ядро. */ };
|
||||
void dm_conversations_trampoline(void* arg);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
+10
-4
@@ -1,7 +1,9 @@
|
||||
/* Локальная история DM: поколения, скрытые беседы и точные отпечатки удалённых сообщений.
|
||||
* Все операции выполняются в uasync с SQLite ядра. init вызывается после создания DM-таблиц.
|
||||
* Удаление скрывает беседу и сохраняет её ключи/счётчики в dm_conversations; show открывает её снова.
|
||||
* Поколение передавать в отложенные операции и проверять current перед применением результата. */
|
||||
* Поколение передавать в отложенные операции и проверять current перед применением результата.
|
||||
* Беседой/seq владеет dm_core, файлами/загрузками — dm_media; этот модуль связывает их удаление.
|
||||
* Аргументы заимствованы на время вызова; самостоятельные транзакции выполняются без внешней транзакции. */
|
||||
#ifndef DM_HISTORY_H
|
||||
#define DM_HISTORY_H
|
||||
#include <stdint.h>
|
||||
@@ -10,7 +12,8 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
struct UTUN_INSTANCE;
|
||||
/* Создать служебные таблицы, сбросить busy файлов после restart. 0=успех, -1=ошибка.
|
||||
/* Создать dm_history, dm_deleted_messages и dm_local_files; сбросить busy после restart.
|
||||
* 0=успех, -1=ошибка. Вызывает dm_core_init; повторять при живых workers нельзя: они ещё держат пути.
|
||||
* inst и его topo_sqlite_db обязательны; частично созданные таблицы при ошибке остаются. */
|
||||
int dm_history_init(struct UTUN_INSTANCE* inst);
|
||||
/* Поколение существующей беседы: 1 до первого удаления; 0 при отсутствии/ошибке.
|
||||
@@ -28,8 +31,11 @@ int dm_history_show(struct UTUN_INSTANCE* inst, const char* conv);
|
||||
int dm_history_deleted(struct UTUN_INSTANCE* inst, const uint8_t* body, size_t len, int dir, uint8_t* media_receipt);
|
||||
/* Удалить видимую беседу текущего generation<INT64_MAX; DM/core/media должны быть запущены.
|
||||
* Самостоятельная транзакция: отпечатки, удаление истории/outbox/медиа, hidden=1, generation+1.
|
||||
* 0 после commit и синхронной отмены операций; -1 при отказе с rollback SQL-изменений.
|
||||
* Файлы убираются отдельно владельцем media; встречный узел и его историю вызов не меняет. */
|
||||
* 0 после commit, отмены invite/transfer и запуска уборки; -1 при отказе с rollback SQL-изменений.
|
||||
* Это не ожидание workers: они сохраняют busy пути и отбрасывают результат старого поколения.
|
||||
* Файлы убираются отдельно по durable манифесту после release; ошибку удаления повторяет media.
|
||||
* Квитанция файла сохраняется только если уже была: удаление не подтверждает скачивание.
|
||||
* Встречный узел, его история и промежуточные S/M-копии этим вызовом не меняются. */
|
||||
int dm_history_delete(struct UTUN_INSTANCE* inst, const char* conv, uint64_t generation);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user