599 lines
62 KiB
Markdown
599 lines
62 KiB
Markdown
# Задачи по проекту
|
||
|
||
Сюда пишется список задач с короткой аннотацией. Если задача большая и имеет ТЗ - то ТЗ оформляется отдельным файлом, а сюда помещается аннотация и ссылка на ТЗ.
|
||
|
||
## Текущая задача
|
||
[ ] **ChatGUI Windows: проверка линковки ZXing без -Os** — 05.10.2026:
|
||
после удаления старых DLL и чистой сборки линкер не находит move-конструктор
|
||
`std::string` в `libZXing.a(Barcode.cpp.obj)`. ZXing задаёт этому файлу -Os;
|
||
такая ошибка описана в https://github.com/msys2/MINGW-packages/issues/31015.
|
||
Для MinGW GCC отключён пофайловый -Os; используются флаги выбранного типа сборки.
|
||
Проверена генерация команд: при MinGW+GNU Barcode.cpp получает -O3 без -Os,
|
||
Linux сохраняет прежний -Os. Компиляция Barcode.cpp на Linux прошла.
|
||
Требуется Windows-проверка обычным `tools/chatgui/build.bat`.
|
||
|
||
[ ] **ChatGUI Windows: подтверждение обновления runtime компилятора** — 05.10.2026:
|
||
`cc-probe.log`: cc1 запускается из корня, но в `tools/chatgui/build` завершается
|
||
с `0xC0000139`; локальные DLL GCC/math/runtime отличаются от UCRT64.
|
||
Перед обеими точками сборки добавлено обновление существующих копий этих DLL;
|
||
раскладка FFmpeg использует для них UCRT64. Проверены обновление, повторный вызов,
|
||
сохранение кодеков и ошибки. Требуется Windows-прогон `check_cc.bat`: cc1 после
|
||
обновления, повтор команды Ninja и сборка Opus должны завершиться с кодом 0.
|
||
|
||
[ ] **ChatGUI Windows: обновление остальных DLL приложения после обновления MSYS2** — 05.10.2026:
|
||
в `cc-probe.log` также отличаются Qt и другие DLL. `tools/chatgui/build.sh`
|
||
пропускает раскладку при наличии основного комплекта, без сравнения версий;
|
||
рекурсивная раскладка копирует только отсутствующие зависимости.
|
||
Runtime GCC обновляется отдельно; для остальных DLL нужно продумать обновление
|
||
с сохранением приоритета собственной сборки FFmpeg и проверить запуск приложения.
|
||
|
||
[+] **Открытие HTTP/HTTPS в сообщениях Android и Qt** — 04.10.2026:
|
||
общая обработка ссылок по диапазонам текста; веб-адреса открываются системным
|
||
обработчиком, `utun://` сохраняет JOIN и короткое отображение PM-приглашений.
|
||
Учтены Unicode, IPv6, скобки, пунктуация, параметры, fragment и emoji в адресах.
|
||
Qt использует одинаковый layout для отрисовки, размеров, нажатия и выделения.
|
||
Ошибки открытия переведены и логируются. Общие 15 случаев прошли в Android и Qt;
|
||
проверки нажатия/drag/copy и приглашений прошли. Чистые сборки Qt и APK — PASS.
|
||
`check.sh`: 124 passed, 0 failed, 1 skipped; proxy/burst/load — PASS.
|
||
APK установлен на Q8, проверено системное открытие HTTP и HTTPS через ACTION_VIEW,
|
||
приложение запущено. Логи: `/tmp/message-links-check.log`,
|
||
`/tmp/message-links-qt-final-tests.log`, `/tmp/message-links-android-tests.log`.
|
||
Контракт: `doc/message_links.md`.
|
||
|
||
[+] **Заголовок PM-приглашения и короткое отображение ссылки** — 04.10.2026:
|
||
`ct=invite` содержит зашифрованные название группы и полную ссылку; GUI получает
|
||
название через `tags.group_name` и добавляет заголовок из перевода интерфейса.
|
||
Android и Qt отображают ссылку одной строкой с многоточием. Нажатие и копирование
|
||
используют полный адрес: долгое нажатие в Android, выделение строки и Ctrl+C в Qt.
|
||
Срок действия ключа — 10 минут. Проверена доставка названия с Unicode, кавычками
|
||
и переносом строки, JOIN и история; Qt проверяет разные ширины, нажатие на конец
|
||
сокращённой ссылки и копирование. Чистые сборки ядра, Qt и APK прошли.
|
||
`check.sh`: 124 passed, 0 failed, 1 skipped; proxy/burst/load — PASS.
|
||
Qt `test_invite_links` и `test_emoji_playback` — PASS. APK установлен на Q8,
|
||
приложение запущено. Логи: `/tmp/pm-invite-title-check.log`,
|
||
`/tmp/pm-invite-title-e2e-final.log`. Контракт: `doc/pm_invites.md`.
|
||
|
||
[+] **История группы после PM-приглашения** — 04.10.2026: регистрация `db_sync`
|
||
создаёт локальное состояние с блокировкой обмена; история запускается после
|
||
успешного member_sync через READY-пиров конкретной группы. Подписка на переходы
|
||
READY сбрасывает обмен при DOWN/REINIT/LEAVE и возобновляет его при восстановлении.
|
||
Обе роли имеют 15-секундный таймаут; ошибки отправки и отказ пира разрешают повтор.
|
||
JOIN_INFO_RESP применяет участника через транзакционный member_sync, обновляя
|
||
Merkle-индекс вместе с моделью. ASAN также выявил и помог исправить использование
|
||
временного content_type после выхода из области видимости при выдаче JSON истории.
|
||
Проверены пять сообщений до приглашения, оба порядка ID, потеря первого обмена,
|
||
восстановление READY на общем транспорте и последующая доставка новых сообщений.
|
||
Эти сценарии, ownership и services прошли под ASAN. Финальный check.sh:
|
||
124 passed, 0 failed, 1 skipped; интеграционные proxy/burst/load — PASS.
|
||
Чистые сборки ядра, Qt и APK прошли. На Q8 после обновления
|
||
существующий `g2` получил все 5 старых сообщений без повторного JOIN;
|
||
SYNC_DONE подтвердил совпадение хеша, SQLite quick_check — ok.
|
||
Диагностика: `/tmp/pm-history-q8-final.log`, `/tmp/pm-history-asan-invite.log`,
|
||
`/tmp/pm-history-asan-timeout.log`, `/tmp/pm-history-final-check.log`.
|
||
Контракт: `src/chat/db_sync_arch.md`.
|
||
|
||
[+] **Android: диагностика несрабатывания VAD** — 03.10.2026: в `debug` добавлены
|
||
решения по каждому изменению состояния и сводки PCM/AGC/модели раз в секунду;
|
||
`debug=trace` показывает каждое окно 32мс. Логи различают сброс подтверждения,
|
||
чужой RX, закрытое окно Android, задержки inference и отложенный BEGIN.
|
||
Регрессии radio_vad, Silero, radio_audio и voice_tx прошли. На Samsung в 19:36:23
|
||
подтверждён отказ после истечения 15-минутного окна: `reason=window_expired`,
|
||
`requested=true screen=true capture=false nativeVad=false`; ручной PTT работает.
|
||
Кнопка на панели показывает сохранённое включение, а не фактическую активность VAD.
|
||
Лог: `/tmp/vad-sm-current.log`. Исправление таймера и индикации — следующая запись.
|
||
|
||
[+] **Android: индикация и фоновый таймер VAD** — 03.10.2026: кнопка показывает
|
||
фактическую активность захвата, гаснет при его остановке; повторное включение
|
||
открывает новое окно. 15-минутный таймер расходуется только в фоне, при видимом
|
||
приложении сбрасывается: после каждого сворачивания доступны полные 15 минут.
|
||
После истечения возврат в приложение возобновляет VAD; погасший экран по-прежнему
|
||
останавливает микрофон, включение экрана открывает новое окно.
|
||
`tests/test_radio_vad_window.kt` проверяет 2 часа в foreground, сброс таймера при
|
||
возврате, полное фоновое окно, границу истечения, экран и сохранение окна при перенастройке.
|
||
На Samsung проверены фактические lifecycle-переходы и выключенная кнопка после
|
||
остановки захвата: `/tmp/vad-background-sm-transitions.log`. Чистая сборка APK прошла,
|
||
Samsung SM-A525F и Q8 обновлены; на Samsung восстановлены прежний канал и активный VAD.
|
||
Уточнение: при возвращении в приложение таймер теперь полностью сбрасывается,
|
||
соответствующая регрессия прошла. Новая чистая сборка — `/tmp/vad-reset-android-build.log`,
|
||
установлена на оба телефона. Повторная проверка lifecycle на Samsung после этого
|
||
обновления не выполнена: телефон заблокирован; правило сброса проверено тестом с искусственным временем.
|
||
|
||
[ ] **Android: время жизни config provider** — 03.10.2026: `utun_bridge_init()`
|
||
передаёт адрес локального `utun_config_provider_t` в `utun_config_set_provider()`,
|
||
который хранит указатель. После возврата из init он недействителен; перед
|
||
использованием `cfg_get_*` обеспечить постоянное хранение provider.
|
||
|
||
[+] **DM: надёжная доставка сообщений, первый этап pm.txt** — 01.10.2026:
|
||
атомарные история/seq/outbox, подписанные точные квитанции после commit,
|
||
live BGP-маршрут и durable очередь на суперузле с проверкой подписанной роли.
|
||
Повторы, out-of-order, rollback БД, неверные ACK и поздний PUT проверены;
|
||
40 offline-сообщений доставлены после restart источника, первый offline DM —
|
||
после restart суперузла при выключенном источнике. Копии удалены по квитанции.
|
||
Подготовка ciphertext медиа использует ключ target и независимые nonce порций;
|
||
проверены границы 32 КиБ, пустые файлы, неверные ключи/UUID/hash и повреждения.
|
||
Clean-сборка ядра/Qt, test_dm, test_dm_e2e и полный check прошли:
|
||
113 PASS, 0 FAIL, 1 SKIP (auto_socket_dynamic требует root); proxy/burst/load PASS.
|
||
Текущие гарантии и оставшаяся интеграция: `doc/dm_arch.md`.
|
||
|
||
[+] **DM: сетевая доставка и pending-медиа по pm.txt** — 01.10.2026:
|
||
отдельные owners dm_media/dm_mailbox_media и файловый transfer с CM handle,
|
||
подписанные метаданные и независимая квитанция файла после durable приёма.
|
||
Прямая доставка или S→M custody, restart S/M при выключенном A, доставка B,
|
||
повтор удаления до DELETE_ACK; поздние STORE/STORED не возрождают копии.
|
||
TTL M по умолчанию 7 дней, резервирование квоты, отмена transfer и stop workers.
|
||
Копирование, шифрование, хеширование, проверка и durable файловые операции —
|
||
в media_async; отправка по 8 КиБ через call_soon. Тест 8 МиБ/0,5 с сохраняет
|
||
heartbeat uasync (максимальный интервал 10,3 мс в проверенном прогоне).
|
||
Qt/Android: оригинальные файловые вложения, сохранение и статусы доставки;
|
||
PM-снимки из ядра асинхронные. Чистые сборки Qt и Android APK прошли.
|
||
test_dm_media/test_dm_worker, rollback/quota/cancel и полный check:
|
||
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 таймеров.
|
||
После cleanup счётчики сходятся, финальная проверка проходит. Перенести
|
||
предварительные счётчики в DEBUG; ERROR оставить для реального расхождения
|
||
после cleanup, отдельно показывать активные и отменённые таймеры.
|
||
|
||
[+] **Статус подключения звонка в Qt и Android** — фактический путь router:
|
||
прямое / reverse / indirect / нет подключения. Учитывается BGP fallback;
|
||
события только при изменении, общий таймер 1с, сброс при завершении.
|
||
Проверены смена путей настоящего звонка, медиа, ASAN, Qt, Android APK и полный check.
|
||
|
||
[+] **Единицы RTT в окне звонка** — `call_post_stats()` переводит
|
||
`ETCP_CONN.rtt_last` из 0.1мс в миллисекунды до отправки CALL_STATS.
|
||
Исправление общее для Qt и Android. DEBUG-категория `debug` раз в секунду
|
||
показывает исходный RTT, RTT в мс, транспортного соседа и метрики аудио.
|
||
|
||
[+] **Восстановление путей conn_mgr на протяжении жизни handle** — начальный
|
||
TIMEOUT отделён от срока цикла проб. Поздний обрыв запускает новый цикл;
|
||
рабочий резерв и NCD сохраняются, при отсутствии резерва INDIRECT стартует сразу.
|
||
Циклы ограничены 15с, паузы 1→2→4→8→15с; новая версия узла или локальные сокеты
|
||
обходят паузу. Прямой READY останавливает поиск и получает приоритет.
|
||
Исходящие request_id и входящие сроки независимы; устаревшие ответы игнорируются.
|
||
UP/DOWN отражают общую доступность, TIMEOUT приходит один раз до первого UP.
|
||
Последний close отменяет восстановление, включая закрытие из callback.
|
||
Проверены десять сетевых сценариев с реальными данными: поздний обрыв,
|
||
повторные циклы, смена адресов/сокетов, потеря маршрута посредника, старый ACK,
|
||
начальный TIMEOUT с поздним UP и close. ASAN без ошибок; собраны Qt и Android APK.
|
||
Полный check: 113 PASS, 0 FAIL, 1 SKIP; пропущенный auto_socket_dynamic отдельно
|
||
прошёл с root в network namespace. Интеграционные proxy, burst и load прошли.
|
||
Алгоритм: `src/routing_layer/conn_mgr_doc.md`.
|
||
|
||
[ ] **Общий лимит check.sh после clean** — 01.10.2026: расчёт по времени предыдущего
|
||
прогона дал 205с вместе с полной сборкой и оборвал проверку после успешных тестов
|
||
до `test_uasync_socket_race`. Повторный запуск на готовой сборке с большим лимитом
|
||
прошёл за 206с, включая proxy/burst/load. Разделить бюджет сборки и запуска тестов
|
||
либо учитывать clean при расчёте.
|
||
|
||
[ ] **Паузы аудио Qt при звонке с SM** — 01.10.2026, звонок
|
||
`939a3ea36f3eeac3`, прямой LAN TCP `192.168.40.250:44604 ↔ 192.168.40.247:44173`.
|
||
Tcpdump: общие паузы входящего трафика до 645мс; ping до роутера в среднем
|
||
100мс, до SM 128мс. ПК на 2.4ГГц, SM на 5ГГц. Одновременно `CallAudio: cb`
|
||
падает до 20–40/с вместо 100/с, буфер Qt растёт до 2с, на SM повторяется refill.
|
||
В 8с strace аудиопоток продолжает обслуживать PulseAudio без долгих syscall;
|
||
причина редких аудиоколлбэков пока не установлена. Добавить измерения интервалов
|
||
и длительности callback, кадров capture/playback и сводку CALL_STATS; сравнить
|
||
сетевые задержки после подключения ПК к 5ГГц или кабелю.
|
||
Повторный звонок `b2a423ed2d36ed83` (01:25:31–01:27:39): DIRECT сохраняется,
|
||
RTT 2–3мс; в начале на SM gap tone длится 1.8–2.0с примерно каждые 6с.
|
||
На Qt частота callback падает вплоть до 4/с, входящий PCM продолжает приходить:
|
||
буфер 65→465→1465→2079мс за 3с, затем растёт до нескольких секунд, dropped=0.
|
||
Локализовано прекращение обслуживания duplex-аудио Qt; ожидание backend и
|
||
задержка обработки PCM пока не разделены. Часы SM отстают от Qt примерно на 1.24с.
|
||
Добавлена диагностика backend/устройств, времени между callback и длительности
|
||
capture/playback, фактической частоты callback и PCM кадров за интервал.
|
||
Счётчики сбрасываются до старта устройства; нужна повторная запись с новой сборкой.
|
||
|
||
[+] **Ложный сигнал потери звука при запуске звонка** — 01.10.2026, звонок
|
||
`bc3efadcac629947`: Qt запускает аудиоколлбэки в 01:54:56.933, включает gap tone в
|
||
01:54:57.415 и выключает в 01:54:58.072 при первом воспроизведении PCM.
|
||
На SM между ACCEPT и первым захваченным кадром проходит около 1.15с;
|
||
часы SM отстают примерно на 1.23с. Qt стабильно обслуживает 100 callback/с,
|
||
RTT около 1мс. Исправлено: до первого PCM начальное ожидание проходит в тишине,
|
||
последующий перебой включает тон через 500мс. Стартовый сбой по-прежнему
|
||
ограничен существующим watchdog отсутствия медиа. Регрессия проверяет ожидание
|
||
1.5с, начальное накопление, настоящий перебой, неполный блок, новый звонок и завершение.
|
||
Ускорение запуска Android: router.start занимает около 775мс; система сообщает
|
||
длительное удержание AudioManager.updateAudioPortCache. Ещё около 370мс проходит
|
||
до запуска capture worker. Гудки теперь останавливаются до startAudio;
|
||
router использует снимок устройств с обновлением по событиям и явной переоценке,
|
||
capture запускается до подготовки/старта playback. Ошибка playback останавливает capture.
|
||
Добавлены длительности отдельных стадий, первого capture и PCM. Ядро, Qt и APK
|
||
собраны после clean; три аудиотеста прошли. Выигрыш на SM требует замера в новом звонке.
|
||
В звонке Qt `180f046ef22defc2` первый PCM пришёл через 1245мс без стартового gap tone.
|
||
Полный check вне песочницы: 113 PASS, 0 FAIL, 1 SKIP; auto_socket_dynamic отдельно
|
||
прошёл с root в private netns. Интеграционные proxy, burst и load прошли.
|
||
|
||
[ ] **Оставшаяся задержка запуска аудио SM** — после обновления 01.10.2026,
|
||
звонок `2b9ad4e21a54d8ba`: стартового gap tone нет на обеих сторонах; Qt
|
||
стабильно работает около 100 callback/с, max_idle=15.3мс, буфер 55мс, RTT=2мс.
|
||
На SM первый capture появляется через 1140мс от startAudio (1164мс от ACCEPT).
|
||
router.start=781мс, получение снимка устройств=0мс, AudioRecord create=24мс,
|
||
startRecording=7мс; от начала startRecording до первого кадра=328мс.
|
||
Capture worker стартует раньше playback, но общий выигрыш пока не виден.
|
||
AudioTrack create=21мс, play=327мс; система сообщает AudioPortCache contention=497мс
|
||
и восстановление AudioTrack при setPreferredDevice. Разделить замеры регистрации
|
||
callback, BT-профиля и применения маршрута; отдельно проверить первый AudioRecord.read.
|
||
На SM входящий буфер за время подготовки вырос до 1149мс и к концу звонка снизился
|
||
до 570мс; ускорение старта playback также сократит начальное накопление.
|
||
Реализован независимый запуск AudioTrack и AudioRecord в play/capture потоках;
|
||
частичный старт отменяется до освобождения устройств/native. AEC измеряется после готовности обоих.
|
||
Добавлены callbackMs/btProfileMs/applyMs/refreshMs, длительность первого read и время первой записи playback.
|
||
Резерв теперь зависит от изменения задержки доставки по timestamps CALL_MEDIA, отдельно от глубины очереди;
|
||
стартовое накопление и догон не повышают запас. Сохранность FIFO и uint32 wrap проверяются регрессиями.
|
||
Сборки ядра, Qt и Android после clean успешны; три аудиотеста прошли. Полный check:
|
||
113 PASS, 0 FAIL, 1 SKIP (auto_socket_dynamic требует root); proxy/burst/load прошли.
|
||
Регрессия refill учитывает остаток SoundTouch: два новых кадра могут уже заполнить запас 60мс.
|
||
SM и Q8 обновлены 01.10.2026 в 03:03. Выигрыш по времени требует пробного звонка с новой версией.
|
||
|
||
[ ] **Гонка invite-файла в test_chat_join_e2e** — `wait_file()` проверяет только
|
||
существование файла, а `wf()` создаёт его до записи. Читатель иногда получает
|
||
пустую ссылку и ошибку префикса `utun://`. Публиковать готовый файл атомарно.
|
||
|
||
[ ] **Некорректное время в выводе check-proxy** — успешные `basic_1mb` и
|
||
`stress_sessions` иногда печатают огромные положительные/отрицательные миллисекунды,
|
||
хотя итоговая скорость и длительность передачи выглядят нормально. Проверить расчёт вывода.
|
||
|
||
[+] **SpeexDSP AEC (эхоподавление) в lib/** — акустическое эхоподавление звонка.
|
||
Вендорен SpeexDSP (mdf/fftwrap/kiss_fft, 3-clause BSD) в `lib/speexdsp/`,
|
||
обёртка `lib/speex_aec.c/h` (C-API, FLOATING_POINT + USE_KISS_FFT, без внешних
|
||
зависимостей). Собственная линия задержки рендера на `delay_frames` кадров +
|
||
синхронный `speex_echo_cancellation` (встроенный буфер SpeexDSP фиксирован в
|
||
2 кадра и для Android не годится); дрейф потоков — через переполнение/недозаполнение
|
||
линии (drop/dup + passthrough). Категория лога `aec`.
|
||
Ядро: `call_audio_set_aec_enabled/delay_frames`, AEC в feed_pcm (захват→encode)
|
||
и pull_pcm (рендер-референс), lifecycle в start/release. Desktop — настройка GUI
|
||
`aec_enabled` (`[chatserver]`, читается в `call_audio_start`). Android — включается
|
||
только на спикерфоне (`route==SPEAKER`, live через JNI `callAudioSetAec`),
|
||
задержка меряется `AudioTrack/AudioRecord.getTimestamp`; `CallAudioEngine.playLoop`
|
||
переведён на absolute-deadline (устранён систематический дрейф). Тест
|
||
`test_speex_aec` (подавление эха 25.5 дБ, сохранность речи 0.956, delay-line, NULL-гарды).
|
||
Сборки: autotools + chatgui CMake + Android CMake/APK.
|
||
|
||
[+] **Silero VAD в lib/** — добавлен детектор голосовой активности (Silero VAD v5,
|
||
стриминговая ONNX-модель + обёртка `lib/silero_vad.c/h` над ONNX Runtime C API).
|
||
API: `silero_vad_create/create_default/destroy/reset/process/process_pcm16`
|
||
(окно 512 сэмплов @16 кГц, вероятность речи [0..1], рекуррентное состояние).
|
||
Модель вшита в бинарник (`silero_vad_model.inc`, `create_default`), файл не нужен.
|
||
Категория лога `vad`. Модуль под `#ifdef HAVE_SILERO_VAD` — без onnxruntime
|
||
компилируется в стабы, поэтому GLOB-сборки (chatgui/Android) не ломаются.
|
||
Сборка: `--with-silero-vad` (autotools), `-DHAVE_SILERO_VAD` + libonnxruntime
|
||
(chatgui CMake, Android NDK). `make check`: 111/0/1.
|
||
|
||
[+] **Авто-PTT рации по VAD** — сделано. Отдельный режим `radio_vad_enabled` (ручной
|
||
PTT не меняется, при зажатой кнопке — override). Сервисный слой `radio_audio.c`:
|
||
ресемплер 48k→16k моно (3-tap box + 3:1, стерео микшируется), накопитель окон
|
||
512, фильтр-гистерезис + машина состояний (`src/radio/radio_vad.h`, чистый,
|
||
юнит-тестируемый): старт при подтверждении 2 окон (~64мс) выше порога
|
||
`radio_vad_threshold`, стоп при тишине `radio_vad_hangover_ms` (200мс) или чужом
|
||
разговоре (busy = RX-кадр <200мс). Перед BEGIN сохраняется до 600мс непереданного PCM;
|
||
prefetch отправляется одной задачей перед live без повторов и задержки на стороне отправителя.
|
||
VAD использует отдельный компрессор с собственной историей усиления. Desktop `radio_audio_engine.cpp` и Android
|
||
`RadioAudioEngine.kt` фидят PCM постоянно в VAD-режиме; JNI `radioVadMode()`.
|
||
Тест `test_radio_vad` (ресемплер + FSM). Android: onnxruntime AAR 1.23.2 в
|
||
`jniLibs/` + `cpp/onnxruntime/include` + `-DHAVE_SILERO_VAD` в CMake.
|
||
Android: настройки VAD (включение, порог, задержка после тишины); микрофон в PTT
|
||
создаётся только для передачи. PTT запускается при касании, после отпускания
|
||
передаёт ещё 250мс; повторное касание отменяет завершение (двойной тап без FIN).
|
||
VAD работает 15 минут при горящем экране;
|
||
SCREEN_OFF освобождает захват, SCREEN_ON автоматически открывает новое окно.
|
||
RX остаётся активен; TX освобождается отдельно через `radio_audio_capture_stop()`.
|
||
|
||
[+] **Отдельный компрессор перед VAD рации** — 03.10.2026:
|
||
копия PCM 48кГц/20мс проходит через общий audio_compressor до ресемплера и Silero.
|
||
Настройки общие с TX, кроме `radio_vad_compressor_max_gain_db` (15дБ по умолчанию,
|
||
1..60дБ); включение также общее. Состояния TX/VAD независимы, prefetch хранит исходный PCM.
|
||
Выключенный компрессор не добавляет ожидания блока; разрыв/новый захват сбрасывает AGC и Silero.
|
||
Android: отдельный ползунок, DataStore/INI/runtime; диагностика `radio` показывает
|
||
параметры, input/output RMS, фактический gain и клиппинг. Диапазоны max_gain согласованы с библиотекой.
|
||
test_voice_tx: точный вход Silero, байтовое совпадение Opus/prefetch, mono/stereo,
|
||
6/30дБ, переменные PCM-чанки и reset. Настоящий Silero распознаёт речь при амплитуде
|
||
0.03 на тихом фоне, останавливается после речи; повторный reset воспроизводит результат.
|
||
Чистые сборки ядра/Qt/APK, целевые тесты и полный check.sh прошли:
|
||
124 PASS / 0 FAIL / 1 SKIP, proxy/burst/load PASS; Qt CTest 2/2, AGC 479/479.
|
||
APK с последующим переносом переключателя VAD установлен на Samsung SM_A525F.
|
||
|
||
[+] **Включение VAD на панели рации Android** — 03.10.2026:
|
||
кнопка VAD расположена слева от правой кнопки плавающего PTT; состояние читается из DataStore Flow.
|
||
Действие в ViewModel сохраняет настройку, обновляет ядро, запрашивает разрешения и перенастраивает захват.
|
||
Переключатель удалён из настроек; порог речи, задержка после тишины и max gain оставлены.
|
||
Чистые сборки ядра/Qt/APK прошли, APK установлен на Samsung; UI настроек проверен через uiautomator.
|
||
|
||
[+] **Верхняя панель рации и VAD в desktop ChatGUI** — 03.10.2026:
|
||
строка под шапкой канала: меню микрофона/выхода, состояние, переключатель VAD слева от PTT.
|
||
PTT перенесена с сообщений в панель; подсветка показывает фактический TX, включая автоматический VAD.
|
||
Настройки рации: вероятность речи, задержка тишины, включение AGC и независимые max gain TX/VAD.
|
||
Общий стек сохраняет prefetch 600мс; настройки применяются в uasync с подтверждением в GUI.
|
||
Перезапускается только capture, RX/output сохраняются; удерживаемые кнопка/хоткей восстанавливаются.
|
||
INI сохраняется атомарно; ошибка микрофона/детектора видна на панели, ручной PTT доступен без VAD.
|
||
Запрос настроек принадлежит reserved uasync task и освобождается также при shutdown до исполнения.
|
||
Исправлено сохранение Audio без посещения страницы: устройства и аудиопараметры предварительно загружаются.
|
||
Чистые сборки ядра/Qt и Qt CTest 16/16; test_radio_panel проверяет Qt-кнопки и настоящие audio workers
|
||
с null backend: auto-TX, 30 кадров prefetch, manual/hotkey, capture recovery, INI restart и отмену запроса.
|
||
Полный check.sh: 124 PASS / 0 FAIL / 1 SKIP, proxy/burst/load PASS; настоящий Silero распознаёт тихую речь.
|
||
Логи: `/tmp/desktop-vad-qt-verified-tests.log`, `/tmp/desktop-vad-core-check-unrestricted.log`.
|
||
|
||
[+] **PTT кнопками громкости на панели рации Android** — 03.10.2026:
|
||
переключатель слева от VAD, включённое состояние подсвечено; панель и настройки используют общий DataStore Flow.
|
||
Значок: динамик по центру, плюс сверху, минус снизу; проверен на панели Samsung после чистых сборок ядра/Qt/APK.
|
||
Сохранение обновляет быстрый кэш после записи; ошибки записи логируются без изменения состояния переключателя.
|
||
Экранная кнопка, оверлей и кнопки громкости имеют независимые владельцы ручной передачи.
|
||
Учёт отдельных volume-клавиш защищает от повторов и посторонних клавиш; потеря фокуса/выключение опции отменяет volume-PTT.
|
||
Чистые сборки ядра/Qt/APK прошли, 12 сценариев владельцев PTT проверены отдельным Kotlin-прогоном.
|
||
APK установлен на Samsung SM_A525F: обе volume-кнопки запускают запись, отпускание завершает её с хвостом 250мс.
|
||
В отдельном канале без других участников проверены переключатель и сохранение выключенного состояния после restart.
|
||
Логи: `/tmp/android-volume-ptt-final-device.log`, `/tmp/android-volume-ptt-apk-final-build.log`.
|
||
|
||
[ ] **Linux ONNX Runtime: повторная регистрация схем при запуске теста** — 03.10.2026:
|
||
test_silero_vad при загрузке модели печатает серии `Schema error: ... already registered`.
|
||
Инференс, речь, тишина и reset проходят; отдельно проверить состав и загрузку Linux-библиотек ONNX.
|
||
Диагностика: `/tmp/vad-compressor-silero-tests.log`; Android-сборка таких сообщений не содержит.
|
||
|
||
[ ] **test_tcp_proxy_full: некорректное время в диагностике** — 03.10.2026:
|
||
полный check.sh проходит, но строки basic/stress/concurrent/half_close выводят
|
||
огромные или отрицательные миллисекунды (например, -1611883348002232012ms).
|
||
Отдельно проверить формат/аргументы вывода длительности; `/tmp/vad-compressor-check.log`.
|
||
|
||
[+] **chatgui (Qt) не собирался: C++ void* в utun_node.cpp** — сделано. Причина:
|
||
`proxy_protocol.h` (C-заголовок, включается в C++ `utun_node.cpp`) делал
|
||
`e->dgram = u_malloc(...)` и `struct proxy_flow* f = arg;` без явных приведений —
|
||
в C++ нет неявного `void*`→T*. Добавлены `(uint8_t*)`/`(struct proxy_flow*)`
|
||
(валидны и в C). `vibechat` собирается (47МБ, с вшитой моделью + onnxruntime).
|
||
|
||
[+] **test_auto_socket_dynamic: root-регрессия и нагрузка** — изолированный namespace,
|
||
корректные маршруты, двусторонний поток через backpressure, строгие seq/payload,
|
||
пределы TX/RX, остановка при обрыве и drain после восстановления, баланс ресурсов.
|
||
Исправлены привязка TCP к интерфейсу, удаление TCP моделей/линков/портов из БД,
|
||
sender receive window при selective ACK, timestamp overflow и нулевой jitter.
|
||
ASan/UBSan/LSan: PASS; дополнительно исправлены невыровненные метки аллокаторов
|
||
и PID-порты в test_etcp_connect. [Разбор и логи](etcp_full_suite_findings.md).
|
||
|
||
[ ] **debug_parse_config: формат и индексы категорий** — parser ожидает `:`, документация
|
||
описывает `=`; преобразование битовой маски осталось после перехода enum на индексы.
|
||
[Разбор](etcp_full_suite_findings.md).
|
||
|
||
[+] **Q8/Android не подключается к локальному чатгуи (LAN)** — сделано. Причина: тип сокета
|
||
в Android-конфиге был захардкожен `type=public`, из-за чего `sock_match` ставил
|
||
`has_priv=0` и не создавал линки к приватным (LAN) адресам пиров (`create_links … → 0 links`).
|
||
Исправлено: (1) `ip_is_public()` вынесен в `lib/platform_compat` (единый классификатор
|
||
IPv4 с корректным ntohl); (2) в `auto_socket_reconcile_config` (Android-режим) тип сокета
|
||
выставляется по адресу (public→PUBLIC, приватный/link-local→NAT), конфиг `type` игнорируется;
|
||
(3) `sock_collect_views` больше не смотрит на `type` из конфига: `has_priv` — по классификации
|
||
локального адреса, `has_inet=1` (исходящие считаем доступными), `is_eim` — по NAT-детекции
|
||
(hairpin). `is_local_subnet` упрощён до `!ip_is_public()`. Тест `test_sock_match` обновлён
|
||
(+ `ip_is_public` кейсы). `check.sh`: 86 passed / 1 failed / 1 skipped.
|
||
|
||
[ ] **DM: прямой p2p чат с любым пользователем группы** — отдельная DM-подсистема.
|
||
ТЗ и архитектура: `/doc/dm_arch.md`. Статус: реализовано (dm_core/dm_mailbox/dm_crypto),
|
||
тесты `test_dm` (крипто) и `test_dm_e2e` (интеграция). Остались задачи ниже.
|
||
|
||
[ ] **Звонок (P2P аудио)** — голос 1:1, из CHAT-группы или p2p-чата (DM). Отдельный
|
||
каталог `src/call/`. Сигналинг + conn_mgr + аудиопоток (Opus) с backpressure
|
||
(локальный буфер отправки при переполнении роутера) + адаптивный джиттер-буфер
|
||
(SoundTouch, ускорение до 1.6x) + тоны (глитч/завершение) + watchdog 20с.
|
||
Android: переключение источника (трубка/громкая/гарнитура).
|
||
Архитектура: `/doc/chat_call_arch.md`.
|
||
|
||
[+] **Сигналинг + релей медиа** — сделано. `call_proto.h` (svc `ETCP_RT_ID_CALL
|
||
0x36`, subcmd INVITE/RINGING/ACCEPT/DECLINE/BUSY/CANCEL/HANGUP/MEDIA),
|
||
`call.c` (машина состояний, реестр сессий, conn_mgr, ring-timeout 45с,
|
||
watchdog no-traffic 20с, потеря соединения → end, события
|
||
CHAT_EVT_CALL_* 34–40), `DEBUG_CATEGORY_CALL 32`. `test_call` (2 инстанса,
|
||
happy path + decline + media-релей). Приём медиа — через `call_set_media_recv_cb`.
|
||
[+] **Headless API** — сделано. Команды control-сокета `call_start/accept/decline/
|
||
hangup` + события `call_incoming/ringing/accepted/ended/declined` (JSON);
|
||
аудио-сокет `call/call_headless.c` (TCP, бинарный фрейм
|
||
`[type:1][call_id:8][len:2][data]`, HELLO/FRAME), конфиг `call_audio_bind`.
|
||
Разрыв conn_mgr — отложенно 500мс (`CALL_TEARDOWN_DELAY_TB`) после ENDED,
|
||
чтобы HANGUP успел долететь; защита от stale-handle через conn-status.
|
||
Тесты: `test_call` (сигналинг+медиа), `test_call_headless` (полный e2e через
|
||
control+audio сокеты).
|
||
[+] **Аудио-контур (time-stretch в сервисном слое)** — сделано. `src/call/audio_jitter.cpp`
|
||
(SoundTouch, C API `ajb_*`: адаптивный джиттер-буфер ≤1000мс, target 60мс,
|
||
tempo 1.0→1.6x — догон live без потери данных при сетевой паузе),
|
||
`src/call/call_tones.c` (глитч при сталле >500мс + тон завершения),
|
||
`src/call/call_audio.c` (Opus 48kHz/20мс + jitter + тоны + pull API финального PCM).
|
||
`call.c`: `call_audio_ops` (on_media c seq/ts, get_stats) + событие
|
||
`CHAT_EVT_CALL_STATS` раз в 1с [rtt_ms, buffer_ms, tempo_x100].
|
||
Desktop `call_audio_engine.cpp` — только miniaudio I/O (получает финальный PCM).
|
||
Тест `test_audio_jitter` (ёмкость/догон/стационар). Демон остаётся C-only.
|
||
[+] **Android** — сделано. JNI (`utun_bridge_call_*`/`nativeCall*`), AudioRecord/Track
|
||
(CallAudioEngine), роутинг источника (CallAudioRouter: EARPIECE/SPEAKER/HEADSET,
|
||
BT SCO), CallScreen + IncomingCallDialog + RingtonePlayer + IncomingCallManager,
|
||
`libutun_lite/call_audio.c` (собственный аудио-контур Opus 48kHz/20мс).
|
||
[+] **Единый голосовой стек (voice stack) + SoundTouch на Android** — сделано.
|
||
Унификация аудио-контура звонка: `libutun_voice` (статическая C++-либа с
|
||
C-интерфейсом `call_audio.h` = SoundTouch + `voice_jitter.cpp` + `call_audio.c`
|
||
+ `call_tones.c`), собирается через `src/call/voice_sources.cmake` и линкуется
|
||
desktop/Android/headless-CLI. Джиттер-буфер держит КОДИРОВАННЫЕ (Opus) кадры,
|
||
декодирует только на pull (непосредственно перед воспроизведением) — память ~48x
|
||
меньше и uasync-поток разгружен. Android переведён с push-FIFO (Kotlin ArrayDeque)
|
||
на pull-модель (`nativeCallAudioPull`), своего джиттера больше нет → убрана
|
||
асимметрия задержки (desktop↔Android). Headless-аудио-сокет отдаёт готовый PCM
|
||
(декод Opus→PCM, без тактирования; клиент сам задаёт темп). Диагностика:
|
||
`CALL_STATS` расширен [dropped, underruns] (раз в 1с), счётчики headless.
|
||
Удалены `audio_jitter.cpp` и `libutun_lite/call_audio.c`. Тест `test_voice_jitter`
|
||
(ёмкость/догон/стационар). Android — `ANDROID_STL=c++_static`.
|
||
[ ] **DM-звонки** (fallback `group_id==0`) — не сделано: `call_is_online`/`call_start`
|
||
требуют реальную группу (`topo_groups_find`), для DM (group_id==0) маршрута нет.
|
||
|
||
## Звонок: две фазы исходящего вызова (индикация + гудки)
|
||
|
||
[+] **Две фазы вызова** — сделано. Исходящий звонок разбит на 2 фазы с индикацией
|
||
в окне вызова и разными гудками:
|
||
- фаза 1 «Пытаемся связаться…» (`OUTGOING` — INVITE/маршрут) — гудок «соединение»;
|
||
- фаза 2 «Звоним…» (`RINGING` — у пира идёт вызов) — длинные гудки (ringback).
|
||
Desktop: `CallWindow::Ringing` + `SoundManager` loop `call_connecting`/`call_ringing`
|
||
(MP3 в qrc), переключение по `onCallRinging`, остановка по accepted/ended/error;
|
||
добавлена обработка `GUI_EVT_CALL_ERROR` (40) — раньше при «пир офлайн» окно/гудок
|
||
зависали навсегда. Android: `CallRingback` (MediaPlayer loop из res/raw),
|
||
подписи `OUTGOING`/`RINGING` в CallScreen + ActiveCallBanner.
|
||
Звуковые события добавлены в SoundSettingsPage (call_connecting/call_ringing).
|
||
|
||
[+] **Надёжность отмены вызова (кодограмма + watchdog)** — сделано. Отмена вызова
|
||
(HANGUP/CANCEL) шлётся один раз, без ретрансмита, и при флапе маршрута (RST/epoch,
|
||
прямой TCP к мобиле не поднимается — только релей) терялась: вызываемый висел в
|
||
RINGING вечно и отвечал BUSY на все новые INVITE. Исправлено моделью «сердцебиения»:
|
||
- вызывающий шлёт кодограмму INVITE каждые 300мс весь период вызова (OUTGOING+RINGING);
|
||
- у вызываемого watchdog 2с — нет кодограммы → вызов сорван (`CALL_REASON_CANCEL`);
|
||
- завершающие кодограммы без ретрансмиссий (потерю покрывает watchdog);
|
||
- запоздалые кодограммы вызова при разговоре (CONNECTING/ACTIVE) игнорируются.
|
||
`CALL_INVITE_RETRY_TB=3000`, `CALL_RING_DEAD_TB=20000`, `CALL_NO_ROUTE_MAX_STRIKES=20`.
|
||
|
||
## Рефакторинг: per-instance chat/dm (однопоточный test_dm_e2e)
|
||
|
||
Сделано (глобальные синглтоны переведены на `UTUN_INSTANCE`):
|
||
- `chat_setting` → `inst->chat_settings` (`struct chat_setting_state`, state-level API для парсера конфига + instance-level для runtime).
|
||
- `chat_event` → `inst->chat_event_handler` (обработчик теперь получает `inst`).
|
||
- `chat_core` (`g_cc`) → `inst->chat_core` (`struct chat_core_ctx*`).
|
||
- `chat_sync` (`g_cs`) → `inst->chat_sync`.
|
||
- `member_sync` callback-списки (`g_props_cbks`/`g_apply_cbks`) → в `chat_core_ctx`.
|
||
- `chat_join` (`g_keys`) → в `chat_core_ctx`.
|
||
- `chat_msg` media-счётчики/backfill → в `chat_core_ctx`.
|
||
- `dm_core` (`g_dm`) → `inst->dm`; `dm_mailbox` (`g_mb`) → `inst->dm_mailbox`.
|
||
- `chat_headless_control` (`g_hc`) → `inst->headless`.
|
||
- `chat_whisper` — сигнатуры `init/available/trigger` принимают `inst`.
|
||
- API протащено через `inst` (chat_core_*/dm_*), обновлены call-сайты в src/ (headless, topo, media_delivery, auto_socket, config_parser).
|
||
- `test_dm_e2e.c` переписан на однопоточный: один `uasync`, три `utun_instance` (A,B,C), master state-machine, без fork. Фаза 2 (B offline) — `utun_instance_destroy(B1)` + пересоздание B2.
|
||
- Попутно исправлен 1-байтовый overflow в `dm_mailbox.c: mb_route_send` (`u_malloc(1+body_len)` → `1+1+body_len`).
|
||
|
||
Статус сборки: `make -C src` (libutun.a + бинарник utun) и `make -C tests` — OK.
|
||
|
||
### Осталось (по порядку)
|
||
|
||
[+] **test_dm_e2e: зависание в utun_instance_destroy(B1)** (фаза P_B_OFFLINE) — сделано.
|
||
Причин было несколько (не одна):
|
||
1. `lib/u_async.c`: если ближайший таймер уже истёк на входе в `uasync_poll`,
|
||
`get_next_timeout` возвращал {0,0}, `timeout_ms=-1` → `epoll_wait(-1)` блокировался
|
||
навсегда, а `process_timeouts` (вызывается только после epoll_wait) не запускался.
|
||
Фикс: `if (timeout_ms == -1 && heap не пуст) timeout_ms = 0`.
|
||
2. `routing_layer/etcp_router.c`: `router_send_conn` с group_id=0 (глобальная
|
||
маршрутизация DM/mailbox) не находил маршрут (`topo_groups_find(0)==NULL`).
|
||
Фикс: fallback на `instance_find_conn(inst, remote)` при group==NULL.
|
||
3. `dm/dm_core.c`: `dm_on_conn_status` PULL-ил только беседы с подключившимся пиром;
|
||
B2 подключается к storage (C), а беседа — с A. Фикс: на подъём любого соединения
|
||
PULL для всех бесед, чей пир не подключён напрямую.
|
||
4. `dm/dm_mailbox.c`: не было ACK после PULL_RESP → storage не чистил dm_mail.
|
||
Фикс: PULL_RESP теперь несёт sender, получатель шлёт MB_SUBCMD_ACK.
|
||
5. `utun_instance.c`: `dm_core_init` ставил deliver-cb до `dm_mailbox_init` →
|
||
mailbox не находился, deliver-cb не регистрировался. Фикс: mailbox init раньше core.
|
||
`test_dm_e2e` проходит (несколько прогонов), `test_dm` тоже.
|
||
|
||
[+] **chatgui-android: переезд на per-instance API** — сделано.
|
||
`instance_lite.c/h` (+`instance_lite_get_instance()`), `jni_bridge.c`, `standby.c`,
|
||
`headless_control.c`, `voice_recorder.c`, `attachment_sender.c`, `video_sender.c`,
|
||
`photo_sender.c` — все call-сайты переведены на `inst`. `libutun_lite` компилируется,
|
||
`jni_bridge.c` — синтакс-чисто. (headless-линковка сломана ПРЕДЫДУЩЕ — instance_lite.c
|
||
ссылается на jni_bridge-функции, не включённые в headless-сборку; отдельная задача.)
|
||
|
||
[+] **test_chat_join: segfault** (регресс рефакторинга) — сделано.
|
||
`chat_join` хранил `join_keys` в `chat_core_ctx` (CC(inst)), а тест использует chat_join
|
||
без chat_core → NULL-deref. Фикс: `join_keys` вынесен в `UTUN_INSTANCE.join_keys`
|
||
(у chat_join собственный init/destroy).
|
||
|
||
[ ] **BGP-гонка: дропнутый REQUEST_TABLE** (отдельная задача, как договорились).
|
||
|
||
**Место:** `topo_group_new_conn()` в `src/routing_layer/topo_group.c:571`. Дедуп-ветка
|
||
«conn already in senders_list» (строки 579–587) делает `return` БЕЗ повторной отправки
|
||
`topo_group_send_table_request()`.
|
||
|
||
**Зачем дедуп:** тот же `ETCP_CONN` стреляет `ETCP_CONN_STATUS_UP` дважды (UDP-линк,
|
||
затем TCP-линк). Повторная обработка задваивает `active_conn_count`, из-за чего
|
||
переподключение не стартует. Поэтому `return` в дедупе — правильный.
|
||
|
||
**В чём гонка:** REQUEST_TABLE — это то, что заставляет пира отдать свою таблицу
|
||
(NODEINFO/узлы группы). Сценарий «разнесённого старта»:
|
||
1. A↔C соединение поднято. A шлёт C `REQUEST_TABLE` для группы G (`topo_group_new_conn`
|
||
→ `topo_group_send_table_request`, `topo_group.c:602`).
|
||
2. У C группа G ещё **не создана** (создаётся позже, по мере загрузки каналов/синка).
|
||
C получает `REQUEST_TABLE`, но `topo_group_handle_request_table` не находит G →
|
||
дропает запрос.
|
||
3. C наконец создаёт G (`topo_groups_create_group` → обход connections →
|
||
`topo_group_new_conn(G, conn_A)` на стороне C) → C шлёт A свой `REQUEST_TABLE`.
|
||
A отвечает своей таблицей → **C узнаёт A**.
|
||
4. Но A больше не шлёт C `REQUEST_TABLE` → **A так и не узнаёт C** в группе G.
|
||
Итог: при разнесённом старте A не видит C в CHAT-группе (односторонняя видимость).
|
||
|
||
**Фикс (предполагаемый):** в дедуп-ветке не просто `return`, а предварительно
|
||
идемпотентно `topo_group_send_table_request(group, conn)`:
|
||
```c
|
||
if (((struct TOPO_GROUP_CONN_ITEM*)se->data)->conn == conn) {
|
||
topo_group_send_table_request(group, conn); /* повторно, идемпотентно */
|
||
DEBUG_INFO(..., "conn already in senders_list, re-request table (%s)", conn->log_name);
|
||
return;
|
||
}
|
||
```
|
||
`topo_group_send_table_request` не ведёт состояния — просто шлёт REQUEST_TABLE,
|
||
пир отвечает снимком таблицы (идемпотентно). Лишний дубль таблицы безвреден.
|
||
|
||
**Замечание:** в однопоточном `test_dm_e2e` (и `test_chat_join_e2e`) из-за
|
||
детерминированного порядка группа успевает создаться до прихода REQUEST_TABLE —
|
||
поэтому тест проходит. Реальная гонка остаётся для асинхронных сетей. Отдельный
|
||
воспроизводимый тест для этой гонки пока не написан.
|
||
|
||
[ ] **«packet undecryptable» на линках C** (вторичное). A, узнав B через BGP, через
|
||
`topo_group_connect` пытается поднять прямой A↔B линк (`seskey 4777737b`), но B не
|
||
настроен принимать A → crypto-шум каждые ~1с. Разобраться с авто-подключением
|
||
(не пытаться соединяться с узлами, для которых нет `[client]`/ncd-конфигурации,
|
||
либо обрабатывать неудачу без спама).
|
||
|
||
[+] **Обновить GUI call-сайты `tools/chatgui` (Qt, desktop)** — сделано.
|
||
- В `chat_core.h` вынесены trampoline-структуры из `.c`: `update_my_name_arg`,
|
||
`save_ui_state_arg`, `chat_setting_arg` (были в chat_profile.c/chat_core.c).
|
||
- `gui_bridge.h/impl`: `gui_bridge_set_inst()`/`gui_bridge_get_inst()` (+`g_inst`).
|
||
- `utun_node.cpp`: `gui_bridge_set_inst(m_instance)`, `chat_event_set_handler(m_instance,…)`,
|
||
`chat_core_sync_my_addresses(m_instance)`.
|
||
- Обновлены ~45 call-сайтов в 16 файлах (`mainwindow`, `messagelist`, `messagedelegate`,
|
||
`memberlistmodel`, `memberpropsdialog`, `accountlist`, `invitedialog`, `inviteby`,
|
||
`joindialog`, `settingsdialog`, `channelsettingsdialog`, `soundsettingspage`,
|
||
`audiodevicesettingspage`, `storagesettingspage`, `statuspage`, `connmonitorwindow`).
|
||
- Сборка `cmake --build . --target vibechat` — OK.
|
||
Попутно: `chat_sync_connect_from_invite` писал `cs->pending_*` на вызывающем (GUI) потоке —
|
||
перенёс в `cm_invite_trampoline` (uasync-поток), убрав гонку; заодно исправил утечку
|
||
`inv->addrs_data` и добавил null-check `cs` в трамплине.
|
||
|
||
[+] **Прогон всех тестов** — сделано. `make clean && make -j4` OK; все тесты проходят
|
||
(кроме `test_auto_socket_dynamic` — пропуск «requires root»). `test_dm_e2e` стабилен
|
||
в нескольких прогонах. В `test_dm_e2e` при teardown остаётся timer-leak (~68 узлов,
|
||
`router_no_route`), не влияет на результат — стоит разобрать отдельно.
|
||
|
||
[ ] **lwip/proxy: утечка pcb в FIN_WAIT_2** — `tcp_shutdown(pcb,0,1)` (half-close по FIN от
|
||
exit) не ставит `TF_RXCLOSED`, поэтому таймаут FIN_WAIT_2 в `tcp_slowtmr` не срабатывает:
|
||
если браузер никогда не пришлёт FIN, pcb навсегда остаётся в FIN_WAIT_2 (утечка, не crash).
|
||
Двойной free по TIME_WAIT уже закрыт (detach по локальному закрытию + guard abort).
|
||
|
||
## Открытые флаки
|
||
[+] **test_chat_join_e2e** — сделано. Причина: ложный критерий успеха джойнера J.
|
||
`wait_group_started()` проверял `senders_list` непуст в предположении «senders_list
|
||
непуст ⟺ J получил JOIN_READY». После коммита b483335c `tgi_to_channel_cb`
|
||
(topo_group_invite.c) при `NCD_EVENT_UP` стал вызывать `topo_group_new_conn` →
|
||
conn попадает в `senders_list` сразу после установки TCP/UDP-соединения, ДО
|
||
завершения хендшейка (JOIN_INFO_REQ→RESP→REQUEST→READY). J выходил «OK» раньше,
|
||
чем A получал JOIN_REQUEST → A не подписывал J → `wait_signed` на A/C падал
|
||
(`get_sign_rc=-1`). Фикс: критерий J — событие `CHAT_EVT_CONNECT_RESULT(result=0)`
|
||
(JOIN_READY), ловится через `chat_event_set_handler`; `wait_group_started` удалён.
|
||
Это также убрало гонку «C вышел раньше, чем J синкает свой рекорд» (C выходит сразу
|
||
после `wait_signed`, не дожидаясь merkle-синка J). 5 прогонов стабильно 3/3.
|
||
|
||
[ ] **test_etcp_reconnect** — флаки под параллельной нагрузкой `make check -j4`:
|
||
phase 4 (reconnect после server restart) таймаутит (`sent=480 recv=0`), при одиночном
|
||
запуске стабильно `TEST PASSED`. Наш код (chat_join/chat_sync) его не трогает.
|