|
|
|
|
@ -6,8 +6,9 @@
|
|
|
|
|
// Сценарий:
|
|
|
|
|
// 1. PM BGP: живой/закрывающийся/исчезнувший путь, выбор общей группы и повторная проверка.
|
|
|
|
|
// 2. happy path из PM: A call_start → B INCOMING → B accept → обе ACTIVE (ACCEPTED)
|
|
|
|
|
// → A hangup → обе ENDED.
|
|
|
|
|
// 3. decline из PM: A call_start → B INCOMING → B decline → обе ENDED(DECLINE).
|
|
|
|
|
// → B hangup с ожидающими видеокомандами → обе ENDED, поздние команды отменены.
|
|
|
|
|
// 3. Новый звонок: поздний close старого порта не закрывает новый, detach отменяет post.
|
|
|
|
|
// 4. decline из PM: A call_start → B INCOMING → B decline → обе ENDED(DECLINE).
|
|
|
|
|
|
|
|
|
|
#include "chat_core.h" |
|
|
|
|
#include "chat_sync.h" |
|
|
|
|
@ -66,10 +67,11 @@ enum call_phase {
|
|
|
|
|
P_VIDEO_CONFIG, P_VIDEO_SEND, P_VIDEO_HOLD, P_VIDEO_BUSY, P_VIDEO_RESUME, P_VIDEO_RANGE_WAIT, P_VIDEO_SWITCH, P_VIDEO_SWITCH_WAIT, |
|
|
|
|
P_VIDEO_SHRINK_WAIT, P_VIDEO_PORT_OPEN, P_VIDEO_PORT_RECEIVE, P_VIDEO_PORT_SEND, P_VIDEO_PORT_ACK, |
|
|
|
|
P_VIDEO_DECODE_OK, P_VIDEO_DECODE_REJECT, |
|
|
|
|
P_HANGUP, /* A: call_hangup */ |
|
|
|
|
P_VIDEO_PORT_CANCELLED, P_VIDEO_PORT_REOPENED, |
|
|
|
|
P_WAIT_ENDED1, /* обе ENDED */ |
|
|
|
|
P_CALL2_START, /* A: call_start (2-й звонок) */ |
|
|
|
|
P_WAIT_INCOMING2, /* B: INCOMING */ |
|
|
|
|
P_VIDEO_NEW_CALL, P_VIDEO_NEW_PORT, |
|
|
|
|
P_DECLINE, /* B: call_decline */ |
|
|
|
|
P_WAIT_ENDED2, /* обе ENDED + A DECLINED */ |
|
|
|
|
P_DONE, |
|
|
|
|
@ -84,6 +86,8 @@ struct call_ctx {
|
|
|
|
|
int phase_ticks; |
|
|
|
|
void* timer; |
|
|
|
|
uint64_t call_id; /* текущий call_id */ |
|
|
|
|
uint64_t previous_call_id; |
|
|
|
|
int video_restart_tested; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
/* записи событий CHAT_EVT_CALL_* на узел */ |
|
|
|
|
@ -688,18 +692,44 @@ static void call_tick(void* arg) {
|
|
|
|
|
if (++t->phase_ticks < 10) break; // Регулятор не должен отменять запрет decoder на следующем tick.
|
|
|
|
|
struct call_video_frame f = video_test_frame(t->call_id, 1); f.frame_id = 2; f.pts_us += 33333; |
|
|
|
|
if (call_video_send_frame(B, &f) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video ignored decoder rejection"); t->result = 2; break; } |
|
|
|
|
// Все команды попадут в uasync после синхронного ENDED; TX ещё принадлежит mailbox.
|
|
|
|
|
if (call_video_port_format(t->call_id, &f.format, 500000) |
|
|
|
|
|| call_video_port_applied(t->call_id, 1, 1, 500000) |
|
|
|
|
|| call_video_port_decoded(t->call_id, 1, 1) || call_video_port_send(&f) != 1) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video teardown commands were not queued"); t->result = 2; break; |
|
|
|
|
} |
|
|
|
|
call_video_port_request_key(t->call_id); |
|
|
|
|
call_video_port_close(t->call_id); |
|
|
|
|
call_video_port_detach(B); // Отменяет уже поставленный post до destroy ядра.
|
|
|
|
|
if (call_video_port_status(t->call_id, &status) != -1) { t->result = 2; break; } |
|
|
|
|
t->phase = P_HANGUP; break; |
|
|
|
|
t->previous_call_id = t->call_id; |
|
|
|
|
call_hangup(B, t->call_id); |
|
|
|
|
t->phase = P_VIDEO_PORT_CANCELLED; break; |
|
|
|
|
} |
|
|
|
|
case P_VIDEO_PORT_CANCELLED: { |
|
|
|
|
struct call_video_port_status status; |
|
|
|
|
struct call_video_frame f = video_test_frame(t->call_id, 1); |
|
|
|
|
if (call_video_port_status(t->call_id, &status) != -1 || call_video_port_send(&f) != -1 || g_port_frame != 1) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "ended video port retained commands or accepted a late frame"); t->result = 2; break; |
|
|
|
|
} |
|
|
|
|
call_video_port_close(t->call_id); // Повторное закрытие после отмены команд.
|
|
|
|
|
if (call_video_port_open(t->call_id, &g_video_peer[0])) { t->result = 2; break; } |
|
|
|
|
t->phase = P_VIDEO_PORT_REOPENED; break; |
|
|
|
|
} |
|
|
|
|
case P_VIDEO_PORT_REOPENED: { |
|
|
|
|
struct call_video_port_status status; |
|
|
|
|
if (call_video_port_status(t->call_id, &status) != -1) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "late open resurrected an ended video session"); t->result = 2; break; |
|
|
|
|
} |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_CALL, "video teardown regression: queued TX/configuration and late open cancelled"); |
|
|
|
|
t->phase = P_WAIT_ENDED1; break; |
|
|
|
|
} |
|
|
|
|
case P_HANGUP: |
|
|
|
|
call_hangup(A, t->call_id); |
|
|
|
|
t->phase = P_WAIT_ENDED1; |
|
|
|
|
break; |
|
|
|
|
case P_WAIT_ENDED1: |
|
|
|
|
if (ea->ended && eb->ended && ea->ended_call_id == t->call_id) t->phase = P_CALL2_START; |
|
|
|
|
if (ea->ended && eb->ended && ea->ended_call_id == t->call_id) { |
|
|
|
|
if (ea->ended_reason != CALL_REASON_REMOTE_HANGUP || eb->ended_reason != CALL_REASON_LOCAL_HANGUP) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video teardown: HANGUP delivery failed caller_reason=%u callee_reason=%u", |
|
|
|
|
ea->ended_reason, eb->ended_reason); |
|
|
|
|
t->result = 2; break; |
|
|
|
|
} |
|
|
|
|
t->phase = P_CALL2_START; |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
case P_CALL2_START: { |
|
|
|
|
memset(ea, 0, sizeof(*ea)); |
|
|
|
|
@ -708,20 +738,44 @@ static void call_tick(void* arg) {
|
|
|
|
|
if (!test_pm_route(A, nb, target)) { |
|
|
|
|
fprintf(stderr, "PM route availability/recheck failed\n"); t->result = 2; uasync_stop(t->ua); return; |
|
|
|
|
} |
|
|
|
|
t->call_id = ((uint64_t)2 << 40) | (uint64_t)(rand() & 0xFFFFF); |
|
|
|
|
++t->call_id; |
|
|
|
|
call_start(A, target, nb, t->call_id); |
|
|
|
|
t->phase = P_WAIT_INCOMING2; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
case P_WAIT_INCOMING2: |
|
|
|
|
if (eb->incoming && eb->incoming_call_id == t->call_id) t->phase = P_DECLINE; |
|
|
|
|
if (eb->incoming && eb->incoming_call_id == t->call_id) { |
|
|
|
|
if (t->video_restart_tested) t->phase = P_DECLINE; |
|
|
|
|
else { call_accept(B, t->call_id); t->phase = P_VIDEO_NEW_CALL; } |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
case P_VIDEO_NEW_CALL: { |
|
|
|
|
if (!ea->accepted || !eb->accepted) break; |
|
|
|
|
if (call_video_configure(A, t->call_id, &g_video_peer[1], &video_ops, (void *)0) |
|
|
|
|
|| call_video_port_open(t->call_id, &g_video_peer[0])) { t->result = 2; break; } |
|
|
|
|
call_video_port_close(t->previous_call_id); |
|
|
|
|
t->phase = P_VIDEO_NEW_PORT; break; |
|
|
|
|
} |
|
|
|
|
case P_VIDEO_NEW_PORT: { |
|
|
|
|
struct call_video_port_status status; |
|
|
|
|
if (call_video_port_status(t->call_id, &status) || status.error) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "late close affected the new video port"); t->result = 2; break; |
|
|
|
|
} |
|
|
|
|
if (!status.have_peer) break; |
|
|
|
|
call_video_port_request_key(t->call_id); |
|
|
|
|
call_video_port_close(t->call_id); |
|
|
|
|
call_video_port_detach(B); // Отменяет уже поставленный post до destroy ядра.
|
|
|
|
|
if (call_video_port_status(t->call_id, &status) != -1) { t->result = 2; break; } |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_CALL, "video teardown regression: new call survives stale close; detach cancels post"); |
|
|
|
|
t->video_restart_tested = 1; |
|
|
|
|
call_hangup(A, t->call_id); t->phase = P_WAIT_ENDED2; break; |
|
|
|
|
} |
|
|
|
|
case P_DECLINE: |
|
|
|
|
call_decline(B, t->call_id); |
|
|
|
|
t->phase = P_WAIT_ENDED2; |
|
|
|
|
break; |
|
|
|
|
case P_WAIT_ENDED2: |
|
|
|
|
if (ea->ended && eb->ended && ea->declined) t->phase = P_DONE; |
|
|
|
|
if (ea->ended && eb->ended) t->phase = ea->declined ? P_DONE : P_CALL2_START; |
|
|
|
|
break; |
|
|
|
|
case P_DONE: |
|
|
|
|
t->result = 1; |
|
|
|
|
|