From c5eb17e59b654bc4f8af49459bb2d75770aecb4a Mon Sep 17 00:00:00 2001 From: evgeny Date: Mon, 26 Oct 2026 13:36:39 +0100 Subject: [PATCH] Record outgoing call admission defect and verification plan --- doc/call_audit_findings.txt | 117 ++++++++++++++++++++++++++++++++++++ doc/tasks.md | 11 ++++ 2 files changed, 128 insertions(+) create mode 100644 doc/call_audit_findings.txt diff --git a/doc/call_audit_findings.txt b/doc/call_audit_findings.txt new file mode 100644 index 00000000..5cb1991c --- /dev/null +++ b/doc/call_audit_findings.txt @@ -0,0 +1,117 @@ +Аудит src/call — 26.10.2026, начат с голосовых вызовов + +Статус: подтверждён дефект допуска второго исходящего звонка. Реализация не +менялась; аудит остановлен перед её исправлением по правилу AGENTS.md. +Полная проверка аудио, видео и платформенных разделов не завершена. +База проверки: f79b9261, чистое рабочее дерево после предыдущего push stream. + +1. Подтверждённый дефект: call_start не проверяет обычную занятость + +call_busy возвращает1 для любой сессии state!=ENDED. Входящий INVITE проверяет +этот же признак других сессий и отвечает BUSY. call_start проверяет только +radio_call_busy, затем создаёт новую call_session без проверки обычного звонка. +В результате один instance может иметь несколько незавершённых обычных сессий. + +Точки кода на базе проверки: +- call.c:605–623: входящий INVITE отклоняется при другом незавершённом звонке; +- call.c:960–965: call_busy учитывает все состояния, кроме ENDED; +- call.c:1064–1137: call_start не использует call_busy, создаёт сессию/CM, + отправляет INVITE и запускает таймеры независимо от уже идущего звонка; +- chat_headless_control.c:750–763: headless-команда напрямую вызывает call_start, + без дополнительной проверки call_busy. Ограничение только в UI недостаточно. + +Реестр может содержать завершённые записи для END_ACK/таймаута и новый звонок; +это штатно и не означает поддержку нескольких одновременно незавершённых. +call_audio_start_impl использует один g_call_id/codec/jitter-контур; start другого +ID освобождает прежний. Аппаратное воспроизведение/переключение в этом аудите +не запускалось, фактическая потеря звука не заявляется. + +2. Изолированное воспроизведение на настоящих ядрах + +/tmp/call-audit-busy.c использует существующий fixture tests/test_call.c: +два UTUN_INSTANCE на одном UASYNC, свои временные конфиги/ключи/SQLite, +настоящие CHAT/BGP/ETCP/router/CM. Сторонних узлов и пользовательской БД нет. +После штатного получения нужного состояния вызывается публичный call_start +с другим call_id для того же пира. Реестр и счётчики читаются в uasync. +Ни состояние первой сессии, ни результат call_busy вручную не подменяются. + +У каждого сценария отдельный процесс. Перед вторым call_start: +call_busy=1, число незавершённых=1. Ожидается отказ без второй сессии/CM, +без изменения первой сессии и c_calls_started. Фактически: + +Сценарий Первая сессия live Новый CM c_calls_started +outgoing caller OUTGOING, state1 1->2 да 1->2 +ringing caller RINGING, state2 1->2 да 1->2 +active обе стороны приняли, ACTIVE3 1->2 да 1->2 +incoming callee RINGING, state2 1->2 да 0->1 +wait_decision caller WAIT_DECISION, state5 1->2 да 1->2 + +Во всех пяти первая сессия/её состояние сохранены, вторая имеет OUTGOING1. +Это две незавершённые сессии, а не доказательство двух ACTIVE-разговоров: +стенд останавливается после проверки локального допуска, до обработки +второго INVITE другой стороной. Два CM handles не означают два физических ETCP. +Все5 отрицательных проверок завершаются exit1; после destroy/poll/uasync_destroy +счётчик tracked allocations возвращается0->0. Утечка этим тестом не обнаружена. + +Диагностика: глобальный error, категория call=debug. В логах видны оба +call_start, ID/peer/group, отдельный CM, исходное состояние, live1->2, +счётчики и cleanup. Все логи пяти сценариев прочитаны полностью. +Пример ACTIVE: busy=1 live=1->2 original_state=3 preserved=1 second_created=1 +second_state=1 second_CM=1 started=1->2 expected_live=1 result=FAIL. + +Сборка через штатный Makefile/LIBUTUN/COMMON_LIBS, без ручного списка src/*.c: +make -C tests -f Makefile -f /tmp/call-audit-busy.mk /tmp/call-audit-busy +PASS: /tmp/call-audit-busy-build.log. +Логи /tmp/call-audit-busy-{outgoing,ringing,active,incoming,wait_decision}.log. + +3. Предлагаемое исправление и проверки + +Единая точка допуска — call_start в uasync. До создания сессии, CM, отправки +INVITE и публикации занятости проверять call_busy. Существующий звонок не +завершать и не заменять. Для нового ID при занятости использовать существующее +событие ENDED/BUSY, как при pending radio invitation, с понятным INFO-логом +нового/занятого ID, состояния и peer/group. Новые состояния/таймеры/wire не нужны. + +До BUSY-события отдельно обработать уже зарегистрированный out_call_id: +повтор/коллизия не должны породить ENDED с ID действующего звонка или вставить +дубликат ключа реестра. Такой вызов безопасно отклонять с WARN без изменения +существующего звонка. Для новой попытки caller выбирает ненулевой уникальный ID. +Это требование к корректности будущего исправления; отдельная runtime-проверка +коллизии ID в текущем аудите не выполнялась. + +Постоянная регрессия должна проверить: +- OUTGOING, caller/callee RINGING, ACTIVE и обе стороны WAIT_DECISION; +- второй вызов тому же/другому пиру и из другой группы/DM, защита на уровне instance; +- отсутствие второй session/CM/INVITE, новых started и изменений исходного звонка; +- BUSY ровно для нового ID, повтор существующего ID без ложного ENDED исходного; +- новый уникальный ID разрешён после ENDED, даже пока живёт старый tombstone; +- сохранение правил pending radio invitation, phone-policy и нормального start; +- исходный call/CM/аудиоприём продолжаются, cleanup без tracked leaks. + +После исправления: сборка, test_call/test_call_headless, тесты голосового +стека соответствующей C++ сборки и общий check.sh. Затем продолжить аудит +call_audio/jitter/delay/timeline/tones/headless, а после них — видео. + +4. Полнота аудита и текущие проверки + +Полностью прочитаны call.c1230 строк, call.h134, call_priv.h43, call_proto.h111, +call_audio.h105 и voice_sources.cmake. Размеры заголовков указаны до правок. +В call_audio.c прочитана полная call_audio_start_impl141–258, остальной файл +целиком ещё не проверен. doc/chat_call_arch.md прочитан до строки320 из465. +Из теста call использован штатный fixture и просмотрены относящиеся к нему +setup/события/сценарии; полноценный аудит всех тестовых/video-функций не заявляется. + +call.h, call_audio.h, call_priv.h отмечены[~]; прежняя отметка call_proto.h[~] +сохранена. Итого0[V]/4[~]/17[ ] для21 заголовка src/call. Остальные голосовые +заголовки пока[ ], видео по просьбе пользователя ещё не проверялось. +doc/chat_call_arch.md[~], doc/call_video.txt[ ] сохранены. В call.h и doc +добавлено предупреждение о подтверждённом дефекте, задача записана в tasks.md. + +Штатный tests/test_call PASS:2/2,0 failed, exit0. +Лог /tmp/call-audit-baseline-test-call.log прочитан полностью; ERROR-сообщения +относятся к ожидаемым отрицательным media/video-проверкам самого теста. +Прохождение штатного набора не закрывает выявленный пробел допуска. +Новая диагностическая программа собрана PASS, пять отрицательных проверок +FAIL ожидаемо. C/C++ реализации и штатные тесты проекта не менялись. +Полная сборка/check.sh, sanitizers, отдельный C++ audio/GUI и Android runtime +в этом продолжении не запускались; прежние результаты stream сюда не переносятся. diff --git a/doc/tasks.md b/doc/tasks.md index e9d588f6..0f63177a 100644 --- a/doc/tasks.md +++ b/doc/tasks.md @@ -35,6 +35,17 @@ Полный аудит остальных контрактов остаётся частичным; wire/формат БД не менялись. Подробности: [member_merkle_audit_findings.txt](member_merkle_audit_findings.txt). +[ ] **call: запрещать второй исходящий вызов при незавершённом обычном звонке** — подтверждено 26.10.2026. + call_start проверяет radio_call_busy, но не call_busy: на двух настоящих CHAT/ETCP-ядрах + во всех5 сценариях OUTGOING/RINGING/ACTIVE/входящий RINGING/WAIT_DECISION live1->2, + создаётся второй CM handle, c_calls_started растёт; исходная сессия остаётся незавершённой. + Проверять обычную занятость до session/CM/INVITE, сохраняя текущий звонок; новый ID отклонять ENDED/BUSY. + Отдельно обрабатывать уже зарегистрированный ID, чтобы отказ не дал ложный ENDED действующему звонку. + Закрепить состояния, оба направления/группы/DM, отсутствие второго INVITE, повтор ID и новый звонок после ENDED. + Штатный test_call2/2 PASS; диагностические5 сценариев ожидаемо FAIL, cleanup allocations0->0. + Реализация ещё не исправлена, аудит остановлен по AGENTS.md. + Подробности: [call_audit_findings.txt](call_audit_findings.txt); логи /tmp/call-audit-busy-*.log. + [+] **stream: закрывать INFO-пробу при истечении её 9-байтового запроса** — исправлено и проверено 26.10.2026. expire_controls сверяет length9 и текущий probe_id; истёкший INFO закрывает flow с OVERLOAD. Лог содержит размер, возраст control и факт закрытия flow; чужой probe_id не закрывает новую попытку.