119 lines
11 KiB
C
119 lines
11 KiB
C
/* dm_media — файлы личных бесед и ограниченное ciphertext-хранилище M.
|
||
* Шифрование/проверка/публикация файлов в 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
|
||
|
||
#include <stdint.h>
|
||
#include <stddef.h>
|
||
#include "../media_async/attachment.h"
|
||
|
||
struct UTUN_INSTANCE;
|
||
|
||
#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
|
||
#define DM_MEDIA_RECEIPT 4
|
||
#define DM_MEDIA_DELETE 5
|
||
#define DM_MEDIA_DELETE_ACK 6
|
||
#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);
|
||
/* Идемпотентно запретить новые задачи, отменить таймер/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.
|
||
* Беседа должна быть видимой; до 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; /* Заимствованное ядро до 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 после проверки канонического 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);
|
||
|
||
/* Внутри транзакции 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);
|
||
/* Зарегистрировать принадлежащий приложению 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);
|
||
|
||
/* Отправить 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
|