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

Файлы участников 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, путём, смещением и точной причиной.