Cover radio phone policy, BGP color flags, retries and caller choice timeout

This commit is contained in:
evgeny committed 2026-10-19 14:59:17 +02:00
1 parent 4683c98c08
commit 79b4235960
2 files changed
+82 -11

No files matched your search

+73 -8
View File
@@ -20,6 +20,7 @@
#include "member_sync.h" #include "member_sync.h"
#include "../radio/radio.h" #include "../radio/radio.h"
#include "../radio/radio_call.h" #include "../radio/radio_call.h"
#include "../call/call.h"
#include "../routing_layer/etcp_router.h" #include "../routing_layer/etcp_router.h"
#include "../routing_layer/topo_node_sqlite.h" #include "../routing_layer/topo_node_sqlite.h"
#include "../routing_layer/topo_group.h" #include "../routing_layer/topo_group.h"
@@ -52,7 +53,7 @@
#define IDX_C 2 #define IDX_C 2
#define TICK_TB 100 /* 10 ms на такт */ #define TICK_TB 100 /* 10 ms на такт */
#define MAX_TICKS 12000 /* включает минуту ожидания решения по вызову */ #define MAX_TICKS 24000 /* включает минутные deadline звонка и выбора продолжения */
#define N_FRAMES 5 #define N_FRAMES 5
enum rad_phase { enum rad_phase {
@@ -111,8 +112,9 @@ static int g_member_radio[3][3];
static int g_member_lists[3]; static int g_member_lists[3];
static int g_member_event_bad; static int g_member_event_bad;
static int g_talk_on[3], g_talk_starts[3], g_talk_stops[3]; static int g_talk_on[3], g_talk_starts[3], g_talk_stops[3];
static struct { int state, reason, finished, incoming; uint64_t finished_tb; } g_calls[8][3]; static struct { int state, reason, finished, incoming; uint64_t finished_tb; } g_calls[20][3];
static int g_call_ready, g_drop_result, g_drop_ack; static int g_call_ready, g_drop_result, g_drop_ack, g_drop_force;
static int g_member_phone[3][3];
static etcp_recv_fn g_call_recv[3]; static etcp_recv_fn g_call_recv[3];
/* Потеря приложения после router ACK: восстановить результат обязан сервис, не транспорт. */ /* Потеря приложения после router ACK: восстановить результат обязан сервис, не транспорт. */
@@ -122,7 +124,8 @@ static void call_packet_loss(struct ETCP_CONN* conn, struct ll_entry* entry) {
if (idx < 0 || !entry) abort(); if (idx < 0 || !entry) abort();
if (entry->len <= ROUTER_SVC_PAYLOAD_OFF) { g_call_recv[idx](conn, entry); return; } if (entry->len <= ROUTER_SVC_PAYLOAD_OFF) { g_call_recv[idx](conn, entry); return; }
int cmd = entry->dgram[ROUTER_SVC_PAYLOAD_OFF]; int cmd = entry->dgram[ROUTER_SVC_PAYLOAD_OFF];
if ((idx == IDX_A && cmd == 3 && g_drop_result-- > 0) || (idx == IDX_C && cmd == 4 && g_drop_ack-- > 0)) { if ((idx == IDX_A && cmd == 3 && g_drop_result-- > 0) || (idx == IDX_C && cmd == 4 && g_drop_ack-- > 0) ||
(idx == IDX_C && cmd == 6 && g_drop_force-- > 0)) {
DEBUG_INFO(DEBUG_CATEGORY_RADIO, "test drops radio result/ACK dst=%d command=%d", idx, cmd); DEBUG_INFO(DEBUG_CATEGORY_RADIO, "test drops radio result/ACK dst=%d command=%d", idx, cmd);
queue_dgram_free(entry); queue_entry_free(entry); return; queue_dgram_free(entry); queue_entry_free(entry); return;
} }
@@ -163,7 +166,7 @@ static void member_event(struct UTUN_INSTANCE* inst, int type, const uint8_t* da
memcpy(&id, data, 8); memcpy(&group, data + 8, 8); memcpy(&peer, data + 16, 8); memcpy(&id, data, 8); memcpy(&group, data + 8, 8); memcpy(&peer, data + 16, 8);
int idx = -1; int idx = -1;
for (int i = 0; i < 3; ++i) if (inst->node_id == g_sh.nid[i]) idx = i; for (int i = 0; i < 3; ++i) if (inst->node_id == g_sh.nid[i]) idx = i;
if (idx < 0 || id < 100 || id > 107 || group != strtoull(CH_ID, NULL, 10)) { g_member_event_bad = 1; return; } if (idx < 0 || id < 100 || id > 119 || group != strtoull(CH_ID, NULL, 10)) { g_member_event_bad = 1; return; }
g_calls[id - 100][idx].state = data[24]; g_calls[id - 100][idx].reason = data[25]; g_calls[id - 100][idx].state = data[24]; g_calls[id - 100][idx].reason = data[25];
if (data[24] == RADIO_CALL_INCOMING) { if (data[24] == RADIO_CALL_INCOMING) {
++g_calls[id - 100][idx].incoming; ++g_calls[id - 100][idx].incoming;
@@ -212,8 +215,10 @@ static void member_event(struct UTUN_INSTANCE* inst, int type, const uint8_t* da
if (chat_core_deserialize_member(data + off + i * CHAT_MEMBER_DISPLAY_SIZE, &member) < 0) { if (chat_core_deserialize_member(data + off + i * CHAT_MEMBER_DISPLAY_SIZE, &member) < 0) {
g_member_event_bad = 1; return; g_member_event_bad = 1; return;
} }
for (int j = 0; j < 3; j++) for (int j = 0; j < 3; j++) if (member.node_id == g_sh.nid[j]) {
if (member.node_id == g_sh.nid[j]) g_member_radio[dst][j] = !!(member.flags & CHAT_MEMBER_FLAG_RADIO); g_member_radio[dst][j] = !!(member.flags & CHAT_MEMBER_FLAG_RADIO);
g_member_phone[dst][j] = member.flags & (CHAT_MEMBER_FLAG_PHONE_KNOWN | CHAT_MEMBER_FLAG_PHONE_BUSY);
}
} }
} }
@@ -598,9 +603,69 @@ static void rad_tick(void* arg) {
t->result = 2; uasync_stop(t->ua); return; 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"); 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; call_set_phone_state(C, 2, CALL_POLICY_WARN_PHONE);
radio_action(A, 108, RADIO_CALL_START, nc); ++t->call_step;
} }
break; break;
case 10:
if (g_calls[8][IDX_A].state != RADIO_CALL_WAIT_DECISION) break;
if (g_calls[8][IDX_C].incoming || g_member_phone[IDX_A][IDX_C]) goto phone_failed;
call_set_phone_state(C, 1, CALL_POLICY_WARN_PHONE);
t->call_started = now; ++t->call_step; break;
case 11:
if (now - t->call_started < 10000) break;
if (g_calls[8][IDX_C].incoming) goto phone_failed; // Освободившийся телефон сам не начинает вызов.
g_drop_force = 2;
radio_action(A, 108, RADIO_CALL_CONTINUE, 0); ++t->call_step; break;
case 12:
if (g_calls[8][IDX_A].state != RADIO_CALL_RINGING) break;
if (g_calls[8][IDX_C].incoming != 1 || g_drop_force > 0) goto phone_failed;
radio_action(C, 108, RADIO_CALL_DECLINE, 0); ++t->call_step; break;
case 13:
if (!g_calls[8][IDX_A].finished) break;
radio_action(A, 108, RADIO_CALL_CONTINUE, 0); // Запоздалое UI-подтверждение не оживляет попытку.
call_set_phone_state(C, 2, CALL_POLICY_PUBLISH_PHONE);
radio_action(A, 109, RADIO_CALL_START, nc); ++t->call_step; break;
case 14:
if (g_calls[9][IDX_A].state != RADIO_CALL_RINGING || g_member_phone[IDX_A][IDX_C] != 0xC0) break;
if (g_calls[9][IDX_C].incoming != 1 || g_calls[8][IDX_C].incoming != 1) goto phone_failed;
{
uint8_t member[CHAT_MEMBER_DISPLAY_SIZE];
if (chat_core_get_single_member(A, CH_ID, nc, member) || (member[10] & 0xC0) != 0xC0) goto phone_failed;
}
radio_action(A, 109, RADIO_CALL_CANCEL, 0); ++t->call_step; break;
case 15:
if (!g_calls[9][IDX_C].finished) break;
call_set_phone_state(C, 2, CALL_POLICY_WARN_PHONE | CALL_POLICY_REJECT_PHONE);
radio_action(A, 110, RADIO_CALL_START, nc); ++t->call_step; break;
case 16:
if (!g_calls[10][IDX_A].finished || g_member_phone[IDX_A][IDX_C]) break;
if (g_calls[10][IDX_A].reason != RADIO_CALL_PHONE_BUSY || g_calls[10][IDX_C].incoming) goto phone_failed;
call_set_phone_state(C, 0, CALL_POLICY_BLOCK_CALLS);
radio_action(A, 111, RADIO_CALL_START, nc); ++t->call_step; break;
case 17:
if (g_calls[11][IDX_A].state != RADIO_CALL_RINGING) break;
radio_action(A, 111, RADIO_CALL_CANCEL, 0); ++t->call_step; break;
case 18:
if (!g_calls[11][IDX_C].finished) break;
call_set_phone_state(C, 2, CALL_POLICY_BLOCK_RADIO | CALL_POLICY_WARN_PHONE);
radio_action(A, 112, RADIO_CALL_START, nc); ++t->call_step; break;
case 19:
if (!g_calls[12][IDX_A].finished) break;
if (g_calls[12][IDX_A].reason != RADIO_CALL_DECLINED || g_calls[12][IDX_C].incoming) goto phone_failed;
call_set_phone_state(C, 2, CALL_POLICY_WARN_PHONE);
t->call_started = now;
radio_action(A, 113, RADIO_CALL_START, nc); ++t->call_step; break;
case 20:
if (!g_calls[13][IDX_A].finished) break;
if (g_calls[13][IDX_A].reason != RADIO_CALL_NO_ANSWER || g_calls[13][IDX_C].incoming ||
now - t->call_started < RADIO_CALL_RING_TB || now - t->call_started > RADIO_CALL_RING_TB + 1000) goto phone_failed;
call_set_phone_state(C, 0, 0);
DEBUG_INFO(DEBUG_CATEGORY_RADIO, "phone policy passed: hidden/published BGP, warn, override with loss, reject, independent blocks, 60s choice");
t->phase = P_RADIO_ON; break;
phone_failed:
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "phone policy failed step=%d", t->call_step);
t->result = 2; uasync_stop(t->ua); return;
} }
break; break;
} }
+9 -3
View File
@@ -70,6 +70,7 @@ extern "C" {
extern "C" { extern "C" {
#include "chat/chat_core.h" #include "chat/chat_core.h"
#include "call/call.h" #include "call/call.h"
#include "call/call_proto.h"
#include "radio/radio_call.h" #include "radio/radio_call.h"
#include "dm/dm_core.h" #include "dm/dm_core.h"
#include "../../lib/mem.h" #include "../../lib/mem.h"
@@ -776,7 +777,7 @@ void MainWindow::onRadioCallRequested(const QString& channel, quint64 peer) {
/* Один диалог на незавершённое приглашение; принятая рация живёт своим обычным циклом. */ /* Один диалог на незавершённое приглашение; принятая рация живёт своим обычным циклом. */
void MainWindow::onRadioCallEvent(const uint8_t* data, int len) { void MainWindow::onRadioCallEvent(const uint8_t* data, int len) {
if (!data || len != 27 || data[24] < RADIO_CALL_OUTGOING || data[24] > RADIO_CALL_WAIT_DECISION || if (!data || len != 27 || data[24] < RADIO_CALL_OUTGOING || data[24] > RADIO_CALL_WAIT_DECISION ||
data[25] > RADIO_CALL_AUDIO_ERROR || data[26] > 1) { data[25] > RADIO_CALL_PHONE_BUSY || data[26] > 1) {
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "desktop invalid radio call event bytes=%d", len); return; DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "desktop invalid radio call event bytes=%d", len); return;
} }
quint64 id, group, peer; quint64 id, group, peer;
@@ -798,7 +799,7 @@ void MainWindow::onRadioCallEvent(const uint8_t* data, int len) {
if (reason == RADIO_CALL_ACCEPTED) return; if (reason == RADIO_CALL_ACCEPTED) return;
} }
if ((incoming && reason != RADIO_CALL_AUDIO_ERROR) || reason == RADIO_CALL_CANCELLED) return; if ((incoming && reason != RADIO_CALL_AUDIO_ERROR) || reason == RADIO_CALL_CANCELLED) return;
const char* text = reason == RADIO_CALL_DECLINED ? "Radio call declined" : const char* text = reason == RADIO_CALL_PHONE_BUSY ? "Subscriber is on a phone call" : reason == RADIO_CALL_DECLINED ? "Radio call declined" :
reason == RADIO_CALL_BUSY ? "Subscriber is busy" : reason == RADIO_CALL_NO_ANSWER ? "No answer" : reason == RADIO_CALL_BUSY ? "Subscriber is busy" : reason == RADIO_CALL_NO_ANSWER ? "No answer" :
reason == RADIO_CALL_AUDIO_ERROR ? "Could not start channel radio" : "Subscriber is unavailable"; reason == RADIO_CALL_AUDIO_ERROR ? "Could not start channel radio" : "Subscriber is unavailable";
auto* notice = new QMessageBox(QMessageBox::Information, QCoreApplication::translate("ChatGui", "Radio call"), auto* notice = new QMessageBox(QMessageBox::Information, QCoreApplication::translate("ChatGui", "Radio call"),
@@ -953,11 +954,16 @@ void MainWindow::onCallAccepted(quint64 callId) {
} }
void MainWindow::onCallEnded(quint64 callId, uint8_t reason) { void MainWindow::onCallEnded(quint64 callId, uint8_t reason) {
Q_UNUSED(reason); const bool phoneBusy = reason == CALL_REASON_PHONE_BUSY && m_activeCalls.contains(callId);
/* тон завершения (~450мс) → авто-teardown аудио-движка */ /* тон завершения (~450мс) → авто-teardown аудио-движка */
if (CallAudioEngine::instance()->callId() == callId) if (CallAudioEngine::instance()->callId() == callId)
CallAudioEngine::instance()->beginEnd(callId); CallAudioEngine::instance()->beginEnd(callId);
closeCallWindow(callId); closeCallWindow(callId);
if (phoneBusy) {
auto* notice = new QMessageBox(QMessageBox::Information, QStringLiteral("vibechat"),
QCoreApplication::translate("ChatGui", "Subscriber is on a phone call"), QMessageBox::Ok, this);
notice->setAttribute(Qt::WA_DeleteOnClose); notice->show();
}
} }
void MainWindow::onCallError(quint64 callId, uint8_t err) { void MainWindow::onCallError(quint64 callId, uint8_t err) {