|
|
|
|
@ -63,6 +63,7 @@ static const char* call_subcmd_name(uint8_t sc) {
|
|
|
|
|
case CALL_SUBCMD_BUSY: return "BUSY"; |
|
|
|
|
case CALL_SUBCMD_CANCEL: return "CANCEL"; |
|
|
|
|
case CALL_SUBCMD_HANGUP: return "HANGUP"; |
|
|
|
|
case CALL_SUBCMD_END_ACK: return "END_ACK"; |
|
|
|
|
case CALL_SUBCMD_MEDIA: return "MEDIA"; |
|
|
|
|
default: return "?"; |
|
|
|
|
} |
|
|
|
|
@ -76,9 +77,12 @@ static void call_stats_timer_cb(void* arg);
|
|
|
|
|
static int call_send(struct call_ctx* ctx, uint64_t group_id, uint64_t dst, |
|
|
|
|
const uint8_t* data, size_t len, int mode) { |
|
|
|
|
struct ll_entry* e = queue_entry_new(0); |
|
|
|
|
if (!e) return -1; |
|
|
|
|
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "call send: entry allocation failed"); return -1; } |
|
|
|
|
e->dgram = u_malloc(1 + len); |
|
|
|
|
if (!e->dgram) { queue_entry_free(e); return -1; } |
|
|
|
|
if (!e->dgram) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "call send: packet allocation failed bytes=%zu", 1 + len); |
|
|
|
|
queue_entry_free(e); return -1; |
|
|
|
|
} |
|
|
|
|
e->dgram[0] = ETCP_RT_ID_CALL; |
|
|
|
|
if (len) memcpy(e->dgram + 1, data, len); |
|
|
|
|
e->len = (uint16_t)(1 + len); |
|
|
|
|
@ -270,23 +274,44 @@ static void call_watch_connection(struct call_session* s) {
|
|
|
|
|
|
|
|
|
|
/* ─────────────────── завершение ─────────────────── */ |
|
|
|
|
|
|
|
|
|
/* Отложенный разрыв: единственная точка, где закрывается conn_mgr (после того,
|
|
|
|
|
* как финальный сигналинг — HANGUP/DECLINE — гарантированно долетел). */ |
|
|
|
|
/* Финальная команда восстанавливается слоем звонка после смены эпохи роутера. */ |
|
|
|
|
static void call_send_end(struct call_session* s) { |
|
|
|
|
struct call_pkt_hangup packet = {s->end_subcmd, s->call_id, s->reason}; |
|
|
|
|
size_t len = s->end_subcmd == CALL_SUBCMD_CANCEL ? sizeof(struct call_pkt_cancel) : sizeof(packet); |
|
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "call end send id=%016llx command=%s elapsed_ms=%llu", |
|
|
|
|
(unsigned long long)s->call_id, call_subcmd_name(s->end_subcmd), |
|
|
|
|
(unsigned long long)((get_time_tb() - s->end_started_tb) / 10)); |
|
|
|
|
call_send(s->ctx, s->group_id, s->peer_node_id, (const uint8_t*)&packet, len, ROUTE_CRYPTO_SIGN | ROUTE_CRYPTO_ENCRYPT); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Медиа уже остановлены. CM держим до подтверждения команды либо ограниченного таймаута. */ |
|
|
|
|
static void call_teardown_timer_cb(void* arg) { |
|
|
|
|
struct call_session* s = (struct call_session*)arg; |
|
|
|
|
if (!s || !s->ctx) return; |
|
|
|
|
struct call_ctx* ctx = s->ctx; |
|
|
|
|
s->teardown_timer = NULL; |
|
|
|
|
if (s->end_subcmd && get_time_tb() - s->end_started_tb < CALL_END_TIMEOUT_TB) { |
|
|
|
|
call_send_end(s); |
|
|
|
|
s->teardown_timer = uasync_set_timeout(ctx->inst->ua, CALL_END_RETRY_TB, s, call_teardown_timer_cb, "call_end_retry"); |
|
|
|
|
if (s->teardown_timer) return; |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "call end retry timer allocation failed id=%016llx", (unsigned long long)s->call_id); |
|
|
|
|
} else if (s->end_subcmd) { |
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_CALL, "call end confirmation timeout id=%016llx command=%s elapsed_ms=%llu", |
|
|
|
|
(unsigned long long)s->call_id, call_subcmd_name(s->end_subcmd), |
|
|
|
|
(unsigned long long)((get_time_tb() - s->end_started_tb) / 10)); |
|
|
|
|
} |
|
|
|
|
if (s->cm_handle) { conn_mgr_close(s->cm_handle); s->cm_handle = NULL; } |
|
|
|
|
call_session_remove_and_free(ctx, s); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void call_end(struct call_ctx* ctx, struct call_session* s, uint8_t reason) { |
|
|
|
|
static void call_end(struct call_ctx* ctx, struct call_session* s, uint8_t reason, uint8_t end_subcmd) { |
|
|
|
|
if (!ctx || !s || s->state == CALL_ST_ENDED) return; |
|
|
|
|
|
|
|
|
|
s->state = CALL_ST_ENDED; |
|
|
|
|
call_video_transport_stop(s); |
|
|
|
|
call_session_cleanup(ctx, s); |
|
|
|
|
s->reason = reason; |
|
|
|
|
s->end_subcmd = end_subcmd; |
|
|
|
|
s->end_started_tb = get_time_tb(); |
|
|
|
|
ctx->c_calls_ended++; |
|
|
|
|
|
|
|
|
|
if (s->ring_timer) { uasync_cancel_timeout(ctx->inst->ua, s->ring_timer); s->ring_timer = NULL; } |
|
|
|
|
@ -302,12 +327,15 @@ static void call_end(struct call_ctx* ctx, struct call_session* s, uint8_t reaso
|
|
|
|
|
call_post_connection(s); |
|
|
|
|
call_event_id(ctx, CHAT_EVT_CALL_ENDED, s->call_id, reason); |
|
|
|
|
|
|
|
|
|
/* Закрытие запроса CM откладываем на 500мс для отправки финального сигналинга. */ |
|
|
|
|
if (s->cm_handle) { |
|
|
|
|
s->teardown_timer = uasync_set_timeout(ctx->inst->ua, CALL_TEARDOWN_DELAY_TB, s, call_teardown_timer_cb, "call_teardown"); |
|
|
|
|
} else { |
|
|
|
|
call_session_remove_and_free(ctx, s); |
|
|
|
|
if (s->end_subcmd) call_send_end(s); |
|
|
|
|
if (s->cm_handle || s->end_subcmd) { |
|
|
|
|
unsigned delay = s->end_subcmd ? CALL_END_RETRY_TB : CALL_TEARDOWN_DELAY_TB; |
|
|
|
|
s->teardown_timer = uasync_set_timeout(ctx->inst->ua, delay, s, call_teardown_timer_cb, "call_teardown"); |
|
|
|
|
if (s->teardown_timer) return; |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "call teardown timer allocation failed id=%016llx", (unsigned long long)s->call_id); |
|
|
|
|
} |
|
|
|
|
if (s->cm_handle) { conn_mgr_close(s->cm_handle); s->cm_handle = NULL; } |
|
|
|
|
call_session_remove_and_free(ctx, s); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* ─────────────────── conn_mgr ─────────────────── */ |
|
|
|
|
@ -359,12 +387,7 @@ static void call_ring_timeout_cb(void* arg) {
|
|
|
|
|
|
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_CALL, "%s: ring timeout id=%016llx -> CANCEL", |
|
|
|
|
CALL_ID, (unsigned long long)s->call_id); |
|
|
|
|
uint8_t pkt[sizeof(struct call_pkt_cancel)]; |
|
|
|
|
struct call_pkt_cancel* c = (struct call_pkt_cancel*)pkt; |
|
|
|
|
c->subcmd = CALL_SUBCMD_CANCEL; |
|
|
|
|
c->call_id = s->call_id; |
|
|
|
|
call_send(ctx, s->group_id, s->peer_node_id, pkt, sizeof(pkt), ROUTE_CRYPTO_SIGN | ROUTE_CRYPTO_ENCRYPT); |
|
|
|
|
call_end(ctx, s, CALL_REASON_RING_TIMEOUT); |
|
|
|
|
call_end(ctx, s, CALL_REASON_RING_TIMEOUT, CALL_SUBCMD_CANCEL); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Периодическая кодограмма INVITE (caller) каждые 300мс на всём протяжении вызова
|
|
|
|
|
@ -390,7 +413,7 @@ static void call_invite_retry_cb(void* arg) {
|
|
|
|
|
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); |
|
|
|
|
call_end(ctx, s, CALL_REASON_CONNECT_FAIL, 0); |
|
|
|
|
return; |
|
|
|
|
} else { |
|
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "%s: INVITE codegram id=%016llx no route (strike %d/%d)", |
|
|
|
|
@ -410,7 +433,7 @@ static void call_ring_dead_cb(void* arg) {
|
|
|
|
|
|
|
|
|
|
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); |
|
|
|
|
call_end(ctx, s, CALL_REASON_CANCEL, 0); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void call_traffic_timeout_cb(void* arg) { |
|
|
|
|
@ -422,13 +445,7 @@ static void call_traffic_timeout_cb(void* arg) {
|
|
|
|
|
|
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_CALL, "%s: no traffic 20s id=%016llx -> HANGUP", |
|
|
|
|
CALL_ID, (unsigned long long)s->call_id); |
|
|
|
|
uint8_t pkt[sizeof(struct call_pkt_hangup)]; |
|
|
|
|
struct call_pkt_hangup* hu = (struct call_pkt_hangup*)pkt; |
|
|
|
|
hu->subcmd = CALL_SUBCMD_HANGUP; |
|
|
|
|
hu->call_id = s->call_id; |
|
|
|
|
hu->reason = CALL_REASON_NO_TRAFFIC; |
|
|
|
|
call_send(ctx, s->group_id, s->peer_node_id, pkt, sizeof(pkt), ROUTE_CRYPTO_SIGN | ROUTE_CRYPTO_ENCRYPT); |
|
|
|
|
call_end(ctx, s, CALL_REASON_NO_TRAFFIC); |
|
|
|
|
call_end(ctx, s, CALL_REASON_NO_TRAFFIC, CALL_SUBCMD_HANGUP); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Периодическое событие CALL_STATS (раз в 1с): rtt_ms, buffer_ms, tempo_x100. */ |
|
|
|
|
@ -597,7 +614,7 @@ static void call_handle_decline(struct call_ctx* ctx, uint64_t from, const uint8
|
|
|
|
|
if (!s || s->state == CALL_ST_ENDED) return; |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_CALL, "%s: DECLINE id=%016llx reason=%d", CALL_ID, (unsigned long long)call_id, reason); |
|
|
|
|
call_event_id(ctx, CHAT_EVT_CALL_DECLINED, call_id, reason); |
|
|
|
|
call_end(ctx, s, CALL_REASON_DECLINE); |
|
|
|
|
call_end(ctx, s, CALL_REASON_DECLINE, 0); |
|
|
|
|
(void)from; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -607,7 +624,7 @@ static void call_handle_busy(struct call_ctx* ctx, uint64_t from, const uint8_t*
|
|
|
|
|
struct call_session* s = call_session_find(ctx, call_id); |
|
|
|
|
if (!s || s->state == CALL_ST_ENDED) return; |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_CALL, "%s: BUSY id=%016llx", CALL_ID, (unsigned long long)call_id); |
|
|
|
|
call_end(ctx, s, CALL_REASON_BUSY); |
|
|
|
|
call_end(ctx, s, CALL_REASON_BUSY, 0); |
|
|
|
|
(void)from; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -617,7 +634,7 @@ static void call_handle_cancel(struct call_ctx* ctx, uint64_t from, const uint8_
|
|
|
|
|
struct call_session* s = call_session_find(ctx, call_id); |
|
|
|
|
if (!s || s->state == CALL_ST_ENDED) return; |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_CALL, "%s: CANCEL id=%016llx", CALL_ID, (unsigned long long)call_id); |
|
|
|
|
call_end(ctx, s, CALL_REASON_CANCEL); |
|
|
|
|
call_end(ctx, s, CALL_REASON_CANCEL, 0); |
|
|
|
|
(void)from; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -628,7 +645,7 @@ static void call_handle_hangup(struct call_ctx* ctx, uint64_t from, const uint8_
|
|
|
|
|
struct call_session* s = call_session_find(ctx, call_id); |
|
|
|
|
if (!s || s->state == CALL_ST_ENDED) return; |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_CALL, "%s: HANGUP id=%016llx reason=%d", CALL_ID, (unsigned long long)call_id, reason); |
|
|
|
|
call_end(ctx, s, CALL_REASON_REMOTE_HANGUP); |
|
|
|
|
call_end(ctx, s, CALL_REASON_REMOTE_HANGUP, 0); |
|
|
|
|
(void)from; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -705,6 +722,36 @@ static void call_etcp_recv(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
int terminal = subcmd == CALL_SUBCMD_HANGUP || subcmd == CALL_SUBCMD_CANCEL || subcmd == CALL_SUBCMD_DECLINE; |
|
|
|
|
if (terminal || subcmd == CALL_SUBCMD_END_ACK) { |
|
|
|
|
size_t expected = subcmd == CALL_SUBCMD_CANCEL || subcmd == CALL_SUBCMD_END_ACK ? 9 : 10; |
|
|
|
|
if (blen != expected) { |
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_CALL, "call end invalid packet command=%s bytes=%zu expected=%zu", |
|
|
|
|
call_subcmd_name(subcmd), blen, expected); |
|
|
|
|
queue_dgram_free(entry); queue_entry_free(entry); return; |
|
|
|
|
} |
|
|
|
|
uint64_t id; memcpy(&id, body + 1, 8); |
|
|
|
|
struct call_session* s = call_session_find(ctx, id); |
|
|
|
|
if (terminal) { |
|
|
|
|
// Подтверждаем также повторы после удаления сессии; повторного ENDED обработчики не создают.
|
|
|
|
|
struct call_pkt_end_ack ack = {CALL_SUBCMD_END_ACK, id}; |
|
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "call end ack id=%016llx command=%s state=%s", |
|
|
|
|
(unsigned long long)id, call_subcmd_name(subcmd), s ? call_state_name(s->state) : "absent"); |
|
|
|
|
call_send(ctx, group, from, (const uint8_t*)&ack, sizeof(ack), ROUTE_CRYPTO_SIGN | ROUTE_CRYPTO_ENCRYPT); |
|
|
|
|
} else { |
|
|
|
|
if (s && s->state == CALL_ST_ENDED && s->end_subcmd) { |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_CALL, "call end confirmed id=%016llx command=%s elapsed_ms=%llu", |
|
|
|
|
(unsigned long long)id, call_subcmd_name(s->end_subcmd), |
|
|
|
|
(unsigned long long)((get_time_tb() - s->end_started_tb) / 10)); |
|
|
|
|
s->end_subcmd = 0; // Ближайший teardown tick освобождает CM и сессию.
|
|
|
|
|
} else { |
|
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "call end late ack ignored id=%016llx state=%s", |
|
|
|
|
(unsigned long long)id, s ? call_state_name(s->state) : "absent"); |
|
|
|
|
} |
|
|
|
|
queue_dgram_free(entry); queue_entry_free(entry); return; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
switch (subcmd) { |
|
|
|
|
case CALL_SUBCMD_INVITE: call_handle_invite(ctx, from, group, body, blen); break; |
|
|
|
|
case CALL_SUBCMD_RINGING: call_handle_ringing(ctx, from, body, blen); break; |
|
|
|
|
@ -944,7 +991,7 @@ void call_start(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t peer_nod
|
|
|
|
|
|
|
|
|
|
call_open_cm(s); |
|
|
|
|
if (call_send_invite(ctx, s) != 0) { |
|
|
|
|
call_end(ctx, s, CALL_REASON_CONNECT_FAIL); |
|
|
|
|
call_end(ctx, s, CALL_REASON_CONNECT_FAIL, 0); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
call_watch_connection(s); |
|
|
|
|
@ -979,15 +1026,8 @@ void call_decline(struct UTUN_INSTANCE* inst, uint64_t call_id) {
|
|
|
|
|
if (!s || s->role != CALL_ROLE_CALLEE || s->state == CALL_ST_ENDED) return; |
|
|
|
|
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_CALL, "%s: call_decline id=%016llx", CALL_ID, (unsigned long long)call_id); |
|
|
|
|
uint8_t pkt[sizeof(struct call_pkt_decline)]; |
|
|
|
|
struct call_pkt_decline* d = (struct call_pkt_decline*)pkt; |
|
|
|
|
d->subcmd = CALL_SUBCMD_DECLINE; |
|
|
|
|
d->call_id = call_id; |
|
|
|
|
d->reason = CALL_REASON_DECLINE; |
|
|
|
|
call_send(ctx, s->group_id, s->peer_node_id, pkt, sizeof(pkt), ROUTE_CRYPTO_SIGN | ROUTE_CRYPTO_ENCRYPT); |
|
|
|
|
|
|
|
|
|
call_event_id(ctx, CHAT_EVT_CALL_DECLINED, call_id, CALL_REASON_DECLINE); |
|
|
|
|
call_end(ctx, s, CALL_REASON_DECLINE); |
|
|
|
|
call_end(ctx, s, CALL_REASON_DECLINE, CALL_SUBCMD_DECLINE); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void call_hangup(struct UTUN_INSTANCE* inst, uint64_t call_id) { |
|
|
|
|
@ -997,14 +1037,7 @@ void call_hangup(struct UTUN_INSTANCE* inst, uint64_t call_id) {
|
|
|
|
|
if (!s || s->state == CALL_ST_ENDED) return; |
|
|
|
|
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_CALL, "%s: call_hangup id=%016llx", CALL_ID, (unsigned long long)call_id); |
|
|
|
|
uint8_t pkt[sizeof(struct call_pkt_hangup)]; |
|
|
|
|
struct call_pkt_hangup* hu = (struct call_pkt_hangup*)pkt; |
|
|
|
|
hu->subcmd = CALL_SUBCMD_HANGUP; |
|
|
|
|
hu->call_id = call_id; |
|
|
|
|
hu->reason = CALL_REASON_LOCAL_HANGUP; |
|
|
|
|
call_send(ctx, s->group_id, s->peer_node_id, pkt, sizeof(pkt), ROUTE_CRYPTO_SIGN | ROUTE_CRYPTO_ENCRYPT); |
|
|
|
|
|
|
|
|
|
call_end(ctx, s, CALL_REASON_LOCAL_HANGUP); |
|
|
|
|
call_end(ctx, s, CALL_REASON_LOCAL_HANGUP, CALL_SUBCMD_HANGUP); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* ─────────────────── trampoline-функции (GUI → uasync) ─────────────────── */ |
|
|
|
|
|