Gate incoming calls on explicit caller decision when phone is busy
This commit is contained in:
1 parent
6ad9c8c80b
commit
be367a08b6
2 files changed
+122
-15
No files matched your search
+121
-15
@@ -19,6 +19,7 @@
|
||||
#include "../dm/dm_core.h"
|
||||
#include "../utun_instance.h"
|
||||
#include "../chat/chat_event.h"
|
||||
#include "../ntp_time.h"
|
||||
#include "../routing_layer/etcp_router.h"
|
||||
#include "../routing_layer/route_crypto.h"
|
||||
#include "../routing_layer/conn_mgr.h"
|
||||
@@ -51,6 +52,7 @@ static const char* call_state_name(uint8_t st) {
|
||||
case CALL_ST_RINGING: return "RINGING";
|
||||
case CALL_ST_ACTIVE: return "ACTIVE";
|
||||
case CALL_ST_ENDED: return "ENDED";
|
||||
case CALL_ST_WAIT_DECISION: return "WAIT_DECISION";
|
||||
default: return "?";
|
||||
}
|
||||
}
|
||||
@@ -65,6 +67,7 @@ static const char* call_subcmd_name(uint8_t sc) {
|
||||
case CALL_SUBCMD_CANCEL: return "CANCEL";
|
||||
case CALL_SUBCMD_HANGUP: return "HANGUP";
|
||||
case CALL_SUBCMD_END_ACK: return "END_ACK";
|
||||
case CALL_SUBCMD_PHONE_BUSY: return "PHONE_BUSY";
|
||||
case CALL_SUBCMD_MEDIA: return "MEDIA";
|
||||
default: return "?";
|
||||
}
|
||||
@@ -104,6 +107,8 @@ static int call_send_invite(struct call_ctx* ctx, struct call_session* s) {
|
||||
inv->call_id = s->call_id;
|
||||
inv->group_id = s->group_id;
|
||||
inv->num_tracks = 1;
|
||||
inv->force = s->force;
|
||||
inv->expires_us = ntp_time_get_us(ctx->inst) + 2000000;
|
||||
inv->tracks[0].type = CALL_TRACK_AUDIO;
|
||||
inv->tracks[0].codec = CALL_CODEC_OPUS;
|
||||
inv->tracks[0].sample_rate = CALL_OPUS_SAMPLE_RATE;
|
||||
@@ -132,6 +137,7 @@ static struct call_session* call_session_new(struct call_ctx* ctx, uint64_t call
|
||||
s->peer_node_id = peer;
|
||||
s->role = role;
|
||||
s->state = CALL_ST_IDLE;
|
||||
s->started_tb = get_time_tb();
|
||||
s->ctx = ctx;
|
||||
s->tx_q = queue_new(ctx->inst->ua, 0, 0, 0, "call_txq");
|
||||
if (!s->tx_q) { queue_entry_free(qe); return NULL; }
|
||||
@@ -199,6 +205,40 @@ static void call_event_incoming(struct call_ctx* ctx, uint64_t group_id, uint64_
|
||||
chat_event_post(ctx->inst, CHAT_EVT_CALL_INCOMING, buf, (int)off);
|
||||
}
|
||||
|
||||
uint8_t call_phone_status(struct UTUN_INSTANCE* inst) {
|
||||
if (!inst) return 0;
|
||||
if (inst->phone_state == 2 || call_busy(inst)) return TOPO_FLAG_PHONE_KNOWN | TOPO_FLAG_PHONE_BUSY;
|
||||
return inst->phone_state == 1 ? TOPO_FLAG_PHONE_KNOWN : 0;
|
||||
}
|
||||
|
||||
/* Меняются только live-флаги. Публикация использует существующее версионирование NODEINFO. */
|
||||
static void call_publish_phone_status(struct UTUN_INSTANCE* inst) {
|
||||
uint8_t status = call_phone_status(inst);
|
||||
for (struct ll_entry* e = inst->topo_groups ? inst->topo_groups->group_list->head : NULL; e; e = e->next) {
|
||||
struct TOPO_GROUP* g = (struct TOPO_GROUP*)e;
|
||||
if (g->group_type != TOPO_GROUP_TYPE_CHAT || !g->local_node || g->local_node->phone_status == status) continue;
|
||||
if (topo_group_update_my_nodeinfo(inst, g) < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "phone status publication failed group=%016llx flags=%u",
|
||||
(unsigned long long)g->group_id, status); continue;
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "phone status group=%016llx flags=%u", (unsigned long long)g->group_id, status);
|
||||
for (struct topo_node_cbk_entry* cb = g->node_cbks; cb; cb = cb->next)
|
||||
cb->fn(g, inst->node_id, TOPO_NODE_EVENT_UPDATE, cb->arg);
|
||||
for (struct ll_entry* p = g->senders_list ? g->senders_list->head : NULL; p; p = p->next) {
|
||||
struct TOPO_GROUP_CONN_ITEM* item = (struct TOPO_GROUP_CONN_ITEM*)p->data;
|
||||
if (item->conn) topo_group_send_nodeinfo(g, g->local_node, item->conn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void call_set_phone_state(struct UTUN_INSTANCE* inst, int state) {
|
||||
if (!inst || state < 0 || state > 2) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "invalid phone state=%d", state); return; }
|
||||
if (inst->phone_state == state) return;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CALL, "phone state old=%u new=%d", inst->phone_state, state);
|
||||
inst->phone_state = (uint8_t)state;
|
||||
call_publish_phone_status(inst);
|
||||
}
|
||||
|
||||
/* ─────────────────── BGP-маршрут до пира ─────────────────── */
|
||||
|
||||
/* Формирует строку BGP-пути до peer_node_id и постит CHAT_EVT_CALL_PATH ([call_id:8][text:var]).
|
||||
@@ -309,6 +349,7 @@ static void call_end(struct call_ctx* ctx, struct call_session* s, uint8_t reaso
|
||||
if (!ctx || !s || s->state == CALL_ST_ENDED) return;
|
||||
|
||||
s->state = CALL_ST_ENDED;
|
||||
call_publish_phone_status(ctx->inst);
|
||||
call_session_cleanup(ctx, s);
|
||||
s->reason = reason;
|
||||
s->end_subcmd = end_subcmd;
|
||||
@@ -329,8 +370,8 @@ static void call_end(struct call_ctx* ctx, struct call_session* s, uint8_t reaso
|
||||
call_event_id(ctx, CHAT_EVT_CALL_ENDED, s->call_id, reason);
|
||||
|
||||
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;
|
||||
if (s->cm_handle || s->end_subcmd || s->role == CALL_ROLE_CALLEE) {
|
||||
unsigned delay = s->end_subcmd ? CALL_END_RETRY_TB : CALL_RING_DEAD_TB + 1000;
|
||||
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);
|
||||
@@ -401,7 +442,7 @@ static void call_invite_retry_cb(void* arg) {
|
||||
struct call_ctx* ctx = s->ctx;
|
||||
s->invite_timer = NULL;
|
||||
if (s->state == CALL_ST_ENDED) return;
|
||||
if (s->state != CALL_ST_OUTGOING && s->state != CALL_ST_RINGING) return; /* ACTIVE — звонок идёт */
|
||||
if (s->state != CALL_ST_OUTGOING && s->state != CALL_ST_RINGING && s->state != CALL_ST_WAIT_DECISION) return;
|
||||
|
||||
if (etcp_router_has_route(ctx->inst, s->group_id, s->peer_node_id)) {
|
||||
s->no_route_strikes = 0;
|
||||
@@ -430,7 +471,7 @@ static void call_ring_dead_cb(void* arg) {
|
||||
if (!s || !s->ctx) return;
|
||||
struct call_ctx* ctx = s->ctx;
|
||||
s->ring_timer = NULL;
|
||||
if (s->state != CALL_ST_RINGING) return;
|
||||
if (s->state != CALL_ST_RINGING && s->state != CALL_ST_WAIT_DECISION) return;
|
||||
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CALL, "%s: ring dead (2с без кодограммы) id=%016llx -> end",
|
||||
CALL_ID, (unsigned long long)s->call_id);
|
||||
@@ -511,25 +552,46 @@ static void call_stats_timer_cb(void* arg) {
|
||||
|
||||
static void call_handle_invite(struct call_ctx* ctx, uint64_t from, uint64_t group,
|
||||
const uint8_t* b, size_t n) {
|
||||
if (n < sizeof(struct call_pkt_invite) - sizeof(struct call_pkt_track)) {
|
||||
if (n < sizeof(struct call_pkt_invite)) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CALL, "%s: INVITE too short n=%zu", CALL_ID, n);
|
||||
return;
|
||||
}
|
||||
uint64_t call_id, inv_group;
|
||||
memcpy(&call_id, b + 1, 8);
|
||||
memcpy(&inv_group, b + 9, 8);
|
||||
struct call_pkt_invite invite;
|
||||
memcpy(&invite, b, sizeof(invite));
|
||||
int64_t now_us = ntp_time_get_us(ctx->inst);
|
||||
if (!call_id || inv_group != group || invite.force > 1 || invite.num_tracks != 1 ||
|
||||
n != sizeof(invite) || invite.expires_us <= now_us || invite.expires_us > now_us + 4000000) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CALL, "invalid/stale call invite id=%016llx force=%u bytes=%zu",
|
||||
(unsigned long long)call_id, invite.force, n); return;
|
||||
}
|
||||
|
||||
/* кодограмма-дубликат (тот же call_id): пока звоним — пере-взводим watchdog и
|
||||
* идемпотентно отвечаем RINGING; в разговоре (ACTIVE) — игнорируем. */
|
||||
struct call_session* s = call_session_find(ctx, call_id);
|
||||
if (s) {
|
||||
if (s->role == CALL_ROLE_CALLEE && s->state == CALL_ST_RINGING) {
|
||||
if (s->peer_node_id != from || s->group_id != group || s->role != CALL_ROLE_CALLEE) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CALL, "call invite tuple/role mismatch id=%016llx", (unsigned long long)call_id); return;
|
||||
}
|
||||
if (s->state == CALL_ST_RINGING || s->state == CALL_ST_WAIT_DECISION) {
|
||||
if (get_time_tb() - s->started_tb >= CALL_RING_TIMEOUT_TB) {
|
||||
call_end(ctx, s, CALL_REASON_RING_TIMEOUT, CALL_SUBCMD_CANCEL); return;
|
||||
}
|
||||
if (s->state == CALL_ST_WAIT_DECISION && invite.force) {
|
||||
s->force = 1; s->state = CALL_ST_RINGING;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CALL, "phone busy overridden id=%016llx", (unsigned long long)call_id);
|
||||
call_event_incoming(ctx, group, from, call_id);
|
||||
call_watch_connection(s);
|
||||
}
|
||||
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);
|
||||
uint64_t remaining = CALL_RING_TIMEOUT_TB - (get_time_tb() - s->started_tb);
|
||||
s->ring_timer = uasync_set_timeout(ctx->inst->ua, remaining < CALL_RING_DEAD_TB ? remaining : CALL_RING_DEAD_TB,
|
||||
s, call_ring_dead_cb, "call_ring_dead");
|
||||
uint8_t pkt[sizeof(struct call_pkt_ringing)];
|
||||
struct call_pkt_ringing* r = (struct call_pkt_ringing*)pkt;
|
||||
r->subcmd = CALL_SUBCMD_RINGING;
|
||||
r->subcmd = s->state == CALL_ST_WAIT_DECISION ? CALL_SUBCMD_PHONE_BUSY : CALL_SUBCMD_RINGING;
|
||||
r->call_id = call_id;
|
||||
call_send(ctx, s->group_id, from, pkt, sizeof(pkt), ROUTE_CRYPTO_SIGN | ROUTE_CRYPTO_ENCRYPT);
|
||||
} else {
|
||||
@@ -539,6 +601,10 @@ static void call_handle_invite(struct call_ctx* ctx, uint64_t from, uint64_t gro
|
||||
return;
|
||||
}
|
||||
|
||||
if (invite.force) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CALL, "call continuation without pending attempt id=%016llx", (unsigned long long)call_id); return;
|
||||
}
|
||||
|
||||
if (radio_call_busy(ctx->inst)) {
|
||||
struct call_pkt_busy busy = {CALL_SUBCMD_BUSY, call_id};
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CALL, "call busy: pending radio invitation id=%016llx", (unsigned long long)call_id);
|
||||
@@ -567,20 +633,23 @@ static void call_handle_invite(struct call_ctx* ctx, uint64_t from, uint64_t gro
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "%s: session alloc failed id=%016llx", CALL_ID, (unsigned long long)call_id);
|
||||
return;
|
||||
}
|
||||
s->state = CALL_ST_RINGING;
|
||||
s->state = ctx->inst->phone_state == 2 ? CALL_ST_WAIT_DECISION : CALL_ST_RINGING;
|
||||
call_publish_phone_status(ctx->inst);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CALL, "%s: INVITE id=%016llx from=0x%016llx group=0x%016llx (declared group=0x%016llx)",
|
||||
CALL_ID, (unsigned long long)call_id, (unsigned long long)from,
|
||||
(unsigned long long)group, (unsigned long long)inv_group);
|
||||
|
||||
call_event_incoming(ctx, group, from, call_id);
|
||||
call_watch_connection(s);
|
||||
if (s->state == CALL_ST_RINGING) {
|
||||
call_event_incoming(ctx, group, from, call_id);
|
||||
call_watch_connection(s);
|
||||
} else DEBUG_INFO(DEBUG_CATEGORY_CALL, "phone busy; incoming call suppressed id=%016llx", (unsigned long long)call_id);
|
||||
|
||||
/* 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)];
|
||||
struct call_pkt_ringing* r = (struct call_pkt_ringing*)pkt;
|
||||
r->subcmd = CALL_SUBCMD_RINGING;
|
||||
r->subcmd = s->state == CALL_ST_WAIT_DECISION ? CALL_SUBCMD_PHONE_BUSY : CALL_SUBCMD_RINGING;
|
||||
r->call_id = call_id;
|
||||
call_send(ctx, group, from, pkt, sizeof(pkt), ROUTE_CRYPTO_SIGN | ROUTE_CRYPTO_ENCRYPT);
|
||||
}
|
||||
@@ -599,11 +668,21 @@ static void call_handle_ringing(struct call_ctx* ctx, uint64_t from, const uint8
|
||||
(void)from;
|
||||
}
|
||||
|
||||
static void call_handle_phone_busy(struct call_ctx* ctx, const uint8_t* b, size_t n) {
|
||||
if (n != sizeof(struct call_pkt_ringing)) { DEBUG_WARN(DEBUG_CATEGORY_CALL, "invalid PHONE_BUSY bytes=%zu", n); return; }
|
||||
uint64_t id; memcpy(&id, b + 1, 8);
|
||||
struct call_session* s = call_session_find(ctx, id);
|
||||
if (!s || s->role != CALL_ROLE_CALLER || s->state != CALL_ST_OUTGOING || s->force) return;
|
||||
s->state = CALL_ST_WAIT_DECISION;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CALL, "phone busy; waiting for caller decision id=%016llx", (unsigned long long)id);
|
||||
call_event_id(ctx, CHAT_EVT_CALL_PHONE_BUSY, id, 0);
|
||||
}
|
||||
|
||||
static void call_handle_accept(struct call_ctx* ctx, uint64_t from, const uint8_t* b, size_t n) {
|
||||
if (n < 9) return;
|
||||
uint64_t call_id; memcpy(&call_id, b + 1, 8);
|
||||
struct call_session* s = call_session_find(ctx, call_id);
|
||||
if (!s || s->role != CALL_ROLE_CALLER || s->state == CALL_ST_ENDED || s->state == CALL_ST_ACTIVE) return;
|
||||
if (!s || s->role != CALL_ROLE_CALLER || (s->state != CALL_ST_OUTGOING && s->state != CALL_ST_RINGING)) return;
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CALL, "%s: ACCEPT id=%016llx", CALL_ID, (unsigned long long)call_id);
|
||||
if (s->ring_timer) { uasync_cancel_timeout(ctx->inst->ua, s->ring_timer); s->ring_timer = NULL; }
|
||||
@@ -716,6 +795,12 @@ static void call_etcp_recv(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
const uint8_t* body = d + ROUTER_SVC_PAYLOAD_OFF;
|
||||
size_t blen = entry->len - ROUTER_SVC_PAYLOAD_OFF;
|
||||
|
||||
if (subcmd != CALL_SUBCMD_MEDIA &&
|
||||
(d[ROUTER_SVC_FLAGS_OFF] & (ROUTER_FLAG_SIGNED | ROUTER_FLAG_ENCRYPTED)) != (ROUTER_FLAG_SIGNED | ROUTER_FLAG_ENCRYPTED)) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CALL, "unauthenticated call signaling command=%u", subcmd);
|
||||
queue_dgram_free(entry); queue_entry_free(entry); return;
|
||||
}
|
||||
|
||||
if ((subcmd == CALL_SUBCMD_MEDIA) != (d[0] == ETCP_RT_ID_CALL_AUDIO)) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CALL, "call packet in wrong channel svc=%u subcmd=%u", d[0], subcmd);
|
||||
queue_dgram_free(entry); queue_entry_free(entry); return;
|
||||
@@ -768,6 +853,7 @@ static void call_etcp_recv(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
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;
|
||||
case CALL_SUBCMD_PHONE_BUSY: call_handle_phone_busy(ctx, body, blen); break;
|
||||
case CALL_SUBCMD_ACCEPT: call_handle_accept(ctx, from, body, blen); break;
|
||||
case CALL_SUBCMD_DECLINE: call_handle_decline(ctx, from, body, blen); break;
|
||||
case CALL_SUBCMD_BUSY: call_handle_busy(ctx, from, body, blen); break;
|
||||
@@ -1009,6 +1095,7 @@ void call_start(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t peer_nod
|
||||
return;
|
||||
}
|
||||
s->state = CALL_ST_OUTGOING;
|
||||
call_publish_phone_status(inst);
|
||||
ctx->c_calls_started++;
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CALL, "%s: call_start id=%016llx peer=0x%016llx group=0x%016llx",
|
||||
@@ -1028,7 +1115,7 @@ void call_accept(struct UTUN_INSTANCE* inst, uint64_t call_id) {
|
||||
struct call_ctx* ctx = call_of(inst);
|
||||
if (!ctx || !ctx->initialized) return;
|
||||
struct call_session* s = call_session_find(ctx, call_id);
|
||||
if (!s || s->role != CALL_ROLE_CALLEE || s->state == CALL_ST_ENDED || s->state == CALL_ST_ACTIVE) return;
|
||||
if (!s || s->role != CALL_ROLE_CALLEE || s->state != CALL_ST_RINGING) return;
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CALL, "%s: call_accept id=%016llx -> ACCEPT + conn_mgr_open",
|
||||
CALL_ID, (unsigned long long)call_id);
|
||||
@@ -1067,6 +1154,25 @@ void call_hangup(struct UTUN_INSTANCE* inst, uint64_t call_id) {
|
||||
|
||||
/* ─────────────────── trampoline-функции (GUI → uasync) ─────────────────── */
|
||||
|
||||
void call_continue(struct UTUN_INSTANCE* inst, uint64_t call_id) {
|
||||
struct call_ctx* ctx = call_of(inst);
|
||||
struct call_session* s = call_session_find(ctx, call_id);
|
||||
if (!s || s->role != CALL_ROLE_CALLER || s->state != CALL_ST_WAIT_DECISION) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CALL, "stale call continuation id=%016llx", (unsigned long long)call_id); return;
|
||||
}
|
||||
if (get_time_tb() - s->started_tb >= CALL_RING_TIMEOUT_TB) {
|
||||
call_end(ctx, s, CALL_REASON_RING_TIMEOUT, CALL_SUBCMD_CANCEL); return;
|
||||
}
|
||||
s->force = 1; s->state = CALL_ST_OUTGOING;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CALL, "caller confirmed phone busy override id=%016llx", (unsigned long long)call_id);
|
||||
call_send_invite(ctx, s);
|
||||
}
|
||||
|
||||
void call_continue_trampoline(void* arg) {
|
||||
struct call_id_arg* a = arg;
|
||||
call_continue(a->inst, a->call_id); u_free(a);
|
||||
}
|
||||
|
||||
void call_start_trampoline(void* arg) {
|
||||
struct call_start_arg* a = (struct call_start_arg*)arg;
|
||||
call_start(a->inst, a->ch_id, a->peer_node_id, a->call_id);
|
||||
|
||||
@@ -88,6 +88,7 @@ extern "C" {
|
||||
#define CHAT_EVT_LOCAL_DELIVERY 63 /* [request:8BE][node:8BE][status:1], local_delivery_status, не результат JOIN */
|
||||
#define CHAT_EVT_LOCAL_DISCOVERY_ACTIVE 64 /* [active:1], Android MulticastLock */
|
||||
#define CHAT_EVT_RADIO_CALL 65 /* [id:8LE][group:8LE][peer:8LE][state:1][reason:1][incoming:1], radio_call.h */
|
||||
#define CHAT_EVT_CALL_PHONE_BUSY 66 /* [id:8LE][reserved:1]; вызывающий выбирает продолжить либо завершить */
|
||||
|
||||
typedef void (*chat_event_handler_fn)(struct UTUN_INSTANCE* inst, int type, const uint8_t* data, int len);
|
||||
|
||||
|
||||
Reference in new issue
Block a user