Exercise media block identity, I/O failure, reentrancy and relay backpressure
This commit is contained in:
1 parent
090e7db598
commit
b09a020855
2 files changed
+411
No files matched your search
@@ -901,6 +901,13 @@ test_media_query_CFLAGS = $(test_media_delivery_download_CFLAGS)
|
||||
test_media_query_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
test_media_query_LDFLAGS = -Wl,--wrap=etcp_route_send -Wl,--wrap=RAND_bytes
|
||||
|
||||
check_PROGRAMS += test_media_blocks
|
||||
test_media_blocks_SOURCES = test_media_blocks.c media_delivery_fixture.h
|
||||
test_media_blocks_CFLAGS = $(test_media_delivery_download_CFLAGS)
|
||||
test_media_blocks_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
test_media_blocks_LDFLAGS = -Wl,--wrap=etcp_route_send -Wl,--wrap=RAND_bytes -Wl,--wrap=fwrite -Wl,--wrap=fread \
|
||||
-Wl,--wrap=fseeko -Wl,--wrap=fclose -Wl,--wrap=fflush -Wl,--wrap=rename -Wl,--wrap=ma_sha256_file
|
||||
|
||||
test_media_delivery_integration_SOURCES = test_media_delivery_integration.c
|
||||
test_media_delivery_integration_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/src/media_delivery -I$(top_srcdir)/src/media_async -I$(top_srcdir)/lib -I$(top_srcdir)/src/transport_layer -I$(top_srcdir)/src/routing_layer -I$(top_srcdir)/src/chat
|
||||
test_media_delivery_integration_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
@@ -0,0 +1,404 @@
|
||||
/* Приём и relay: реальные подписи/файлы/SQLite/очереди router, ошибки I/O и повторный вход из callbacks. */
|
||||
#include "media_delivery_fixture.h"
|
||||
#include "../src/media_async/media_async.h"
|
||||
#include <errno.h>
|
||||
|
||||
enum io_fault { IO_NONE, IO_WRITE, IO_READ, IO_SEEK, IO_CLOSE, IO_FLUSH, IO_RENAME, IO_HASH };
|
||||
static enum io_fault fault;
|
||||
static int fault_skip, fault_hits;
|
||||
|
||||
static int fail_io(enum io_fault operation) {
|
||||
if (fault != operation) return 0;
|
||||
if (fault_skip > 0) { fault_skip--; return 0; }
|
||||
fault = IO_NONE; fault_hits++; errno = ENOSPC; return 1;
|
||||
}
|
||||
|
||||
size_t __real_fwrite(const void*, size_t, size_t, FILE*);
|
||||
size_t __wrap_fwrite(const void* bytes, size_t size, size_t count, FILE* file) {
|
||||
if (file != stdout && file != stderr && fail_io(IO_WRITE)) return 0;
|
||||
return __real_fwrite(bytes, size, count, file);
|
||||
}
|
||||
size_t __real_fread(void*, size_t, size_t, FILE*);
|
||||
size_t __wrap_fread(void* bytes, size_t size, size_t count, FILE* file) {
|
||||
if (fail_io(IO_READ)) return 0;
|
||||
return __real_fread(bytes, size, count, file);
|
||||
}
|
||||
int __real_fseeko(FILE*, off_t, int);
|
||||
int __wrap_fseeko(FILE* file, off_t offset, int origin) {
|
||||
return fail_io(IO_SEEK) ? -1 : __real_fseeko(file, offset, origin);
|
||||
}
|
||||
int __real_fclose(FILE*);
|
||||
int __wrap_fclose(FILE* file) {
|
||||
int failed = fail_io(IO_CLOSE), rc = __real_fclose(file);
|
||||
return failed ? EOF : rc; // Дескриптор закрывается и при моделируемой ошибке.
|
||||
}
|
||||
int __real_fflush(FILE*);
|
||||
int __wrap_fflush(FILE* file) { return fail_io(IO_FLUSH) ? EOF : __real_fflush(file); }
|
||||
int __real_rename(const char*, const char*);
|
||||
int __wrap_rename(const char* source, const char* destination) {
|
||||
return fail_io(IO_RENAME) ? -1 : __real_rename(source, destination);
|
||||
}
|
||||
int __real_ma_sha256_file(const char*, uint8_t[32]);
|
||||
int __wrap_ma_sha256_file(const char* path, uint8_t hash[32]) {
|
||||
return fail_io(IO_HASH) ? -1 : __real_ma_sha256_file(path, hash);
|
||||
}
|
||||
|
||||
struct fixture { struct UTUN_INSTANCE *server, *client; struct media_index_result metadata; };
|
||||
|
||||
static struct fixture ready(unsigned size) {
|
||||
reset(); fault = IO_NONE; fault_skip = fault_hits = 0;
|
||||
struct fixture f = {.server = instance(SERVER), .client = instance(CLIENT), .metadata = result(1)};
|
||||
f.metadata.file_size = f.metadata.block_size = size;
|
||||
uint8_t* bytes = u_malloc(size); REQUIRE(bytes);
|
||||
for (unsigned i = 0; i < size; i++) bytes[i] = (uint8_t)i;
|
||||
unsigned length;
|
||||
REQUIRE(sc_ed25519_sign(author_key, bytes, size, f.metadata.block_sigs) == SC_OK);
|
||||
REQUIRE(EVP_Digest(bytes, size, f.metadata.content_hash, &length, EVP_sha256(), NULL) == 1 && length == 32);
|
||||
u_free(bytes);
|
||||
seed(f.server, &f.metadata, 1, 2, 0); start(f.client, &f.metadata);
|
||||
struct packet response = answer(f.server); receive(f.client, &response);
|
||||
REQUIRE(download(f.client) && download(f.client)->blocks[0].state == MD_BLK_REQ && !packet_count);
|
||||
return f;
|
||||
}
|
||||
|
||||
static void finish(struct fixture* f) {
|
||||
fault = IO_NONE; sent_hook = NULL; capture_blocks = 0;
|
||||
destroy(f->client); destroy(f->server); media_index_result_free(&f->metadata);
|
||||
char path[512]; snprintf(path, sizeof(path), "%s/result.bin", directory); remove(path);
|
||||
}
|
||||
|
||||
static struct packet chunk_packet(struct UTUN_INSTANCE* inst, uint32_t offset, uint16_t length) {
|
||||
struct media_download* dl = download(inst); REQUIRE(dl);
|
||||
struct media_download_block* b = &dl->blocks[0];
|
||||
struct packet p = {.from = b->holders[b->holder_idx], .to = inst->node_id,
|
||||
.group = dl->group_id, .len = MEDIA_BLOCK_CHUNK_HDR_SIZE + length};
|
||||
REQUIRE(p.len <= sizeof(p.data));
|
||||
struct media_pkt_block_chunk* ch = (struct media_pkt_block_chunk*)p.data;
|
||||
ch->subcmd = MEDIA_SUBCMD_BLOCK_CHUNK; ch->offset = offset; ch->data_len = length;
|
||||
memcpy(ch->media_id, dl->media_id, 16); memcpy(ch->block_id, b->block_id, 16); memcpy(ch->transfer_id, b->transfer_id, 16);
|
||||
for (unsigned i = 0; i < length; i++) p.data[sizeof(*ch) + i] = (uint8_t)(offset + i);
|
||||
return p;
|
||||
}
|
||||
|
||||
static struct packet done_packet(struct UTUN_INSTANCE* inst) {
|
||||
struct media_download* dl = download(inst); REQUIRE(dl);
|
||||
struct media_download_block* b = &dl->blocks[0];
|
||||
struct packet p = {.from = b->holders[b->holder_idx], .to = inst->node_id, .group = dl->group_id, .len = MEDIA_BLOCK_DONE_SIZE};
|
||||
struct media_pkt_block_done* done = (struct media_pkt_block_done*)p.data;
|
||||
done->subcmd = MEDIA_SUBCMD_BLOCK_DONE; done->total_size = b->expected_size;
|
||||
memcpy(done->media_id, dl->media_id, 16); memcpy(done->block_id, b->block_id, 16); memcpy(done->transfer_id, b->transfer_id, 16);
|
||||
return p;
|
||||
}
|
||||
|
||||
static void unchanged(struct UTUN_INSTANCE* inst, const struct packet* packet) {
|
||||
struct media_download* dl = download(inst), snapshot = *dl;
|
||||
struct media_download_block block = dl->blocks[0];
|
||||
receive(inst, packet);
|
||||
REQUIRE(download(inst) == dl && !memcmp(&snapshot, dl, sizeof(snapshot)) && !memcmp(&block, dl->blocks, sizeof(block)));
|
||||
}
|
||||
|
||||
static void test_identity_and_bounds(void) {
|
||||
struct fixture f = ready(4);
|
||||
struct packet good = chunk_packet(f.client, 0, 2);
|
||||
for (int variant = 0; variant < 6; variant++) {
|
||||
struct packet bad = good;
|
||||
struct media_pkt_block_chunk* ch = (struct media_pkt_block_chunk*)bad.data;
|
||||
switch (variant) {
|
||||
case 0: bad.group++; break;
|
||||
case 1: bad.from++; break;
|
||||
case 2: ch->media_id[0]++; break;
|
||||
case 3: ch->block_id[0]++; break;
|
||||
case 4: ch->transfer_id[0]++; break;
|
||||
case 5: ch->chunk = UINT32_MAX; break;
|
||||
}
|
||||
unchanged(f.client, &bad);
|
||||
}
|
||||
for (size_t length = 0; length < MEDIA_BLOCK_CHUNK_HDR_SIZE; length++) {
|
||||
struct packet bad = good; bad.len = length; unchanged(f.client, &bad);
|
||||
}
|
||||
receive(f.client, &good); unchanged(f.client, &good);
|
||||
REQUIRE(download(f.client)->blocks[0].source->available == 2);
|
||||
struct packet done = done_packet(f.client);
|
||||
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) {
|
||||
struct packet bad = done;
|
||||
struct media_pkt_block_done* d = (struct media_pkt_block_done*)bad.data;
|
||||
switch (variant) {
|
||||
case 0: bad.group++; break;
|
||||
case 1: bad.from++; break;
|
||||
case 2: d->media_id[0]++; break;
|
||||
case 3: d->block_id[0]++; break;
|
||||
case 4: d->transfer_id[0]++; break;
|
||||
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);
|
||||
}
|
||||
}
|
||||
finish(&f); REQUIRE(done_count == 1 && done_err == -2);
|
||||
puts("block identity, exact input bounds and duplicate without progress: PASS");
|
||||
}
|
||||
|
||||
static void test_rejected_attempts(void) {
|
||||
for (int variant = 0; variant < 12; variant++) {
|
||||
struct fixture f = ready(4);
|
||||
struct media_download* dl = download(f.client);
|
||||
char previous[FILE_IO_PATH_MAX]; snprintf(previous, sizeof(previous), "%s", dl->blocks[0].source->storage.temporary);
|
||||
struct packet old_chunk = chunk_packet(f.client, 0, 2), old_done = done_packet(f.client);
|
||||
struct packet bad = chunk_packet(f.client, 0, 1);
|
||||
struct media_pkt_block_chunk* ch = (struct media_pkt_block_chunk*)bad.data;
|
||||
switch (variant) {
|
||||
case 0: bad.len--; break;
|
||||
case 1: bad.len++; break;
|
||||
case 2: ch->data_len = 0; bad.len = sizeof(*ch); break;
|
||||
case 3: ch->data_len = MEDIA_BLOCK_DATA_MAX + 1; bad.len = sizeof(*ch) + ch->data_len; break;
|
||||
case 4: ch->offset = 4; break;
|
||||
case 5: ch->offset = UINT32_MAX; break;
|
||||
case 6: ch->offset = 1; break;
|
||||
case 7: receive(f.client, &old_chunk); bad = chunk_packet(f.client, 1, 2); break;
|
||||
default: {
|
||||
if (variant != 8) {
|
||||
struct packet full = chunk_packet(f.client, 0, 4);
|
||||
if (variant == 11) full.data[MEDIA_BLOCK_CHUNK_HDR_SIZE] ^= 1;
|
||||
receive(f.client, &full);
|
||||
}
|
||||
bad = done_packet(f.client);
|
||||
struct media_pkt_block_done* d = (struct media_pkt_block_done*)bad.data;
|
||||
if (variant == 9) d->total_size--;
|
||||
if (variant == 10) d->status = 17;
|
||||
}
|
||||
}
|
||||
receive(f.client, &bad);
|
||||
dl = download(f.client); REQUIRE(dl && !done_count);
|
||||
struct media_download_block* b = &dl->blocks[0];
|
||||
REQUIRE(b->attempts == 1 && b->holder_idx == 1 && b->state == MD_BLK_REQ && !b->bytes_received);
|
||||
REQUIRE(strcmp(previous, b->source->storage.temporary) && access(previous, F_OK));
|
||||
unchanged(f.client, &old_chunk); unchanged(f.client, &old_done);
|
||||
old_chunk.from = b->holders[b->holder_idx]; unchanged(f.client, &old_chunk); // Старый UUID от текущего узла также запрещён.
|
||||
finish(&f);
|
||||
}
|
||||
puts("malformed payload/ranges/DONE/signature fail over to an isolated attempt; late packets ignored: PASS");
|
||||
}
|
||||
|
||||
static void old_destination(struct UTUN_INSTANCE* inst) {
|
||||
FILE* file = fopen(download(inst)->dest_path, "wb");
|
||||
REQUIRE(file && fwrite("old", 1, 3, file) == 3 && !fclose(file));
|
||||
}
|
||||
|
||||
static void require_old_destination(void) {
|
||||
char path[512]; snprintf(path, sizeof(path), "%s/result.bin", directory);
|
||||
FILE* file = fopen(path, "rb"); REQUIRE(file);
|
||||
REQUIRE(fgetc(file) == 'o' && fgetc(file) == 'l' && fgetc(file) == 'd' && fgetc(file) == EOF && !fclose(file));
|
||||
}
|
||||
|
||||
static void test_io_failures(void) {
|
||||
for (int variant = 0; variant < 11; variant++) {
|
||||
struct fixture f = ready(4); old_destination(f.client);
|
||||
struct packet chunk = chunk_packet(f.client, 0, 4);
|
||||
struct packet done = done_packet(f.client);
|
||||
if (variant < 2) {
|
||||
fault = variant ? IO_SEEK : IO_WRITE; receive(f.client, &chunk);
|
||||
} else {
|
||||
receive(f.client, &chunk);
|
||||
const enum io_fault operations[] = {IO_READ, IO_CLOSE, IO_WRITE, IO_READ, IO_CLOSE, IO_CLOSE, IO_FLUSH, IO_RENAME, IO_HASH};
|
||||
fault = operations[variant - 2];
|
||||
if (variant == 5 || variant == 6) fault_skip = 1;
|
||||
if (variant == 7) fault_skip = 2;
|
||||
receive(f.client, &done);
|
||||
}
|
||||
REQUIRE(fault_hits == 1 && !download(f.client) && done_count == 1 && done_err == -1);
|
||||
REQUIRE(!f.client->md.destinations && !f.client->md.block_sources->head);
|
||||
require_old_destination(); finish(&f);
|
||||
}
|
||||
for (int zero = 0; zero < 2; zero++) {
|
||||
struct fixture f = ready(4); old_destination(f.client);
|
||||
memset(download(f.client)->content_hash, zero ? 0 : 0x77, 32);
|
||||
struct packet chunk = chunk_packet(f.client, 0, 4); receive(f.client, &chunk);
|
||||
struct packet done = done_packet(f.client); receive(f.client, &done);
|
||||
REQUIRE(!download(f.client) && done_count == 1 && done_err == -1);
|
||||
require_old_destination(); finish(&f);
|
||||
}
|
||||
puts("seek/read/write/close/flush/hash/rename failures terminate once and preserve destination; zero hash checked: PASS");
|
||||
}
|
||||
|
||||
struct cancel_context { struct fixture* fixture; int inside, callbacks, restart; };
|
||||
static void cancelled(void* arg, int error) {
|
||||
struct cancel_context* ctx = arg;
|
||||
REQUIRE(!ctx->inside && error == -2 && !ctx->callbacks++);
|
||||
completed(NULL, error);
|
||||
}
|
||||
static void cancel_progress(void* arg, int done, int total) {
|
||||
struct cancel_context* ctx = arg;
|
||||
struct UTUN_INSTANCE* inst = ctx->fixture->client;
|
||||
ctx->inside = 1; REQUIRE(done == 1 && total == 1);
|
||||
REQUIRE(!media_download_cancel(inst, ctx->fixture->metadata.media_id, NULL, 0));
|
||||
REQUIRE(!download(inst) && !ctx->callbacks && !done_count);
|
||||
REQUIRE(media_download_cancel(inst, ctx->fixture->metadata.media_id, NULL, 0) == -1);
|
||||
if (ctx->restart) start(inst, &ctx->fixture->metadata);
|
||||
ctx->inside = 0;
|
||||
}
|
||||
|
||||
static void test_reentrant_cancel(void) {
|
||||
for (int restart = 0; restart < 2; restart++) {
|
||||
struct fixture f = ready(1); old_destination(f.client);
|
||||
struct cancel_context ctx = {.fixture = &f, .restart = restart};
|
||||
struct media_download* dl = download(f.client);
|
||||
dl->progress_cb = cancel_progress; dl->progress_arg = &ctx; dl->done_cb = cancelled; dl->done_arg = &ctx;
|
||||
struct packet chunk = chunk_packet(f.client, 0, 1), done = done_packet(f.client);
|
||||
receive(f.client, &chunk); receive(f.client, &done);
|
||||
REQUIRE(ctx.callbacks == 1 && done_count == 1 && done_err == -2);
|
||||
require_old_destination();
|
||||
if (restart) {
|
||||
unchanged(f.client, &chunk); unchanged(f.client, &done);
|
||||
struct packet response = answer(f.server); receive(f.client, &response);
|
||||
unchanged(f.client, &chunk); unchanged(f.client, &done);
|
||||
deliver_block(f.client, download(f.client), 0);
|
||||
REQUIRE(done_count == 2 && !done_err && !download(f.client));
|
||||
}
|
||||
finish(&f);
|
||||
}
|
||||
puts("cancel/repeated cancel/restart at final progress: callback ownership, old packets and reservation release: PASS");
|
||||
}
|
||||
|
||||
static struct packet request_packet(struct media_download* dl, uint64_t peer) {
|
||||
struct packet p = {.from = peer, .group = dl->group_id, .len = MEDIA_BLOCK_REQ_SIZE};
|
||||
struct media_pkt_block_req* req = (struct media_pkt_block_req*)p.data;
|
||||
req->subcmd = MEDIA_SUBCMD_BLOCK_REQ;
|
||||
memcpy(req->media_id, dl->media_id, 16); memcpy(req->block_id, dl->blocks[0].block_id, 16);
|
||||
REQUIRE(!media_index_generate_uuid(req->transfer_id)); return p;
|
||||
}
|
||||
|
||||
static struct packet cancellation(const struct packet* request) {
|
||||
const struct media_pkt_block_req* req = (const struct media_pkt_block_req*)request->data;
|
||||
struct packet p = {.from = request->from, .group = request->group, .len = sizeof(struct media_pkt_cancel)};
|
||||
struct media_pkt_cancel* cancel = (struct media_pkt_cancel*)p.data;
|
||||
cancel->subcmd = MEDIA_SUBCMD_CANCEL; cancel->chunk = req->chunk;
|
||||
memcpy(cancel->media_id, req->media_id, 16); memcpy(cancel->block_id, req->block_id, 16);
|
||||
memcpy(cancel->transfer_id, req->transfer_id, 16); return p;
|
||||
}
|
||||
|
||||
static void occupy_slow_queue(struct UTUN_INSTANCE* inst, uint64_t group, uint64_t peer, int cmd) {
|
||||
if (peer != 0x300 || cmd != MEDIA_SUBCMD_BLOCK_CHUNK) return;
|
||||
struct ETCP_ROUTER_CONN* conn = etcp_router_conn_get(inst, group, peer, ETCP_RT_ID_MEDIA_DELIVERY); REQUIRE(conn);
|
||||
struct ll_entry* entry = queue_entry_new(0); REQUIRE(entry);
|
||||
REQUIRE(!queue_data_put(conn->send_q, entry));
|
||||
}
|
||||
|
||||
static void poll_ready(void) { for (int i = 0; i < 12; i++) uasync_poll(ua, 0); }
|
||||
|
||||
static void check_relay_packets(uint32_t bytes[2], int done[2]) {
|
||||
while (packet_count) {
|
||||
struct packet p = take_packet();
|
||||
if (p.to != 0x300 && p.to != 0x400) continue;
|
||||
int peer = p.to == 0x300 ? 0 : 1;
|
||||
if (p.data[0] == MEDIA_SUBCMD_BLOCK_CHUNK) {
|
||||
const struct media_pkt_block_chunk* ch = (const struct media_pkt_block_chunk*)p.data;
|
||||
REQUIRE(!done[peer] && ch->offset == bytes[peer] && p.len == sizeof(*ch) + ch->data_len);
|
||||
for (unsigned i = 0; i < ch->data_len; i++) REQUIRE(p.data[sizeof(*ch) + i] == (uint8_t)(bytes[peer] + i));
|
||||
bytes[peer] += ch->data_len;
|
||||
} else if (p.data[0] == MEDIA_SUBCMD_BLOCK_DONE) {
|
||||
const struct media_pkt_block_done* d = (const struct media_pkt_block_done*)p.data;
|
||||
REQUIRE(!done[peer]++ && d->status == MEDIA_BLOCK_OK && d->total_size == 3000 && bytes[peer] == 3000);
|
||||
} else REQUIRE(0);
|
||||
}
|
||||
}
|
||||
|
||||
static void test_growing_relay(void) {
|
||||
struct fixture f = ready(3000); capture_blocks = 1; sent_hook = occupy_slow_queue;
|
||||
struct media_download* dl = download(f.client);
|
||||
struct media_block_source* source = dl->blocks[0].source;
|
||||
char temporary[FILE_IO_PATH_MAX]; snprintf(temporary, sizeof(temporary), "%s", source->storage.temporary);
|
||||
struct packet requests[2] = {request_packet(dl, 0x300), request_packet(dl, 0x400)};
|
||||
receive(f.client, &requests[0]); receive(f.client, &requests[1]);
|
||||
REQUIRE(source->refs == 3); poll_ready(); REQUIRE(!packet_count);
|
||||
struct ETCP_ROUTER_CONN* slow = etcp_router_conn_get(f.client, GROUP, 0x300, ETCP_RT_ID_MEDIA_DELIVERY);
|
||||
queue_set_threshold(slow->send_q, 0, 0);
|
||||
struct packet first = chunk_packet(f.client, 0, 1024); receive(f.client, &first);
|
||||
struct packet second = chunk_packet(f.client, 1024, 1024); receive(f.client, &second);
|
||||
poll_ready(); uint32_t bytes[2] = {0}; int done[2] = {0}; check_relay_packets(bytes, done);
|
||||
REQUIRE(bytes[0] == 1024 && bytes[1] == 2048 && !done[0] && !done[1]);
|
||||
unchanged(f.client, &first); poll_ready(); REQUIRE(!packet_count && source->available == 2048);
|
||||
struct packet tail = chunk_packet(f.client, 2048, 952); receive(f.client, &tail);
|
||||
struct packet end = done_packet(f.client); receive(f.client, &end);
|
||||
REQUIRE(!download(f.client) && done_count == 1 && !done_err && !access(temporary, F_OK));
|
||||
poll_ready(); check_relay_packets(bytes, done); REQUIRE(done[1] == 1 && !done[0]);
|
||||
for (int i = 0; i < 2; i++) {
|
||||
struct ll_entry* entry = queue_data_get(slow->send_q); REQUIRE(entry); queue_entry_free(entry);
|
||||
poll_ready(); check_relay_packets(bytes, done); REQUIRE(!done[0] && !access(temporary, F_OK));
|
||||
}
|
||||
REQUIRE(bytes[0] == 3000);
|
||||
struct ll_entry* entry = queue_data_get(slow->send_q); REQUIRE(entry); queue_entry_free(entry);
|
||||
poll_ready(); check_relay_packets(bytes, done);
|
||||
REQUIRE(done[0] == 1 && access(temporary, F_OK) && !f.client->md.streams && !f.client->md.block_sources->head);
|
||||
finish(&f);
|
||||
puts("growing relay: independent offsets, real queue backpressure, duplicate, retained temp, DATA tail before DONE: PASS");
|
||||
}
|
||||
|
||||
static void test_relay_cancel(void) {
|
||||
struct fixture f = ready(1); capture_blocks = 1;
|
||||
struct media_download* dl = download(f.client); struct media_block_source* source = dl->blocks[0].source;
|
||||
char temporary[FILE_IO_PATH_MAX]; snprintf(temporary, sizeof(temporary), "%s", source->storage.temporary);
|
||||
struct packet a = request_packet(dl, 0x300), b = request_packet(dl, 0x400), cancel = cancellation(&a);
|
||||
receive(f.client, &a); receive(f.client, &b); REQUIRE(source->refs == 3);
|
||||
receive(f.client, &a); REQUIRE(source->refs == 3); // Повтор запроса не создаёт новую выдачу.
|
||||
for (size_t length = 0; length < cancel.len; length++) {
|
||||
struct packet bad = cancel; bad.len = length; receive(f.client, &bad); REQUIRE(source->refs == 3);
|
||||
}
|
||||
struct packet bad = cancel; ((struct media_pkt_cancel*)bad.data)->transfer_id[0]++;
|
||||
receive(f.client, &bad); REQUIRE(source->refs == 3);
|
||||
bad = cancel; bad.group++; receive(f.client, &bad); REQUIRE(source->refs == 3);
|
||||
bad = cancel; bad.from++; receive(f.client, &bad); REQUIRE(source->refs == 3);
|
||||
receive(f.client, &cancel); REQUIRE(source->refs == 2);
|
||||
REQUIRE(!media_download_cancel(f.client, dl->media_id, NULL, 0)); REQUIRE(!access(temporary, F_OK));
|
||||
poll_ready(); int errors = 0;
|
||||
while (packet_count) {
|
||||
struct packet p = take_packet();
|
||||
if (p.to != 0x400) continue;
|
||||
REQUIRE(p.data[0] == MEDIA_SUBCMD_BLOCK_DONE && ((struct media_pkt_block_done*)p.data)->status == MEDIA_BLOCK_UNAVAILABLE);
|
||||
errors++;
|
||||
}
|
||||
REQUIRE(errors == 1 && access(temporary, F_OK) && !f.client->md.streams && done_count == 1 && done_err == -2);
|
||||
finish(&f);
|
||||
puts("relay cancellation: exact attempt, idempotent request, sibling isolation and producer failure cleanup: 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);
|
||||
REQUIRE(!media_delivery_reserve(f.client, alias));
|
||||
struct media_index_result other = result(1); other.media_id[0]++;
|
||||
REQUIRE(media_download_start(f.client, GROUP, &other, alias, directory, SERVER, completed, NULL, NULL, NULL) == -1);
|
||||
REQUIRE(!done_count && download(f.client) == dl);
|
||||
REQUIRE(!media_download_cancel(f.client, dl->media_id, NULL, 0));
|
||||
struct media_destination* writer = media_delivery_reserve(f.client, alias); REQUIRE(writer);
|
||||
REQUIRE(media_download_start(f.client, GROUP, &other, alias, directory, SERVER, completed, NULL, NULL, NULL) == -1);
|
||||
media_delivery_release(f.client, writer); start(f.client, &other);
|
||||
REQUIRE(download(f.client)); media_index_result_free(&other); finish(&f);
|
||||
puts("normalized destination reservation is shared by incoming and outgoing writers and released on cancel: 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);
|
||||
/* Настоящий ключ с первым байтом 00: все успешные сценарии обязаны проверить его подпись. */
|
||||
for (unsigned seed = 1;; seed++) {
|
||||
memcpy(author_key, &seed, sizeof(seed)); uint8_t public_key[32]; size_t size = 32;
|
||||
EVP_PKEY* key = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, author_key, 32); REQUIRE(key);
|
||||
REQUIRE(EVP_PKEY_get_raw_public_key(key, public_key, &size) == 1); EVP_PKEY_free(key);
|
||||
if (!public_key[0]) break;
|
||||
}
|
||||
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();
|
||||
REQUIRE(!rmdir(directory)); uasync_destroy(ua, 0);
|
||||
puts("media block regressions: PASS"); return 0;
|
||||
}
|
||||
Reference in new issue
Block a user