Files
utun2/doc/dm_devgroup_plan.txt
T

206 lines
21 KiB
Plaintext

Личные сообщения: устройства и подгруппы
======================================
Дата: 03.11.2026. Ветка реализации: feature/dm-devgroups.
Статус: согласованная схема; выполнение этапов отмечается ниже.
1. Два режима беседы
-------------------
DEVICE — существующая личка конкретное устройство ↔ конкретное устройство,
включая переписку между своими устройствами. Наличие подгрупп ничего не меняет.
Сохраняются существующие идентификаторы таких бесед и поведение интерфейса.
GROUPS — один режим для обеих сторон: сторона представлена своей подгруппой,
а при отсутствии подгруппы — конкретным устройством. Это не четыре режима.
Выбранные стороны фиксируются в беседе, самопроизвольного перехода между
режимами при создании/удалении подгруппы нет. Переименование не меняет ID.
Send message на подгруппе открывает GROUPS. На устройстве с подгруппой — DEVICE.
Если устройство назначения без подгруппы, а у отправителя подгруппа есть,
интерфейс предлагает «С подгруппами» / «Только устройства».
Если подгрупп нет у обеих сторон — DEVICE.
Идентичности сторон: тип + ID узла или публичный Ed25519 ID подгруппы;
криптографический X25519 ключ связан с проверенной подписанной записью.
ID GROUPS выводится из режима и канонически упорядоченной пары идентичностей.
Короткий conv_id не заменяет сравнение полной пары при загрузке/приёме.
2. Получатели и сообщения
------------------------
Для каждого сообщения фиксируется список всех известных отправляющему узлу
проверенных устройств обеих сторон, включая офлайн-устройства. Используются
существующие node registry и проверка подписей devgroup; новый глобальный
каталог и поиск неизвестных устройств не вводятся. Источник уже имеет копию.
Устройства, обнаруженные позднее, участвуют в следующих сообщениях; доставка
старой истории новым устройствам в эту задачу не входит.
DEVICE адресуется только выбранным двум узлам.
При превышении допустимого размера списка отправка отклоняется целиком:
нельзя молча обрезать адресатов или показывать успешную полную доставку.
Ключ сообщения: (conv_id, author_node_id, seq). Счётчик устойчивый, отдельный
для автора в беседе. Время не определяет уникальность. Проверенные собственные
реплики показываются исходящими, но не становятся новым источником рассылки.
Повтор допускается только с тем же подписанным телом; конфликт отклоняется.
Подпись охватывает стороны, снимок адресатов, содержимое и дескриптор вложения.
Все изменения истории, счётчиков и очереди атомарны. ACK только после commit.
Для каждого адресата отдельно учитываются попытки, дедлайн прямой доставки,
принятие промежуточным сервером и подписанный ACK получателя. Сначала доставка
через существующий маршрут P2P; маршрутизируемый транзит ETCP допустим.
При отсутствии маршрута или истечении времени без ACK — offline-mailbox S.
Наличие маршрута само по себе не должно бесконечно откладывать fallback.
Нет разрешённой общей CHAT-группы/сервера — явное ожидание с причиной в логе.
Принятие S и транспортный ACK не означают доставку в историю адресата.
Повторы, рестарт и параллельный путь через S не создают второе сообщение.
3. Шифрование
-------------
В GROUPS стороны используют ключ подгруппы, при её отсутствии — ключ узла.
В DEVICE всегда ключи конкретных устройств. Текст доступен обеим сторонам.
Для КАЖДОГО вложения создаётся отдельная X25519 пара F_public/F_private.
Один шифротекст хранится и передаётся всеми источниками без перешифрования.
F_private независимо упаковывается два раза: открытым ключом стороны
отправителя и открытым ключом стороны получателя. Обе упаковки находятся
в подписанном сообщении. Любой член PM раскрывает доступную ему упаковку.
Это две независимые упаковки, не последовательное двойное шифрование.
Серверу хранения передаются только шифротекст и подписанный дескриптор.
Криптографическая реализация: стандартный HPKE X25519/HKDF-SHA256 для
инкапсуляции к F_public; export-only контекст выводит ключ существующего
потокового AES-256-CCM. HPKE AES-256-GCM независимо оборачивает F_private
для каждой стороны. Encapsulation, параметры, UUID, размеры и SHA256
шифротекста входят в подписанный дескриптор. Info/AAD разделяют назначения
и связывают операции с беседой, автором, UUID и стороной; не создают зависимости
ключа от ещё не вычисленного хеша шифротекста. Все входы вывода файлового
ключа фиксируются до фоновой подготовки файла. Секреты очищаются после работы.
Требование OpenSSL HPKE проверяется сборкой на всех платформах, без самодельного
криптографического fallback. Повтор отправки использует исходные ключи/тело;
новая подготовка — новые UUID и пару. Nonce не переиспользуется с другим текстом.
4. Независимые факты и автозагрузка
----------------------------------
MESSAGE_RECEIVED: сообщение сохранено адресатом.
FILE_RECEIVED: файл полностью скачан, проверен и опубликован локально.
TAKEN: получатель действительно открыл вложение по действию пользователя.
Эти факты не заменяют друг друга; квитанции подписывает конкретное устройство.
Автозагрузка, получение сообщения, показ карточки/миниатюры и начало скачивания
не создают TAKEN. Открытие на устройстве стороны отправителя не удаляет custody.
В DEVICE между своими устройствами выбранное второе устройство — получатель.
Автозагрузка соблюдает настройки локального устройства и лимит размера.
Ручное скачивание разрешено при выключенной автозагрузке. Поскольку отправитель
не знает чужие локальные настройки, отсутствие готовой копии/отказ от
автозагрузки — отдельный результат файловой доставки, не ACK сообщения.
Сначала передаём P2P. Если остались устройства без файла (в том числе свои)
из-за офлайна, настроек или ошибки — заказываем хранение на M через S.
Этот заказ существует независимо от очереди сообщений: сообщение может быть
подтверждено всеми устройствами, а файл ещё нужен. Один blob на вложение.
Исключение: при политике «до загрузки всеми получателями» выполнение этого
условия завершает хранение; собственные офлайн-устройства его не продлевают.
5. Источники и скачивание
------------------------
Перед скачиванием параллельно опрашиваются все известные онлайн-устройства
своей стороны, собеседника и назначенные серверы хранения. Запрос HAVE относится
к точному UUID/хешу и имеет ID. Ответ подтверждает наличие проверенной копии.
В ограниченном окне собираются ответы, выбирается минимальный измеренный RTT
запроса к конечному источнику. Одновременно идёт одна передача данных.
После ошибки — следующий ответивший источник; исчерпаны — новый ограниченный
цикл проб. Поздние ответы старого запроса игнорируются; успешную передачу
не переключаем из-за позднего меньшего RTT.
Каждый получивший файл узел сохраняет проверенный шифротекст и может отдавать
его разрешённым участникам этой беседы. Проверяется право конкретного узла,
а не знание UUID. M не получает секретов. FILE_RECEIVED — факт успешного
сохранения в прошлом, не обещание вечного хранения локального кеша.
Переход между источниками первоначально начинает временный файл с нуля:
существующий file_transfer поддерживает resume внутри передачи одному пиру,
но не подтверждает совместимость временного файла разных операций.
6. Политика M и удаление
-----------------------
Настройка сервера: dm_media_delete_on_open (по умолчанию 1).
Срок — существующая dm_media_ttl_sec. Политика и expires_at фиксируются при
первом принятии вложения. Дубликаты/рестарт срок не продлевают; изменение
настройки действует для новых заданий. S получает принятую политику и срок.
delete_on_open=1 delete_on_open=0
истёк срок удалить удалить
первый TAKEN получателя удалить сам по себе не удаляет
все получатели FILE_RECEIVED
недостаточно удалить
«Все получатели» — фиксированный получательский список этого сообщения;
в DEVICE в нём один узел. Собственные устройства отправителя не задерживают
удаление. Список не расширяется задним числом. Сервер проверяет отдельные
подписанные квитанции, а не доверяет неподписанному счётчику от S.
При delete_on_open=1 автозагрузка всеми не удаляет уже принятую копию.
При delete_on_open=0 TAKEN до загрузки остальными не запрещает нужный STORE.
TAKEN — факт открытия, не безусловная команда DELETE. Его подпись и принадлежность
открывшего получательской стороне данного сообщения проверяются до применения
политики. Поддельные, чужие, повторные квитанции не меняют условие завершения.
Если условие завершения уже выполнено, запоздалый STORE не создаёт новую копию.
Удаление: устойчиво записать причину завершения → запретить новые выдачи →
отменить передачи custody → удалить файл worker-ом → сохранить tombstone →
подтвердить физическое удаление. Поздние STORE/STORED/callback не воскрешают файл.
Локальный endpoint-кеш и custody разделены; удаление M не удаляет личную копию.
При недоступном M удаление происходит после получения команды или по его TTL.
Сообщение и ключевые упаковки сохраняются независимо от времени жизни файла M.
После удаления M доступ зависит от копий участников: офлайн — ожидание,
утрата всех копий — файл недоступен. Не обещаем восстановление без источников.
7. Владение модулями и состояния
--------------------------------
dm_identity: типизированные стороны, их разрешение и проверенные устройства.
dm_protocol: единый разбор/сериализация подписанного тела и квитанций;
не размножать фиксированные смещения по core/mailbox/media/frontend.
dm_core: беседы, история и доставка сообщений адресатам.
dm_history: локальное удаление, поколения и защита от повторного появления.
dm_media: подготовка, локальные файлы, поиск источников, автозагрузка/открытие.
dm_mailbox: промежуточная доставка сообщений.
dm_mailbox_media: устойчивый координатор хранения и подтверждений.
file_transfer: существующий асинхронный транспорт неизменяемого файла.
SQLite/маршрутизация/состояния выполняются в uasync-потоке; операции с файлами
и тяжёлая криптография — существующими media workers. Сохраняются generation,
busy/hold и порядок quiesce/drain/destroy. Нет второй независимой DM-системы.
Список доставки и сообщения имеют одного владельца; реплики не создают рассылку.
Изменение формата допустимо без совместимости со старым сетевым протоколом.
Не сбрасывать счётчики/историю существующих прямых бесед и не переподписывать
старое сообщение с уже использованным ID. Преобразование схемы и точное
представление старых записей требуется проверить до изменения рабочего кода.
Qt/Android/headless получают итоговый conv_id от ядра и явный mode;
не вычисляют результат по двум node_id. Асинхронный ответ сопоставляется
по request_id. Перепроверить приглашения/удаление/поколения и маршрут звонка,
чтобы выбор конкретного устройства не потерялся при появлении GROUPS.
Логи dm/media отражают ID беседы/сообщения/файла, адресата, переход состояния,
попытку, выбранный маршрут/RTT, принятый срок/политику и причину удаления/отказа.
Ключи, plaintext и PIN в лог не выводятся. Ошибочные ветки не молчат.
8. Этапы реализации и проверки
-----------------------------
[+] Создать отдельную ветку и сохранить план; исходный рабочий diff сохранён
в /tmp/utun2-before-dm-devgroups.patch, существующие UI-правки не откатываются.
[ ] Идентичности/проверка membership, два режима, канонический ID GROUPS,
разрешение списка известных устройств; unit-тесты подделки/коллизии/лимита.
[ ] Подписанный формат, счётчики по автору, схема истории/доставки и её переход;
DEVICE↔DEVICE и свои устройства, два автора с seq=1, точный/конфликтный повтор.
[ ] Рассылка, per-device ACK, fallback по дедлайну, mailbox/restart;
A1/A2/B1/B2, офлайн собственный узел, отсутствующий общий канал, поздний ACK.
[ ] Персональная пара ключей файла и две упаковки, потоковая подготовка;
обе стороны раскрывают один blob, третья не раскрывает, порча/чужой контекст
отвергаются; сборки desktop/Android проверяют наличие HPKE.
[ ] Автозагрузка/ручная загрузка, FILE_RECEIVED/TAKEN, сохранение ciphertext,
независимый заказ хранения, параллельный HAVE/RTT/failover и авторизация.
[ ] Политика M, TTL и завершение по получательскому snapshot; потеря/дубли
квитанций, рестарт, поздние STORE/STORED, удаление во время worker/передачи,
SQL/дисковые ошибки, совмещённый S=M и endpoint=M.
[ ] Qt/Android/headless: действия подгруппы/устройства, выбор режима при fallback,
открытие файла, статусы отдельных фактов, request_id и возврат conv_id ядром.
[ ] Обновить нормативную документацию; выполнить целевые DM/devgroup тесты,
Qt/Android проверки и полный ./check.sh. Известные исходные отказы из
doc/tasks.md отделять от новых регрессий. Итог фиксировать с логами.