diff --git a/src/radio/radio_call.c b/src/radio/radio_call.c index 7c9c5519..e997a892 100644 --- a/src/radio/radio_call.c +++ b/src/radio/radio_call.c @@ -18,7 +18,6 @@ #include "../../lib/u_async.h" #define RC_SLOTS 64 -#define RC_TICK_TB 500 /* 50мс, deadline первого ответа проверяется без продления */ enum { RC_INVITE = 1, RC_RINGING, RC_RESULT, RC_ACK }; /* wire: command:1, id:8LE, expires_us:8LE, reason:1. Результат повторяется до ACK. * INVITE действителен 2с: застрявшая в роутере попытка не запускает поздний рингтон. @@ -33,9 +32,39 @@ struct radio_call_session { struct radio_call_context { struct UTUN_INSTANCE* inst; void* timer; + int stopping; struct radio_call_session sessions[RC_SLOTS]; }; +static void rc_tick(void* arg); + +/* Только ближайший deadline/повтор: у idle-контекста вообще нет таймера. + * Кэш подтверждённых результатов очищается лениво при следующем поиске. */ +static void rc_schedule(struct radio_call_context* ctx) { + if (ctx->timer) { uasync_cancel_timeout(ctx->inst->ua, ctx->timer); ctx->timer = NULL; } + if (ctx->stopping) return; + uint64_t now = get_time_tb(), next = 0; + for (int i = 0; i < RC_SLOTS; ++i) { + struct radio_call_session* s = &ctx->sessions[i]; + if (!s->id) continue; + uint64_t due; + if (s->state == RADIO_CALL_FINISHED) { + if (!s->awaiting_ack || now - s->finished >= RADIO_CALL_RESPONSE_TB * 10) continue; + due = s->last_send + RADIO_CALL_RETRY_TB; + } else { + due = s->started + (!s->incoming && s->state == RADIO_CALL_OUTGOING ? RADIO_CALL_RESPONSE_TB : RADIO_CALL_RING_TB); + uint64_t heartbeat = s->incoming ? s->last_peer + RADIO_CALL_RESPONSE_TB : s->last_send + RADIO_CALL_RETRY_TB; + if (heartbeat < due) due = heartbeat; + } + if (!next || due < next) next = due; + } + if (!next) return; + int delay = next > now ? (int)(next - now) : 10; + if (delay < 10) delay = 10; + ctx->timer = uasync_set_timeout(ctx->inst->ua, delay, ctx, rc_tick, "radio_call"); + if (!ctx->timer) DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "radio call deadline timer allocation failed delay_tb=%d", delay); +} + /* Формат события: id/group/peer:8LE, state/reason/incoming:1 (27 байт). */ static void rc_event(struct radio_call_context* ctx, const struct radio_call_session* s) { uint8_t data[27]; @@ -104,6 +133,7 @@ static void rc_finish(struct radio_call_context* ctx, struct radio_call_session* if (s->state == RADIO_CALL_FINISHED) return; s->state = RADIO_CALL_FINISHED; s->reason = reason; s->finished = get_time_tb(); s->awaiting_ack = send; if (send) rc_send(ctx, s, RC_RESULT, reason); + rc_schedule(ctx); rc_event(ctx, s); } @@ -141,8 +171,7 @@ static void rc_tick(void* arg) { else if (s->incoming && now - s->last_peer >= RADIO_CALL_RESPONSE_TB) rc_finish(ctx, s, RADIO_CALL_CANCELLED, 1); else if (!s->incoming && now - s->last_send >= RADIO_CALL_RETRY_TB) rc_send(ctx, s, RC_INVITE, 0); } - ctx->timer = uasync_set_timeout(ctx->inst->ua, RC_TICK_TB, ctx, rc_tick, "radio_call"); - if (!ctx->timer) DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "radio call timer allocation failed"); + rc_schedule(ctx); } /* Получатель доверяет identity из подписанного router envelope, а не физическому соседу. */ @@ -209,6 +238,10 @@ static void rc_receive(struct ETCP_CONN* conn, struct ll_entry* entry) { if (s->incoming && (reason == RADIO_CALL_ACCEPTED || reason == RADIO_CALL_DECLINED || reason == RADIO_CALL_BUSY)) { DEBUG_WARN(DEBUG_CATEGORY_RADIO, "radio call result invalid for callee id=%016llx reason=%u", (unsigned long long)id, reason); goto done; } + if (!s->incoming && s->state == RADIO_CALL_OUTGOING && get_time_tb() - s->started >= RADIO_CALL_RESPONSE_TB) + rc_finish(ctx, s, RADIO_CALL_UNAVAILABLE, 1); + else if (s->state != RADIO_CALL_FINISHED && get_time_tb() - s->started >= RADIO_CALL_RING_TB) + rc_finish(ctx, s, RADIO_CALL_NO_ANSWER, 1); rc_send(ctx, s, RC_ACK, reason); rc_finish(ctx, s, reason, 0); } else if (s->state == RADIO_CALL_FINISHED) rc_send(ctx, s, RC_RESULT, s->reason); @@ -221,12 +254,13 @@ static void rc_receive(struct ETCP_CONN* conn, struct ll_entry* entry) { } done: if (entry) { queue_dgram_free(entry); queue_entry_free(entry); } + if (ctx) rc_schedule(ctx); } /* UI-команды сериализованы с сетью; запоздалое нажатие не меняет завершённый вызов. */ void radio_call_execute(const struct radio_call_request* req) { struct radio_call_context* ctx = req && req->inst ? req->inst->radio_call : NULL; - if (!ctx || !req->id) { DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "radio call command without context/id"); return; } + if (!ctx || ctx->stopping || !req->id) { DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "radio call command without live context/id"); return; } if (req->action == RADIO_CALL_START) { struct radio_call_session* s = rc_slot(ctx, req->id, req->group, req->peer, 0); if (s) { DEBUG_WARN(DEBUG_CATEGORY_RADIO, "radio call duplicate local start id=%016llx", (unsigned long long)req->id); return; } @@ -244,13 +278,17 @@ void radio_call_execute(const struct radio_call_request* req) { if (!g || !topo_group_find_conn_for_node(g, req->peer)) { rc_finish(ctx, s, RADIO_CALL_UNAVAILABLE, 0); return; } rc_event(ctx, s); rc_send(ctx, s, RC_INVITE, 0); + rc_schedule(ctx); return; } struct radio_call_session* s = NULL; for (int i = 0; i < RC_SLOTS; ++i) if (ctx->sessions[i].id == req->id && ctx->sessions[i].state != RADIO_CALL_FINISHED) s = &ctx->sessions[i]; if (!s) { DEBUG_DEBUG(DEBUG_CATEGORY_RADIO, "radio call stale UI command id=%016llx action=%d", (unsigned long long)req->id, req->action); return; } - if (req->action == RADIO_CALL_READY && s->incoming) { s->ready = 1; rc_send(ctx, s, RC_RINGING, 0); return; } + if (req->action == RADIO_CALL_READY && s->incoming) { + s->ready = 1; rc_send(ctx, s, RC_RINGING, 0); rc_schedule(ctx); return; + } + if (get_time_tb() - s->started >= RADIO_CALL_RING_TB) { rc_finish(ctx, s, RADIO_CALL_NO_ANSWER, 1); return; } uint8_t reason = 0; if (req->action == RADIO_CALL_ACCEPT && s->incoming) reason = RADIO_CALL_ACCEPTED; else if (req->action == RADIO_CALL_DECLINE && s->incoming) reason = RADIO_CALL_DECLINED; @@ -272,8 +310,6 @@ int radio_call_init(struct UTUN_INSTANCE* inst) { if (!ctx) { DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "radio call context allocation failed"); return -1; } ctx->inst = inst; inst->radio_call = ctx; if (etcp_router_bind(inst, ETCP_RT_ID_RADIO_CALL, rc_receive)) { radio_call_destroy(inst); return -1; } - ctx->timer = uasync_set_timeout(inst->ua, RC_TICK_TB, ctx, rc_tick, "radio_call"); - if (!ctx->timer) { DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "radio call init timer failed"); radio_call_destroy(inst); return -1; } DEBUG_INFO(DEBUG_CATEGORY_RADIO, "radio call initialized first_response_ms=2000 ring_ms=60000 cache=%d", RC_SLOTS); return 0; } @@ -281,7 +317,8 @@ int radio_call_init(struct UTUN_INSTANCE* inst) { void radio_call_destroy(struct UTUN_INSTANCE* inst) { struct radio_call_context* ctx = inst ? inst->radio_call : NULL; if (!ctx) return; - if (ctx->timer) uasync_cancel_timeout(inst->ua, ctx->timer); + ctx->stopping = 1; + if (ctx->timer) { uasync_cancel_timeout(inst->ua, ctx->timer); ctx->timer = NULL; } for (int i = 0; i < RC_SLOTS; ++i) if (ctx->sessions[i].id && ctx->sessions[i].state != RADIO_CALL_FINISHED) rc_finish(ctx, &ctx->sessions[i], RADIO_CALL_CANCELLED, 0); etcp_router_unbind(inst, ETCP_RT_ID_RADIO_CALL);