Schedule only active radio deadlines and reject late terminal answers

This commit is contained in:
evgeny committed 2026-10-19 14:09:10 +02:00
1 parent 5f9e9975d3
commit 613a75becc
1 file changed
+45 -8
+45 -8
View File
@@ -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);