From f7b064a11a2217f9302fe63fe325f7d5877132d0 Mon Sep 17 00:00:00 2001 From: evgeny Date: Sat, 24 Oct 2026 18:12:07 +0200 Subject: [PATCH] docs(member-files): specify wire codec and worker ownership --- src/member_files/protocol.h | 36 +++++++++++++++++++++++++++--------- src/member_files/worker.h | 12 +++++++++++- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/src/member_files/protocol.h b/src/member_files/protocol.h index ec068e7b..8a5ecfa0 100644 --- a/src/member_files/protocol.h +++ b/src/member_files/protocol.h @@ -1,4 +1,5 @@ -/* Файловый протокол участников CHAT-группы. Все числа на проводе little-endian. +/* Файловый протокол участников CHAT-группы. Codec не проверяет членство/подпись/шифрование: это делает сервис. + * Все числа на проводе little-endian. * Группа и подписанный отправитель приходят от router; owner — UID каталога. * version — непрозрачный снимок объекта, upload — случайный ID временного файла. * Нулевая version в BEGIN означает создание; замена требует version из STAT. @@ -27,6 +28,8 @@ extern "C" { #define MF_AVATAR_MAX_BYTES (1024 * 1024) #define MF_AVATAR_ORIGINAL_MAX_BYTES (32 * 1024 * 1024) #define MF_AVATAR_MANIFEST_MAX_BYTES 4096 +/* Имя объекта аватарки:64 lowercase hex и .png/.jpg/.webp/.json; вход — NUL-строка, NULL даёт0. + * valid/manifest_valid возвращают1/0; file_limit — лимит32MiB для изображения,4096 для JSON,0 при неверном имени. */ int mf_avatar_file_valid(const char* file); int mf_avatar_manifest_valid(const char* file); uint64_t mf_avatar_file_limit(const char* file); @@ -48,22 +51,37 @@ enum mf_status { }; struct mf_message { - uint8_t kind, command, type, more; - uint16_t status; - uint64_t request_id, owner, offset, size, cursor, mtime; - uint8_t version[16], upload[16], hash[32]; - char path[MF_PATH_MAX + 1], target[MF_PATH_MAX + 1]; - uint32_t data_len; - uint8_t data[MF_BLOCK_SIZE]; + uint8_t kind, command; // MF_REQUEST/RESPONSE и mf_command; group/src берутся отдельно из router. + uint8_t type, more; // MF_FILE/DIRECTORY и признак продолжения LIST/READ (0/1). + uint16_t status; // mf_status; у запроса всегда MF_OK. + uint64_t request_id; // Ненулевой ID одной неизменной команды; у DATA/END/CANCEL — ID потока. + uint64_t owner; // UID дерева;0 только для виртуального LIST корня группы. + uint64_t offset, size; // Диапазон READ/WRITE/DATA либо полный размер BEGIN/END/STAT. + uint64_t cursor; // LIST: число выданных записей; открытие STREAM:ID потока; mutation BUSY:retry_after_tb. + uint64_t mtime; // Unix seconds в ответах/записях каталога. + uint8_t version[16]; // Непрозрачный снимок объекта; нули BEGIN означают создание. + uint8_t upload[16]; // Случайный токен temp/потока; проверяется вместе с group/peer/owner/path. + uint8_t hash[32]; // SHA256 полного файла для BEGIN/COMMIT/END. + char path[MF_PATH_MAX + 1], target[MF_PATH_MAX + 1]; // NUL-строки; target используется RENAME. + uint32_t data_len; // Длина data; фактический предел MF_BLOCK_SIZE, wire использует u16. + uint8_t data[MF_BLOCK_SIZE]; // Байты файла, LIST entries или QUOTA, по команде. }; +/* Прочитать/записать ровно8 байт LE; буферы обязательны и имеют достаточный размер. */ uint64_t mf_get64(const uint8_t* p); void mf_put64(uint8_t* p, uint64_t value); +/* 1 — все n байт нулевые, иначе0. */ int mf_zero(const uint8_t* p, size_t n); -/* Проверка UTF-8 и переносимых компонентов. Пустой путь допустим только для корня. */ +/* 1/0: строгий UTF-8, путь<=1024 байт, до60 компонентов по1..255 байт; root_allowed разрешает пустую строку. + * Отвергает leading '.', trailing '.'/' ', ASCII C0/DEL, Windows device names и \\: *?"<>| (без разрешения пробела после двоеточия). */ int mf_path_valid(const char* path, int root_allowed); +/* Записать wire: длина>0 либо-1 при переполнении; выход не меняется при отказе длины. + * Проверяет размеры, но не семантику command/ID/path. message/wire обязательны и не перекрываются. */ int mf_encode(const struct mf_message* message, uint8_t* wire, size_t capacity); +/* 0/-1: проверить формат/точную длину/общие поля и декодировать. При ошибке message может быть изменён. + * Невалидный UTF-8/путь и смысл полей отдельных команд проверяет вызывающий сервис/storage. */ int mf_decode(const uint8_t* wire, size_t length, struct mf_message* message); +/* Заимствованные статические строки; неизвестное значение возвращает Unknown error/UNKNOWN. */ const char* mf_status_text(int status); const char* mf_command_text(int command); #ifdef __cplusplus diff --git a/src/member_files/worker.h b/src/member_files/worker.h index 3b9fb0c2..a9e16d9a 100644 --- a/src/member_files/worker.h +++ b/src/member_files/worker.h @@ -1,21 +1,31 @@ /* Единственный дисковый поток модуля. submit/done выполняются в uasync, work — * в pthread. Очередь ограничена; completion резервируется до передачи работы. * Для обязательной очистки reserve выполняется до открытия файлов. destroy вне - * callbacks ждёт работ и доставляет done, включая поставленные ими cleanup jobs. */ + * callbacks ждёт работ и доставляет done, включая поставленные ими cleanup jobs. + * data/arg заимствованы до done; worker освобождает только собственную оболочку работы. + * ua/work/done и worker обязательны; ua живёт до конца destroy. Останавливать worker из done нельзя. */ #ifndef MEMBER_FILES_WORKER_H #define MEMBER_FILES_WORKER_H struct UASYNC; struct mf_worker; struct mf_work; typedef void (*mf_work_fn)(void* arg); +/* error=0 — work завершён, результат операции находится в data; -1 — обычная задача не принята (синхронно). */ typedef void (*mf_done_fn)(void* arg, int error); +/* Создать pthread/mutex/condition; возвращает owner либо NULL, без дискового I/O. */ struct mf_worker* mf_worker_create(struct UASYNC* ua); +/* Резерв оболочки и completion, NULL при OOM. Сам по себе не занимает очередь/не задаёт worker. */ struct mf_work* mf_worker_reserve(void); +/* Освободить только ещё не переданный в submit_reserved резерв; NULL допустим. */ void mf_worker_unreserve(struct mf_work* work); +/* До64 обычных задач, включая ожидающие done. При отказе done(-1) синхронно, work не вызывается. */ void mf_worker_submit(struct mf_worker* worker, mf_work_fn work, void* data, mf_done_fn done, void* arg); +/* Передать резерв worker без проверки лимита64: только обязательные cleanup continuations. + * После вызова резерв нельзя освобождать/использовать повторно. FIFO, done ровно один раз. */ void mf_worker_submit_reserved(struct mf_worker* worker, struct mf_work* reserved, mf_work_fn work, void* data, mf_done_fn done, void* arg); // После quiesce владельца, вне callbacks: дождаться работ и их cleanup continuations, не останавливая pthread. void mf_worker_drain(struct mf_worker* worker); +/* Drain, затем join и освобождение. Работы не прерываются и получают обычный done(0); NULL допустим. */ void mf_worker_destroy(struct mf_worker* worker); #endif