From ad577318d92ba1a2f5cc07a44bb1b7141ecb5270 Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 27 Oct 2026 00:55:53 +0100 Subject: [PATCH] docs(dm): specify media protocol and worker lifetime --- src/dm/dm_media.h | 72 ++++++++++++++++++++++++++++++++++-------- src/dm/dm_media_priv.h | 15 ++++++--- 2 files changed, 69 insertions(+), 18 deletions(-) diff --git a/src/dm/dm_media.h b/src/dm/dm_media.h index 5b7e02f6..2ecb68e0 100644 --- a/src/dm/dm_media.h +++ b/src/dm/dm_media.h @@ -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 diff --git a/src/dm/dm_media_priv.h b/src/dm/dm_media_priv.h index a17a08a4..6487d0d0 100644 --- a/src/dm/dm_media_priv.h +++ b/src/dm/dm_media_priv.h @@ -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 +/* Верхняя граница канонического тела с 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