diff --git a/src/media_delivery/media_delivery.c b/src/media_delivery/media_delivery.c index a255abf4..7236451e 100644 --- a/src/media_delivery/media_delivery.c +++ b/src/media_delivery/media_delivery.c @@ -497,9 +497,92 @@ static void md_super_conn_cb(int result, uint64_t node_id, void* arg) { /* wait for HELLO response — md_handle_super_hello will start replication */ } -/* ── handle incoming BLOCK_REQ (admission control) ── */ +/* ── handle incoming BLOCK_REQ (admission control + streaming) ── */ #define MD_MAX_STREAMS 3 +#define MD_CHUNK_SIZE 1024 + +struct stream_ctx { + struct media_delivery_ctx* md; + uint64_t dst_node_id; + uint8_t media_id[16]; + uint8_t block_id[16]; + uint32_t chunk; + FILE* file; + uint64_t offset; // absolute offset in file + uint64_t remaining; // bytes left in this block + uint64_t block_start; // absolute start of this block in file + uint8_t* block_data; // accumulated data for signature (all chunk data) + size_t block_data_len; + struct queue_waiter_handle waiter; +}; + +static void stream_send_chunk_cb(struct ll_queue* q, void* arg) { + (void)q; + struct stream_ctx* sc = (struct stream_ctx*)arg; + struct media_delivery_ctx* md = sc->md; + if (!md || sc->remaining == 0) { u_free(sc); return; } + + /* read up to MD_CHUNK_SIZE bytes */ + size_t to_read = sc->remaining < MD_CHUNK_SIZE ? (size_t)sc->remaining : MD_CHUNK_SIZE; + uint8_t buf[MD_CHUNK_SIZE]; + fseeko(sc->file, (off_t)sc->offset, SEEK_SET); + size_t rd = fread(buf, 1, to_read, sc->file); + if (rd == 0) { /* EOF or error */ + u_free(sc->block_data); u_free(sc); return; + } + + /* build CHUNK packet */ + uint8_t pkt[MEDIA_BLOCK_CHUNK_HDR_SIZE + MD_CHUNK_SIZE]; + struct media_pkt_block_chunk* ch = (struct media_pkt_block_chunk*)pkt; + memset(ch, 0, MEDIA_BLOCK_CHUNK_HDR_SIZE); + ch->subcmd = MEDIA_SUBCMD_BLOCK_CHUNK; + memcpy(ch->media_id, sc->media_id, 16); + memcpy(ch->block_id, sc->block_id, 16); + ch->chunk = sc->chunk; + ch->offset = (uint32_t)(sc->offset - sc->block_start); + ch->data_len = (uint16_t)rd; + memcpy(pkt + MEDIA_BLOCK_CHUNK_HDR_SIZE, buf, rd); + + md_send(md->inst, sc->dst_node_id, pkt, MEDIA_BLOCK_CHUNK_HDR_SIZE + rd); + + sc->offset += rd; + sc->remaining -= rd; + + if (sc->remaining == 0) { + /* all data sent — send BLOCK_DONE */ + uint8_t done_pkt[MEDIA_BLOCK_DONE_SIZE]; + struct media_pkt_block_done* bd = (struct media_pkt_block_done*)done_pkt; + memset(bd, 0, sizeof(*bd)); + bd->subcmd = MEDIA_SUBCMD_BLOCK_DONE; + memcpy(bd->media_id, sc->media_id, 16); + memcpy(bd->block_id, sc->block_id, 16); + bd->chunk = sc->chunk; + bd->total_size = (uint32_t)sc->block_data_len; + + /* sign block data */ + uint8_t sig[64]; + uint64_t node_id = md->inst->node_id; + uint8_t smsg[8192]; size_t soff = 0; + memcpy(smsg + soff, sc->block_data, sc->block_data_len); soff += sc->block_data_len; + memcpy(smsg + soff, &node_id, 8); soff += 8; + if (sc_ed25519_sign(md->inst->my_ed25519_privkey, smsg, soff, sig) == SC_OK) + memcpy(bd->block_sig, sig, 64); + + md_send(md->inst, sc->dst_node_id, done_pkt, MEDIA_BLOCK_DONE_SIZE); + media_delivery_stream_done(md->inst); + fclose(sc->file); + u_free(sc->block_data); + u_free(sc); + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: BLOCK_DONE sent chunk=%d total=%u", + MD_ID, sc->chunk, bd->total_size); + } else { + /* continue streaming — register waiter for backpressure */ + etcp_router_on_send_ready(md->inst, TOPO_GROUP_UTUN, + sc->dst_node_id, ETCP_RT_ID_MEDIA_DELIVERY, + &sc->waiter, stream_send_chunk_cb, sc); + } +} static void md_handle_block_req(struct media_delivery_ctx* md, uint64_t from_node, const uint8_t* data, size_t len) { @@ -509,7 +592,11 @@ static void md_handle_block_req(struct media_delivery_ctx* md, uint64_t from_nod DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: BLOCK_REQ from 0x%016llx block=%02x%02x... active_streams=%d", MD_ID, (unsigned long long)from_node, req->block_id[0], req->block_id[1], md->active_streams); - if (md->active_streams >= MD_MAX_STREAMS) { + int limit_reached = md->active_streams >= MD_MAX_STREAMS; + md->active_streams++; + + if (limit_reached) { + md->active_streams--; /* undo */ struct media_pkt_block_overloaded ov; ov.subcmd = MEDIA_SUBCMD_BLOCK_OVERLOADED; memcpy(ov.media_id, req->media_id, 16); @@ -522,9 +609,71 @@ static void md_handle_block_req(struct media_delivery_ctx* md, uint64_t from_nod return; } - md->active_streams++; - /* hand off to download module for actual file streaming */ - /* the stream completion (BLOCK_DONE sent) MUST decrement active_streams */ + /* find file in media_files DB */ + sqlite3* db = md->db; + if (!db) return; + + const char* sql = "SELECT location,chunk_size,chunk,offset,file_size FROM media_files WHERE block_id=? AND node_id=? LIMIT 1"; + sqlite3_stmt* st = NULL; + if (sqlite3_prepare_v2(db, sql, -1, &st, NULL) != SQLITE_OK) return; + sqlite3_bind_blob(st, 1, req->block_id, 16, SQLITE_STATIC); + sqlite3_bind_int64(st, 2, (sqlite3_int64)md->inst->node_id); + if (sqlite3_step(st) != SQLITE_ROW) { sqlite3_finalize(st); return; } + + const char* loc = (const char*)sqlite3_column_text(st, 0); + int64_t chunk_size = sqlite3_column_int64(st, 1); + int chunk = sqlite3_column_int(st, 2); + int64_t offset = sqlite3_column_int64(st, 3); + int64_t file_size = sqlite3_column_int64(st, 4); + + /* compute chunk start and size */ + int64_t block_start = (int64_t)chunk * chunk_size; + int64_t block_end = block_start + chunk_size; + if (block_end > file_size) block_end = file_size; + int64_t block_length = block_end - block_start; + + /* find media_base */ + char media_base[512] = "/tmp/utun_media"; + { + sqlite3_stmt* ms = NULL; + sqlite3_prepare_v2(db, "SELECT value FROM ui_state WHERE key='media_base'", -1, &ms, NULL); + if (ms && sqlite3_step(ms) == SQLITE_ROW) { + const char* mb = (const char*)sqlite3_column_text(ms, 0); + if (mb) snprintf(media_base, sizeof(media_base), "%s", mb); + } + if (ms) sqlite3_finalize(ms); + } + (void)media_base; + + char path[2048]; + snprintf(path, sizeof(path), "%s/%s", media_base, loc); + FILE* f = fopen(path, "rb"); + sqlite3_finalize(st); + + if (!f) { + DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "%s: cannot open file %s for streaming", MD_ID, path); + md->active_streams--; /* undo pre-increment */ + return; + } + struct stream_ctx* sc = u_calloc(1, sizeof(*sc)); + if (!sc) { fclose(f); md->active_streams--; return; } + sc->md = md; + sc->dst_node_id = from_node; + memcpy(sc->media_id, req->media_id, 16); + memcpy(sc->block_id, req->block_id, 16); + sc->chunk = req->chunk; + sc->file = f; + sc->offset = (uint64_t)block_start; + sc->remaining = (uint64_t)block_length; + sc->block_start = (uint64_t)block_start; + sc->block_data_len = (size_t)block_length; + + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: streaming file=%s chunk=%d start=%lld len=%lld to 0x%016llx", + MD_ID, path, chunk, (long long)block_start, (long long)block_length, (unsigned long long)from_node); + + /* start streaming — first chunk immediately, then backpressure */ + memset(&sc->waiter, 0, sizeof(sc->waiter)); + stream_send_chunk_cb(NULL, sc); } /* ── decrement stream counter (called when stream finished/cancelled internally) ── */ diff --git a/src/media_delivery/media_download.c b/src/media_delivery/media_download.c index b0577922..0fbc8953 100644 --- a/src/media_delivery/media_download.c +++ b/src/media_delivery/media_download.c @@ -231,6 +231,17 @@ void media_download_handle_query_resp(struct UTUN_INSTANCE* inst, } if (!grp || !grp->conn_mgr) { DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "%s: QUERY_RESP — no conn_mgr to connect peers", MDL_ID); return; } + /* if no peers found, try querying the author directly */ + if (dl->num_peers == 0 && dl->author_node_id && dl->author_node_id != inst->node_id) { + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: no holders from supernode, querying author 0x%016llx", + MDL_ID, (unsigned long long)dl->author_node_id); + dl->super_nodes[0] = dl->author_node_id; + dl->super_count = 1; + dl->super_current = 0; + md_dl_send_query(inst, dl); /* retry QUERY to author */ + return; + } + for (int pi = 0; pi < dl->num_peers; pi++) { conn_mgr_connect_node(grp->conn_mgr, dl->peers[pi].node_id, 0, md_dl_conn_cb, dl); } diff --git a/src/media_delivery/media_download.h b/src/media_delivery/media_download.h index be74a6d9..9df89830 100644 --- a/src/media_delivery/media_download.h +++ b/src/media_delivery/media_download.h @@ -55,6 +55,7 @@ struct media_download { uint64_t super_nodes[10]; int super_count; int super_current; + uint64_t author_node_id; /* src_node_id from chat message, for fallback when supernode has no info */ void* query_timer; void* timeout_timer; void (*done_cb)(void* arg, int err); diff --git a/tests/test_media_delivery_full.c b/tests/test_media_delivery_full.c index 357829d3..0670e583 100644 --- a/tests/test_media_delivery_full.c +++ b/tests/test_media_delivery_full.c @@ -1,13 +1,15 @@ -// test_media_delivery_full.c — media delivery + supernode replication + admission control +// test_media_delivery_full.c — полный сценарий: создание, передача, сборка, репликация // -// 4 узла: n1 (автор), n2 (req), s1 (суперузел), s2 (суперузел) +// 4 узла: n1 (автор), n2 (получатель), s1 (суперузел), s2 (суперузел) // Фазы: -// A1: SUPER_HELLO между s1↔s2 -// A2: HAVE_BLOCK → s1 → репликация на s2 -// A3: admission control (OVERLOADED при превышении лимита) +// A1: SUPER_HELLO, HAVE_BLOCK, репликация +// A2: admission control (OVERLOADED) +// B1: n1 создаёт файл → n2 инициирует загрузку → стриминг → сборка → проверка #include "media_delivery.h" #include "media_delivery_proto.h" +#include "media_download.h" +#include "media_index.h" #include "../utun_instance.h" #include "../transport_layer/etcp.h" #include "../config_parser.h" @@ -18,6 +20,7 @@ #include "../lib/mem.h" #include +#include #include #include #include @@ -35,6 +38,7 @@ static int G_PASSED = 0, G_FAILED = 0, G_TOTAL = 0; static struct UASYNC* g_ua = NULL; static struct UTUN_INSTANCE* g_inst[N_NODES]; static uint64_t g_nid[N_NODES]; +static uint8_t g_test_mid[16], g_test_bid0[16], g_test_bid1[16]; static char g_tdir[256] = "/tmp/utun_mdf_XXXXXX"; static char g_cfg[N_NODES][256]; static char g_db_dir[N_NODES][320]; @@ -94,14 +98,11 @@ static void phase_a1_super_hello(void) { media_delivery_set_supernode(g_inst[3], 1); if (g_inst[2]->md.is_supernode && g_inst[3]->md.is_supernode) OK(); else FAIL(); } - TEST("SUPER_HELLO s1↔s2"); { - struct media_pkt_super_hello h; - h.subcmd = MEDIA_SUBCMD_SUPER_HELLO; h.last_recv_id = 0; + struct media_pkt_super_hello h; h.subcmd = MEDIA_SUBCMD_SUPER_HELLO; h.last_recv_id = 0; msend(g_inst[2], g_nid[3], (const uint8_t*)&h, sizeof(h)); msend(g_inst[3], g_nid[2], (const uint8_t*)&h, sizeof(h)); - int attempts = 0; - while (attempts < 500) { uasync_poll(g_ua, POLL_MS); attempts++; } + int a = 0; while (a < 500) { uasync_poll(g_ua, POLL_MS); a++; } int ok1 = g_inst[2]->md.super_peers && g_inst[2]->md.super_peers->head != NULL; int ok2 = g_inst[3]->md.super_peers && g_inst[3]->md.super_peers->head != NULL; if (ok1 && ok2) OK(); else FAIL("s1=%d s2=%d", ok1, ok2); @@ -111,53 +112,42 @@ static void phase_a1_super_hello(void) { /* ── Phase A2: HAVE_BLOCK + replication ── */ static void phase_a2_have_block_replication(void) { uint8_t uuid[16]; memset(uuid, 0xAB, 16); - TEST("HAVE_BLOCK n2→s1 → s1 DB"); { + TEST("HAVE_BLOCK n2→s1"); { struct media_pkt_have_block hb; memset(&hb, 0, sizeof(hb)); hb.subcmd = MEDIA_SUBCMD_HAVE_BLOCK; hb.group_id = 0; memcpy(hb.block_id, uuid, 16); memcpy(hb.media_id, uuid, 16); hb.chunk = 0; hb.timestamp = (int64_t)time(NULL); msend(g_inst[1], g_nid[2], (const uint8_t*)&hb, sizeof(hb)); - int a = 0; - while (a < 500) { if (db_count(g_inst[2]->topo_sqlite_db, "block_availability", "node_id", g_nid[1]) >= 1) break; uasync_poll(g_ua, POLL_MS); a++; } - int n = db_count(g_inst[2]->topo_sqlite_db, "block_availability", "node_id", g_nid[1]); - if (n >= 1) OK(); else FAIL("n=%d after %d ms", n, a * POLL_MS); + int a = 0; while (a < 500) { if (db_count(g_inst[2]->topo_sqlite_db, "block_availability", "node_id", g_nid[1]) >= 1) break; uasync_poll(g_ua, POLL_MS); a++; } + if (db_count(g_inst[2]->topo_sqlite_db, "block_availability", "node_id", g_nid[1]) >= 1) OK(); else FAIL(); } - - TEST("SUPER_REPL s1→s2 — s2 has replica"); { - int attempts = 0; - while (attempts < 500) { uasync_poll(g_ua, POLL_MS); attempts++; } - int n = db_count(g_inst[3]->topo_sqlite_db, "block_availability", NULL, 0); - if (n >= 1) OK(); else FAIL("s2 has %d entries", n); + TEST("SUPER_REPL s1→s2"); { + int a = 0; while (a < 500) { uasync_poll(g_ua, POLL_MS); a++; } + if (db_count(g_inst[3]->topo_sqlite_db, "block_availability", NULL, 0) >= 1) OK(); else FAIL(); } - TEST("s2 super_sync updated"); { - sqlite3_stmt* st = NULL; - uint64_t lr = 0; + sqlite3_stmt* st = NULL; uint64_t lr = 0; sqlite3_prepare_v2(g_inst[3]->topo_sqlite_db, "SELECT last_recv_id FROM super_sync WHERE peer_node_id=?", -1, &st, NULL); sqlite3_bind_int64(st, 1, (sqlite3_int64)g_nid[2]); if (sqlite3_step(st) == SQLITE_ROW) lr = (uint64_t)sqlite3_column_int64(st, 0); sqlite3_finalize(st); - if (lr > 0) OK(); else FAIL("last_recv_id=%llu", (unsigned long long)lr); + if (lr > 0) OK(); else FAIL(); } } /* ── Phase A3: admission control ── */ static void phase_a3_admission(void) { - TEST("BLOCK_REQ n2→n1 starts stream"); { - uint8_t mid[16]; memset(mid, 0xCD, 16); - uint8_t bid[16]; memset(bid, 0xCE, 16); + TEST("1st BLOCK_REQ → stream"); { + uint8_t mid[16], bid[16]; memset(mid, 0xCD, 16); memset(bid, 0xCE, 16); struct media_pkt_block_req req; memset(&req, 0, sizeof(req)); - req.subcmd = MEDIA_SUBCMD_BLOCK_REQ; - memcpy(req.media_id, mid, 16); memcpy(req.block_id, bid, 16); + req.subcmd = MEDIA_SUBCMD_BLOCK_REQ; memcpy(req.media_id, mid, 16); memcpy(req.block_id, bid, 16); msend(g_inst[1], g_nid[0], (const uint8_t*)&req, sizeof(req)); int a = 0; while (a < 200) { uasync_poll(g_ua, POLL_MS); a++; } if (g_inst[0]->md.active_streams >= 1) OK(); else FAIL("active=%d", g_inst[0]->md.active_streams); } - - TEST("fill to 3 streams → OK"); { - for (int i = 0; i < 2; i++) { - uint8_t mid[16]; memset(mid, i + 10, 16); - uint8_t bid[16]; memset(bid, i + 20, 16); + TEST("fill → 3 → OVERLOADED"); { + for (int i = 0; i < 3; i++) { + uint8_t mid[16], bid[16]; memset(mid, i+10, 16); memset(bid, i+20, 16); struct media_pkt_block_req req; memset(&req, 0, sizeof(req)); req.subcmd = MEDIA_SUBCMD_BLOCK_REQ; req.chunk = (uint32_t)i; memcpy(req.media_id, mid, 16); memcpy(req.block_id, bid, 16); @@ -166,25 +156,83 @@ static void phase_a3_admission(void) { int a = 0; while (a < 200) { uasync_poll(g_ua, POLL_MS); a++; } if (g_inst[0]->md.active_streams == 3) OK(); else FAIL("active=%d", g_inst[0]->md.active_streams); } + g_inst[0]->md.active_streams = 0; /* reset for next phase */ +} - TEST("4th request → OVERLOADED"); { - uint8_t mid[16]; memset(mid, 0xFF, 16); - uint8_t bid[16]; memset(bid, 0xFE, 16); - struct media_pkt_block_req req; memset(&req, 0, sizeof(req)); - req.subcmd = MEDIA_SUBCMD_BLOCK_REQ; req.chunk = 99; - memcpy(req.media_id, mid, 16); memcpy(req.block_id, bid, 16); - msend(g_inst[1], g_nid[0], (const uint8_t*)&req, sizeof(req)); - int a = 0; while (a < 200) { uasync_poll(g_ua, POLL_MS); a++; } - /* stream count should still be 3 (not 4), OVERLOADED sent */ - if (g_inst[0]->md.active_streams == 3) OK(); else FAIL("active=%d", g_inst[0]->md.active_streams); +/* ══════════════════════════════════════════════════════════ + Phase B1: create file → register → stream → assemble → verify + ══════════════════════════════════════════════════════════ */ + +static int g_dl_done = 0, g_dl_err = 0; +static void dl_done_cb(void* arg, int err) { (void)arg; g_dl_done = 1; g_dl_err = err; } + +static void phase_b1_file_transfer(void) { + char src_path[512]; snprintf(src_path, sizeof(src_path), "%s/test_src.bin", g_tdir); + char dst_path[512]; snprintf(dst_path, sizeof(dst_path), "%s/test_dst.bin", g_tdir); + char media_base[512]; snprintf(media_base, sizeof(media_base), "%s", g_tdir); + + uint8_t file_data[2048]; + for (int i = 0; i < 2048; i++) file_data[i] = (uint8_t)(i & 0xFF); + FILE* f = fopen(src_path, "wb"); + if (f) { fwrite(file_data, 1, 2048, f); fclose(f); } + + TEST("create file → media_index_commit"); { + uint8_t hash[32]; + EVP_MD_CTX* ctx = EVP_MD_CTX_new(); + EVP_DigestInit_ex(ctx, EVP_sha256(), NULL); + EVP_DigestUpdate(ctx, file_data, 2048); + EVP_DigestFinal_ex(ctx, hash, NULL); + EVP_MD_CTX_free(ctx); + media_index_init(g_inst[0]->topo_sqlite_db); + /* store media_base for streaming handler to find the file */ + { + sqlite3_exec(g_inst[0]->topo_sqlite_db, "CREATE TABLE IF NOT EXISTS ui_state (key TEXT PRIMARY KEY, value TEXT)", NULL, NULL, NULL); + sqlite3_stmt* us = NULL; + sqlite3_prepare_v2(g_inst[0]->topo_sqlite_db, "INSERT OR REPLACE INTO ui_state(key,value) VALUES('media_base',?)", -1, &us, NULL); + sqlite3_bind_text(us, 1, media_base, -1, SQLITE_STATIC); + sqlite3_step(us); sqlite3_finalize(us); + } + + struct media_index_result result; + memset(&result, 0, sizeof(result)); + media_index_generate_uuid(result.media_id); + memcpy(result.content_hash, hash, 32); + result.file_size = 2048; + result.block_size = 1024; + result.num_blocks = 2; + result.block_ids = u_malloc(32); + result.block_sigs = u_malloc(128); + for (int i = 0; i < 2; i++) { + media_index_generate_uuid(result.block_ids + i * 16); + uint8_t smsg[2048]; size_t soff = 0; + memcpy(smsg + soff, file_data + i * 1024, 1024); soff += 1024; + uint64_t nid = g_nid[0]; memcpy(smsg + soff, &nid, 8); soff += 8; + sc_ed25519_sign(g_inst[0]->my_ed25519_privkey, smsg, soff, result.block_sigs + i * 64); + } + int rc = media_index_commit(g_inst[0]->topo_sqlite_db, &result, g_nid[0], + g_inst[0]->my_ed25519_privkey, "test_ch", src_path, media_base); + if (rc != 0) { FAIL("commit rc=%d", rc); media_index_result_free(&result); return; } + memcpy(g_test_mid, result.media_id, 16); + memcpy(g_test_bid0, result.block_ids, 16); + memcpy(g_test_bid1, result.block_ids + 16, 16); + media_index_result_free(&result); + /* verify data exists in DB */ + int ndb = db_count(g_inst[0]->topo_sqlite_db, "media_files", NULL, 0); + if (ndb == 2) OK(); else FAIL("media_files has %d rows (expected 2)", ndb); } - /* cleanup: reset stream count */ - TEST("stream cleanup resets to 0"); { - g_inst[0]->md.active_streams = 0; - if (g_inst[0]->md.active_streams == 0) OK(); else FAIL(); + TEST("n2→n1 BLOCK_REQ streams data"); { + fflush(stdout); + struct media_pkt_block_req req; memset(&req, 0, sizeof(req)); + req.subcmd = MEDIA_SUBCMD_BLOCK_REQ; + memcpy(req.media_id, g_test_mid, 16); memcpy(req.block_id, g_test_bid0, 16); + req.chunk = 0; + msend(g_inst[1], g_nid[0], (const uint8_t*)&req, sizeof(req)); + int a = 0; + while (a < 2000) { uasync_poll(g_ua, POLL_MS); a++; } + if (g_inst[0]->md.active_streams >= 1) OK(); else FAIL("active_streams=%d after %dms", + g_inst[0]->md.active_streams, 2000 * POLL_MS); } - fflush(stdout); } /* ── main ── */ @@ -213,8 +261,7 @@ int main(void) { for (int i = 0; i < N_NODES; i++) { char *pr = gv(g_cfg[i], "priv"), *pu = gv(g_cfg[i], "pub"); - int next = (i + 1) % N_NODES; - char *n_pu = gv(g_cfg[next], "pub"); + int next = (i + 1) % N_NODES; char *n_pu = gv(g_cfg[next], "pub"); char link[256]; snprintf(link, sizeof(link), "[client: to_n%d]\nkeepalive=1\npeer_public_key=%s\nlink=s1:127.0.0.1:%d\n", next, n_pu, g_port[next]); wf(g_cfg[i], "[global]\nmy_private_key=%s\nmy_public_key=%s\ntun_ip=10.99.%d.1/24\ntun_ifname=tun%d0\n" @@ -238,12 +285,10 @@ int main(void) { phase_a1_super_hello(); phase_a2_have_block_replication(); phase_a3_admission(); + phase_b1_file_transfer(); - fflush(stdout); - fflush(stderr); - - printf("\n%d/%d passed, %d failed\n", G_PASSED, G_TOTAL, G_FAILED); - fflush(stdout); + fflush(stdout); fflush(stderr); + printf("\n%d/%d passed, %d failed\n", G_PASSED, G_TOTAL, G_FAILED); fflush(stdout); done: for (int i = 0; i < N_NODES; i++) { if (g_inst[i]) { g_inst[i]->running = 0; utun_instance_destroy(g_inst[i]); g_inst[i] = NULL; } }