|
|
|
@ -56,8 +56,8 @@ struct call_session { |
|
|
|
struct call_ctx* ctx; /* обратная ссылка (для коллбэков conn_mgr/таймеров) */ |
|
|
|
struct call_ctx* ctx; /* обратная ссылка (для коллбэков conn_mgr/таймеров) */ |
|
|
|
struct CONN_MGR_HANDLE* cm_handle; |
|
|
|
struct CONN_MGR_HANDLE* cm_handle; |
|
|
|
struct ETCP_CONN* data_conn; /* прямой data-коннект звонка (NULL для INDIRECT) */ |
|
|
|
struct ETCP_CONN* data_conn; /* прямой data-коннект звонка (NULL для INDIRECT) */ |
|
|
|
void* ring_timer; /* caller: 45с ожидания ответа */ |
|
|
|
void* ring_timer; /* caller: 45с ожидания ответа; callee: 2с watchdog кодограмм */ |
|
|
|
void* invite_timer; /* caller: периодический ретрансмит INVITE (до RINGING) */ |
|
|
|
void* invite_timer; /* caller: кодограмма INVITE каждые 300мс (OUTGOING+RINGING) */ |
|
|
|
uint8_t no_route_strikes; /* caller: подряд ретрансмитов без маршрута (fail-fast) */ |
|
|
|
uint8_t no_route_strikes; /* caller: подряд ретрансмитов без маршрута (fail-fast) */ |
|
|
|
void* traffic_timer; /* ACTIVE: 20с без медиа (запускается по первому медиа) */ |
|
|
|
void* traffic_timer; /* ACTIVE: 20с без медиа (запускается по первому медиа) */ |
|
|
|
void* teardown_timer; /* отложенный разрыв conn_mgr (500мс после завершения) */ |
|
|
|
void* teardown_timer; /* отложенный разрыв conn_mgr (500мс после завершения) */ |
|
|
|
@ -382,22 +382,24 @@ static void call_ring_timeout_cb(void* arg) { |
|
|
|
call_end(ctx, s, CALL_REASON_RING_TIMEOUT); |
|
|
|
call_end(ctx, s, CALL_REASON_RING_TIMEOUT); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* Периодический ретрансмит INVITE (caller, до RINGING). Покрывает потерю INVITE/RINGING
|
|
|
|
/* Периодическая кодограмма INVITE (caller) каждые 300мс на всём протяжении вызова
|
|
|
|
* и появление маршрута позже. При стойком отсутствии маршрута — fail-fast CONNECT_FAIL. */ |
|
|
|
* (OUTGOING + RINGING) — «сердцебиение»: покрывает потерю INVITE/RINGING, держит |
|
|
|
|
|
|
|
* маршрут и кормит watchdog вызываемого (2с без кодограммы → вызов сорван). |
|
|
|
|
|
|
|
* При стойком отсутствии маршрута — fail-fast CONNECT_FAIL. */ |
|
|
|
static void call_invite_retry_cb(void* arg) { |
|
|
|
static void call_invite_retry_cb(void* arg) { |
|
|
|
struct call_session* s = (struct call_session*)arg; |
|
|
|
struct call_session* s = (struct call_session*)arg; |
|
|
|
if (!s || !s->ctx) return; |
|
|
|
if (!s || !s->ctx) return; |
|
|
|
struct call_ctx* ctx = s->ctx; |
|
|
|
struct call_ctx* ctx = s->ctx; |
|
|
|
s->invite_timer = NULL; |
|
|
|
s->invite_timer = NULL; |
|
|
|
if (s->state == CALL_ST_ENDED) return; |
|
|
|
if (s->state == CALL_ST_ENDED) return; |
|
|
|
if (s->state != CALL_ST_OUTGOING) return; /* RINGING — INVITE доставлен, ретрансмит не нужен */ |
|
|
|
if (s->state != CALL_ST_OUTGOING && s->state != CALL_ST_RINGING) return; /* CONNECTING/ACTIVE — звонок идёт */ |
|
|
|
|
|
|
|
|
|
|
|
if (etcp_router_has_route(ctx->inst, s->group_id, s->peer_node_id)) { |
|
|
|
if (etcp_router_has_route(ctx->inst, s->group_id, s->peer_node_id)) { |
|
|
|
s->no_route_strikes = 0; |
|
|
|
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); |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "%s: INVITE codegram id=%016llx (route ok)", CALL_ID, (unsigned long long)s->call_id); |
|
|
|
call_send_invite(ctx, s); |
|
|
|
call_send_invite(ctx, s); |
|
|
|
} else if (++s->no_route_strikes >= CALL_NO_ROUTE_MAX_STRIKES) { |
|
|
|
} 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", |
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_CALL, "%s: no route after %d strikes id=%016llx -> CONNECT_FAIL", |
|
|
|
CALL_ID, s->no_route_strikes, (unsigned long long)s->call_id); |
|
|
|
CALL_ID, s->no_route_strikes, (unsigned long long)s->call_id); |
|
|
|
uint8_t buf[9]; |
|
|
|
uint8_t buf[9]; |
|
|
|
memcpy(buf, &s->call_id, 8); |
|
|
|
memcpy(buf, &s->call_id, 8); |
|
|
|
@ -406,13 +408,26 @@ static void call_invite_retry_cb(void* arg) { |
|
|
|
call_end(ctx, s, CALL_REASON_CONNECT_FAIL); |
|
|
|
call_end(ctx, s, CALL_REASON_CONNECT_FAIL); |
|
|
|
return; |
|
|
|
return; |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "%s: INVITE retry id=%016llx no route (strike %d/%d)", |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "%s: INVITE codegram id=%016llx no route (strike %d/%d)", |
|
|
|
CALL_ID, (unsigned long long)s->call_id, s->no_route_strikes, CALL_NO_ROUTE_MAX_STRIKES); |
|
|
|
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"); |
|
|
|
s->invite_timer = uasync_set_timeout(ctx->inst->ua, CALL_INVITE_RETRY_TB, s, call_invite_retry_cb, "call_invite_retry"); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Вызываемый: 2с без кодограммы INVITE → вызывающий пропал (отмена потерялась) — закрываем. */ |
|
|
|
|
|
|
|
static void call_ring_dead_cb(void* arg) { |
|
|
|
|
|
|
|
struct call_session* s = (struct call_session*)arg; |
|
|
|
|
|
|
|
if (!s || !s->ctx) return; |
|
|
|
|
|
|
|
struct call_ctx* ctx = s->ctx; |
|
|
|
|
|
|
|
s->ring_timer = NULL; |
|
|
|
|
|
|
|
if (s->state != CALL_ST_RINGING) return; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_CALL, "%s: ring dead (2с без кодограммы) id=%016llx -> end", |
|
|
|
|
|
|
|
CALL_ID, (unsigned long long)s->call_id); |
|
|
|
|
|
|
|
call_end(ctx, s, CALL_REASON_CANCEL); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static void call_traffic_timeout_cb(void* arg) { |
|
|
|
static void call_traffic_timeout_cb(void* arg) { |
|
|
|
struct call_session* s = (struct call_session*)arg; |
|
|
|
struct call_session* s = (struct call_session*)arg; |
|
|
|
if (!s || !s->ctx) return; |
|
|
|
if (!s || !s->ctx) return; |
|
|
|
@ -513,15 +528,23 @@ static void call_handle_invite(struct call_ctx* ctx, uint64_t from, uint64_t gro |
|
|
|
memcpy(&call_id, b + 1, 8); |
|
|
|
memcpy(&call_id, b + 1, 8); |
|
|
|
memcpy(&inv_group, b + 9, 8); |
|
|
|
memcpy(&inv_group, b + 9, 8); |
|
|
|
|
|
|
|
|
|
|
|
/* повторная доставка INVITE (тот же call_id) — идемпотентно RINGING */ |
|
|
|
/* кодограмма-дубликат (тот же call_id): пока звоним — пере-взводим watchdog и
|
|
|
|
|
|
|
|
* идемпотентно отвечаем RINGING; в разговоре (CONNECTING/ACTIVE) — игнорируем. */ |
|
|
|
struct call_session* s = call_session_find(ctx, call_id); |
|
|
|
struct call_session* s = call_session_find(ctx, call_id); |
|
|
|
if (s && s->state != CALL_ST_ENDED) { |
|
|
|
if (s) { |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "%s: INVITE retransmit id=%016llx -> re-RINGING", CALL_ID, (unsigned long long)call_id); |
|
|
|
if (s->role == CALL_ROLE_CALLEE && s->state == CALL_ST_RINGING) { |
|
|
|
|
|
|
|
if (s->ring_timer) { uasync_cancel_timeout(ctx->inst->ua, s->ring_timer); s->ring_timer = NULL; } |
|
|
|
|
|
|
|
s->ring_timer = uasync_set_timeout(ctx->inst->ua, CALL_RING_DEAD_TB, s, call_ring_dead_cb, "call_ring_dead"); |
|
|
|
|
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "%s: INVITE codegram id=%016llx -> re-RINGING + re-arm dead", CALL_ID, (unsigned long long)call_id); |
|
|
|
uint8_t pkt[sizeof(struct call_pkt_ringing)]; |
|
|
|
uint8_t pkt[sizeof(struct call_pkt_ringing)]; |
|
|
|
struct call_pkt_ringing* r = (struct call_pkt_ringing*)pkt; |
|
|
|
struct call_pkt_ringing* r = (struct call_pkt_ringing*)pkt; |
|
|
|
r->subcmd = CALL_SUBCMD_RINGING; |
|
|
|
r->subcmd = CALL_SUBCMD_RINGING; |
|
|
|
r->call_id = call_id; |
|
|
|
r->call_id = call_id; |
|
|
|
call_send(ctx, s->group_id, from, pkt, sizeof(pkt), ROUTE_CRYPTO_SIGN | ROUTE_CRYPTO_ENCRYPT); |
|
|
|
call_send(ctx, s->group_id, from, pkt, sizeof(pkt), ROUTE_CRYPTO_SIGN | ROUTE_CRYPTO_ENCRYPT); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "%s: INVITE codegram id=%016llx ignored (state=%s)", |
|
|
|
|
|
|
|
CALL_ID, (unsigned long long)call_id, call_state_name(s->state)); |
|
|
|
|
|
|
|
} |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -555,6 +578,9 @@ static void call_handle_invite(struct call_ctx* ctx, uint64_t from, uint64_t gro |
|
|
|
call_event_incoming(ctx, group, from, call_id); |
|
|
|
call_event_incoming(ctx, group, from, call_id); |
|
|
|
call_post_bgp_path(ctx, s); |
|
|
|
call_post_bgp_path(ctx, s); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* watchdog: вызывающий шлёт кодограммы INVITE каждые 300мс; 2с тишины → вызов сорван */ |
|
|
|
|
|
|
|
s->ring_timer = uasync_set_timeout(ctx->inst->ua, CALL_RING_DEAD_TB, s, call_ring_dead_cb, "call_ring_dead"); |
|
|
|
|
|
|
|
|
|
|
|
uint8_t pkt[sizeof(struct call_pkt_ringing)]; |
|
|
|
uint8_t pkt[sizeof(struct call_pkt_ringing)]; |
|
|
|
struct call_pkt_ringing* r = (struct call_pkt_ringing*)pkt; |
|
|
|
struct call_pkt_ringing* r = (struct call_pkt_ringing*)pkt; |
|
|
|
r->subcmd = CALL_SUBCMD_RINGING; |
|
|
|
r->subcmd = CALL_SUBCMD_RINGING; |
|
|
|
@ -569,7 +595,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 || s->role != CALL_ROLE_CALLER || s->state == CALL_ST_ENDED) return; |
|
|
|
if (s->state == CALL_ST_OUTGOING) { |
|
|
|
if (s->state == CALL_ST_OUTGOING) { |
|
|
|
s->state = CALL_ST_RINGING; |
|
|
|
s->state = CALL_ST_RINGING; |
|
|
|
if (s->invite_timer) { uasync_cancel_timeout(ctx->inst->ua, s->invite_timer); s->invite_timer = NULL; } |
|
|
|
/* invite_timer не трогаем — кодограмма INVITE продолжается и в RINGING */ |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_CALL, "%s: RINGING id=%016llx", CALL_ID, (unsigned long long)call_id); |
|
|
|
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); |
|
|
|
call_event_id(ctx, CHAT_EVT_CALL_RINGING, call_id, 0); |
|
|
|
} |
|
|
|
} |
|
|
|
@ -585,6 +611,7 @@ static void call_handle_accept(struct call_ctx* ctx, uint64_t from, const uint8_ |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_CALL, "%s: ACCEPT id=%016llx -> conn_mgr_open", CALL_ID, (unsigned long long)call_id); |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_CALL, "%s: ACCEPT id=%016llx -> conn_mgr_open", CALL_ID, (unsigned long long)call_id); |
|
|
|
s->state = CALL_ST_CONNECTING; |
|
|
|
s->state = CALL_ST_CONNECTING; |
|
|
|
if (s->ring_timer) { uasync_cancel_timeout(ctx->inst->ua, s->ring_timer); s->ring_timer = NULL; } |
|
|
|
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; } |
|
|
|
|
|
|
|
|
|
|
|
int rc = conn_mgr_open(ctx->inst, s->group_id, s->peer_node_id, call_conn_cb, s, &s->cm_handle); |
|
|
|
int rc = conn_mgr_open(ctx->inst, s->group_id, s->peer_node_id, call_conn_cb, s, &s->cm_handle); |
|
|
|
if (rc != 0) { |
|
|
|
if (rc != 0) { |
|
|
|
@ -900,6 +927,7 @@ void call_accept(struct UTUN_INSTANCE* inst, uint64_t call_id) { |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_CALL, "%s: call_accept id=%016llx -> ACCEPT + conn_mgr_open", |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_CALL, "%s: call_accept id=%016llx -> ACCEPT + conn_mgr_open", |
|
|
|
CALL_ID, (unsigned long long)call_id); |
|
|
|
CALL_ID, (unsigned long long)call_id); |
|
|
|
s->state = CALL_ST_CONNECTING; |
|
|
|
s->state = CALL_ST_CONNECTING; |
|
|
|
|
|
|
|
if (s->ring_timer) { uasync_cancel_timeout(ctx->inst->ua, s->ring_timer); s->ring_timer = NULL; } /* watchdog кодограмм */ |
|
|
|
|
|
|
|
|
|
|
|
uint8_t pkt[sizeof(struct call_pkt_accept)]; |
|
|
|
uint8_t pkt[sizeof(struct call_pkt_accept)]; |
|
|
|
struct call_pkt_accept* a = (struct call_pkt_accept*)pkt; |
|
|
|
struct call_pkt_accept* a = (struct call_pkt_accept*)pkt; |
|
|
|
|