12 KiB
Задачи по проекту
Сюда пишется список задач с короткой аннотацией. Если задача большая и имеет ТЗ - то ТЗ оформляется отдельным файлом, а сюда помещается аннотация и ссылка на ТЗ.
Текущая задача
[ ] 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. Статус: спроектировано, ждёт реализации.
Рефакторинг: 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_synccallback-списки (g_props_cbks/g_apply_cbks) → вchat_core_ctx.chat_join(g_keys) → вchat_core_ctx.chat_msgmedia-счётчики/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), не влияет на результат — стоит разобрать отдельно.
Открытые флаки
[ ] test_etcp_reconnect — флаки под параллельной нагрузкой make check -j4:
phase 4 (reconnect после server restart) таймаутит (sent=480 recv=0), при одиночном
запуске стабильно TEST PASSED. Наш код (chat_join/chat_sync) его не трогает.