Record outgoing call admission defect and verification plan

This commit is contained in:
evgeny committed 2026-10-26 14:56:54 +01:00
1 parent aa09459c70
commit c5eb17e59b
2 files changed
+128

No files matched your search

+117
View File
@@ -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 сюда не переносятся.
+11
View File
@@ -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 не закрывает новую попытку.