diff --git a/src/call/call.c b/src/call/call.c index f1aedc5b..af6ea401 100644 --- a/src/call/call.c +++ b/src/call/call.c @@ -57,6 +57,8 @@ struct call_session { struct CONN_MGR_HANDLE* cm_handle; struct ETCP_CONN* data_conn; /* прямой data-коннект звонка (NULL для INDIRECT) */ void* ring_timer; /* caller: 45с ожидания ответа */ + void* invite_timer; /* caller: периодический ретрансмит INVITE (до RINGING) */ + uint8_t no_route_strikes; /* caller: подряд ретрансмитов без маршрута (fail-fast) */ void* traffic_timer; /* ACTIVE: 20с без медиа (запускается по первому медиа) */ void* teardown_timer; /* отложенный разрыв conn_mgr (500мс после завершения) */ void* stats_timer; /* ACTIVE: раз в 1с — CALL_STATS (rtt/buffer/tempo) */ @@ -136,6 +138,22 @@ static int call_send(struct call_ctx* ctx, uint64_t group_id, uint64_t dst, return rc; } +/* Собрать и отправить INVITE для сессии вызывающего. */ +static int call_send_invite(struct call_ctx* ctx, struct call_session* s) { + uint8_t buf[sizeof(struct call_pkt_invite)]; + struct call_pkt_invite* inv = (struct call_pkt_invite*)buf; + inv->subcmd = CALL_SUBCMD_INVITE; + inv->call_id = s->call_id; + inv->group_id = s->group_id; + inv->num_tracks = 1; + inv->tracks[0].type = CALL_TRACK_AUDIO; + inv->tracks[0].codec = CALL_CODEC_OPUS; + inv->tracks[0].sample_rate = CALL_OPUS_SAMPLE_RATE; + inv->tracks[0].channels = 1; + inv->tracks[0].bitrate = 32000; + return call_send(ctx, s->group_id, s->peer_node_id, buf, sizeof(buf), ROUTE_CRYPTO_SIGN | ROUTE_CRYPTO_ENCRYPT); +} + /* ─────────────────── реестр сессий ─────────────────── */ static struct call_session* call_session_find(struct call_ctx* ctx, uint64_t call_id) { @@ -289,6 +307,7 @@ static void call_end(struct call_ctx* ctx, struct call_session* s, uint8_t reaso ctx->c_calls_ended++; if (s->ring_timer) { uasync_cancel_timeout(ctx->inst->ua, s->ring_timer); s->ring_timer = NULL; } + if (s->invite_timer) { uasync_cancel_timeout(ctx->inst->ua, s->invite_timer); s->invite_timer = NULL; } if (s->traffic_timer) { uasync_cancel_timeout(ctx->inst->ua, s->traffic_timer); s->traffic_timer = NULL; } if (s->stats_timer) { uasync_cancel_timeout(ctx->inst->ua, s->stats_timer); s->stats_timer = NULL; } @@ -363,6 +382,37 @@ static void call_ring_timeout_cb(void* arg) { call_end(ctx, s, CALL_REASON_RING_TIMEOUT); } +/* Периодический ретрансмит INVITE (caller, до RINGING). Покрывает потерю INVITE/RINGING + * и появление маршрута позже. При стойком отсутствии маршрута — fail-fast CONNECT_FAIL. */ +static void call_invite_retry_cb(void* arg) { + struct call_session* s = (struct call_session*)arg; + if (!s || !s->ctx) return; + struct call_ctx* ctx = s->ctx; + s->invite_timer = NULL; + if (s->state == CALL_ST_ENDED) return; + if (s->state != CALL_ST_OUTGOING) return; /* RINGING — INVITE доставлен, ретрансмит не нужен */ + + if (etcp_router_has_route(ctx->inst, s->group_id, s->peer_node_id)) { + s->no_route_strikes = 0; + DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "%s: INVITE retry id=%016llx (route ok)", CALL_ID, (unsigned long long)s->call_id); + call_send_invite(ctx, s); + } else if (++s->no_route_strikes >= CALL_NO_ROUTE_MAX_STRIKES) { + DEBUG_WARN(DEBUG_CATEGORY_CALL, "%s: no route after %d retries id=%016llx -> CONNECT_FAIL", + CALL_ID, s->no_route_strikes, (unsigned long long)s->call_id); + uint8_t buf[9]; + memcpy(buf, &s->call_id, 8); + buf[8] = 2; /* err: peer unreachable (no route) */ + chat_event_post(ctx->inst, CHAT_EVT_CALL_ERROR, buf, sizeof(buf)); + call_end(ctx, s, CALL_REASON_CONNECT_FAIL); + return; + } else { + DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "%s: INVITE retry id=%016llx no route (strike %d/%d)", + CALL_ID, (unsigned long long)s->call_id, s->no_route_strikes, CALL_NO_ROUTE_MAX_STRIKES); + } + + s->invite_timer = uasync_set_timeout(ctx->inst->ua, CALL_INVITE_RETRY_TB, s, call_invite_retry_cb, "call_invite_retry"); +} + static void call_traffic_timeout_cb(void* arg) { struct call_session* s = (struct call_session*)arg; if (!s || !s->ctx) return; @@ -519,6 +569,7 @@ static void call_handle_ringing(struct call_ctx* ctx, uint64_t from, const uint8 if (!s || s->role != CALL_ROLE_CALLER || s->state == CALL_ST_ENDED) return; if (s->state == CALL_ST_OUTGOING) { s->state = CALL_ST_RINGING; + if (s->invite_timer) { uasync_cancel_timeout(ctx->inst->ua, s->invite_timer); s->invite_timer = NULL; } DEBUG_INFO(DEBUG_CATEGORY_CALL, "%s: RINGING id=%016llx", CALL_ID, (unsigned long long)call_id); call_event_id(ctx, CHAT_EVT_CALL_RINGING, call_id, 0); } @@ -692,6 +743,7 @@ void call_destroy(struct UTUN_INSTANCE* inst) { struct call_session* s = (struct call_session*)e->data; s->state = CALL_ST_ENDED; if (s->ring_timer) { uasync_cancel_timeout(inst->ua, s->ring_timer); s->ring_timer = NULL; } + if (s->invite_timer) { uasync_cancel_timeout(inst->ua, s->invite_timer); s->invite_timer = NULL; } if (s->traffic_timer) { uasync_cancel_timeout(inst->ua, s->traffic_timer); s->traffic_timer = NULL; } if (s->stats_timer) { uasync_cancel_timeout(inst->ua, s->stats_timer); s->stats_timer = NULL; } if (s->teardown_timer) { uasync_cancel_timeout(inst->ua, s->teardown_timer); s->teardown_timer = NULL; } @@ -827,27 +879,16 @@ void call_start(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t peer_nod s->state = CALL_ST_OUTGOING; ctx->c_calls_started++; - uint8_t buf[sizeof(struct call_pkt_invite)]; - struct call_pkt_invite* inv = (struct call_pkt_invite*)buf; - inv->subcmd = CALL_SUBCMD_INVITE; - inv->call_id = out_call_id; - inv->group_id = group_id; - inv->num_tracks = 1; - inv->tracks[0].type = CALL_TRACK_AUDIO; - inv->tracks[0].codec = CALL_CODEC_OPUS; - inv->tracks[0].sample_rate = CALL_OPUS_SAMPLE_RATE; - inv->tracks[0].channels = 1; - inv->tracks[0].bitrate = 32000; - DEBUG_INFO(DEBUG_CATEGORY_CALL, "%s: call_start id=%016llx peer=0x%016llx group=0x%016llx", CALL_ID, (unsigned long long)out_call_id, (unsigned long long)peer_node_id, (unsigned long long)group_id); - if (call_send(ctx, group_id, peer_node_id, buf, sizeof(buf), ROUTE_CRYPTO_SIGN | ROUTE_CRYPTO_ENCRYPT) != 0) { + if (call_send_invite(ctx, s) != 0) { call_end(ctx, s, CALL_REASON_CONNECT_FAIL); return; } call_post_bgp_path(ctx, s); s->ring_timer = uasync_set_timeout(inst->ua, CALL_RING_TIMEOUT_TB, s, call_ring_timeout_cb, "call_ring"); + s->invite_timer = uasync_set_timeout(inst->ua, CALL_INVITE_RETRY_TB, s, call_invite_retry_cb, "call_invite_retry"); } void call_accept(struct UTUN_INSTANCE* inst, uint64_t call_id) { diff --git a/src/call/call_proto.h b/src/call/call_proto.h index c48c1269..80521a56 100644 --- a/src/call/call_proto.h +++ b/src/call/call_proto.h @@ -85,6 +85,8 @@ struct call_pkt_media { /* ── таймауты и ёмкости (timebase 0.1ms) ── */ #define CALL_RING_TIMEOUT_TB 450000 /* 45с — нет ответа → CANCEL */ +#define CALL_INVITE_RETRY_TB 20000 /* 2с — период ретрансмита INVITE (до RINGING) */ +#define CALL_NO_ROUTE_MAX_STRIKES 3 /* 3 × 2с ≈ 6с без маршрута → fail-fast CONNECT_FAIL */ #define CALL_NO_TRAFFIC_TB 200000 /* 20с — нет входящих медиа → HANGUP */ #define CALL_TEARDOWN_DELAY_TB 5000 /* 500мс — задержка разрыва conn_mgr после завершения */ #define CALL_MAX_TX_FRAMES 50 /* ~1с буфер отправки (50 кадров × 20мс) */ diff --git a/src/chat/merkle_sync.c b/src/chat/merkle_sync.c index 4cca95f3..14a5824c 100644 --- a/src/chat/merkle_sync.c +++ b/src/chat/merkle_sync.c @@ -891,6 +891,11 @@ int merkle_sync_start(struct UTUN_INSTANCE* inst, uint64_t peer, } } + /* я в SLEEP-фазе — не инициирую синхронизацию (client-side throttling) */ +#ifdef UTUN_HAVE_STANDBY + if (standby_get_sleep_tb() > 0) return 0; +#endif + struct ms_session* s = _session_find(ms, peer, ns); DEBUG_TRACE(DEBUG_CATEGORY_MEMBER_SYNC, "%s: start peer=%016llx ns=%s new=%d", MS_ID, (unsigned long long)peer, ns, s ? 0 : 1); if (!s) { diff --git a/src/routing_layer/etcp_router.c b/src/routing_layer/etcp_router.c index 6a1a441e..2648c28a 100644 --- a/src/routing_layer/etcp_router.c +++ b/src/routing_layer/etcp_router.c @@ -194,21 +194,30 @@ static struct ETCP_ROUTER_CONN* router_conn_find(struct UTUN_INSTANCE* inst, // ==================================================================== // Найти ETCP_CONN для отправки (учитывая indirect-посредников). NULL = нет маршрута. -static struct ETCP_CONN* router_send_conn(struct ETCP_ROUTER_CONN* rconn) { - struct UTUN_INSTANCE* inst = rconn->inst; - struct TOPO_GROUP* group = topo_groups_find(inst->topo_groups, rconn->group_id); +static struct ETCP_CONN* router_route_conn(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t remote_node_id) { + struct TOPO_GROUP* group = topo_groups_find(inst->topo_groups, group_id); if (group) { - struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(group, rconn->remote_node_id); + struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(group, remote_node_id); if (nq && nq->conn_mgr_type == CONN_TYPE_INDIRECT && nq->conn_mgr_intermediariy_count > 0) { for (uint8_t i = 0; i < nq->conn_mgr_intermediariy_count; i++) { struct ETCP_CONN* c = topo_group_find_conn_for_node(group, nq->conn_mgr_intermediaries[i]); if (c) return c; } } - return topo_group_find_conn_for_node(group, rconn->remote_node_id); + return topo_group_find_conn_for_node(group, remote_node_id); } /* group_id=0 (или группа не найдена) — глобальная маршрутизация: прямое соединение по node_id */ - return instance_find_conn(inst, rconn->remote_node_id); + return instance_find_conn(inst, remote_node_id); +} + +static struct ETCP_CONN* router_send_conn(struct ETCP_ROUTER_CONN* rconn) { + return router_route_conn(rconn->inst, rconn->group_id, rconn->remote_node_id); +} + +int etcp_router_has_route(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t dst_node_id) { + if (!inst) return 0; + if (dst_node_id == inst->node_id) return 1; + return router_route_conn(inst, group_id, dst_node_id) != NULL; } // Собрать и закодировать (подпись/шифрование) пакет в начале отправки. diff --git a/src/routing_layer/etcp_router.h b/src/routing_layer/etcp_router.h index e07dbcc4..16b4e23f 100644 --- a/src/routing_layer/etcp_router.h +++ b/src/routing_layer/etcp_router.h @@ -207,6 +207,10 @@ int etcp_route_send(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t dst struct ETCP_ROUTER_CONN* etcp_router_conn_get(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t remote_node_id, uint8_t svc_id); +// Есть ли сейчас физический маршрут до узла (прямой, indirect-посредник или глобальный). +// 1 = есть ETCP-соединение для отправки, 0 = нет (передача встанет в no_route). +int etcp_router_has_route(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t dst_node_id); + // Отправить данные с авто-seq и контролем inflight // data: payload без svc_id, mode: ROUTE_CRYPTO_SIGN / ROUTE_CRYPTO_ENCRYPT (0 = обычный) int etcp_router_conn_send(struct ETCP_ROUTER_CONN* rconn, diff --git a/tools/chatgui-android/libutun_lite/standby.c b/tools/chatgui-android/libutun_lite/standby.c index d1cbd172..546dc02d 100644 --- a/tools/chatgui-android/libutun_lite/standby.c +++ b/tools/chatgui-android/libutun_lite/standby.c @@ -122,6 +122,9 @@ static void standby_phase_timer_cb(void* arg) { if (!g_enabled) return; uint64_t elapsed = get_time_tb() - g_phase_start_tb; g_phase = (g_phase == PHASE_ACTIVE) ? PHASE_SLEEP : PHASE_ACTIVE; + /* переармить фазу ДО фазового колбэка: колбэк читает g_phase_end_tb + (ka_active_remaining_01s), иначе он увидит старый (уже истёкший) дедлайн. */ + standby_arm_phase(); if (g_phase == PHASE_SLEEP) { g_cnt_sleeps++; DEBUG_INFO(DEBUG_CATEGORY_GENERAL, @@ -135,7 +138,6 @@ static void standby_phase_timer_cb(void* arg) { (int)(elapsed / STANDBY_TB_PER_SEC), (int)((elapsed % STANDBY_TB_PER_SEC) / 1000)); if (g_phase_cb) g_phase_cb(0, g_phase_cb_arg); } - standby_arm_phase(); } /* ── список ожидающих ── */ diff --git a/tools/chatgui/src/radio_audio_engine.cpp b/tools/chatgui/src/radio_audio_engine.cpp index 8be3b5cd..85767692 100644 --- a/tools/chatgui/src/radio_audio_engine.cpp +++ b/tools/chatgui/src/radio_audio_engine.cpp @@ -7,6 +7,7 @@ extern "C" { #include "radio/radio_audio.h" #include "../../lib/debug_config.h" +#include "../../lib/u_async.h" } #include @@ -94,6 +95,13 @@ int RadioAudioEngine::initDevice() { } m_deviceStarted = true; m_capturePos = 0; + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, + "RadioAudio: device started sr=%u cap='%s' fmt=%d internalSr=%u pb='%s' fmt=%d internalSr=%u", + m_device->sampleRate, + m_device->capture.name ? m_device->capture.name : "?", + (int)m_device->capture.format, m_device->capture.internalSampleRate, + m_device->playback.name ? m_device->playback.name : "?", + (int)m_device->playback.format, m_device->playback.internalSampleRate); return 0; } @@ -135,6 +143,31 @@ void RadioAudioEngine::dataCallback(const void* in, void* out, unsigned int fram const int16_t* in16 = (const int16_t*)in; int16_t* out16 = (int16_t*)out; + /* диагностика (DEBUG_CATEGORY_DEBUG): cadence коллбэка — gap между вызовами (снаружи) + * и длительность тела (внутри), чтобы отличить «девайс не дёргает» от «заблокирован внутри». */ + uint64_t now = get_time_tb(); + if (m_dbgFrames == 0) { + m_dbgFrames = frames; + DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "RadioAudio: callback frames=%u in=%s out=%s", + frames, in16 ? "yes" : "no", out16 ? "yes" : "no"); + } + m_dbgCbCount++; + if (m_dbgPrevCbTb != 0) { + uint64_t gap = now - m_dbgPrevCbTb; + if (gap > m_dbgGapMax) m_dbgGapMax = gap; + } + m_dbgPrevCbTb = now; + if (m_dbgLastTb == 0) { + m_dbgLastTb = now; + } else if (now - m_dbgLastTb >= 10000) { + DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "RadioAudio: cb=%u/s frames=%u gapMax=%.1fms bodyMax=%.1fms", + m_dbgCbCount, frames, (double)m_dbgGapMax / 10.0, (double)m_dbgBodyMax / 10.0); + m_dbgCbCount = 0; + m_dbgLastTb = now; + m_dbgGapMax = 0; + m_dbgBodyMax = 0; + } + /* захват: копим 960 samples → encode+send (сервисный слой). Только когда PTT зажата. */ if (in16 && m_talking.load()) { for (unsigned int i = 0; i < frames; i++) { @@ -150,4 +183,8 @@ void RadioAudioEngine::dataCallback(const void* in, void* out, unsigned int fram int n = radio_audio_pull_pcm(m_groupId.load(), out16, (int)frames); if (n < (int)frames) std::memset(out16 + n, 0, (size_t)(frames - n) * sizeof(int16_t)); + + uint64_t end = get_time_tb(); + uint64_t body = end - now; + if (body > m_dbgBodyMax) m_dbgBodyMax = body; } diff --git a/tools/chatgui/src/radio_audio_engine.h b/tools/chatgui/src/radio_audio_engine.h index 76a12578..48ff83ea 100644 --- a/tools/chatgui/src/radio_audio_engine.h +++ b/tools/chatgui/src/radio_audio_engine.h @@ -50,4 +50,12 @@ private: std::vector m_captureBuf; /* 960 samples, аудио-поток */ int m_capturePos = 0; + + /* диагностика (DEBUG_CATEGORY_DEBUG): фактический cadence miniaudio-коллбэка */ + unsigned int m_dbgFrames = 0; + uint32_t m_dbgCbCount = 0; + uint64_t m_dbgLastTb = 0; /* момент предыдущего сброса статистики (для cb/s) */ + uint64_t m_dbgPrevCbTb = 0; /* момент входа предыдущего коллбэка */ + uint64_t m_dbgGapMax = 0; /* макс. gap между коллбэками за окно (tb 0.1мс) */ + uint64_t m_dbgBodyMax = 0; /* макс. длительность тела коллбэка за окно (tb 0.1мс) */ };