You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

314 lines
13 KiB

// test_media_delivery_download.c — тест скачивания блоков (chunk → done → assembly)
//
// Покрытие:
// 1. media_download_start — регистрация в очереди, сбор суперузлов
// 2. media_download_handle_chunk — запись чанка в .chunk_N
// 3. media_download_handle_chunk — неизвестный media_id → игнор
// 4. media_download_handle_done — проверка подписи, отметка validated
// 5. media_download_handle_done — неверная подпись → сброс started/received
// 6. media_download_handle_done — все блоки получены → сборка файла
// 7. media_download_cancel — отправка CANCEL, done_cb с err=-2
#include "media_delivery.h"
#include "media_delivery_proto.h"
#include "media_download.h"
#include "media_index.h"
#include "../lib/u_async.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "../transport_layer/secure_channel.h"
#include "../routing_layer/topo_node_sqlite.h"
#include <sqlite3.h>
#include <openssl/evp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#define TEMP_DIR "/tmp/test_mdl_dl_XXXXXX"
#define CHUNK_SIZE 100
#define NUM_BLOCKS 3
static int g_passed = 0, g_failed = 0, g_total = 0;
static char g_temp_dir[256];
static struct UASYNC* g_ua = NULL;
#define TEST(name) do { g_total++; printf(" %-55s", name); } while(0)
#define OK() do { g_passed++; printf("OK\n"); } while(0)
#define FAIL(fmt, ...) do { g_failed++; printf("FAIL: " fmt "\n", ##__VA_ARGS__); } while(0)
/* ── helpers ── */
static void make_uuid(uint8_t buf[16]) { for (int i = 0; i < 16; i++) buf[i] = (uint8_t)(rand() & 0xFF); }
static void write_file(const char* path, const uint8_t* data, size_t len) {
FILE* f = fopen(path, "wb");
if (f) { fwrite(data, 1, len, f); fclose(f); }
}
static int file_exists(const char* path) { return access(path, F_OK) == 0; }
static int file_size(const char* path) {
struct stat st; if (stat(path, &st) != 0) return -1; return (int)st.st_size;
}
static void test_setup(void) {
snprintf(g_temp_dir, sizeof(g_temp_dir), "%s", TEMP_DIR);
if (!mkdtemp(g_temp_dir)) { fprintf(stderr, "mkdtemp failed\n"); exit(1); }
debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR);
g_ua = uasync_create();
}
static void test_cleanup(void) {
char cmd[512]; snprintf(cmd, sizeof(cmd), "rm -rf %s", g_temp_dir); system(cmd);
if (g_ua) { uasync_destroy(g_ua, 0); g_ua = NULL; }
}
/* ── create minimal UTUN_INSTANCE with media_delivery ── */
#include "../src/utun_instance.h"
static struct UTUN_INSTANCE* make_minimal_instance(sqlite3* db) {
struct UTUN_INSTANCE* inst = u_calloc(1, sizeof(*inst));
if (!inst) return NULL;
inst->ua = g_ua;
inst->node_id = 0xDEADBEEFDEADBEEFULL;
inst->topo_sqlite_db = db;
/* generate Ed25519 key pair — just random bytes for test */
for (int i = 0; i < 32; i++) inst->my_ed25519_privkey[i] = (uint8_t)(rand() & 0xFF);
memset(inst->my_ed25519_pubkey, 0xDD, 32);
/* init media_delivery */
inst->md.inst = inst;
inst->md.db = db;
inst->md.self_node_id = inst->node_id;
inst->md.initialized = 1;
return inst;
}
/* ── test cases ── */
static void test_download_chunk(void) {
TEST("download handle_chunk writes to temp file"); {
sqlite3* db = NULL; sqlite3_open(":memory:", &db);
struct UTUN_INSTANCE* inst = make_minimal_instance(db);
inst->md.downloads = queue_new(inst->ua, 256, offsetof(struct media_download, media_id), 16, "md_dl_test");
struct media_download dl_buf; memset(&dl_buf, 0, sizeof(dl_buf));
make_uuid(dl_buf.media_id);
dl_buf.num_blocks = NUM_BLOCKS;
snprintf(dl_buf.dest_path, sizeof(dl_buf.dest_path), "%s/test_output.bin", g_temp_dir);
dl_buf.block_ids = u_malloc(NUM_BLOCKS * 16);
dl_buf.block_sigs = u_malloc(NUM_BLOCKS * 64);
for (int i = 0; i < NUM_BLOCKS; i++) make_uuid(dl_buf.block_ids + i * 16);
dl_buf.active = 1;
memcpy(dl_buf.ll.data, dl_buf.media_id, 16);
struct ll_entry* qe = queue_entry_new(sizeof(struct media_download));
if (qe) { memcpy(qe->data, &dl_buf, sizeof(dl_buf)); queue_data_put_with_index(inst->md.downloads, qe); }
uint8_t chunk_data[256];
for (int i = 0; i < 256; i++) chunk_data[i] = (uint8_t)(i & 0xFF);
uint8_t pkt[MEDIA_BLOCK_CHUNK_HDR_SIZE + sizeof(chunk_data)];
struct media_pkt_block_chunk* ch = (struct media_pkt_block_chunk*)pkt;
ch->subcmd = MEDIA_SUBCMD_BLOCK_CHUNK;
memcpy(ch->media_id, dl_buf.media_id, 16);
memcpy(ch->block_id, dl_buf.block_ids, 16);
ch->chunk = 0; ch->offset = 0; ch->data_len = (uint16_t)sizeof(chunk_data);
memcpy(pkt + MEDIA_BLOCK_CHUNK_HDR_SIZE, chunk_data, sizeof(chunk_data));
media_download_handle_chunk(inst, pkt, sizeof(pkt));
char tmp[1024]; snprintf(tmp, sizeof(tmp), "%s.chunk_0", dl_buf.dest_path);
int ok1 = file_exists(tmp) && file_size(tmp) == (int)sizeof(chunk_data);
/* send second chunk with same block_id → appends */
ch->offset = (uint32_t)sizeof(chunk_data);
media_download_handle_chunk(inst, pkt, sizeof(pkt));
int ok2 = file_size(tmp) == 2 * (int)sizeof(chunk_data);
if (ok1 && ok2) OK(); else FAIL("chunk: ok1=%d ok2=%d size=%d", ok1, ok2, file_size(tmp));
u_free(dl_buf.block_ids); u_free(dl_buf.block_sigs);
if (inst->md.downloads) queue_free(inst->md.downloads);
u_free(inst); sqlite3_close(db);
}
TEST("download handle_chunk unknown media_id"); {
sqlite3* db = NULL; sqlite3_open(":memory:", &db);
struct UTUN_INSTANCE* inst = make_minimal_instance(db);
inst->md.downloads = queue_new(inst->ua, 256, offsetof(struct media_download, media_id), 16, "md_dl_test2");
uint8_t pkt[MEDIA_BLOCK_CHUNK_HDR_SIZE];
struct media_pkt_block_chunk* ch = (struct media_pkt_block_chunk*)pkt;
ch->subcmd = MEDIA_SUBCMD_BLOCK_CHUNK;
make_uuid(ch->media_id); make_uuid(ch->block_id);
ch->chunk = 0; ch->offset = 0; ch->data_len = 0;
media_download_handle_chunk(inst, pkt, sizeof(pkt));
OK(); /* should not crash or create files */
if (inst->md.downloads) queue_free(inst->md.downloads);
u_free(inst); sqlite3_close(db);
}
}
static void test_download_done(void) {
TEST("download handle_done sig ok + assembly"); {
sqlite3* db = NULL; sqlite3_open(":memory:", &db);
struct UTUN_INSTANCE* inst = make_minimal_instance(db);
inst->md.downloads = queue_new(inst->ua, 256, offsetof(struct media_download, media_id), 16, "md_dl_test3");
struct media_download dl; memset(&dl, 0, sizeof(dl));
make_uuid(dl.media_id);
dl.num_blocks = 2;
snprintf(dl.dest_path, sizeof(dl.dest_path), "%s/out.bin", g_temp_dir);
dl.block_ids = u_malloc(16 * 2); dl.block_sigs = u_malloc(64 * 2);
make_uuid(dl.block_ids); make_uuid(dl.block_ids + 16);
memset(dl.block_sigs, 0x42, 64); memset(dl.block_sigs + 64, 0x43, 64);
dl.block_size = CHUNK_SIZE; dl.file_size = CHUNK_SIZE * 2;
dl.active = 1;
memcpy(dl.ll.data, dl.media_id, 16);
struct ll_entry* qe = queue_entry_new(sizeof(struct media_download));
if (!qe) { FAIL("queue_entry_new"); goto done3; }
memcpy(qe->data, &dl, sizeof(dl)); queue_data_put_with_index(inst->md.downloads, qe);
/* write chunk files */
uint8_t data0[CHUNK_SIZE]; memset(data0, 0xA0, CHUNK_SIZE);
uint8_t data1[CHUNK_SIZE]; memset(data1, 0xB1, CHUNK_SIZE);
{
char c0[1024]; snprintf(c0, sizeof(c0), "%s.chunk_0", dl.dest_path);
char c1[1024]; snprintf(c1, sizeof(c1), "%s.chunk_1", dl.dest_path);
write_file(c0, data0, CHUNK_SIZE); write_file(c1, data1, CHUNK_SIZE);
}
/* BLOCK_DONE for block 0 */
{
uint8_t pkt[MEDIA_BLOCK_DONE_SIZE];
struct media_pkt_block_done* bd = (struct media_pkt_block_done*)pkt;
memset(pkt, 0, sizeof(pkt));
bd->subcmd = MEDIA_SUBCMD_BLOCK_DONE;
memcpy(bd->media_id, dl.media_id, 16);
memcpy(bd->block_id, dl.block_ids, 16);
bd->chunk = 0; bd->total_size = CHUNK_SIZE;
memcpy(bd->block_sig, dl.block_sigs, 64); /* match expected */
media_download_handle_done(inst, pkt, sizeof(pkt));
}
/* BLOCK_DONE for block 1 */
{
uint8_t pkt[MEDIA_BLOCK_DONE_SIZE];
struct media_pkt_block_done* bd = (struct media_pkt_block_done*)pkt;
memset(pkt, 0, sizeof(pkt));
bd->subcmd = MEDIA_SUBCMD_BLOCK_DONE;
memcpy(bd->media_id, dl.media_id, 16);
memcpy(bd->block_id, dl.block_ids + 16, 16);
bd->chunk = 1; bd->total_size = CHUNK_SIZE;
memcpy(bd->block_sig, dl.block_sigs + 64, 64);
media_download_handle_done(inst, pkt, sizeof(pkt));
}
/* verify assembly */
if (file_exists(dl.dest_path)) {
int sz = file_size(dl.dest_path);
if (sz == CHUNK_SIZE * 2) OK(); else FAIL("assembled size %d != %d", sz, CHUNK_SIZE * 2);
} else { FAIL("assembled file not created"); }
done3:
u_free(dl.block_ids); u_free(dl.block_sigs);
if (inst->md.downloads) queue_free(inst->md.downloads);
u_free(inst); sqlite3_close(db);
}
TEST("download handle_done bad sig → retry"); {
sqlite3* db = NULL; sqlite3_open(":memory:", &db);
struct UTUN_INSTANCE* inst = make_minimal_instance(db);
inst->md.downloads = queue_new(inst->ua, 256, offsetof(struct media_download, media_id), 16, "md_dl_test4");
struct media_download dl; memset(&dl, 0, sizeof(dl));
make_uuid(dl.media_id);
dl.num_blocks = 1;
snprintf(dl.dest_path, sizeof(dl.dest_path), "%s/bad_sig.bin", g_temp_dir);
dl.block_ids = u_malloc(16); dl.block_sigs = u_malloc(64);
make_uuid(dl.block_ids);
memset(dl.block_sigs, 0x55, 64); /* expected sig */
dl.block_size = CHUNK_SIZE; dl.file_size = CHUNK_SIZE;
dl.active = 1;
memcpy(dl.ll.data, dl.media_id, 16);
struct ll_entry* qe = queue_entry_new(sizeof(struct media_download));
if (!qe) { FAIL("queue_entry_new"); goto done4; }
memcpy(qe->data, &dl, sizeof(dl)); queue_data_put_with_index(inst->md.downloads, qe);
/* write chunk file */
uint8_t data[CHUNK_SIZE]; memset(data, 0xFF, CHUNK_SIZE);
char c0[1024]; snprintf(c0, sizeof(c0), "%s.chunk_0", dl.dest_path);
write_file(c0, data, CHUNK_SIZE);
/* send BLOCK_DONE with WRONG signature */
uint8_t pkt[MEDIA_BLOCK_DONE_SIZE];
struct media_pkt_block_done* bd = (struct media_pkt_block_done*)pkt;
memset(pkt, 0, sizeof(pkt));
bd->subcmd = MEDIA_SUBCMD_BLOCK_DONE;
memcpy(bd->media_id, dl.media_id, 16);
memcpy(bd->block_id, dl.block_ids, 16);
bd->chunk = 0; bd->total_size = CHUNK_SIZE;
memset(bd->block_sig, 0xAA, 64); /* WRONG */
media_download_handle_done(inst, pkt, sizeof(pkt));
/* file should NOT be assembled (sig mismatch) */
if (!file_exists(dl.dest_path)) OK(); else FAIL("assembled despite bad sig");
done4:
u_free(dl.block_ids); u_free(dl.block_sigs);
if (inst->md.downloads) queue_free(inst->md.downloads);
u_free(inst); sqlite3_close(db);
}
}
static void test_download_cancel(void) {
TEST("download cancel marks inactive and calls done_cb"); {
sqlite3* db = NULL; sqlite3_open(":memory:", &db);
struct UTUN_INSTANCE* inst = make_minimal_instance(db);
inst->md.downloads = queue_new(inst->ua, 256, offsetof(struct media_download, media_id), 16, "md_dl_test5");
struct media_download dl; memset(&dl, 0, sizeof(dl));
make_uuid(dl.media_id);
dl.num_blocks = 1;
snprintf(dl.dest_path, sizeof(dl.dest_path), "%s/cancel.bin", g_temp_dir);
dl.block_ids = u_malloc(16); make_uuid(dl.block_ids);
dl.block_sigs = u_malloc(64); memset(dl.block_sigs, 0, 64);
dl.active = 1;
memcpy(dl.ll.data, dl.media_id, 16);
struct ll_entry* qe = queue_entry_new(sizeof(struct media_download));
if (!qe) { FAIL("queue_entry_new"); goto done5; }
memcpy(qe->data, &dl, sizeof(dl)); queue_data_put_with_index(inst->md.downloads, qe);
int rc = media_download_cancel(inst, dl.media_id, NULL, 0);
if (rc == 0) OK(); else FAIL("cancel returned %d", rc);
done5:
u_free(dl.block_ids); u_free(dl.block_sigs);
if (inst->md.downloads) queue_free(inst->md.downloads);
u_free(inst); sqlite3_close(db);
}
}
int main(void) {
test_setup();
printf("=== test_media_delivery_download ===\n");
test_download_chunk();
test_download_done();
test_download_cancel();
printf("\n%d/%d passed, %d failed\n", g_passed, g_total, g_failed);
test_cleanup();
return g_failed > 0 ? 1 : 0;
}