docs(dm): specify media protocol and worker lifetime

This commit is contained in:
evgeny committed 2026-10-27 01:06:45 +01:00
1 parent 1cfafc43a4
commit ad577318d9
2 files changed
+69 -18

No files matched your search

+58 -14
View File
@@ -1,10 +1,13 @@
/* dm_media — файлы личных бесед и ограниченное ciphertext-хранилище M.
* Шифрование/проверка/публикация файлов в media_async; БД и сеть в uasync.
* Шифрование/проверка/публикация файлов в media_async; все API ниже, БД и сеть — в uasync.
* dm_core вызывает track внутри своей транзакции. Квитанция файла независима
* от квитанции сообщения; создаётся после проверки, публикации и commit.
* Суперузлом и заданиями удаления владеет dm_mailbox_media.
* Квота M проверяется после успешного чтения занятого места и finalize SQL-запроса.
* Ошибки SQL логируются без REJECT, сохраняя штатный повтор STORE со стороны S.
* Instance/SQLite/группы должны пережить workers и их callbacks; остановка — quiesce, drain, destroy.
* Входные строки/буферы заимствуются до возврата, кроме явно переданного временного файла.
* Из C++ этот C API включать внутри extern "C", как в клиентах Qt/JNI.
*/
#ifndef DM_MEDIA_H
#define DM_MEDIA_H
@@ -15,10 +18,17 @@
struct UTUN_INSTANCE;
#define DM_MEDIA_DESC_SIZE 56 /* UUID:16, plaintext_size:8, SHA256(ciphertext):32 */
#define DM_MEDIA_DESC_SIZE 56 /* UUID:16, plaintext_size:8 native-endian, SHA256(ciphertext):32. */
#define DM_MEDIA_RECEIPT_SIZE 152
#define DM_MEDIA_MAX_SIZE (1024ULL * 1024 * 1024)
/* ETCP_RT_ID_DM_MEDIA=0x38: [cmd:1][body], router-подпись обязательна; числа native-endian.
* OFFER: [recipient:8][holder:8][expires:8][каноническое file-сообщение]. A предлагает себя с expires=0.
* STORE: [recipient:8][file-сообщение] от S к M; STORED: [UUID:16][expires:8].
* RECEIPT/DELETE: ["DMFILE01":8][conv:8][seq:8][author:8][recipient:8][UUID:16][SHA256(message):32][sig_B:64].
* B подписывает первые 88 байт; DELETE переносит ту же квитанцию, не создаёт новую.
* DELETE_ACK/EXPIRED: [UUID:16]; REJECT: [UUID:16][reason:1], reason=1 — недостаток квоты.
* EXPIRED завершает промежуточное хранение, а не подтверждает доставку B. */
#define DM_MEDIA_OFFER 1
#define DM_MEDIA_STORE 2
#define DM_MEDIA_STORED 3
@@ -28,47 +38,81 @@ struct UTUN_INSTANCE;
#define DM_MEDIA_EXPIRED 7
#define DM_MEDIA_REJECT 8
/* После dm_core_init с SQLite/config/router/ua и media_async ядра: таблицы, каталоги, file_transfer, bind и таймер.
* 0=успех, -1=ошибка, в том числе повторный init живого owner. При отказе созданные таблицы/каталоги остаются.
* Корень: db_path/dm_media и db_path/dm_pending; без db_path используется текущий каталог.
* После restart сбрасываются downloading/retry_at; история, custody и квитанции сохраняются. */
int dm_media_init(struct UTUN_INSTANCE* inst);
/* quiesce до media_async_destroy; destroy после workers, до групп/core/mailbox. */
/* Идемпотентно запретить новые задачи, отменить таймер/bind и file_transfer с его CM handles.
* Вызвать до media_async_destroy; callbacks отмены ещё используют живые owner/SQLite/группы. NULL допустим. */
void dm_media_quiesce(struct UTUN_INSTANCE* inst);
/* После drain/destroy media_async, до групп/core/mailbox: освободить owner, сохранить durable данные.
* При живых jobs/cleanup_active логирует ошибку и оставляет owner для повторного destroy. NULL допустим. */
void dm_media_destroy(struct UTUN_INSTANCE* inst);
/* Передать подготовку копии и enqueue file-сообщения в media_async.
* Строки conv/path и info копируются при вызове; сам файл должен быть доступен worker до завершения.
* 0=submit вызван, -1=отказ до submit. Submit может синхронно завершиться ошибкой через callback;
* готовность/ошибка подготовки сообщается CHAT_EVT_ATTACHMENT_PREPARATION, включая до возврата0.
* remove_source=1 передаёт worker собственный временный файл приложения для удаления; при -1 его убирает caller. */
* готовность/ошибка подготовки сообщается CHAT_EVT_ATTACHMENT_PREPARATION, включая до возврата 0.
* Беседа должна быть видимой; до 4 одновременных подготовок, plaintext до DM_MEDIA_MAX_SIZE.
* remove_source=1 передаёт worker собственный временный файл приложения для удаления; при -1 его убирает caller.
* Успех подготовки означает commit file-сообщения, не доставку. Пользовательский исходник: remove_source=0. */
int dm_send_file(struct UTUN_INSTANCE* inst, const char* conv, const char* path, const struct attachment_info* info,
int remove_source);
struct dm_file_req {
struct UTUN_INSTANCE* inst;
char conv[64];
uint64_t generation;
char path[1024];
struct attachment_info info;
struct UTUN_INSTANCE* inst; /* Заимствованное ядро до callback. */
char conv[64]; /* Десятичный ID беседы с NUL. */
uint64_t generation; /* Ожидаемое поколение; 0=захватить при входе в uasync. */
char path[1024]; /* Путь исходника с NUL; копируется при принятии. */
struct attachment_info info; /* Проверенные метаданные вложения. */
int remove_source; /* Только принадлежащий приложению временный файл. */
};
/* arg — один блок u_malloc/u_calloc(dm_file_req); трамплин освобождает его, также при отказе.
* Проверяет generation до submit; непринятый remove_source удаляет через worker. */
void dm_send_file_trampoline(void* arg);
/* Внутри транзакции dm_core, 0=записано/точный повтор. Для text — пустая операция. */
/* Внутри транзакции dm_core после проверки канонического msg/len; dir: 0=входящее, 1=исходящее.
* 0=записано/точный повтор UUID и тела, -1=ошибка/конфликт; rollback делает вызывающий.
* Для не-file — пустая операция. Исходящий file требует уже подготовленного .file нужного размера. */
int dm_media_track(struct UTUN_INSTANCE* inst, int dir, const uint8_t* msg, size_t len);
/* Заимствованный descriptor внутри точного канонического file-тела либо NULL.
* Проверяет только формат/длину, не подпись и не размер/хеш файла; указатель живёт вместе с msg. */
const uint8_t* dm_message_media(const uint8_t* msg, size_t len);
/* Ciphertext bytes для plain_size<=DM_MEDIA_MAX_SIZE: plaintext плюс 16 байт на порцию 32768 (для пустого — 16). */
uint64_t dm_media_cipher_size(uint64_t plain_size);
/* receipt — обязательные 152 доступных байта; подпись B/идентичность проверяются по nodes SQLite.
* При msg!=NULL проверяется соответствие каноническому file-телу msg/len, UUID и полному SHA256.
* 0=корректно, -1=отказ. Ничего не записывает, факт скачивания этим вызовом не устанавливается. */
int dm_media_verify_receipt(struct UTUN_INSTANCE* inst, const uint8_t* receipt, const uint8_t* msg, size_t len);
/* Для локального A проверить 152-байтовую квитанцию и сохранить её в точном исходящем dm_files.
* 0=сохранена/повтор либо точное подтверждение уже удалённого файла; -1=ошибка/неизвестное тело.
* Историю и собственный файл A не удаляет; text outbox от этой квитанции не меняется. */
int dm_media_accept_receipt(struct UTUN_INSTANCE* inst, const uint8_t* receipt);
/* Удаление: prepare внутри транзакции, committed после commit (callbacks отмены синхронны). */
/* Внутри транзакции dm_history_delete: сохранить свои пути в манифесте поколения и удалить dm_files беседы.
* 0=успех, -1=ошибка; требует живого dm_media, commit/rollback делает вызывающий. Не удаляет файлы/transfer. */
int dm_media_forget_prepare(struct UTUN_INSTANCE* inst, const char* conv, uint64_t generation);
/* Только после commit: отменить transfer удалённых UUID и начать асинхронную уборку старых поколений.
* Callback отмены transfer синхронен; ожидания workers здесь нет, busy защищает их пути. */
void dm_media_forget_committed(struct UTUN_INSTANCE* inst, const char* conv);
/* Владение путями worker: hold до запуска, release после завершения. Уборка ждёт busy=0. */
/* Зарегистрировать принадлежащий приложению path и увеличить busy до запуска worker/transfer.
* conv/generation должны быть текущими и видимыми; 0=захват, -1=ошибка/чужое поколение пути. */
int dm_media_file_hold(struct UTUN_INSTANCE* inst, const char* conv, uint64_t generation, const char* path);
/* Освободить один успешный hold после callback; SQL/нарушение счётчика логируются.
* Уборка старого поколения ждёт освобождения всех holds; busy=-1 означает захват уборщиком. */
void dm_media_file_release(struct UTUN_INSTANCE* inst, const char* path);
/* Общие операции control для endpoint/M и mailbox/S. Все пакеты подписаны. */
/* Отправить cmd/body/len через подписанный router в group к peer; нужен живой dm_media.
* 0=пакет принят транспортом, ненулевое значение=ошибка/backpressure. Durable очередь не создаёт.
* Локальный peer обрабатывается синхронно: 0 означает вызов обработчика, не его durable успех.
* body заимствован до возврата; вызывающий соблюдает формат команды, size<=DM_MEDIA_BODY_MAX+24. */
int dm_media_send(struct UTUN_INSTANCE* inst, uint64_t group, uint64_t peer,
uint8_t cmd, const uint8_t* body, size_t len);
/* 1, если node/author/recipient — живые мемберы group и node имеет role=yes в adm_tags.
* role=NULL проверяет только членство; 0 при отсутствии полномочий/ошибке SQL. Не проверяет сетевой путь. */
int dm_media_scope(struct UTUN_INSTANCE* inst, uint64_t group, uint64_t node,
const char* role, uint64_t author, uint64_t recipient);
/* Опубликовать CHAT_EVT_DM_MSG_RECEIVED [conv_len:1][conv:decimal][local_node:8] для обновления снимка UI. */
void dm_media_notify(struct UTUN_INSTANCE* inst, uint64_t conv);
#endif
+11 -4
View File
@@ -1,4 +1,7 @@
/* Внутренние SQL/control операции двух владельцев DM media; снаружи не использовать. */
/* Внутренние SQL/control операции dm_media и dm_mailbox_media; снаружи не использовать.
* Все вызовы в uasync с живыми inst/topo_sqlite_db. Workers не обращаются к этим helpers.
* Соединением SQLite владеет ядро; вызовы не закрывают его и не меняют внешнее владение транзакцией.
* Из C++ включать внутри extern "C". */
#ifndef DM_MEDIA_PRIV_H
#define DM_MEDIA_PRIV_H
@@ -6,17 +9,21 @@
#include "dm_core.h"
#include <sqlite3.h>
/* Верхняя граница канонического тела с ct<=255, CCM tag, descriptor и подписью; без cmd/router-заголовка. */
#define DM_MEDIA_BODY_MAX (DM_MSG_FIXED_HDR + 255 + 2 + DM_DATA_MAX + 16 + DM_MEDIA_DESC_SIZE + DM_MSG_SIG_SIZE)
/* SQLite ядра, только uasync: prepare возвращает statement либо NULL с логом ошибки.
* inst/db/sql обязательны; вызывающий связывает параметры и освобождает statement. */
* inst/db/sql обязательны; вызывающий проверяет bind/step для чтения и всегда освобождает statement.
* Для записи владение можно передать dm_media_step; prepare сам транзакцию не начинает. */
sqlite3_stmt* dm_media_prepare(struct UTUN_INSTANCE* inst, const char* sql);
/* Шаг записи, затем finalize (включая ошибку): statement передаётся во владение helper.
* 0 только при SQLITE_DONE и успешном finalize, иначе -1 с логом; внутри транзакции rollback делает вызывающий. */
* 0 только при SQLITE_DONE и успешном finalize, иначе -1 с логом; NULL даёт -1 после ошибки prepare.
* Не предназначен для SELECT; bind проверяет вызывающий, внутри транзакции rollback делает вызывающий. */
int dm_media_step(struct UTUN_INSTANCE* inst, sqlite3_stmt* st);
/* Выполнить SQL без результата: 0/-1 с логом. Владение транзакцией определяется SQL вызывающего. */
int dm_media_exec(struct UTUN_INSTANCE* inst, const char* sql);
/* Заимствованный координатор mailbox либо NULL при остановленном/отсутствующем inst. */
/* Заимствованный координатор mailbox либо NULL при остановленном/отсутствующем inst.
* Не освобождать; действителен до dm_mailbox_destroy в том же uasync-потоке. */
struct dm_mb_media* dm_mailbox_get_media(struct UTUN_INSTANCE* inst);
#endif