You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
176 lines
14 KiB
176 lines
14 KiB
Файлы участников CHAT-группы |
|
=========================== |
|
|
|
Модуль: src/member_files/. Desktop-браузер: tools/chatgui/src/member_files/. |
|
Отдельный сервис router ETCP_RT_ID_MEMBER_FILES (0x39), отдельно от вложений |
|
сообщений media_delivery и E2E-файлов DM. Серверы выбираются явно; между |
|
серверными деревьями автоматической репликации нет. |
|
|
|
Хранение и конфигурация |
|
---------------------- |
|
|
|
Корень по умолчанию: <db_path>/member_files/ (без db_path — ./member_files/). |
|
Публичное дерево: <корень>/<group_id>/<node_id>/<относительный путь>. |
|
Временные файлы: <корень>/.uploads/<upload UUID в hex>. |
|
Корень создаётся лениво по первому разрешённому файловому запросу. |
|
Очистка кэша вложений media/ это дерево не обходит. |
|
|
|
Настройки [chatserver] / [chat]: |
|
member_files_enabled=1 |
|
member_files_quota_mb=1024 |
|
member_files_upload_ttl_sec=3600 |
|
member_files_path=/путь/к/хранилищу (опционально, до 255 байт UTF-8) |
|
|
|
Путь, квота и TTL читаются при старте чат-сервиса; изменение требует его restart. |
|
enabled проверяется на каждом запросе. Квота общая для дерева всех групп, |
|
включает опубликованные файлы и полный конечный размер временных загрузок. |
|
Замена требует места одновременно для прежнего файла и нового. |
|
Нехватка квоты/места возвращает NO_SPACE; опубликованные файлы не вытесняются. |
|
|
|
Сервер — действующий полный мембер группы с подписанным adm_tags.storage="yes". |
|
Колонка peers.storage — производный сетевой приоритет, для полномочий не используется. |
|
Каждый запрос и ответ подписаны Ed25519 и зашифрованы end-to-end средствами |
|
route_crypto. group/src берутся только из проверенного заголовка router; |
|
ETCP peer промежуточного хопа не считается автором запроса. |
|
Читать могут действующие участники той же группы. Изменять дерево может только |
|
src == owner. Удалённые записи и source=1 (плейсхолдеры) доступа не дают. |
|
Открытый fd/UUID загрузки не отменяют повторную проверку членства. |
|
Файлы удалённого участника сохраняются на диске, но через сервис не доступны. |
|
|
|
Wire, версия 1 |
|
-------------- |
|
|
|
Все числа LE, без сырых структур и padding. Фиксированный заголовок 125 байт: |
|
0 protocol_version:u8 = 1 |
|
1 kind:u8 (1=request, 2=response) |
|
2 command:u8 |
|
3 status:u16 (request=0) |
|
5 request_id:u64, ненулевой случайный ID |
|
13 owner:u64 (UID каталога; 0 только для LIST виртуального корня группы) |
|
21 offset:u64 |
|
29 size:u64 |
|
37 cursor:u64 |
|
45 version:16 |
|
61 upload:16 |
|
77 hash:32 |
|
109 mtime:u64, Unix seconds |
|
117 type:u8 (1=file, 2=directory) |
|
118 more:u8 (0/1) |
|
119 path_len:u16 |
|
121 target_len:u16 |
|
123 data_len:u16 |
|
После заголовка: path[path_len], target[target_len], data[data_len], без NUL. |
|
Длина пакета обязана точно совпадать с указанными размерами. |
|
Путь максимум 1024 байта, компонент максимум 255 байт, максимум 60 компонентов. |
|
Данные максимум 8192 байт; вместе с полным router-путём и crypto укладываются |
|
в лимит normalizer 16384 байта. Размер файла/смещение ограничены INT64_MAX. |
|
Пути — строгий UTF-8, относительные, разделитель '/'. Запрещены пустые компоненты, |
|
компоненты с ведущей '.', завершающей точкой/пробелом, управляющие символы, |
|
Windows device names и символы \ : * ? " < > |. Корень внутри UID — пустой путь. |
|
POSIX разрешает путь через *at/O_NOFOLLOW; regular-файлы с hardlink отвергаются. |
|
Windows удерживает родительские каталоги без SHARE_DELETE и не принимает reparse points. |
|
|
|
Команды (1..10) |
|
--------------- |
|
|
|
1 LIST: owner/path/cursor/version. Первая страница cursor=0, version=0. |
|
Ответ data — записи: name_len:u16,type:u8,size:u64,mtime:u64,version:16,name. |
|
cursor — сколько записей уже выдано. more=1 требует следующую страницу с |
|
возвращёнными cursor/version. При изменении снимка — STALE, начать сначала. |
|
Размер каталога не ограничивает память страницы. Виртуальный корень группы |
|
выдаёт UID действующих полных участников, даже если каталог ещё не создан. |
|
Изменение namespace сервера консервативно инвалидирует дисковые LIST-курсоры. |
|
|
|
2 STAT: owner/path. Ответ type/size/mtime/version. Не хеширует весь файл. |
|
version — непрозрачный снимок identity/size/mtime/ctime и эпохи сервера. |
|
Закрытие fd по таймауту токен не меняет; restart меняет дисковые токены. |
|
|
|
3 READ: owner/path/version/offset/size (1..8192). |
|
Ответ size — полный размер файла, data_len — фактически прочитано, |
|
more — есть ли байты после ответа. offset==EOF возвращает 0 байт/more=0; |
|
offset>EOF — INVALID_RANGE. Изменение файла между блоками — STALE. |
|
Клиент не смешивает версии и публикует локальный файл только по завершении. |
|
|
|
4 BEGIN_WRITE: owner/path/upload/size/hash/version. |
|
upload — 16 случайных байт, size — конечный размер, hash — SHA256 содержимого. |
|
version=0 означает создать новый файл, существующий возвращает EXISTS. |
|
Для замены требуется версия из STAT/LIST. В один путь одна активная загрузка. |
|
UUID привязан к group/src/owner/path; повтор BEGIN с теми же параметрами безопасен. |
|
|
|
5 WRITE: owner/path/upload/offset/size/data. size==data_len, диапазон внутри |
|
конечного размера. Можно писать в произвольном порядке и повторять блок. |
|
ACK подтверждает запись в temp, а не durable публикацию всего файла. |
|
|
|
6 COMMIT: owner/path/upload. Проверяет SHA256, fsync/_commit и ожидаемую |
|
версию назначения, затем атомарно заменяет файл и синхронизирует namespace. |
|
Прежний файл виден до публикации. Ответ содержит размер/версию/SHA256. |
|
Незаписанные sparse-области читаются как нули; неправильное содержимое не |
|
проходит SHA256. Пустые файлы поддерживаются. При I/O-ошибке во время |
|
публикации результат может быть неопределённым; нужно повторно проверить STAT. |
|
|
|
7 ABORT: owner/path/upload. Удаляет temp, освобождает резервацию. |
|
Неизвестный UUID — OK; опубликованные файлы никогда не удаляет. |
|
|
|
8 MKDIR: owner/path. Создаёт один каталог; родители должны существовать. |
|
9 RENAME: owner/path/target/version. Только внутри UID/группы, без замены |
|
существующего назначения. Активная загрузка в затрагиваемом пути — BUSY. |
|
10 REMOVE: owner/path/version. Файл или пустой каталог, без рекурсивного удаления. |
|
|
|
Статусы (0..13): OK, ACCESS_DENIED, NOT_FOUND, INVALID_PATH, INVALID_RANGE, |
|
EXISTS, BUSY, STALE, NO_SPACE, IO_ERROR, BAD_PACKET, HASH_MISMATCH, CANCELLED, TIMEOUT. |
|
Мутации COMMIT/MKDIR/RENAME/REMOVE сохраняют успешный ответ на 120 секунд |
|
(64 квитанции); тот же request_id с другим содержимым — BAD_PACKET. |
|
COMMIT дополнительно узнаётся по upload UUID. При заполнении квитанций новая |
|
мутация возвращает BUSY до истечения старых. Клиент повторяет запрос с прежним |
|
request_id раз в 5 секунд, общий срок 120 секунд. Повторы после этого срока |
|
не дают гарантии того же результата; перед новой мутацией требуется STAT. |
|
|
|
Потоки, таймауты и остановка |
|
--------------------------- |
|
|
|
На экземпляр один дисковый pthread с очередью до 64 обычных задач. Сеть, |
|
ACL, таймеры и state machines выполняются в uasync; open/stat/readdir/read/write, |
|
resize/hash/sync/rename/remove/close — в дисковом потоке. GUI только передаёт |
|
команды через bridge и получает события. Нет вызова дискового I/O из callback |
|
таймера. Completion резервируется до постановки задачи; без polling workers. |
|
Локальные файлы transfer и серверное дерево используют тот же дисковый поток. |
|
Для обязательной очистки резервируется отдельная задача до открытия файла. |
|
Обращение к своему серверу доставляется через отдельную ограниченную очередь |
|
call_soon в тот же обработчик, без сетевого канала. ACL и дисковый поток общие; |
|
сетевой recv не принимает unsigned даже при src == self. |
|
|
|
Сервер держит до 64 открытых файлов. Каждый READ/WRITE/BEGIN продлевает idle. |
|
После 10 секунд простоя fd закрывается на очередном дисковом sweep (таймер 1 с); |
|
занятый дисковый поток откладывает sweep до освобождения. Закрытие не публикует |
|
и не удаляет файл. Temp можно открыть заново до истечения upload TTL. |
|
После restart незавершённые temp удаляются; опубликованные файлы остаются. |
|
|
|
Лимиты: 16 клиентских запросов, 32 серверных задания/ответа, 16 временных |
|
загрузок, 4 общих transfer. Router-очередь обслуживается через backpressure. |
|
Transfer владеет CM handle до окончания/отмены; DOWN/TIMEOUT handle не освобождает. |
|
quiesce запрещает новые команды, отменяет таймеры, waiter и запросы; destroy |
|
вне callbacks дожидается только своих работ/completions и закрывает дисковый |
|
поток до уничтожения CHAT-групп. Контексты не освобождаются под работающим I/O. |
|
Отмена не прерывает текущий системный I/O или хеширование, а ждёт его completion. |
|
|
|
Desktop |
|
------- |
|
|
|
Контекстное меню канала: Group files. Меню участника: Member files. |
|
Окно: выбор сервера/UID, путь, Up/Refresh, таблица каталогов/файлов, |
|
Upload/Download/New folder/Rename/Delete, прогресс и Cancel transfer. |
|
Чужой UID доступен только для чтения. Автор может быть offline. |
|
Open скачивает в выбранный пользователем локальный файл и открывает его |
|
приложением ОС после завершения. Смена контекста отбрасывает поздние ответы. |
|
|
|
Проверки и диагностика |
|
--------------------- |
|
|
|
tests/test_member_files: wire/UTF-8, произвольные блоки, повторы, EOF, версии, |
|
короткая замена, SHA256, пустой файл, квота, ссылки, expiry/restart, 10-секундный |
|
fd idle и принадлежность work/done потокам. |
|
tests/test_member_files_network: настоящие A/B/M, подписанная storage-роль, |
|
upload/download, побайтная проверка, чужая запись, виртуальный корень, |
|
подписанное удаление читателя. test_services — stop/start общего чат-сервиса. |
|
Логи: media=debug для операций/диапазонов/ACL/fd; info — публикации и transfer, |
|
warn/error — отказы с группой, UID, путём, смещением и точной причиной.
|
|
|