From 8c12dfc02f4c8f3dbd3cb091def02d0079056836 Mon Sep 17 00:00:00 2001 From: evgeny Date: Wed, 14 Oct 2026 22:14:30 +0200 Subject: [PATCH] Verify pending video is discarded when peer capabilities shrink --- src/call/call_video_transport.c | 8 ++++++++ tests/test_call.c | 21 ++++++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/call/call_video_transport.c b/src/call/call_video_transport.c index 561d2dcd..777c9dcd 100644 --- a/src/call/call_video_transport.c +++ b/src/call/call_video_transport.c @@ -214,6 +214,10 @@ int call_video_configure(struct UTUN_INSTANCE *inst, uint64_t id, const struct c call_video_resume_receive(inst, id); DEBUG_INFO(DEBUG_CATEGORY_CALL, "video configured call=%llu encode=%u/%u decode=%u/%u", (unsigned long long)id, v->local.encode[0].width, v->local.encode[1].width, v->local.decode[0].width, v->local.decode[1].width); + for (unsigned i = 0; i < 2; i++) DEBUG_DEBUG(DEBUG_CATEGORY_CALL, + "video local limits codec=%u encode_level=%u bps=%u vbv=%u decode_level=%u bps=%u vbv=%u", + i + 1, v->local.encode[i].level, v->local.encode[i].maximum_bps, v->local.encode[i].maximum_vbv_bits, + v->local.decode[i].level, v->local.decode[i].maximum_bps, v->local.decode[i].maximum_vbv_bits); return control_send(v, VIDEO_CAPS); } int call_video_send_frame(struct UTUN_INSTANCE *inst, const struct call_video_frame *f) { @@ -360,6 +364,10 @@ static void receive(struct ETCP_CONN *conn, struct ll_entry *entry) { v->peer = caps; v->have_peer = 1; DEBUG_INFO(DEBUG_CATEGORY_CALL, "video peer capabilities call=%llu encode=%u/%u decode=%u/%u", (unsigned long long)id, caps.encode[0].width, caps.encode[1].width, caps.decode[0].width, caps.decode[1].width); + for (unsigned i = 0; i < 2; i++) DEBUG_DEBUG(DEBUG_CATEGORY_CALL, + "video peer limits codec=%u encode_level=%u bps=%u vbv=%u decode_level=%u bps=%u vbv=%u", + i + 1, caps.encode[i].level, caps.encode[i].maximum_bps, caps.encode[i].maximum_vbv_bits, + caps.decode[i].level, caps.decode[i].maximum_bps, caps.decode[i].maximum_vbv_bits); if (v->ops.capabilities) v->ops.capabilities(v->arg, &v->peer); control_send(v, VIDEO_CAPS_ACK); } else if (data[0] == VIDEO_CAPS_ACK && length == 17 && revision && revision <= v->local_revision) { diff --git a/tests/test_call.c b/tests/test_call.c index 8128c432..564a7460 100644 --- a/tests/test_call.c +++ b/tests/test_call.c @@ -63,6 +63,7 @@ enum call_phase { P_SEND_MEDIA, /* A: call_send_media (тестовый Opus-кадр) */ P_WAIT_MEDIA, /* B: media_recv_cb получил кадр */ 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_HANGUP, /* A: call_hangup */ P_WAIT_ENDED1, /* обе ENDED */ P_CALL2_START, /* A: call_start (2-й звонок) */ @@ -119,7 +120,9 @@ static int g_video_frames, g_video_ready, g_video_error; static uint64_t g_rate_generation, g_rate_request; static uint32_t g_rate_bps; static unsigned g_rate_commands; +static unsigned g_video_keys[2]; static void video_caps(void *arg, const struct call_video_caps *caps) { g_video_peer[(size_t)arg] = *caps; } +static void video_need_key(void *arg) { g_video_keys[(size_t)arg]++; } static int video_ready(void *arg, uint32_t bytes) { (void)arg; return bytes <= sizeof(g_video_data) && g_video_ready; } static void video_frame(void *arg, const struct call_video_frame *f) { (void)arg; @@ -132,7 +135,7 @@ static void video_bitrate(void *arg, uint64_t generation, uint64_t request, uint (void)arg; g_rate_generation = generation; g_rate_request = request; g_rate_bps = bps; g_rate_commands++; } static const struct call_video_ops video_ops = { - .capabilities = video_caps, .receive_ready = video_ready, .frame = video_frame, .bitrate = video_bitrate + .capabilities = video_caps, .receive_ready = video_ready, .frame = video_frame, .bitrate = video_bitrate, .need_key = video_need_key }; static struct call_video_frame video_test_frame(uint64_t id, unsigned generation) { struct call_video_frame f = {0}; @@ -582,8 +585,24 @@ static void call_tick(void* arg) { struct call_video_frame f = video_test_frame(t->call_id, 3); if (call_video_configure_rate(A, t->call_id, &f.format, 350000, 400000)) { t->result = 2; break; } if (call_video_send_frame(A, &f) != 1) { t->result = 2; break; } + struct call_video_caps caps = g_video_peer[0]; caps.decode[0].maximum_bps = 3000000; + g_video_keys[0] = 0; + if (call_video_configure(B, t->call_id, &caps, &video_ops, (void*)1)) { t->result = 2; break; } + } + t->phase = P_VIDEO_SHRINK_WAIT; break; + case P_VIDEO_SHRINK_WAIT: { + if (g_video_peer[0].decode[0].maximum_bps != 3000000 || !g_video_keys[0]) break; + if (g_video_frames != 2) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video escaped changed peer envelope"); t->result = 2; break; } + struct call_video_frame f = video_test_frame(t->call_id, 3); f.format.maximum_bps = 3000000; + if (call_video_configure_rate(A, t->call_id, &f.format, 350000, 400000) != -1) { + DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video envelope changed without new generation"); t->result = 2; break; + } + f.format.generation = f.frame_id = 4; + if (call_video_configure_rate(A, t->call_id, &f.format, 350000, 400000) || call_video_send_frame(A, &f) != 1) { + DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video failed to replace withdrawn envelope"); t->result = 2; break; } t->phase = P_HANGUP; break; + } case P_HANGUP: call_hangup(A, t->call_id); t->phase = P_WAIT_ENDED1;