Wait for caller override before ringing a phone-busy radio subscriber

This commit is contained in:
evgeny committed 2026-10-19 14:48:39 +02:00
1 parent be367a08b6
commit b7aaf268af
2 files changed
+43 -17

No files matched your search

+39 -15
View File
@@ -19,7 +19,7 @@
#include "../../lib/u_async.h"
#define RC_SLOTS 64
enum { RC_INVITE = 1, RC_RINGING, RC_RESULT, RC_ACK };
enum { RC_INVITE = 1, RC_RINGING, RC_RESULT, RC_ACK, RC_PHONE_BUSY, RC_FORCE };
/* wire: command:1, id:8LE, expires_us:8LE, reason:1. Результат повторяется до ACK.
* INVITE действителен 2с: застрявшая в роутере попытка не запускает поздний рингтон.
* Часы общие с NTP ядра; deadline самой попытки всегда монотонный. */
@@ -28,7 +28,9 @@ enum { RC_INVITE = 1, RC_RINGING, RC_RESULT, RC_ACK };
struct radio_call_session {
uint64_t id, group, peer; /* ключ — вся тройка, чужой пакет не меняет попытку */
uint64_t started, last_peer, last_send, finished;
uint64_t response_started; /* 2с для первоначального запроса и подтверждённого продолжения */
uint8_t state, reason, incoming, ready, awaiting_ack, blocked;
uint8_t force; /* подтверждение действительно только внутри данной попытки */
};
struct radio_call_context {
struct UTUN_INSTANCE* inst;
@@ -53,7 +55,9 @@ static void rc_schedule(struct radio_call_context* ctx) {
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);
due = s->started + RADIO_CALL_RING_TB;
if (!s->incoming && s->state == RADIO_CALL_OUTGOING && s->response_started + RADIO_CALL_RESPONSE_TB < due)
due = s->response_started + RADIO_CALL_RESPONSE_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;
}
@@ -68,7 +72,7 @@ static void rc_schedule(struct radio_call_context* ctx) {
/* Формат события: id/group/peer:8LE, state/reason/incoming:1 (27 байт). */
static void rc_event(struct radio_call_context* ctx, const struct radio_call_session* s) {
static const char* states[] = {"invalid", "outgoing", "incoming", "ringing", "finished"};
static const char* states[] = {"invalid", "outgoing", "incoming", "ringing", "finished", "wait_decision"};
static const char* reasons[] = {"none", "accepted", "declined", "busy", "cancelled", "unavailable", "no_answer", "audio_error"};
uint8_t data[27];
memcpy(data, &s->id, 8); memcpy(data + 8, &s->group, 8); memcpy(data + 16, &s->peer, 8);
@@ -168,11 +172,11 @@ static void rc_tick(void* arg) {
}
struct TOPO_GROUP* g = topo_groups_find(ctx->inst->topo_groups, s->group);
if (!g) { rc_finish(ctx, s, RADIO_CALL_UNAVAILABLE, 1); continue; }
if (!s->incoming && s->state == RADIO_CALL_OUTGOING && now - s->started >= RADIO_CALL_RESPONSE_TB)
if (!s->incoming && s->state == RADIO_CALL_OUTGOING && now - s->response_started >= RADIO_CALL_RESPONSE_TB)
rc_finish(ctx, s, RADIO_CALL_UNAVAILABLE, 1);
else if (now - s->started >= RADIO_CALL_RING_TB) rc_finish(ctx, s, RADIO_CALL_NO_ANSWER, 1);
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);
else if (!s->incoming && now - s->last_send >= RADIO_CALL_RETRY_TB) rc_send(ctx, s, s->force ? RC_FORCE : RC_INVITE, 0);
}
rc_schedule(ctx);
}
@@ -198,18 +202,20 @@ static void rc_receive(struct ETCP_CONN* conn, struct ll_entry* entry) {
uint8_t command = b[0], reason = b[17];
DEBUG_TRACE(DEBUG_CATEGORY_RADIO, "radio call RX id=%016llx group=%016llx peer=%016llx command=%u reason=%u",
(unsigned long long)id, (unsigned long long)group, (unsigned long long)peer, command, reason);
if (!id || command < RC_INVITE || command > RC_ACK || reason > RADIO_CALL_AUDIO_ERROR ||
if (!id || command < RC_INVITE || command > RC_FORCE || reason > RADIO_CALL_AUDIO_ERROR ||
((command == RC_RESULT || command == RC_ACK) ? !reason : reason != 0)) {
DEBUG_WARN(DEBUG_CATEGORY_RADIO, "radio call invalid command=%u reason=%u id=%llu", command, reason, (unsigned long long)id); goto done;
}
struct radio_call_session* s = rc_slot(ctx, id, group, peer, 0);
if (command == RC_INVITE && !s) {
if (command == RC_INVITE || command == RC_FORCE) {
int64_t expires; memcpy(&expires, b + 9, 8);
int64_t now_us = ntp_time_get_us(ctx->inst);
if (expires <= now_us || expires > now_us + 4000000 || !rc_group(ctx->inst, group, peer)) {
DEBUG_WARN(DEBUG_CATEGORY_RADIO, "radio call stale/invalid invite id=%016llx expires_us=%lld now_us=%lld",
(unsigned long long)id, (long long)expires, (long long)now_us); goto done;
}
}
if (command == RC_INVITE && !s) {
int busy = radio_call_busy(ctx->inst) || call_busy(ctx->inst);
s = rc_slot(ctx, id, group, peer, 1);
if (!s) {
@@ -217,9 +223,12 @@ static void rc_receive(struct ETCP_CONN* conn, struct ll_entry* entry) {
struct radio_call_session rejected = {.id = id, .group = group, .peer = peer};
rc_send(ctx, &rejected, RC_RESULT, RADIO_CALL_BUSY); goto done;
}
s->incoming = 1; s->state = RADIO_CALL_INCOMING;
s->incoming = 1; s->state = ctx->inst->phone_state == 2 ? RADIO_CALL_WAIT_DECISION : RADIO_CALL_INCOMING;
if (busy) rc_finish(ctx, s, RADIO_CALL_BUSY, 1);
else rc_event(ctx, s); /* приложение подтверждает показ через READY */
else if (s->state == RADIO_CALL_WAIT_DECISION) {
DEBUG_INFO(DEBUG_CATEGORY_RADIO, "phone busy; incoming radio call suppressed id=%016llx", (unsigned long long)id);
rc_send(ctx, s, RC_PHONE_BUSY, 0);
} else rc_event(ctx, s); /* приложение подтверждает показ через READY */
goto done;
}
if (!s) {
@@ -241,18 +250,28 @@ 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)
if (!s->incoming && s->state == RADIO_CALL_OUTGOING && get_time_tb() - s->response_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);
else if (command == RC_INVITE && s->incoming) {
else if ((command == RC_INVITE || command == RC_FORCE) && s->incoming) {
if (get_time_tb() - s->started >= RADIO_CALL_RING_TB) { rc_finish(ctx, s, RADIO_CALL_NO_ANSWER, 1); goto done; }
s->last_peer = get_time_tb();
if (s->state == RADIO_CALL_WAIT_DECISION && command == RC_FORCE) {
s->force = 1; s->state = RADIO_CALL_INCOMING;
DEBUG_INFO(DEBUG_CATEGORY_RADIO, "phone busy overridden for radio id=%016llx", (unsigned long long)id);
rc_event(ctx, s);
}
if (s->state == RADIO_CALL_WAIT_DECISION) rc_send(ctx, s, RC_PHONE_BUSY, 0);
if (s->ready) rc_send(ctx, s, RC_RINGING, 0);
} else if (command == RC_PHONE_BUSY && !s->incoming && !s->force && s->state == RADIO_CALL_OUTGOING) {
if (get_time_tb() - s->response_started >= RADIO_CALL_RESPONSE_TB) rc_finish(ctx, s, RADIO_CALL_UNAVAILABLE, 1);
else { s->state = RADIO_CALL_WAIT_DECISION; rc_event(ctx, s); }
} else if (command == RC_RINGING && !s->incoming && s->state == RADIO_CALL_OUTGOING) {
if (get_time_tb() - s->started >= RADIO_CALL_RESPONSE_TB) rc_finish(ctx, s, RADIO_CALL_UNAVAILABLE, 1);
if (get_time_tb() - s->response_started >= RADIO_CALL_RESPONSE_TB) rc_finish(ctx, s, RADIO_CALL_UNAVAILABLE, 1);
else { s->state = RADIO_CALL_RINGING; rc_event(ctx, s); }
}
done:
@@ -276,6 +295,7 @@ void radio_call_execute(const struct radio_call_request* req) {
rc_event(ctx, &failed); return;
}
s->state = RADIO_CALL_OUTGOING;
s->response_started = s->started;
struct TOPO_GROUP* g = rc_group(req->inst, req->group, req->peer);
if (busy) { rc_finish(ctx, s, RADIO_CALL_BUSY, 0); return; }
if (!g || !topo_group_find_conn_for_node(g, req->peer)) { rc_finish(ctx, s, RADIO_CALL_UNAVAILABLE, 0); return; }
@@ -288,12 +308,16 @@ void radio_call_execute(const struct radio_call_request* req) {
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) {
if (get_time_tb() - s->started >= RADIO_CALL_RING_TB) { rc_finish(ctx, s, RADIO_CALL_NO_ANSWER, 1); return; }
if (req->action == RADIO_CALL_CONTINUE && !s->incoming && s->state == RADIO_CALL_WAIT_DECISION) {
s->force = 1; s->state = RADIO_CALL_OUTGOING; s->response_started = get_time_tb();
rc_event(ctx, s); rc_send(ctx, s, RC_FORCE, 0); rc_schedule(ctx); return;
}
if (req->action == RADIO_CALL_READY && s->incoming && s->state == RADIO_CALL_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) {
if (req->action == RADIO_CALL_ACCEPT && s->incoming && s->state == RADIO_CALL_INCOMING) {
reason = radio_is_active(req->inst, s->group) ? RADIO_CALL_ACCEPTED : RADIO_CALL_AUDIO_ERROR;
if (reason == RADIO_CALL_AUDIO_ERROR) DEBUG_ERROR(DEBUG_CATEGORY_RADIO,
"radio call accept without active channel subscription id=%016llx group=%016llx",
+4 -2
View File
@@ -14,14 +14,16 @@ struct UTUN_INSTANCE;
extern "C" {
#endif
enum radio_call_state { RADIO_CALL_OUTGOING = 1, RADIO_CALL_INCOMING, RADIO_CALL_RINGING, RADIO_CALL_FINISHED };
enum radio_call_state {
RADIO_CALL_OUTGOING = 1, RADIO_CALL_INCOMING, RADIO_CALL_RINGING, RADIO_CALL_FINISHED, RADIO_CALL_WAIT_DECISION
};
enum radio_call_reason {
RADIO_CALL_NONE, RADIO_CALL_ACCEPTED, RADIO_CALL_DECLINED, RADIO_CALL_BUSY,
RADIO_CALL_CANCELLED, RADIO_CALL_UNAVAILABLE, RADIO_CALL_NO_ANSWER, RADIO_CALL_AUDIO_ERROR
};
enum radio_call_action {
RADIO_CALL_START = 1, RADIO_CALL_READY, RADIO_CALL_ACCEPT, RADIO_CALL_DECLINE,
RADIO_CALL_CANCEL, RADIO_CALL_FAIL, RADIO_CALL_REJECT_BUSY
RADIO_CALL_CANCEL, RADIO_CALL_FAIL, RADIO_CALL_REJECT_BUSY, RADIO_CALL_CONTINUE
};
/* Таймауты в timebase 0.1мс; повторы не сдвигают первоначальный deadline. */