// test_etcp_link_stress.c - Multilink test: добавляет линки в 5 вариантах (V1/V2 one-way, // V3a/b/c коллизия), проверяет per-link трафик и инвариант «нет реинита пока жив хотя бы один линк». #include #include #include #include "../lib/platform_compat.h" #include "test_utils.h" #include "etcp.h" #include "etcp_api.h" #include "etcp_connections.h" #include "etcp_dump.h" #include "../src/config_parser.h" #include "../src/utun_instance.h" #include "routing.h" #include "../src/tun_if.h" #include "secure_channel.h" #include "../lib/u_async.h" #include "../lib/ll_queue.h" #include "../lib/debug_config.h" #include "../lib/mem.h" #define PACKET_SIZE 64 #define TRAFFIC_PACKETS 200 #define LINK_UP_TIMEOUT_MS 8000 #define TRAFFIC_TIMEOUT_MS 8000 #define LINK_DOWN_TIMEOUT_MS 8000 #define COLLISION_TIMEOUT_MS 15000 enum { COL_A = 0, COL_B, COL_C }; static struct UTUN_INSTANCE* server_instance = NULL; static struct UTUN_INSTANCE* client_instance = NULL; static struct UASYNC* ua = NULL; static char temp_dir[] = "/tmp/utun_test_XXXXXX"; static char server_config_path[256]; static char client_config_path[256]; static int server_port = 0; static int client_port = 0; static int next_port = 0; static uint32_t packets_sent = 0; static uint32_t packets_received = 0; // globals for generic poll_until conditions static struct ETCP_LINK* g_link = NULL; static struct ETCP_SOCKET* g_sock = NULL; static struct ETCP_CONN* g_conn = NULL; static uint16_t g_port = 0; static int traffic_corrupt = 0; static int create_temp_configs(void) { if (test_mkdtemp(temp_dir) != 0) { fprintf(stderr, "Failed to create temp directory\n"); return -1; } int base_port = 50000 + (getpid() % 15000); server_port = base_port; client_port = base_port + 1; next_port = base_port + 100; snprintf(server_config_path, sizeof(server_config_path), "%s/server.conf", temp_dir); snprintf(client_config_path, sizeof(client_config_path), "%s/client.conf", temp_dir); FILE* f = fopen(server_config_path, "w"); if (!f) { fprintf(stderr, "Failed to create server config file\n"); return -1; } fprintf(f, "[global]\n" "my_node_id=0x1111111111111111\n" "my_private_key=38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68\n" "my_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n" "tun_ip=10.99.0.1/24\n" "tun_ifname=tun99\n" "keepalive_interval=100\n" "keepalive_adaptive=0\n" "\n" "[server: udp0]\n" "addr=127.0.0.1:%d\n" "type=public\n" "\n" "[allowed_keys]\n" "allow_all=1\n", server_port); fclose(f); f = fopen(client_config_path, "w"); if (!f) { fprintf(stderr, "Failed to create client config file\n"); test_unlink(server_config_path); return -1; } fprintf(f, "[global]\n" "my_node_id=0x2222222222222222\n" "my_private_key=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f\n" "my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n" "tun_ip=10.99.0.2/24\n" "tun_ifname=tun98\n" "keepalive_interval=100\n" "keepalive_adaptive=0\n" "\n" "[server: udp0]\n" "addr=127.0.0.1:%d\n" "type=public\n" "\n" "[client: test_client]\n" "keepalive=1\n" "peer_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n" "link=udp0:127.0.0.1:%d\n" "\n" "[allowed_keys]\n" "allow_all=1\n", client_port, server_port); fclose(f); return 0; } static void cleanup_temp_configs(void) { if (server_config_path[0]) test_unlink(server_config_path); if (client_config_path[0]) test_unlink(client_config_path); if (temp_dir[0]) test_rmdir(temp_dir); } // ===== helpers ===== static struct ETCP_CONN* get_conn(struct UTUN_INSTANCE* inst) { if (!inst || !inst->connections || !inst->connections->head) return NULL; struct conn_queue_entry* ce = (struct conn_queue_entry*)inst->connections->head->data; return ce ? ce->conn : NULL; } static int conn_up(struct UTUN_INSTANCE* inst) { struct ETCP_CONN* conn = get_conn(inst); return conn && conn->initialized && conn->links_up; } static int count_links(struct ETCP_CONN* conn) { int n = 0; for (struct ETCP_LINK* l = conn ? conn->links : NULL; l; l = l->next) n++; return n; } static int count_links_up(struct ETCP_CONN* conn) { int n = 0; for (struct ETCP_LINK* l = conn ? conn->links : NULL; l; l = l->next) if (l->link_status) n++; return n; } static void mk_addr(struct sockaddr_storage* out, uint16_t port) { memset(out, 0, sizeof(*out)); struct sockaddr_in* sin = (struct sockaddr_in*)out; sin->sin_family = AF_INET; sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK); sin->sin_port = htons(port); } static int alloc_port(void) { return next_port++; } static struct ETCP_SOCKET* add_udp_socket(struct UTUN_INSTANCE* inst, uint16_t port, const char* name) { struct CFG_SERVER cfg; memset(&cfg, 0, sizeof(cfg)); snprintf(cfg.name, sizeof(cfg.name), "%s", name); cfg.type = CFG_SERVER_TYPE_PUBLIC; cfg.mtu = PACKET_DATA_MAX_MTU; mk_addr(&cfg.ip, port); return etcp_socket_add(inst, &cfg); } static struct ETCP_LINK* find_link_by_remote_port(struct ETCP_CONN* conn, uint16_t port) { for (struct ETCP_LINK* l = conn ? conn->links : NULL; l; l = l->next) { if (l->remote_addr.ss_family == AF_INET && ntohs(((struct sockaddr_in*)&l->remote_addr)->sin_port) == port) return l; } return NULL; } static struct ETCP_LINK* first_link_on_socket(struct ETCP_SOCKET* sock) { if (!sock || !sock->links_queue || !sock->links_queue->head) return NULL; struct link_queue_entry* lqe = (struct link_queue_entry*)sock->links_queue->head->data; return lqe ? lqe->link : NULL; } static uint64_t client_link_acked(uint16_t srv_port) { struct ETCP_LINK* l = find_link_by_remote_port(get_conn(client_instance), srv_port); return l ? l->acked_packets : 0; } static void dump_state(void) { printf("--- STATE DUMP ---\n"); if (server_instance) etcp_dump_all_conns(server_instance); if (client_instance) etcp_dump_all_conns(client_instance); printf("--- END DUMP ---\n"); fflush(stdout); } #define CHECK(cond, ...) do { if (!(cond)) { \ printf("FAIL %s:%d: ", __func__, __LINE__); \ printf(__VA_ARGS__); \ printf("\n"); \ dump_state(); \ goto fail; } } while (0) // ===== poll conditions ===== static int c_conn_up_both(void) { return conn_up(server_instance) && conn_up(client_instance); } static int c_link_up(void) { return g_link && g_link->initialized && g_link->link_status == 1; } static int c_link_down(void) { return g_link && g_link->link_status == 0; } static int c_socket_link_up(void) { struct ETCP_LINK* l = first_link_on_socket(g_sock); return l && l->initialized && l->link_status == 1; } static int c_link_on_port_up(void) { struct ETCP_LINK* l = find_link_by_remote_port(g_conn, g_port); return l && l->initialized && l->link_status == 1; } static int c_client_all_down(void) { struct ETCP_CONN* c = get_conn(client_instance); return !c || c->links_up == 0; } static int c_server_all_down(void) { struct ETCP_CONN* c = get_conn(server_instance); return !c || c->links_up == 0; } static int poll_until(int (*cond)(void), uint64_t timeout_ms, const char* what) { uint64_t deadline = get_time_tb() + timeout_ms * 10; while (get_time_tb() < deadline) { if (cond()) return 0; uasync_poll(ua, 5); } printf("TIMEOUT waiting for: %s (%llums)\n", what, (unsigned long long)timeout_ms); return -1; } // ===== traffic ===== static void receive_test_packet(struct ETCP_CONN* conn, struct ll_entry* pkt) { (void)conn; uint8_t expected[PACKET_SIZE]; expected[0] = 0x7f; uint32_t seq = packets_received++; for (int i = 0; i < 4; i++) expected[1 + i] = (uint8_t)(seq >> (i * 8)); for (int i = 5; i < PACKET_SIZE; i++) expected[i] = (uint8_t)(seq + i); if (pkt->len != PACKET_SIZE || memcmp(pkt->dgram, expected, PACKET_SIZE) != 0) { fprintf(stderr, "FAIL: multilink payload/order mismatch at packet %u len=%u\n", seq, pkt->len); traffic_corrupt = 1; } queue_dgram_free(pkt); queue_entry_free(pkt); } static int run_traffic(int n, uint64_t timeout_ms) { struct ETCP_CONN* cc = get_conn(client_instance); if (!cc || !cc->input_queue) return -1; uint32_t target = packets_sent + (uint32_t)n; uint64_t deadline = get_time_tb() + timeout_ms * 10; while (1) { while (packets_sent < target && queue_entry_count(cc->send_input_q) == 0) { uint8_t buf[PACKET_SIZE]; uint32_t s = packets_sent; buf[0] = 0x7f; for (int i = 0; i < 4; i++) buf[1 + i] = (uint8_t)(s >> (i * 8)); for (int i = 5; i < PACKET_SIZE; i++) buf[i] = (uint8_t)(s + i); struct ll_entry* pkt = ll_alloc_lldgram(PACKET_SIZE); if (!pkt) return -1; pkt->len = PACKET_SIZE; memcpy(pkt->dgram, buf, PACKET_SIZE); if (etcp_send(cc, pkt) != 0) { queue_dgram_free(pkt); queue_entry_free(pkt); return -1; } packets_sent++; } if (traffic_corrupt || packets_received > target) return -1; if (packets_received == target && !cc->input_wait_ack->count && !cc->input_send_q->count) return 0; if (get_time_tb() > deadline) { printf("traffic timeout: sent=%u recv=%u target=%u\n", packets_sent, packets_received, target); return -1; } uasync_poll(ua, 5); } } // Проверяет, что конкретный линк (по серверному порту) реально перенёс данные. // Линк может пропустить короткий burst из-за keepalive-стабилизации link_status, // поэтому повторяем до нескольких keepalive-тиков. Возвращает 0 если линк нёс трафик. static int check_link_traffic(uint16_t srv_port, const char* label) { for (int attempt = 0; attempt < 6; attempt++) { uint64_t before = client_link_acked(srv_port); if (run_traffic(TRAFFIC_PACKETS, TRAFFIC_TIMEOUT_MS) != 0) return -1; if (client_link_acked(srv_port) > before) return 0; uasync_poll(ua, 50); } printf(" [%s] link never carried traffic\n", label); return -1; } // ===== instance lifecycle ===== static int start_instances(void) { packets_sent = packets_received = 0; traffic_corrupt = 0; server_instance = utun_instance_create(ua, server_config_path); if (!server_instance || utun_instance_init(server_instance) < 0) { printf("failed to create server\n"); return -1; } client_instance = utun_instance_create(ua, client_config_path); if (!client_instance || utun_instance_init(client_instance) < 0) { printf("failed to create client\n"); return -1; } etcp_bind(server_instance, 0x7f, receive_test_packet); if (poll_until(c_conn_up_both, LINK_UP_TIMEOUT_MS, "initial connection") != 0) { printf("initial connection timeout\n"); dump_state(); return -1; } printf("connection established: server links_up=%d/%d client links_up=%d/%d\n", count_links_up(get_conn(server_instance)), count_links(get_conn(server_instance)), count_links_up(get_conn(client_instance)), count_links(get_conn(client_instance))); return 0; } static void stop_instances(void) { if (server_instance) { server_instance->running = 0; utun_instance_destroy(server_instance); server_instance = NULL; } if (client_instance) { client_instance->running = 0; utun_instance_destroy(client_instance); client_instance = NULL; } } // Убить серверный сокет (линк со стороны сервера закрывается сразу), ускорить детект на клиенте. static void kill_server_socket(struct ETCP_SOCKET* sock, uint16_t srv_port) { struct ETCP_LINK* cl = find_link_by_remote_port(get_conn(client_instance), srv_port); if (cl) cl->last_recv_local_time = 0; etcp_socket_remove(sock); } // ===== Session A: one-way adds + kill failover ===== static int session_a(void) { printf("\n=== Session A: one-way adds (V1/V2) + link kill failover ===\n"); if (start_instances() != 0) return -1; struct ETCP_CONN* sc = get_conn(server_instance); struct ETCP_CONN* cc = get_conn(client_instance); int held = 0; CHECK(sc && cc, "no conn after start"); CHECK(etcp_conn_ref_take(sc) == 0 && etcp_conn_ref_take(cc) == 0, "test conn references"); held = 1; uint32_t srv_reinit0 = sc->reinit_count; uint32_t cli_reinit0 = cc->reinit_count; struct ETCP_SOCKET* s0 = server_instance->etcp_sockets; // начальный серверный сокет udp0 struct ETCP_SOCKET* c0 = client_instance->etcp_sockets; // клиентский сокет udp0 (source для V1) // -- per-link трафик на исходном линке -- printf("[A] traffic on initial link...\n"); CHECK(check_link_traffic(server_port, "initial") == 0, "initial link carried no traffic"); CHECK(sc->reinit_count == srv_reinit0 && cc->reinit_count == cli_reinit0, "reinit during initial traffic"); // -- V1: клиент дозванивается до нового серверного сокета S1 -- int sp1 = alloc_port(); struct ETCP_SOCKET* s1 = add_udp_socket(server_instance, sp1, "dyn_s1"); CHECK(s1, "V1: failed to add server socket S1"); struct sockaddr_storage s1_addr; mk_addr(&s1_addr, sp1); struct ETCP_LINK* lc1 = etcp_link_new(cc, c0, &s1_addr, 0); CHECK(lc1, "V1: failed to create client link"); printf("[A] V1: client link %d -> server port %d\n", lc1->local_link_id, sp1); g_link = lc1; CHECK(poll_until(c_link_up, LINK_UP_TIMEOUT_MS, "V1 link up (client)") == 0, "V1 link did not come up"); g_sock = s1; CHECK(poll_until(c_socket_link_up, LINK_UP_TIMEOUT_MS, "V1 link up (server)") == 0, "V1 server link did not come up"); CHECK(sc->reinit_count == srv_reinit0 && cc->reinit_count == cli_reinit0, "reinit during V1"); CHECK(check_link_traffic(sp1, "V1") == 0, "V1 link carried no traffic"); // -- V2: сервер дозванивается до клиента -- int sp2 = alloc_port(); struct ETCP_SOCKET* s2 = add_udp_socket(server_instance, sp2, "dyn_s2"); CHECK(s2, "V2: failed to add server socket S2"); struct sockaddr_storage c0_addr; mk_addr(&c0_addr, client_port); struct ETCP_LINK* ls2 = etcp_link_new(sc, s2, &c0_addr, 0); CHECK(ls2, "V2: failed to create server outbound link"); printf("[A] V2: server link %d -> client port %d\n", ls2->local_link_id, client_port); g_link = ls2; CHECK(poll_until(c_link_up, LINK_UP_TIMEOUT_MS, "V2 link up (server)") == 0, "V2 link did not come up"); g_conn = cc; g_port = sp2; CHECK(poll_until(c_link_on_port_up, LINK_UP_TIMEOUT_MS, "V2 link up (client)") == 0, "V2 client link did not come up"); CHECK(sc->reinit_count == srv_reinit0 && cc->reinit_count == cli_reinit0, "reinit during V2"); CHECK(check_link_traffic(sp2, "V2") == 0, "V2 link carried no traffic"); printf("[A] after adds: server links_up=%d/%d client links_up=%d/%d\n", count_links_up(sc), count_links(sc), count_links_up(cc), count_links(cc)); // -- kill V2 link: failover, без реинита -- printf("[A] killing V2 link (server socket %d)...\n", sp2); kill_server_socket(s2, sp2); g_link = find_link_by_remote_port(cc, sp2); CHECK(poll_until(c_link_down, LINK_DOWN_TIMEOUT_MS, "V2 link down") == 0, "V2 link did not go down"); CHECK(cc->links_up == 1, "client links_up != 1 after V2 kill (got %d)", cc->links_up); CHECK(sc->links_up == 1, "server links_up != 1 after V2 kill (got %d)", sc->links_up); CHECK(cc->initialized == 1, "client initialized lost after V2 kill"); CHECK(sc->reinit_count == srv_reinit0 && cc->reinit_count == cli_reinit0, "reinit after V2 kill"); CHECK(run_traffic(TRAFFIC_PACKETS, TRAFFIC_TIMEOUT_MS) == 0, "failover traffic after V2 kill failed"); // -- kill V1 link -- printf("[A] killing V1 link (server socket %d)...\n", sp1); kill_server_socket(s1, sp1); g_link = find_link_by_remote_port(cc, sp1); CHECK(poll_until(c_link_down, LINK_DOWN_TIMEOUT_MS, "V1 link down") == 0, "V1 link did not go down"); CHECK(cc->links_up == 1, "client links_up != 1 after V1 kill (got %d)", cc->links_up); CHECK(cc->initialized == 1, "client initialized lost after V1 kill"); CHECK(sc->reinit_count == srv_reinit0 && cc->reinit_count == cli_reinit0, "reinit after V1 kill"); CHECK(run_traffic(TRAFFIC_PACKETS, TRAFFIC_TIMEOUT_MS) == 0, "failover traffic after V1 kill failed"); // -- kill initial (последний) линк: всё вниз, реинита нет -- printf("[A] killing initial link (server socket %d)...\n", server_port); kill_server_socket(s0, server_port); g_link = find_link_by_remote_port(cc, server_port); CHECK(poll_until(c_link_down, LINK_DOWN_TIMEOUT_MS, "initial link down") == 0, "initial link did not go down"); CHECK(poll_until(c_client_all_down, LINK_DOWN_TIMEOUT_MS, "client all links down") == 0, "client links not all down"); CHECK(poll_until(c_server_all_down, LINK_DOWN_TIMEOUT_MS, "server all links down") == 0, "server links not all down"); CHECK(cc->reinit_count == cli_reinit0, "reinit when last link died (client %u->%u)", cli_reinit0, cc->reinit_count); CHECK(sc->reinit_count == srv_reinit0, "reinit when last link died (server %u->%u)", srv_reinit0, sc->reinit_count); printf("[A] Session A PASSED\n"); etcp_conn_ref_free(sc); etcp_conn_ref_free(cc); stop_instances(); return 0; fail: if (held) { etcp_conn_ref_free(sc); etcp_conn_ref_free(cc); } stop_instances(); return -1; } // ===== Session: коллизии ===== static int session_collision(int which) { const char* names[] = { "V3a: both correct addr (true collision)", "V3b: client wrong addr (server-only establish)", "V3c: server wrong addr (client-only establish)", }; printf("\n=== Session %s ===\n", names[which]); if (start_instances() != 0) return -1; struct ETCP_CONN* sc = get_conn(server_instance); struct ETCP_CONN* cc = get_conn(client_instance); CHECK(sc && cc, "no conn"); uint32_t srv_reinit0 = sc->reinit_count; uint32_t cli_reinit0 = cc->reinit_count; uint64_t srv_epoch = sc->reset_id, cli_epoch = cc->reset_id; CHECK(run_traffic(TRAFFIC_PACKETS, TRAFFIC_TIMEOUT_MS) == 0, "traffic before link add failed"); int sp3 = alloc_port(); int cp1 = alloc_port(); int dead = alloc_port(); struct ETCP_SOCKET* s3 = add_udp_socket(server_instance, sp3, "dyn_s3"); CHECK(s3, "failed to add server socket S3"); struct ETCP_SOCKET* c1 = add_udp_socket(client_instance, cp1, "dyn_c1"); CHECK(c1, "failed to add client socket C1"); struct sockaddr_storage s3_addr; mk_addr(&s3_addr, sp3); struct sockaddr_storage c1_addr; mk_addr(&c1_addr, cp1); struct sockaddr_storage dead_addr; mk_addr(&dead_addr, dead); struct ETCP_LINK* lc3 = NULL; struct ETCP_LINK* ls3 = NULL; if (which == COL_A) { lc3 = etcp_link_new(cc, c1, &s3_addr, 0); // клиент -> S3 (верно) ls3 = etcp_link_new(sc, s3, &c1_addr, 0); // сервер -> C1 (верно) } else if (which == COL_B) { lc3 = etcp_link_new(cc, c1, &dead_addr, 0); // клиент -> мёртвый порт ls3 = etcp_link_new(sc, s3, &c1_addr, 0); // сервер -> C1 (верно) } else { lc3 = etcp_link_new(cc, c1, &s3_addr, 0); // клиент -> S3 (верно) ls3 = etcp_link_new(sc, s3, &dead_addr, 0); // сервер -> мёртвый порт } CHECK(lc3 && ls3, "link creation failed (lc3=%p ls3=%p)", (void*)lc3, (void*)ls3); if (which == COL_A) { CHECK(run_traffic(TRAFFIC_PACKETS, TRAFFIC_TIMEOUT_MS) == 0, "traffic during collision failed"); g_conn = cc; g_port = sp3; CHECK(poll_until(c_link_on_port_up, COLLISION_TIMEOUT_MS, "collision client link up") == 0, "client link down"); g_conn = sc; g_port = cp1; CHECK(poll_until(c_link_on_port_up, COLLISION_TIMEOUT_MS, "collision server link up") == 0, "server link down"); struct ETCP_LINK* cl = find_link_by_remote_port(cc, sp3); struct ETCP_LINK* sl = find_link_by_remote_port(sc, cp1); CHECK(cl->is_server != sl->is_server, "collision left both ends in the same role"); CHECK(count_links(sc) == 2 && count_links(cc) == 2, "collision created duplicate links"); CHECK(check_link_traffic(sp3, "collision") == 0, "new link carried no traffic"); } else { struct ETCP_LINK* good = (which == COL_B) ? ls3 : lc3; g_link = good; CHECK(poll_until(c_link_up, LINK_UP_TIMEOUT_MS, "good link up") == 0, "good link did not come up"); uint16_t peer_port = (which == COL_B) ? sp3 : cp1; g_conn = (which == COL_B) ? cc : sc; g_port = peer_port; CHECK(poll_until(c_link_on_port_up, LINK_UP_TIMEOUT_MS, "peer incoming link up") == 0, "peer incoming link did not come up"); CHECK(sc->reinit_count == srv_reinit0 && cc->reinit_count == cli_reinit0, "unexpected reinit (srv %u->%u cli %u->%u)", srv_reinit0, sc->reinit_count, cli_reinit0, cc->reinit_count); CHECK(run_traffic(TRAFFIC_PACKETS, TRAFFIC_TIMEOUT_MS) == 0, "traffic failed"); } CHECK(sc->reinit_count == srv_reinit0 && cc->reinit_count == cli_reinit0, "link add reset the stream"); CHECK(sc->reset_id == srv_epoch && cc->reset_id == cli_epoch, "link add changed session epochs"); printf("[%s] PASSED\n", names[which]); stop_instances(); return 0; fail: stop_instances(); return -1; } int main(void) { if (create_temp_configs() != 0) return 1; debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR); if (getenv("UTUN_TEST_DEBUG")) { debug_set_level(DEBUG_LEVEL_DEBUG); debug_set_category_level(DEBUG_CATEGORY_ETCP, DEBUG_LEVEL_DEBUG); debug_set_category_level(DEBUG_CATEGORY_CONNECTION, DEBUG_LEVEL_DEBUG); debug_set_category_level(DEBUG_CATEGORY_KEEPALIVE, DEBUG_LEVEL_DEBUG); debug_set_category_level(DEBUG_CATEGORY_DEBUG, DEBUG_LEVEL_DEBUG); } printf("=== ETCP Multilink Test ===\n"); printf("server_port=%d client_port=%d dyn_ports=%d..\n", server_port, client_port, next_port); utun_instance_set_tun_init_enabled(0); ua = uasync_create(); int rc = 0; rc |= session_a() != 0; rc |= session_collision(COL_A) != 0; rc |= session_collision(COL_B) != 0; rc |= session_collision(COL_C) != 0; if (ua) { uasync_poll(ua, 0); uasync_destroy(ua, 0); ua = NULL; } cleanup_temp_configs(); if (rc == 0) { printf("\n=== ALL TESTS PASSED ===\n"); return 0; } printf("\n=== TESTS FAILED ===\n"); return 1; }