From fabeb770a77bebc73ad88473f9ec13ba469f0cbe Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 22 Oct 2026 19:37:28 +0200 Subject: [PATCH] Verify media source admission, redirect bounds and transmit I/O failure cleanup --- tests/test_media_blocks.c | 69 ++++++++++++++++++++++++++++++++++----- 1 file changed, 60 insertions(+), 9 deletions(-) diff --git a/tests/test_media_blocks.c b/tests/test_media_blocks.c index d989cef5..5140569c 100644 --- a/tests/test_media_blocks.c +++ b/tests/test_media_blocks.c @@ -122,8 +122,7 @@ static void test_identity_and_bounds(void) { for (size_t length = 0; length < done.len; length++) { struct packet bad = done; bad.len = length; unchanged(f.client, &bad); } - for (int variant = 0; variant < 12; variant++) { - if (variant < 6) { + for (int variant = 0; variant < 6; variant++) { struct packet bad = done; struct media_pkt_block_done* d = (struct media_pkt_block_done*)bad.data; switch (variant) { @@ -135,14 +134,24 @@ static void test_identity_and_bounds(void) { case 5: d->chunk++; break; } unchanged(f.client, &bad); - } else { - struct packet bad = {.from = good.from, .group = GROUP, .len = sizeof(struct media_pkt_block_overloaded)}; - struct media_pkt_block_overloaded* ov = (struct media_pkt_block_overloaded*)bad.data; - ov->subcmd = MEDIA_SUBCMD_BLOCK_OVERLOADED; memcpy(ov->media_id, f.metadata.media_id, 16); - memcpy(ov->block_id, f.metadata.block_ids, 16); memset(ov->transfer_id, 0xFF, 16); - unchanged(f.client, &bad); - } } + struct packet stale = {.from = good.from, .group = GROUP, .len = sizeof(struct media_pkt_block_overloaded)}; + struct media_pkt_block_overloaded* ov = (struct media_pkt_block_overloaded*)stale.data; + ov->subcmd = MEDIA_SUBCMD_BLOCK_OVERLOADED; memcpy(ov->media_id, f.metadata.media_id, 16); + memcpy(ov->block_id, f.metadata.block_ids, 16); memset(ov->transfer_id, 0xFF, 16); unchanged(f.client, &stale); + struct packet redirect = {.from = good.from, .group = GROUP, .len = MEDIA_RELAY_FULL_HDR_SIZE + 8}; + struct media_pkt_block_relay_full* full = (struct media_pkt_block_relay_full*)redirect.data; + full->subcmd = MEDIA_SUBCMD_BLOCK_RELAY_FULL; full->num_relay_nodes = 1; + memcpy(full->media_id, f.metadata.media_id, 16); memcpy(full->block_id, f.metadata.block_ids, 16); + memcpy(full->transfer_id, download(f.client)->blocks[0].transfer_id, 16); + uint64_t node = 0x300; memcpy(redirect.data + sizeof(*full), &node, 8); + for (size_t length = 0; length < redirect.len; length++) { + struct packet bad = redirect; bad.len = length; unchanged(f.client, &bad); + } + stale = redirect; stale.len++; unchanged(f.client, &stale); + stale = redirect; ((struct media_pkt_block_relay_full*)stale.data)->num_relay_nodes = UINT16_MAX; + unchanged(f.client, &stale); + stale = redirect; ((struct media_pkt_block_relay_full*)stale.data)->transfer_id[0]++; unchanged(f.client, &stale); finish(&f); REQUIRE(done_count == 1 && done_err == -2); puts("block identity, exact input bounds and duplicate without progress: PASS"); } @@ -436,6 +445,47 @@ static void test_relay_limit_and_idle(void) { puts("relay admission permits five readers, echoes redirect attempt and releases idle readers: PASS"); } +static void test_indexed_source(void) { + for (int variant = 0; variant < 4; variant++) { + struct fixture f = ready(4); + struct media_download* dl = download(f.client); + struct packet requests[4]; + for (int i = 0; i < 4; i++) requests[i] = request_packet(dl, 0x300 + i); + struct packet chunk = chunk_packet(f.client, 0, 4), done = done_packet(f.client); + receive(f.client, &chunk); receive(f.client, &done); REQUIRE(done_count == 1 && !done_err); + capture_blocks = 1; + receive(f.client, &requests[0]); REQUIRE(f.client->md.active_streams == 1); + if (variant) { + const enum io_fault operations[] = {IO_NONE, IO_READ, IO_SEEK, IO_CLOSE}; + fault = operations[variant]; poll_ready(); REQUIRE(fault_hits == 1 && !f.client->md.active_streams); + REQUIRE(!f.client->md.streams && !f.client->md.file_loads->head && packet_count == 1); + REQUIRE(packets[0].data[0] == MEDIA_SUBCMD_BLOCK_DONE + && ((struct media_pkt_block_done*)packets[0].data)->status == MEDIA_BLOCK_UNAVAILABLE); + } else { + receive(f.client, &requests[0]); REQUIRE(f.client->md.active_streams == 1); + receive(f.client, &requests[1]); receive(f.client, &requests[2]); receive(f.client, &requests[3]); + REQUIRE(f.client->md.active_streams == 3 && packet_count == 1 && packets[0].data[0] == MEDIA_SUBCMD_BLOCK_RELAY_FULL); + const struct media_pkt_block_relay_full* full = (const struct media_pkt_block_relay_full*)packets[0].data; + REQUIRE(full->num_relay_nodes == 3 && !memcmp(full->transfer_id, + ((struct media_pkt_block_req*)requests[3].data)->transfer_id, 16)); + struct packet cancel = cancellation(&requests[2]); receive(f.client, &cancel); + struct md_file_load* load = md_file_load_find(&f.client->md, f.metadata.media_id); + REQUIRE(f.client->md.active_streams == 2 && load->active_downloads == 2 + && load->downloader_ids[0] == 0x300 && load->downloader_ids[1] == 0x301); + cancel = cancellation(&requests[0]); receive(f.client, &cancel); + REQUIRE(load->active_downloads == 1 && load->downloader_ids[0] == 0x301); + receive(f.client, &requests[3]); REQUIRE(f.client->md.active_streams == 2); + media_delivery_cancel_channel(f.client, GROUP); + REQUIRE(!f.client->md.streams && !f.client->md.active_streams && !f.client->md.file_loads->head); + poll_ready(); REQUIRE(packet_count == 1); // Отменённые pump не отправили DATA/DONE. + } + char path[512]; snprintf(path, sizeof(path), "%s/result.bin", directory); + uint8_t hash[32]; REQUIRE(!ma_sha256_file(path, hash) && !memcmp(hash, f.metadata.content_hash, 32)); + finish(&f); + } + puts("indexed source: idempotency, source admission/redirect, exact cancellation counters and TX I/O failures: PASS"); +} + int main(void) { debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR); if (getenv("UTUN_TEST_DEBUG")) debug_set_category_level(DEBUG_CATEGORY_MEDIA, DEBUG_LEVEL_DEBUG); @@ -450,6 +500,7 @@ int main(void) { snprintf(directory, sizeof(directory), "/tmp/utun-blocks-XXXXXX"); REQUIRE(!test_mkdtemp(directory)); test_identity_and_bounds(); test_rejected_attempts(); test_io_failures(); test_reentrant_cancel(); test_growing_relay(); test_relay_cancel(); test_destinations(); test_registration_failure_and_keys(); test_relay_limit_and_idle(); + test_indexed_source(); REQUIRE(!rmdir(directory)); uasync_destroy(ua, 0); puts("media block regressions: PASS"); return 0; }