Browse Source

call: ретрансмит INVITE каждые 2с + fail-fast при отсутствии маршрута (6с)

proxy
evgeny 1 week ago
parent
commit
0e625da109
  1. 67
      src/call/call.c
  2. 2
      src/call/call_proto.h
  3. 5
      src/chat/merkle_sync.c
  4. 21
      src/routing_layer/etcp_router.c
  5. 4
      src/routing_layer/etcp_router.h
  6. 4
      tools/chatgui-android/libutun_lite/standby.c
  7. 37
      tools/chatgui/src/radio_audio_engine.cpp
  8. 8
      tools/chatgui/src/radio_audio_engine.h

67
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) {

2
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мс) */

5
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) {

21
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;
}
// Собрать и закодировать (подпись/шифрование) пакет в начале отправки.

4
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,

4
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();
}
/* ── список ожидающих ── */

37
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 <cstring>
@ -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;
}

8
tools/chatgui/src/radio_audio_engine.h

@ -50,4 +50,12 @@ private:
std::vector<int16_t> 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мс) */
};

Loading…
Cancel
Save