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