Verify idle radio calls have no timers and reject invalid invitation expiry

This commit is contained in:
evgeny committed 2026-10-19 14:13:45 +02:00
1 parent e61dc8cd06
commit c0626e0387
2 files changed
+49 -7

No files matched your search

+6 -6
View File
@@ -1,6 +1,6 @@
/* Короткоживущие приглашения в рацию; один pending, ограниченный кэш результатов.
* Повторы нужны также после смены router epoch. Один таймер владеет ближайшим
* всеми deadline; callback не хранит указателей на освобождаемые сессии. */
* Повторы нужны также после смены router epoch. Один таймер обслуживает ближайший
* deadline; callback не хранит указателей на освобождаемые сессии. */
#include <string.h>
#include "radio_call.h"
#include "radio.h"
@@ -205,10 +205,10 @@ static void rc_receive(struct ETCP_CONN* conn, struct ll_entry* entry) {
struct radio_call_session* s = rc_slot(ctx, id, group, peer, 0);
if (command == RC_INVITE && !s) {
int64_t expires; memcpy(&expires, b + 9, 8);
int64_t remaining = expires - ntp_time_get_us(ctx->inst);
if (remaining <= 0 || remaining > 4000000 || !rc_group(ctx->inst, group, peer)) {
DEBUG_WARN(DEBUG_CATEGORY_RADIO, "radio call stale/invalid invite id=%016llx remaining_us=%lld",
(unsigned long long)id, (long long)remaining); goto done;
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;
}
int busy = radio_call_busy(ctx->inst) || call_busy(ctx->inst);
s = rc_slot(ctx, id, group, peer, 1);
+43 -1
View File
@@ -10,6 +10,8 @@
// кадры в порядке seq 0..4 и FIN.
// 3. C radio_off (обрезание) → A снова говорит → B получает, C не получает.
// 4. Без FIN: C снимает индикацию при radio_off, B — по таймауту источника.
// До передачи: вызовы через B, deadline 2с/60с, потери RESULT/ACK, отказ/отмена/занятость,
// отсутствие живого BGP-пути, некорректный срок INVITE и отсутствие таймеров рации в покое.
#include "chat_core.h"
#include "chat_sync.h"
@@ -132,6 +134,16 @@ static void radio_action(struct UTUN_INSTANCE* inst, uint64_t id, int action, ui
radio_call_execute(&req);
}
static int radio_invitation_timers(struct UASYNC* ua) {
int count = 0;
for (size_t i = 0; i < ua->timeout_heap->size; ++i) {
TimeoutEntry* entry = &ua->timeout_heap->heap[i];
struct timeout_node* node = entry->data;
if (!entry->deleted && node && node->name && !strcmp(node->name, "radio_call")) ++count;
}
return count;
}
/* Проверяем именно UI-события: self, прямой пир и пир за посредником. */
static void member_event(struct UTUN_INSTANCE* inst, int type, const uint8_t* data, int len) {
if (type == CHAT_EVT_RADIO_CALL) {
@@ -456,6 +468,27 @@ static void rad_tick(void* arg) {
case 0: {
uint8_t key[32];
if (member_sync_verified_ed25519(A, CH_ID, nc, key) || member_sync_verified_ed25519(C, CH_ID, na, key)) break;
if (radio_invitation_timers(t->ua)) {
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "test idle radio invitation service has timers");
t->result = 2; uasync_stop(t->ua); return;
}
/* Уже расшифрованный пакет от проверенного пира: даже INT64_MIN обязан безопасно отбрасываться. */
struct ll_entry* expired = ll_alloc_lldgram(ROUTER_SVC_HDR_SIZE + 18);
if (!expired) abort();
memset(expired->dgram, 0, expired->len);
expired->dgram[0] = ETCP_RT_ID_RADIO_CALL;
memcpy(expired->dgram + ROUTER_SVC_SRC_OFF, &nc, 8); memcpy(expired->dgram + ROUTER_SVC_DST_OFF, &na, 8);
memcpy(expired->dgram + ROUTER_SVC_GROUP_OFF, &gid, 8);
expired->dgram[ROUTER_SVC_FLAGS_OFF] = ROUTER_FLAG_SIGNED | ROUTER_FLAG_ENCRYPTED;
expired->dgram[ROUTER_SVC_PAYLOAD_OFF] = 1;
uint64_t expired_id = 108; int64_t expired_at = INT64_MIN;
memcpy(expired->dgram + ROUTER_SVC_PAYLOAD_OFF + 1, &expired_id, 8);
memcpy(expired->dgram + ROUTER_SVC_PAYLOAD_OFF + 9, &expired_at, 8);
struct ETCP_CONN source = {.instance = A};
A->router_bindings.callbacks[ETCP_RT_ID_RADIO_CALL](&source, expired);
if (radio_call_busy(A) || g_member_event_bad) {
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "test malformed invite created a call"); t->result = 2; uasync_stop(t->ua); return;
}
struct TOPO_GROUP* cg = topo_groups_find(C->topo_groups, gid);
struct ETCP_CONN* path = topo_group_find_conn_for_node(cg, na);
if (!path) break;
@@ -544,7 +577,16 @@ static void rad_tick(void* arg) {
t->result = 2; uasync_stop(t->ua); return;
}
}
DEBUG_INFO(DEBUG_CATEGORY_RADIO, "test radio calls passed: 2s/60s deadlines, accept, decline, cancel, busy, lost result/ACK");
t->call_started = now; ++t->call_step;
}
break;
case 9:
if (now - t->call_started >= 5000) {
if (radio_invitation_timers(t->ua) || radio_call_busy(A) || radio_call_busy(B) || radio_call_busy(C)) {
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "test finished invitations retain timers or busy state");
t->result = 2; uasync_stop(t->ua); return;
}
DEBUG_INFO(DEBUG_CATEGORY_RADIO, "test radio calls passed: 2s/60s, accept/decline/cancel/busy, lost result/ACK, idle has no timer");
t->phase = P_RADIO_ON;
}
break;