Новый модуль src/radio (radio/radio_audio/radio_headless) — push-to-talk
радио поверх topo-групп, доставка аудио в канал, headless-режим и тесты.
Интеграция в chatgui (ptt_hotkey_manager, radio_audio_engine, радионастройки,
вызовы) и chatgui-android (RadioAudioEngine, RadioOverlayService, UI).
Без call_audio_init(inst) g_inst оставался NULL и call_set_audio_ops не
регистрировался: TX (feed_pcm) падал на !ua, RX (audio_ops->on_media) не
вызывался — тишина и спам 'callAudioFeed returned false'. Зеркалим desktop:
utun_bridge_call_audio_start теперь делает call_audio_init + call_audio_start.
topo_recovery_callback: detach cb on UP handoff and close handle on
DOWN/TIMEOUT so no handle with cb_arg==ctx outlives ctx (ASAN
heap-buffer-overflow in topo_recovery_callback).
tgi_to_channel_cb: force_close handle on NCD_EVENT_TIMEOUT so a late UP
cannot fire into the freed tgi_conn_ctx.
Also: android chatgui reverseLayout scroll + msg order/stable pagination.
- topo_node: сокет рекламирует INTERFACE (до NAT) + NAT (после NAT, EIM);
strict/direct отдельным адресом не передаются
- topo_node_sqlite: addr_type_classify различает NAT_VERIFIED_*; добавлен
ADDR_TYPE_NAT (тип неизвестен), fallback больше не даёт DIRECT по IP
- chat_member: бейдж NAT-типа по приоритету (public>nat_eim>nat>nat_strict>local)
вместо MIN(addr_type)
- GUI (desktop+android): адреса мембера с socket_id и ролью (local/public/nat/nat_eim/nat_strict)
- C-слой: video_sender (content_type video/mp4, media_video=0 без FFmpeg)
- chat_msg_submit: duration/width/height для метаданных видео в теле сообщения
- JNI: nativeVideoSend + NativeLib/Repository/ViewModel
- VideoRecordScreen: CameraX VideoCapture, 4 пресета качества (разрешение+битрейт),
выбор камеры (селфи/широкая/обычная/макро/теле по фокусу), тумблер подсветки,
запись/пауза/отправить, лимит 60с
- InputBar: единая кнопка — короткий тап переключает voice/video, удержание — запись
- режим камеры/микрофона сохраняется в ViewModel
standby.h/c: duty-cycle ACTIVE/SLEEP, standby_wait() будит в burst сразу,
в SLEEP — до следующего burst; notify_network_activity для раннего пробуждения.
Интеграция (по флагу UTUN_HAVE_STANDBY, десктоп без изменений):
- conn_mgr: bg_ping/candidate_ping в SLEEP defer через standby_wait
- route_connectivity: probe_node откладывается (PROBE_STATUS_DEFERRED)
- topo_group_connect: пауза между попытками — активный режим 1с, фон standby_wait
- jni_bridge/ConfigProvider/NetworkActivityMonitor: set_active + сетевая активность
- chat_setting: standby_active_sec/sleep_sec/min_sleep_sec
+ юнит-тест test_standby (31 проверок)
- dedup double ETCP_CONN_STATUS_UP (UDP+TCP links) in topo_group_new_conn
- close conn_mgr handle on TIMEOUT in Phase2/3 (fix stuck 'already connecting')
- infinite Phase1→2→3 cycle with pause (1s active / 30s standby)
- CHAT_EVT_CONNECTING_NODES: yellow circle in member list while connecting
- Android: client_activity wiring via onStart/onStop + nativeSetActive
- function-level comments in topo_group_connect.c
- android_jni_bridge: utun_bridge_set_debug_level actually calls debug_set_level / debug_set_category_level_by_name (was stub)
- SettingsScreen: CATEGORIES updated to match current g_debug_categories from debug_config.c
- ConfigProvider: persist category levels in [debug] section of generated INI config
- u_async: poll() log uses computed timeout_ms instead of confusing %d.%dms format
- u_async: timeout_node char name[16] → const char* name (no truncation, smaller struct)
- memory_pool: pool alloc/free debug logs moved from DEBUG to TRACE
- instance_lite: poll timeout changed from 10ms (100tb) to infinite (-1) — sleeps until traffic
- instance_lite: removed useless heartbeat timer
- Добавлен device_type (1 байт, CLIENT_TYPE_*) в INIT_REQUEST и INIT_RESPONSE
- Добавлен keepalive_interval (2 байта) в INIT_RESPONSE
- STCP handshake расширен с 43 до 46 байт (device_type+keepalive)
- negotiate_keepalive(): оба desktop/server → min, иначе (есть mobile) → max
- keepalive_interval вынесен в UTUN_INSTANCE для использования в handshake
- stcp_client_connect() принимает device_type и keepalive_interval
- peer_device_type/peer_keepalive_interval передаются через stcp_link в ETCP_LINK
auto_socket.c: create_iface_tcp_socket returns stcp_server* instead of int,
ifa->v4_tcp/ifa->v6_tcp now properly track the created server, preventing
duplicate bind/listen on every reconcile_iface call.
etcp_connections.c: tcp_socket_add now resolves interface_addr via
get_interface_ip_by_index/get_interface_ipv6_addr_nl for any type (not just
PUBLIC), preventing NAT TCP v4 sockets from being destroyed due to INADDR_ANY.
- u_async.c: process immediate_queue BEFORE epoll_wait in uasync_poll
- stcp_link.c: defensive NULL etcp_conn/etcp_link in stcp_link_close
- utun_instance.c: move stcp_server_list_destroy_all to Phase J (before ETCP),
add deferred drain in both Phase J and L
- stcp.h/c: STCP_HS_ENC_CLIENT 6→38, STCP_HS_ENC_SERVER 7→39,
exchange ed25519_pubkey in both handshake directions
- etcp_connections.c: copy peer_ed25519 from STCP to ETCP_CONN in
etcp_link_enter_ready_tcp; guard link->etcp in tcp_link_close_cb
- chat_sync.c: fix lk->conn NULL dereference for TCP links
- test_stcp.c: update stcp_server_create/stcp_client_connect callsites
- tools/chat_tcp_test: change transport udp→tcp
Root cause: instance_lite_restart was creating new thread without
re-setting g_event_handler, causing all chat events (CHANNEL_UPDATED,
CONNECT_RESULT, etc.) to be silently dropped after app restart.
Fixes:
- instance_lite_restart: save/restore g_event_handler across hard restart
- instance_thread restart loop: re-call chat_event_set_handler after uasync_create
- utun_bridge_restart: call instance_lite_set_event_handler before restart
- AppEventHandler SharedFlow replay: 1 → 16 to not lose early init events
- ViewModel: added diagnostic logging for channel/connect events
- Keep diagnostic logs in instance_lite (set_handler, chat_event_forward NULL check)
- instance_event_handler: removed static for cross-block forward declaration
* prefix prepended in on_media_registered before b64 data
on_msg_inserted, chat_core_attachment_download, get_messages_json
only attempt b64_decode when prefix starts with '*' marker
Android: file picker → pending preview bar (name+size+X) →
combined send with text; cache copy, save dialog for downloads
Desktop: attach stores pending, preview bar before send,
combined with text in onSendClicked
C: attachment_sender.c — include filename in msg data for display