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.
 
 
 
 
 
 

720 lines
24 KiB

// test_etcp_router_unit.c — Юнит-тест etcp_router: reorder, seq, ACK, conn lifecycle
// Без ETCP-соединений, без сокетов, без BGP.
// Инжектит SVC_ROUTE пакеты напрямую через etcp_router_recv_cb из api_bindings.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/time.h>
#include "../src/etcp.h"
#include "../src/etcp_api.h"
#include "../src/etcp_router.h"
#include "../src/utun_instance.h"
#include "../lib/u_async.h"
#include "../lib/ll_queue.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#define TEST_SVC_ID 0x42
#define TEST_SVC_ID2 0x43
#define TEST_REMOTE_NODE 0xAAAA000000000001ULL
#define TEST_REMOTE_NODE2 0xAAAA000000000002ULL
// ======================== Test handler state ========================
static struct {
int delivered;
int errors;
uint32_t expected_seq;
uint32_t last_seq;
uint8_t marker;
} rx;
static int g_notify_count = 0;
static void test_handler(struct ETCP_CONN* conn, struct ll_entry* entry) {
(void)conn;
if (!entry || !entry->dgram || entry->len < 2) {
g_notify_count++;
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); }
return;
}
// close/restart: len==9 (svc_id + node_id), conn==NULL
if (entry->len == 9 && !conn) {
g_notify_count++;
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
if (entry->dgram[1] != rx.marker) { rx.errors++; } else { rx.delivered++; rx.last_seq = rx.expected_seq; rx.expected_seq++; }
queue_entry_free(entry); queue_dgram_free(entry);
}
static void rx_reset(uint8_t marker) {
memset(&rx, 0, sizeof(rx));
rx.marker = marker;
}
// ======================== Inject helper ========================
static struct ETCP_CONN fake_conn;
static void inject(etcp_recv_fn recv_cb, struct UTUN_INSTANCE* inst,
uint64_t src, uint8_t svc_id, uint32_t seq,
const uint8_t* pl, size_t pl_len, int is_ack, uint8_t flags) {
struct SVC_ROUTE_HDR hdr;
memset(&hdr, 0, sizeof(hdr));
hdr.cmd = ETCP_ID_SVC_ROUTE;
hdr.dst_node_id = inst->node_id;
hdr.src_node_id = src;
hdr.seq = seq;
hdr.svc_id = svc_id;
hdr.flags = flags;
size_t total = SVC_ROUTE_HDR_SIZE + (is_ack ? 0 : pl_len);
struct ll_entry* e = queue_entry_new(0);
if (!e) { printf(" inject: queue_entry_new failed\n"); return; }
e->dgram = u_malloc(total);
if (!e->dgram) { queue_entry_free(e); return; }
memcpy(e->dgram, &hdr, SVC_ROUTE_HDR_SIZE);
if (!is_ack && pl_len > 0)
memcpy(e->dgram + SVC_ROUTE_HDR_SIZE, pl, pl_len);
e->len = total;
recv_cb(&fake_conn, e);
}
// ======================== Setup ========================
#define SETUP() do { \
ua = uasync_create(); \
if (!ua) { printf(" SETUP: uasync_create failed\n"); return -1; } \
memset(&inst, 0, sizeof(inst)); \
inst.ua = ua; \
inst.node_id = 0xBBBB000000000001ULL; \
debug_config_init(); \
debug_set_level(DEBUG_LEVEL_ERROR); \
debug_set_categories(DEBUG_CATEGORY_ALL); \
memset(&fake_conn, 0, sizeof(fake_conn)); \
fake_conn.instance = &inst; \
TESTASSERT(etcp_router_init(&inst) == 0); \
TESTASSERT(etcp_router_bind(&inst, TEST_SVC_ID, test_handler) == 0); \
recv_cb = inst.api_bindings.callbacks[ETCP_ID_SVC_ROUTE]; \
TESTASSERT(recv_cb != NULL); \
} while(0)
#define TEARDOWN() do { \
etcp_router_destroy(&inst); \
uasync_destroy(ua, 0); \
ua = NULL; \
} while(0)
// ======================== Macros ========================
static int test_total = 0, test_passed = 0, test_failed = 0;
#define TESTASSERT(cond) do { \
if (!(cond)) { printf(" FAIL: %s:%d — %s\n", __FILE__, __LINE__, #cond); return -1; } \
} while(0)
#define TEST(name) do { \
test_total++; \
printf("TEST %d: %-50s ", test_total, name); fflush(stdout); \
} while(0)
#define PASS() do { puts("PASS"); test_passed++; } while(0)
#define FAIL(msg) do { printf("FAIL: %s\n", msg); test_failed++; return -1; } while(0)
// ======================== Tests ========================
static int test_init_destroy(void) {
TEST("init/destroy");
struct UASYNC* ua = uasync_create();
if (!ua) FAIL("uasync_create");
struct UTUN_INSTANCE inst;
memset(&inst, 0, sizeof(inst));
inst.ua = ua;
inst.node_id = 1;
debug_config_init();
debug_set_level(DEBUG_LEVEL_ERROR);
debug_set_categories(DEBUG_CATEGORY_ALL);
memset(&fake_conn, 0, sizeof(fake_conn));
fake_conn.instance = &inst;
if (etcp_router_init(&inst) != 0) FAIL("init");
if (inst.router_conns == NULL) FAIL("router_conns NULL after init");
if (inst.api_bindings.callbacks[ETCP_ID_SVC_ROUTE] == NULL) FAIL("recv_cb not bound");
etcp_router_destroy(&inst);
if (inst.router_conns != NULL) FAIL("router_conns not NULL after destroy");
if (inst.api_bindings.callbacks[ETCP_ID_SVC_ROUTE] != NULL) FAIL("recv_cb still bound");
uasync_destroy(ua, 0);
PASS();
return 0;
}
static int test_bind_unbind(void) {
TEST("bind/unbind API");
struct UASYNC* ua = uasync_create();
struct UTUN_INSTANCE inst;
memset(&inst, 0, sizeof(inst));
inst.ua = ua;
inst.node_id = 1;
debug_config_init();
debug_set_level(DEBUG_LEVEL_ERROR);
debug_set_categories(DEBUG_CATEGORY_ALL);
if (etcp_router_init(&inst) != 0) FAIL("init");
// bind
if (etcp_router_bind(&inst, 0xEE, test_handler) != 0) FAIL("bind");
if (inst.router_bindings.callbacks[0xEE] != test_handler) FAIL("cb not set");
// rebind (overwrite)
if (etcp_router_bind(&inst, 0xEE, test_handler) != 0) FAIL("rebind");
// unbind
if (etcp_router_unbind(&inst, 0xEE) != 0) FAIL("unbind");
if (inst.router_bindings.callbacks[0xEE] != NULL) FAIL("cb not cleared");
// double unbind
if (etcp_router_unbind(&inst, 0xEE) == 0) FAIL("double unbind should fail");
// null args
if (etcp_router_bind(NULL, 0, test_handler) == 0) FAIL("bind null inst");
etcp_router_destroy(&inst);
uasync_destroy(ua, 0);
PASS();
return 0;
}
static int test_conn_get_create(void) {
TEST("conn_get — create");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
struct ETCP_ROUTER_CONN* c = etcp_router_conn_get(&inst, TEST_REMOTE_NODE, TEST_SVC_ID);
if (!c) FAIL("conn_get returned NULL");
if (c->remote_node_id != TEST_REMOTE_NODE) FAIL("wrong remote_node_id");
if (c->svc_id != TEST_SVC_ID) FAIL("wrong svc_id");
if (c->tx_seq != 0) FAIL("tx_seq != 0");
if (c->rx_seq != 0) FAIL("rx_seq != 0");
if (c->tx_acked != 0) FAIL("tx_acked != 0");
if (c->last_sent_ack_seq != 0) FAIL("last_sent_ack_seq != 0");
if (c->recv_q == NULL) FAIL("recv_q is NULL");
if (c->last_dgram_ts == 0) FAIL("last_dgram_ts not set");
if (c->ack_timer != NULL) FAIL("ack_timer set at creation");
etcp_router_conn_close(c);
TEARDOWN();
PASS();
return 0;
}
static int test_conn_get_reuse(void) {
TEST("conn_get — reuse / different");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
struct ETCP_ROUTER_CONN* c1 = etcp_router_conn_get(&inst, TEST_REMOTE_NODE, TEST_SVC_ID);
if (!c1) FAIL("c1 NULL");
struct ETCP_ROUTER_CONN* c2 = etcp_router_conn_get(&inst, TEST_REMOTE_NODE, TEST_SVC_ID);
if (c1 != c2) FAIL("reuse: different pointers");
struct ETCP_ROUTER_CONN* c3 = etcp_router_conn_get(&inst, TEST_REMOTE_NODE2, TEST_SVC_ID);
if (c3 == c1) FAIL("different remote, same pointer");
struct ETCP_ROUTER_CONN* c4 = etcp_router_conn_get(&inst, TEST_REMOTE_NODE, TEST_SVC_ID2);
if (c4 == c1) FAIL("different svc, same pointer");
etcp_router_conn_close(c1);
etcp_router_conn_close(c3);
etcp_router_conn_close(c4);
TEARDOWN();
PASS();
return 0;
}
static int test_conn_close(void) {
TEST("conn_close — new after close");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
struct ETCP_ROUTER_CONN* c1 = etcp_router_conn_get(&inst, TEST_REMOTE_NODE, TEST_SVC_ID);
if (!c1) FAIL("c1 NULL");
c1->tx_seq = 99; // mark
etcp_router_conn_close(c1);
struct ETCP_ROUTER_CONN* c2 = etcp_router_conn_get(&inst, TEST_REMOTE_NODE, TEST_SVC_ID);
if (!c2) FAIL("c2 NULL");
if (c2->tx_seq != 0 || c2->rx_seq != 0) FAIL("not fresh after close+reopen");
etcp_router_conn_close(c2);
TEARDOWN();
PASS();
return 0;
}
// ======================== Seq/reorder tests ========================
static int test_in_order(void) {
TEST("in-order delivery (seq 0,1,2)");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
struct ETCP_ROUTER_CONN* c = etcp_router_conn_get(&inst, TEST_REMOTE_NODE, TEST_SVC_ID);
if (!c) FAIL("conn_get failed");
rx_reset(0xAA);
uint8_t data[] = { TEST_SVC_ID, 0xAA, 0 };
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, data + 1, 2, 0, 0);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 1, data + 1, 2, 0, 0);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 2, data + 1, 2, 0, 0);
if (rx.delivered != 3) FAIL("delivered != 3");
if (rx.errors != 0) FAIL("errors != 0");
if (c->rx_seq != 3) FAIL("rx_seq != 3");
etcp_router_conn_close(c);
TEARDOWN();
PASS();
return 0;
}
static int test_out_of_order(void) {
TEST("out-of-order (0,2,3 then 1 → assembly to 3)");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
struct ETCP_ROUTER_CONN* c = etcp_router_conn_get(&inst, TEST_REMOTE_NODE, TEST_SVC_ID);
if (!c) FAIL("conn_get failed");
rx_reset(0xBB);
uint8_t data[] = { TEST_SVC_ID, 0xBB, 0 };
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, data + 1, 2, 0, 0);
if (rx.delivered != 1) FAIL("after seq 0: delivered != 1");
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 2, data + 1, 2, 0, 0);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 3, data + 1, 2, 0, 0);
if (rx.delivered != 1) FAIL("after seq 2,3: still delivered != 1 (gap at 1)");
// Fill the gap
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 1, data + 1, 2, 0, 0);
if (rx.delivered != 4) FAIL("after seq 1 fill: delivered != 4");
if (rx.errors != 0) FAIL("errors != 0");
if (c->rx_seq != 4) FAIL("rx_seq != 4");
etcp_router_conn_close(c);
TEARDOWN();
PASS();
return 0;
}
static int test_duplicate_same(void) {
TEST("duplicate — same seq injected twice");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
struct ETCP_ROUTER_CONN* c = etcp_router_conn_get(&inst, TEST_REMOTE_NODE, TEST_SVC_ID);
if (!c) FAIL("conn_get failed");
rx_reset(0xCC);
uint8_t data[] = { TEST_SVC_ID, 0xCC, 0 };
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, data + 1, 2, 0, 0);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 1, data + 1, 2, 0, 0);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 1, data + 1, 2, 0, 0); // dup
if (rx.delivered != 2) FAIL("delivered != 2 (dup dropped)");
if (c->rx_seq != 2) FAIL("rx_seq != 2");
etcp_router_conn_close(c);
TEARDOWN();
PASS();
return 0;
}
static int test_duplicate_past(void) {
TEST("duplicate — past seq (already delivered)");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
struct ETCP_ROUTER_CONN* c = etcp_router_conn_get(&inst, TEST_REMOTE_NODE, TEST_SVC_ID);
if (!c) FAIL("conn_get failed");
rx_reset(0xDD);
uint8_t data[] = { TEST_SVC_ID, 0xDD, 0 };
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, data + 1, 2, 0, 0);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 1, data + 1, 2, 0, 0);
if (rx.delivered != 2) FAIL("delivered != 2");
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, data + 1, 2, 0, 0); // past
if (rx.delivered != 2) FAIL("delivered != 2 (past dup dropped)");
if (c->rx_seq != 2) FAIL("rx_seq != 2");
etcp_router_conn_close(c);
TEARDOWN();
PASS();
return 0;
}
static int test_out_of_bounds(void) {
TEST("out of bounds — seq too far ahead");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
struct ETCP_ROUTER_CONN* c = etcp_router_conn_get(&inst, TEST_REMOTE_NODE, TEST_SVC_ID);
if (!c) FAIL("conn_get failed");
rx_reset(0xEE);
uint8_t data[] = { TEST_SVC_ID, 0xEE, 0 };
uint32_t bad_seq = ROUTER_MAX_INFLIGHT + 2;
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, bad_seq, data + 1, 2, 0, 0);
if (rx.delivered != 0) FAIL("out-of-bounds delivered");
if (c->rx_seq != 0) FAIL("rx_seq changed on bounds drop");
etcp_router_conn_close(c);
TEARDOWN();
PASS();
return 0;
}
static int test_circular_wrap(void) {
TEST("32-bit circular wrap-around");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
struct ETCP_ROUTER_CONN* c = etcp_router_conn_get(&inst, TEST_REMOTE_NODE, TEST_SVC_ID);
if (!c) FAIL("conn_get failed");
c->rx_seq = 0xFFFFFFFE;
rx_reset(0x11);
rx.expected_seq = 0xFFFFFFFE;
uint8_t data[] = { TEST_SVC_ID, 0x11, 0 };
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0xFFFFFFFE, data + 1, 2, 0, 0);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0xFFFFFFFF, data + 1, 2, 0, 0);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, data + 1, 2, 0, 0);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 1, data + 1, 2, 0, 0);
if (rx.delivered != 4) FAIL("delivered != 4 (wrap)");
if (c->rx_seq != 2) FAIL("rx_seq != 2 after wrap (wrapped)");
etcp_router_conn_close(c);
TEARDOWN();
PASS();
return 0;
}
static int test_data_integrity(void) {
TEST("data integrity — payload preserved");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
struct ETCP_ROUTER_CONN* c = etcp_router_conn_get(&inst, TEST_REMOTE_NODE, TEST_SVC_ID);
if (!c) FAIL("conn_get failed");
rx_reset(0x77);
uint8_t payload[10] = { 0x77, 0xA1, 0xB2, 0xC3, 0xD4, 0xE5, 0xF6, 0x07, 0x18, 0x29 };
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, payload, 10, 0, 0);
if (rx.delivered != 1) FAIL("delivered != 1");
if (rx.errors != 0) FAIL("data mismatch");
etcp_router_conn_close(c);
TEARDOWN();
PASS();
return 0;
}
static int test_ack_receive(void) {
TEST("ACK receive — tx_acked updated");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
struct ETCP_ROUTER_CONN* c = etcp_router_conn_get(&inst, TEST_REMOTE_NODE, TEST_SVC_ID);
if (!c) FAIL("conn_get failed");
rx_reset(0x00);
// Send ACK: is_ack=1, seq=7 means remote has rx_seq=7
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 7, NULL, 0, 1, 0);
if (c->tx_acked != 7) FAIL("tx_acked not updated from ACK");
if (rx.delivered != 0) FAIL("ACK delivered to handler (should not)");
if (c->last_dgram_ts == 0) FAIL("last_dgram_ts not updated on ACK");
etcp_router_conn_close(c);
TEARDOWN();
PASS();
return 0;
}
static int test_ack_timer(void) {
TEST("ACK timer scheduled on data receive");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
struct ETCP_ROUTER_CONN* c = etcp_router_conn_get(&inst, TEST_REMOTE_NODE, TEST_SVC_ID);
if (!c) FAIL("conn_get failed");
if (c->ack_timer != NULL) FAIL("ack_timer set before any data");
rx_reset(0x55);
uint8_t data[] = { TEST_SVC_ID, 0x55, 0 };
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, data + 1, 2, 0, 0);
if (c->ack_timer == NULL && c->last_sent_ack_seq != 1) FAIL("ack not sent (no timer, no immediate)");
if (c->ack_timer != NULL && c->last_sent_ack_seq != 0) FAIL("last_sent_ack_seq changed before timer fired");
// Cancel timer manually (it would fire in real event loop)
if (c->ack_timer) uasync_cancel_timeout(ua, c->ack_timer);
c->ack_timer = NULL;
etcp_router_conn_close(c);
TEARDOWN();
PASS();
return 0;
}
static int test_send_q(void) {
TEST("send_q — inflight full queues, not lost");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
struct ETCP_ROUTER_CONN* c = etcp_router_conn_get(&inst, TEST_REMOTE_NODE, TEST_SVC_ID);
if (!c) FAIL("conn_get failed");
// Simulate full inflight: set tx_acked far behind tx_seq
c->tx_seq = ROUTER_MAX_INFLIGHT;
c->tx_acked = 0;
if (c->send_q == NULL) FAIL("send_q is NULL");
// Send via conn_send — should queue, not drop
uint8_t data[4] = { 1, 2, 3, 4 };
int ret = etcp_router_conn_send(c, data, sizeof(data));
if (ret != 0) FAIL("conn_send should return 0 (queued in send_q)");
if (c->send_blocked == 0) FAIL("send_blocked not set");
if (c->send_resume_timer == NULL) FAIL("send_resume_timer not set");
if (queue_entry_count(c->send_q) != 1) FAIL("send_q count != 1");
if (c->tx_seq != ROUTER_MAX_INFLIGHT) FAIL("tx_seq advanced while queued");
// Cancel timers manually
if (c->send_resume_timer) { uasync_cancel_timeout(ua, c->send_resume_timer); c->send_resume_timer = NULL; }
etcp_router_conn_close(c);
TEARDOWN();
PASS();
return 0;
}
static int test_loopback(void) {
TEST("loopback — etcp_route_send to self");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
rx_reset(0x66);
uint8_t buf[4] = { TEST_SVC_ID, 0x66, 0x00, 0x00 };
struct ll_entry* e = queue_entry_new(0);
if (!e) FAIL("queue_entry_new");
e->dgram = u_malloc(4);
if (!e->dgram) { queue_entry_free(e); FAIL("u_malloc"); }
memcpy(e->dgram, buf, 4);
e->len = 4;
if (etcp_route_send(&inst, inst.node_id, e) != 0) FAIL("loopback send failed");
if (rx.delivered != 1) FAIL("loopback not delivered");
if (rx.errors != 0) FAIL("loopback data mismatch");
TEARDOWN();
PASS();
return 0;
}
static int test_timestamp(void) {
TEST("last_dgram_ts updated on data");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
struct ETCP_ROUTER_CONN* c = etcp_router_conn_get(&inst, TEST_REMOTE_NODE, TEST_SVC_ID);
if (!c) FAIL("conn_get failed");
uint16_t before = c->last_dgram_ts;
rx_reset(0x99);
uint8_t data[] = { TEST_SVC_ID, 0x99, 0 };
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, data + 1, 2, 0, 0);
if (c->last_dgram_ts == 0) FAIL("last_dgram_ts zero after data");
// Cancel ack_timer
if (c->ack_timer) { uasync_cancel_timeout(ua, c->ack_timer); c->ack_timer = NULL; }
etcp_router_conn_close(c);
TEARDOWN();
PASS();
return 0;
}
static int test_conn_send_no_bgp(void) {
TEST("conn_send — no BGP returns error");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
struct ETCP_ROUTER_CONN* c = etcp_router_conn_get(&inst, TEST_REMOTE_NODE, TEST_SVC_ID);
if (!c) FAIL("conn_get failed");
uint8_t data[4] = { 1, 2, 3, 4 };
int ret = etcp_router_conn_send(c, data, sizeof(data));
if (ret == 0) FAIL("conn_send should fail without BGP");
etcp_router_conn_close(c);
TEARDOWN();
PASS();
return 0;
}
// ======================== Helper: pump random data ========================
static int pump_data(etcp_recv_fn recv_cb, struct UTUN_INSTANCE* inst,
uint8_t marker, int count, uint8_t flags) {
uint8_t data[] = { TEST_SVC_ID, marker, 0 };
for (int i = 0; i < count; i++)
inject(recv_cb, inst, TEST_REMOTE_NODE, TEST_SVC_ID, i, data + 1, 2, 0, flags);
return count;
}
// ======================== Restart/session tests ========================
static int test_rst_flag(void) {
TEST("RST flag — rconn destroyed, service notified");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
g_notify_count = 0;
int n = rand() % 10 + 1;
rx_reset(0xA1);
pump_data(recv_cb, &inst, 0xA1, n, 0);
if (rx.delivered != n) FAIL("initial pump failed");
uint8_t data[] = { TEST_SVC_ID, 0x01, 0 };
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, n, data + 1, 2, 0, ROUTER_FLAG_RST);
if (g_notify_count != 1) FAIL("RST should notify service");
struct ETCP_ROUTER_CONN* c = etcp_router_conn_get(&inst, TEST_REMOTE_NODE, TEST_SVC_ID);
if (!c) FAIL("new rconn should exist");
etcp_router_conn_close(c);
TEARDOWN();
PASS();
return 0;
}
static int test_client_restart(void) {
TEST("client restart — START flag detected after random data");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
g_notify_count = 0;
int n = rand() % 10 + 1;
rx_reset(0xB1);
pump_data(recv_cb, &inst, 0xB1, n, 0);
if (rx.delivered != n) FAIL("initial pump failed");
rx_reset(0xB2);
uint8_t data[] = { TEST_SVC_ID, 0xB2, 0 };
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, data + 1, 2, 0, ROUTER_FLAG_START);
if (g_notify_count != 1) FAIL("restart not notified");
if (rx.delivered < 1) FAIL("data after restart not delivered");
TEARDOWN();
PASS();
return 0;
}
static int test_sess_id_change(void) {
TEST("sess_id change — 0→1 with START triggers restart");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
g_notify_count = 0;
int n = rand() % 10 + 1;
rx_reset(0xC1);
pump_data(recv_cb, &inst, 0xC1, n, 0);
if (rx.delivered != n) FAIL("initial pump failed");
rx_reset(0xC2);
uint8_t data[] = { TEST_SVC_ID, 0xC2, 0 };
uint8_t f = (1 << ROUTER_SESS_ID_SHIFT) | ROUTER_FLAG_START;
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, data + 1, 2, 0, f);
if (g_notify_count != 1) FAIL("restart not notified");
if (rx.delivered < 1) FAIL("data after restart not delivered");
TEARDOWN();
PASS();
return 0;
}
static int test_sess_id_wrap(void) {
TEST("sess_id wrap — 3→0 with START triggers restart");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
g_notify_count = 0;
int n = rand() % 10 + 1;
uint8_t f3 = (3 << ROUTER_SESS_ID_SHIFT);
rx_reset(0xD1);
struct ETCP_ROUTER_CONN* c = etcp_router_conn_get(&inst, TEST_REMOTE_NODE, TEST_SVC_ID);
if (!c) FAIL("rconn not found");
c->peer_sess_id = 3;
pump_data(recv_cb, &inst, 0xD1, n, f3);
if (rx.delivered != n) FAIL("initial pump failed");
rx_reset(0xD2);
g_notify_count = 0;
uint8_t data[] = { TEST_SVC_ID, 0xD2, 0 };
uint8_t f0 = (0 << ROUTER_SESS_ID_SHIFT) | ROUTER_FLAG_START;
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, data + 1, 2, 0, f0);
if (g_notify_count != 1) FAIL("restart not notified on wrap");
if (rx.delivered < 1) FAIL("data after wrap not delivered");
TEARDOWN();
PASS();
return 0;
}
static int test_server_reinit(void) {
TEST("server reinit — new rconn gets start_sent=0 sess_id=0");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
int n = rand() % 10 + 1;
pump_data(recv_cb, &inst, 0xE1, n, 0);
etcp_router_destroy(&inst);
memset(&inst.api_bindings, 0, sizeof(inst.api_bindings));
memset(&inst.router_bindings, 0, sizeof(inst.router_bindings));
if (etcp_router_init(&inst) != 0) FAIL("reinit failed");
etcp_router_bind(&inst, TEST_SVC_ID, test_handler);
recv_cb = inst.api_bindings.callbacks[ETCP_ID_SVC_ROUTE];
if (!recv_cb) FAIL("recv_cb not bound");
struct ETCP_ROUTER_CONN* c = etcp_router_conn_get(&inst, TEST_REMOTE_NODE, TEST_SVC_ID);
if (!c) FAIL("rconn after reinit");
if (c->start_sent != 0) FAIL("start_sent != 0 after reinit");
if (c->sess_id != 0) FAIL("sess_id != 0 after reinit");
etcp_router_conn_close(c);
TEARDOWN();
PASS();
return 0;
}
// ======================== Main ========================
int main(void) {
// Run all tests
test_init_destroy();
test_bind_unbind();
test_conn_get_create();
test_conn_get_reuse();
test_conn_close();
test_in_order();
test_out_of_order();
test_duplicate_same();
test_duplicate_past();
test_out_of_bounds();
test_circular_wrap();
test_data_integrity();
test_ack_receive();
test_ack_timer();
test_send_q();
test_loopback();
test_timestamp();
test_conn_send_no_bgp();
test_rst_flag();
test_client_restart();
test_sess_id_change();
test_sess_id_wrap();
test_server_reinit();
printf("\n=== Results: %d/%d passed, %d failed ===\n", test_passed, test_total, test_failed);
return test_failed > 0 ? 1 : 0;
}