/* Настоящие transport/fragment/uasync, управляемый router без сетевых таймеров. */ #define etcp_router_conn_get test_router_conn_get #define etcp_router_conn_send test_router_conn_send #define etcp_router_tx_stats test_router_tx_stats #define etcp_router_send_q_has_room test_router_send_q_has_room #define etcp_router_resume_receive test_router_resume_receive #define etcp_router_on_send_ready test_router_on_send_ready #define etcp_router_cancel_send_ready test_router_cancel_send_ready #include "../src/call/call_video_transport.c" #undef etcp_router_conn_get #undef etcp_router_conn_send #undef etcp_router_tx_stats #undef etcp_router_send_q_has_room #undef etcp_router_resume_receive #undef etcp_router_on_send_ready #undef etcp_router_cancel_send_ready #include #include /* Один batch, без poll/timeouts: тест не может случайно продолжить TX таймером 50мс. */ extern void process_immediate_queue(struct UASYNC *ua); static struct ETCP_ROUTER_CONN router; static unsigned packets, received, room, resumed; static uint64_t debt; static queue_threshold_callback_fn ready_callback; static void *ready_arg; enum { FRAME_BYTES = 45001 }; static uint8_t output[FRAME_BYTES]; struct ETCP_ROUTER_CONN *test_router_conn_get(struct UTUN_INSTANCE *inst, uint64_t group, uint64_t peer, uint8_t svc) { (void)inst; (void)group; (void)peer; assert(svc == ETCP_RT_ID_CALL_VIDEO); return &router; } int test_router_conn_send(struct ETCP_ROUTER_CONN *conn, const uint8_t *data, size_t len, int mode) { assert(conn == &router && mode == (ROUTE_CRYPTO_SIGN | ROUTE_CRYPTO_ENCRYPT)); struct call_video_frame f; assert(!call_video_fragment_parse(data, len, &f)); assert(f.call_id == 7 && f.frame_id == 1 && f.size == FRAME_BYTES && f.offset == received); assert(received + f.length <= sizeof(output)); memcpy(output + received, f.data, f.length); received += f.length; packets++; return 0; } int test_router_tx_stats(struct UTUN_INSTANCE *inst, uint64_t group, uint64_t peer, uint8_t svc, uint64_t now_us, struct ETCP_ROUTER_TX_STATS *out) { (void)inst; (void)group; (void)peer; (void)svc; (void)now_us; *out = (struct ETCP_ROUTER_TX_STATS){.inflight_bytes = debt, .ready = 1}; return 0; } int test_router_send_q_has_room(struct UTUN_INSTANCE *inst, uint64_t group, uint64_t peer, uint8_t svc) { (void)inst; (void)group; (void)peer; (void)svc; return room; } void test_router_resume_receive(struct UTUN_INSTANCE *inst, uint64_t group, uint64_t peer, uint8_t svc) { (void)inst; (void)group; (void)peer; (void)svc; resumed++; } void test_router_on_send_ready(struct UTUN_INSTANCE *inst, uint64_t group, uint64_t peer, uint8_t svc, struct queue_waiter_handle *h, queue_threshold_callback_fn callback, void *arg) { (void)inst; (void)group; (void)peer; (void)svc; (void)h; ready_callback = callback; ready_arg = arg; } void test_router_cancel_send_ready(struct UTUN_INSTANCE *inst, uint64_t group, uint64_t peer, uint8_t svc, struct queue_waiter_handle *h) { (void)inst; (void)group; (void)peer; (void)svc; (void)h; ready_callback = NULL; ready_arg = NULL; } static struct call_video_transport *setup(struct call_session *s) { struct call_video_transport *v = u_calloc(1, sizeof(*v)); assert(v); v->session = s; s->video = v; v->receiver = call_video_receiver_create(); assert(v->receiver); v->have_peer = 1; v->local_revision = v->acknowledged_revision = 1; v->local.encode[0] = (struct call_video_limit){1280, 720, 30, 1280 * 720 * 30, 4000000, CALL_VIDEO_BASELINE, 31}; v->peer.decode[0] = v->local.encode[0]; v->audio_end_pts = 1000000; v->tx_count = 1; v->tx_bytes = FRAME_BYTES; struct video_tx_frame *head = &v->tx[0]; head->data = u_malloc(FRAME_BYTES); assert(head->data); for (unsigned i = 0; i < FRAME_BYTES; i++) head->data[i] = (uint8_t)(i * 17 + i / 257); head->started_tb = get_time_tb(); head->frame = (struct call_video_frame){.call_id = 7, .frame_id = 1, .pts_us = 1000000, .format = {1, 640, 480, 30, 0, CALL_VIDEO_H264, 31, 4000000, CALL_VIDEO_BASELINE}, .size = FRAME_BYTES}; packets = received = resumed = 0; room = 1; debt = 0; memset(output, 0, sizeof(output)); return v; } static void check_complete(struct call_video_transport *v) { assert(packets == 38 && received == FRAME_BYTES && !v->tx_count && !v->tx_bytes && !v->flush_pending); for (unsigned i = 0; i < FRAME_BYTES; i++) assert(output[i] == (uint8_t)(i * 17 + i / 257)); } static void cleanup(struct call_session *s) { call_video_transport_stop(s); assert(!s->video && resumed == 1); unsigned before = packets; uasync_drain_immediate(s->ctx->inst->ua); assert(packets == before); assert(s->ctx->inst->ua->timer_alloc_count == s->ctx->inst->ua->timer_free_count); } int main(void) { debug_set_level(DEBUG_LEVEL_WARN); struct UASYNC *ua = uasync_create(); assert(ua); struct UTUN_INSTANCE inst = {.ua = ua}; struct call_ctx ctx = {.inst = &inst}; struct call_session s = {.ctx = &ctx, .call_id = 7}; router.send_q = queue_new(ua, 0, 0, 0, "video_flush_test"); assert(router.send_q); struct call_video_transport *v = setup(&s); flush(v); assert(packets == 16 && v->flush_pending); process_immediate_queue(ua); assert(packets == 32 && v->flush_pending); process_immediate_queue(ua); check_complete(v); cleanup(&s); puts("PASS: 45001-byte frame completes in bounded batches without audio or periodic timers; bytes preserved"); for (unsigned gate = 0; gate < 2; gate++) { v = setup(&s); flush(v); if (gate) debt = 1000000; else room = 0; process_immediate_queue(ua); assert(packets == 16 && !v->flush_pending && v->flush_net_wait == 1); process_immediate_queue(ua); assert(packets == 16); room = 1; debt = 0; if (!gate) { assert(ready_callback && ready_arg == v); ready_callback(router.send_q, ready_arg); } else flush(v); uasync_drain_immediate(ua); check_complete(v); cleanup(&s); } puts("PASS: router room and inflight debt stop continuation without spinning; TX resumes exactly once"); v = setup(&s); v->audio_end_pts--; flush(v); assert(!packets && !v->flush_pending); call_video_audio_sent(&s, 1000000); uasync_drain_immediate(ua); check_complete(v); cleanup(&s); v = setup(&s); v->acknowledged_revision = 0; flush(v); assert(!packets && !v->flush_pending); v->acknowledged_revision = 1; flush(v); uasync_drain_immediate(ua); check_complete(v); cleanup(&s); puts("PASS: audio position and CAPS acknowledgement remain required"); v = setup(&s); flush(v); call_video_audio_sent(&s, 1020000); assert(packets == 32); process_immediate_queue(ua); check_complete(v); cleanup(&s); puts("PASS: audio flush supersedes pending continuation without duplicate fragments"); v = setup(&s); flush(v); cleanup(&s); assert(packets == 16); v = setup(&s); flush(v); discard_tx(v, "test_detach"); assert(!v->tx_count && !v->flush_pending && v->need_key); uasync_drain_immediate(ua); assert(packets == 16); cleanup(&s); puts("PASS: stop and discard cancel pending callbacks before freeing their owner/data"); queue_free(router.send_q); uasync_destroy(ua, 0); return 0; }