diff --git a/doc/dm_arch.md b/doc/dm_arch.md index 8d678ce7..890d7b2d 100644 --- a/doc/dm_arch.md +++ b/doc/dm_arch.md @@ -14,6 +14,7 @@ - Подготовка, шифрование, проверка и durable публикация медиа в рабочих потоках. - Прямая передача файлов либо custody на отдельном M, доставка после restart и подтверждённое удаление. - TTL и квота M; вложения и статусы доставки в Qt и Android. +- Голосовые Opus и видео MP4 с зашифрованными метаданными; общий ввод и отображение с каналами. ## Идентичность и шифрование @@ -37,9 +38,20 @@ AES-256-CCM: nonce 13 байт, tag 16 байт. Максимальная пор Автор подписывает все поля до sig. conv связывает автора с получателем. Для `ct=file` перед подписью добавляется descriptor `[UUID:16][plaintext_size:8][SHA256(ciphertext):32]`. -В зашифрованном теле находится имя файла; пути хранения строятся только из UUID. +В зашифрованном теле находится `attachment_info`; пути хранения строятся только из UUID. Размер plaintext сообщения ограничен 1024 байтами. seq — положительное int64. +```text +[kind:1][name_len:2][duration_ms:4][width:2][height:2][UTF8_name:name_len][waveform:100, только voice] +``` + +Поля метаданных многобайтной длины имеют big-endian формат. kind: 1=file, +2=voice, 3=video. Имя — basename до 255 байт, без разделителей, управляющих символов +и некорректного UTF-8. FILE имеет нулевые duration/dimensions; VOICE — положительную +длительность и 100 символов уровней waveform; VIDEO — длительность и размеры. +Длительность ограничена 24 часами, размеры — 16384. Метаданные проверяются до +локального commit и до подтверждения получателем; S/M видят только ciphertext. + Медиа ciphertext — последовательность порций с отдельным tag каждой порции. Последняя порция короче; пустой файл содержит один tag. Размер plaintext и SHA256 ciphertext должны входить в подписанный manifest. Потоковая расшифровка проверяет все @@ -54,6 +66,9 @@ tags, точный размер и хеш; владелец публикует - `dm_media`: файлы endpoint, загрузки и отдельная custody M. - `dm_mailbox_media`: durable задания S, выбор M и повтор удаления до DELETE_ACK. - `file_transfer`: подписанная передача байтов, backpressure и отдельный CM handle каждой операции. +- `attachment`: формат, валидация и тип метаданных, без сети и БД. +- `voice_file`: общий Opus-кодек отправки; `attachment_send` готовит файл в worker + и передаёт результат отдельно в `chat_core` либо `dm_media`. ```text dm_conversations(conv_id, peer_node_id, peer_x25519, peer_ed25519, peer_name, @@ -146,8 +161,30 @@ Qt и Android получают историю через события ядра 45 — JSON бесед. Снимки ограничены 200 записями. Отправка вложения вызывает `dm_send_file` в uasync; длительная обработка выполняется в `media_async`. Android копирует выбранный URI и сохраняет полученный файл через `Dispatchers.IO`; -Qt сохраняет файл из контекстного меню в отдельном потоке. Фото и видео пока -отправляются как оригинальные файловые вложения, без канального транскодирования. +Qt сохраняет файл из контекстного меню в отдельном потоке. Выбор «как файл» сохраняет +оригинал; «как видео» в Qt использует общий FFmpeg worker и метаданные конечного MP4. +Android записывает MP4 через CameraX; импорт MP4 проходит через платформенный +MediaMetadataRetriever в IO. Другие контейнеры на Android отправляются как файлы. + +Голосовая запись отдаёт worker собственный PCM либо компрессор без копирования +в GUI/uasync; worker выполняет flush, Opus-кодирование и вычисление waveform. +Кодирование Qt и Android использует один `voice_file`. Адресат фиксируется до +записи, выбора URI и подготовки; завершение не читает текущую беседу GUI. +Подготовленный PM-файл и Android cache удаляются после копирования, ошибки или отмены; +пользовательский исходник Qt остаётся во владении пользователя. + +Событие 46 `[target_len:1][target][job_UUID:16][state:1]` сообщает начало (0), +готовность (1) и ошибку (2). Target канала — числовой ID, PM — `dm:`. +Подготовка, доставка и воспроизведение имеют независимые состояния. UI отображает +только задания текущей беседы. Состояния файла и delivery receipt приходят в JSON +41; `content_type`, duration, dimensions и waveform преобразуются в общие роли +сообщения. Канальные скачивания вызываются контроллером только для каналов; +PM-файлы доставляются автоматически через `dm_media`. + +Android использует общие `ConversationComposer`/`ConversationMessages` для обеих +бесед; Qt — `InputBar` и `MessageDelegate`. Превью Qt создаются в общем пуле +с двумя workers; декодирование длинного Opus-файла выполняется вне GUI. Оба плеера +проверяют заголовок, пакеты и end marker; Qt ограничивает PCM буфер 256 МиБ. ## Проверки @@ -165,11 +202,21 @@ ACK чужого тела, отказ mailbox без роли, точное уд прямая передача, custody, restart S/M при выключенном A, точный файл на B, удаление копии и тела S, поздние STORE/STORED, TTL, отказ по квоте, неверная квитанция, SQL rollback регистрации, состояния JSON и отмена операции с CM handle. +Голосовое передаётся напрямую; метаданные видео сохраняются после offline custody +и restart S/M при выключенном источнике. `test_dm_worker`: повторное шифрование 8 МиБ не менее 0,5 с и проверка расшифровки; work выполняется вне uasync, done в его потоке, heartbeat продолжает работать. +Destroy отменяет уже posted completion, callback вызывается ровно один раз. + +`test_attachment`: строгий формат, повреждения и граничные длины, реальное +кодирование/декодирование 50 Opus-кадров, длительность и waveform. +`test_attachment_send`: выбранная беседа после смены текущего target, освобождение +PCM в worker, остановка до completion без регистрации и удаление отклонённого cache. +Qt `test_voice_file`: реальные PCM/Opus, повреждённые файлы и отмена async decoder. Проверка 01.10.2026: чистые сборки Qt и Android APK; полный `check.sh` — -115 PASS, 0 FAIL, 1 SKIP (auto_socket_dynamic требует root), proxy/burst/load PASS. +117 PASS, 0 FAIL, 1 SKIP (auto_socket_dynamic требует root), proxy/burst/load PASS. +Qt CTest: 7 PASS, 0 FAIL; Android APK собран после `clean`. В тесте worker максимальный интервал heartbeat составил 10,3 мс. Интерактивная проверка GUI на телефоне и десктопе не выполнялась. diff --git a/doc/service_lifecycle.md b/doc/service_lifecycle.md index 2a995885..d9bf10d5 100644 --- a/doc/service_lifecycle.md +++ b/doc/service_lifecycle.md @@ -44,6 +44,9 @@ SQLite открывается ядром один раз: `db_path/chats.db`, л закрывает медиа и сервисы, синхронизацию и группы, после чего освобождает chat core. `media_async_destroy()` дожидается завершения native workers и вызывает каждый ожидающий callback с `MEDIA_ASYNC_CANCELLED`, пока его контекст ещё существует. +Worker резервирует completion до запуска и передаёт его через `uasync_post_reserved`; +периодического polling нет. Destroy выполняется вне callbacks uasync, после join +отменяет pending post и завершает done ровно один раз. Uasync живёт до конца этой очистки. Результаты отменённых задач не записываются в БД. Это синхронное ожидание: длительное транскодирование или распознавание речи может задержать stop; принудительного прерывания этих библиотек нет. diff --git a/doc/tasks.md b/doc/tasks.md index 04edf63c..f04a29a7 100644 --- a/doc/tasks.md +++ b/doc/tasks.md @@ -30,6 +30,21 @@ 115 PASS, 0 FAIL, 1 SKIP; proxy/burst/load PASS. GUI на устройствах не проверялся. Протокол, владение и проверки: `doc/dm_arch.md`. +[+] **DM: голосовые/видео и общий GUI вложений** — 01.10.2026: + тип FILE/VOICE/VIDEO и метаданные зашифрованы ключом target; в transport остаётся + подписанный descriptor, без новых ключей и канального протокола в DM. + Общие attachment/voice_file/attachment_send: Opus, waveform, копирование и видео + готовятся в workers; completion через reserved post вместо polling. PCM передаётся + без копии; адресат закреплён до записи/выбора. Отмена освобождает собственные файлы. + Android: ConversationComposer/ConversationMessages для каналов и PM, CameraX MP4, + IO для URI/метаданных; Qt: общий InputBar/delegate, FFmpeg, фоновые превью/Opus. + Подготовка адресована беседе и UUID задачи; статусы доставки показаны для всех типов. + test_attachment/test_attachment_send проверяют кодек, формат, владение и отмену; + test_dm_media — voice напрямую и encrypted video metadata после offline restart. + Чистые сборки ядра/Qt/APK, 117 PASS / 0 FAIL / 1 SKIP в check.sh; proxy/burst/load PASS. + Qt CTest: 7 PASS, включая повреждения Opus и отмену decoder. APK не устанавливался, + интерактивная проверка устройств не выполнялась. Подробности: `doc/dm_arch.md`. + [ ] **uasync: ложные ERROR об утечке перед cleanup** — 01.10.2026: test_dm_e2e и существующий test_conn_mgr проходят, но uasync_destroy() пишет Timer leak до освобождения отменённых heap/immediate таймеров.