From 8f97cf8070197bfb6a83fb30763b2e4c0b4e1a0f Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 22 Oct 2026 19:45:45 +0200 Subject: [PATCH] Verify old relay readers survive a new download generation independently --- tests/test_media_blocks.c | 37 +++++++++++++++++++++++++++- tests/test_media_delivery_download.c | 15 +++++++---- 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/tests/test_media_blocks.c b/tests/test_media_blocks.c index 66bc9709..913d224d 100644 --- a/tests/test_media_blocks.c +++ b/tests/test_media_blocks.c @@ -389,6 +389,40 @@ static void test_relay_cancel(void) { puts("relay cancellation: exact attempt, idempotent request, sibling isolation and producer failure cleanup: PASS"); } +static void test_relay_generation(void) { + for (int cancel_new = 0; cancel_new < 2; cancel_new++) { + struct fixture f = ready(1); + struct media_download* dl = download(f.client); + struct media_block_source* old_source = dl->blocks[0].source; + char old_path[FILE_IO_PATH_MAX]; snprintf(old_path, sizeof(old_path), "%s", old_source->storage.temporary); + struct packet request = request_packet(dl, 0x300); receive(f.client, &request); + struct ETCP_ROUTER_CONN* slow = etcp_router_conn_get(f.client, GROUP, 0x300, ETCP_RT_ID_MEDIA_DELIVERY); + queue_set_threshold(slow->send_q, -1, 0); + struct packet old_chunk = chunk_packet(f.client, 0, 1), old_done = done_packet(f.client); + receive(f.client, &old_chunk); receive(f.client, &old_done); poll_ready(); + REQUIRE(!download(f.client) && old_source->refs == 1 && old_source->verified && !access(old_path, F_OK)); + start(f.client, &f.metadata); struct packet response = answer(f.server); receive(f.client, &response); + dl = download(f.client); struct media_block_source* new_source = dl->blocks[0].source; + REQUIRE(new_source != old_source && !old_source->indexed && new_source->indexed && new_source->refs == 1); + unchanged(f.client, &old_chunk); unchanged(f.client, &old_done); + if (cancel_new) REQUIRE(!media_download_cancel(f.client, dl->media_id, NULL, 0)); + capture_blocks = 1; queue_set_threshold(slow->send_q, 0, 0); poll_ready(); + REQUIRE(access(old_path, F_OK) && !f.client->md.streams && packet_count == 2); + REQUIRE(packets[0].data[0] == MEDIA_SUBCMD_BLOCK_CHUNK && packets[1].data[0] == MEDIA_SUBCMD_BLOCK_DONE); + REQUIRE(!memcmp(((struct media_pkt_block_chunk*)packets[0].data)->transfer_id, + ((struct media_pkt_block_req*)request.data)->transfer_id, 16)); + REQUIRE(((struct media_pkt_block_done*)packets[1].data)->status == MEDIA_BLOCK_OK); + if (!cancel_new) { + REQUIRE(download(f.client) == dl && dl->blocks[0].source == new_source && new_source->indexed && !new_source->available); + struct packet chunk = chunk_packet(f.client, 0, 1), done = done_packet(f.client); + receive(f.client, &chunk); receive(f.client, &done); + REQUIRE(done_count == 2 && !done_err && !download(f.client)); + } else REQUIRE(done_count == 2 && done_err == -2); + finish(&f); + } + puts("relay across restart: old verified file drains independently of a new producer's success or cancellation: PASS"); +} + static void test_destinations(void) { struct fixture f = ready(1); struct media_download* dl = download(f.client); char alias[1024]; snprintf(alias, sizeof(alias), "%s/./result.bin", directory); @@ -509,7 +543,8 @@ int main(void) { ua = uasync_create(); REQUIRE(ua); 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_growing_relay(); test_relay_cancel(); test_relay_generation(); 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; diff --git a/tests/test_media_delivery_download.c b/tests/test_media_delivery_download.c index 5b08e764..95fa8949 100644 --- a/tests/test_media_delivery_download.c +++ b/tests/test_media_delivery_download.c @@ -94,7 +94,15 @@ static struct UTUN_INSTANCE* make_minimal_instance(sqlite3* db) { inst->node_id = 0xDEADBEEFDEADBEEFULL; inst->topo_sqlite_db = db; for (int i = 0; i < 32; i++) inst->my_ed25519_privkey[i] = (uint8_t)(rand() & 0xFF); - memset(inst->my_ed25519_pubkey, 0xDD, 32); + EVP_PKEY* author = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, inst->my_ed25519_privkey, 32); + size_t key_size = 32; + if (!author || EVP_PKEY_get_raw_public_key(author, inst->my_ed25519_pubkey, &key_size) != 1) exit(1); + EVP_PKEY_free(author); + if (sqlite3_exec(db, "CREATE TABLE nodes(node_id INTEGER PRIMARY KEY,ed25519_pubkey BLOB)", NULL, NULL, NULL)) exit(1); + sqlite3_stmt* row = NULL; + if (sqlite3_prepare_v2(db, "INSERT INTO nodes VALUES(0xAAA,?)", -1, &row, NULL) != SQLITE_OK + || sqlite3_bind_blob(row, 1, inst->my_ed25519_pubkey, 32, SQLITE_STATIC) != SQLITE_OK + || sqlite3_step(row) != SQLITE_DONE || sqlite3_finalize(row) != SQLITE_OK) exit(1); inst->md.inst = inst; inst->md.db = db; @@ -129,10 +137,7 @@ static struct media_download* make_test_dl(sqlite3* db, struct UTUN_INSTANCE* in dl->active = 1; dl->inst = inst; dl->author_node_id = 0xAAAA000000000001ULL; - EVP_PKEY* author = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, inst->my_ed25519_privkey, 32); - size_t key_size = 32; - if (!author || EVP_PKEY_get_raw_public_key(author, dl->author_pubkey, &key_size) != 1) exit(1); - EVP_PKEY_free(author); + memcpy(dl->author_pubkey, inst->my_ed25519_pubkey, 32); struct ll_entry* qe = queue_entry_new(sizeof(*dl)); if (!qe) { u_free(dl->blocks); u_free(dl->block_sigs); u_free(dl); return NULL; }