Files
utun2/tests/test_etcp_router_unit.c
T

1379 lines
55 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 "etcp.h"
#include "etcp_api.h"
#include "etcp_router.h"
#include "topo_group.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"
#include "../src/transport_layer/secure_channel.h"
#include <openssl/sha.h>
#define TEST_SVC_ID 0x42
#define TEST_SVC_ID2 0x43
#define TEST_REMOTE_NODE 0xAAAA000000000001ULL
#define TEST_REMOTE_NODE2 0xAAAA000000000002ULL
#define TEST_E2E_PEER 0xEEEE000000000001ULL
// ======================== 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 < ROUTER_SVC_HDR_SIZE) {
g_notify_count++;
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); }
return;
}
// close/restart: [svc_id][src][dst] без payload
if (entry->len == ROUTER_SVC_HDR_SIZE) {
g_notify_count++;
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
if (entry->dgram[ROUTER_SVC_PAYLOAD_OFF] != rx.marker) { rx.errors++; } else { rx.delivered++; rx.last_seq = rx.expected_seq; rx.expected_seq++; }
queue_dgram_free(entry); queue_entry_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;
/* Older unit cases construct the immutable body; add the mandatory direct-hop trailer. */
static void inject_wire(struct ETCP_CONN* conn, struct ll_entry* e) {
size_t len = e->len;
uint64_t src = ((struct SVC_ROUTE_HDR*)e->dgram)->src_node_id;
uint8_t* wire = u_malloc(len + 9); if (!wire) abort();
memcpy(wire, e->dgram, len); memcpy(wire + len, &src, 8); wire[len + 8] = 1;
queue_dgram_free(e); e->dgram = wire; e->len = len + 9;
conn->peer_node_id = src;
conn->instance->api_bindings.callbacks[ETCP_RT_ID_SVC_ROUTE](conn, e);
}
// Настоящий HELLO/challenge/CONFIRM через публичный wire callback; состояние не подменяем.
static void establish(etcp_recv_fn cb, struct UTUN_INSTANCE* inst, uint64_t src, uint8_t svc, uint64_t epoch) {
struct ETCP_ROUTER_CONN* c = etcp_router_conn_get(inst, TOPO_GROUP_UTUN, src, svc);
if (c->peer_reset_id == epoch) return;
c->challenge_sent_tb = 0; // тест не ждёт throttle между двумя намеренными рестартами
struct SVC_ROUTE_HDR h = {0};
h.cmd = ETCP_RT_ID_SVC_ROUTE; h.group_id = TOPO_GROUP_UTUN;
h.src_node_id = src; h.dst_node_id = inst->node_id; h.svc_id = svc;
h.reset_id = epoch; h.flags = ROUTER_FLAG_START;
struct ll_entry* e = ll_alloc_lldgram(sizeof(h));
memcpy(e->dgram, &h, sizeof(h)); e->len = sizeof(h); cb(&fake_conn, e);
h.flags = ROUTER_FLAG_START | ROUTER_FLAG_RST; h.peer_reset_id = c->reset_id; h.challenge = c->pending_challenge;
e = ll_alloc_lldgram(sizeof(h)); memcpy(e->dgram, &h, sizeof(h)); e->len = sizeof(h); cb(&fake_conn, e);
}
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_RT_ID_SVC_ROUTE;
hdr.group_id = TOPO_GROUP_UTUN;
hdr.dst_node_id = inst->node_id;
hdr.src_node_id = src;
hdr.seq = seq;
hdr.svc_id = svc_id;
struct ETCP_ROUTER_CONN* c = etcp_router_conn_get(inst, TOPO_GROUP_UTUN, src, svc_id);
if (!c->peer_reset_id) establish(recv_cb, inst, src, svc_id, src);
hdr.reset_id = c->peer_reset_id; hdr.peer_reset_id = c->reset_id;
hdr.flags = is_ack ? flags : (flags & ~ROUTER_FLAG_START);
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);
}
// Аналог inject, но с явным reset_id (для тестов эпохи/рестарта).
static void inject_epoch(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, uint64_t reset_id) {
struct SVC_ROUTE_HDR hdr;
memset(&hdr, 0, sizeof(hdr));
hdr.cmd = ETCP_RT_ID_SVC_ROUTE;
hdr.group_id = TOPO_GROUP_UTUN;
hdr.dst_node_id = inst->node_id;
hdr.src_node_id = src;
hdr.seq = seq;
hdr.svc_id = svc_id;
struct ETCP_ROUTER_CONN* c = etcp_router_conn_get(inst, TOPO_GROUP_UTUN, src, svc_id);
if (flags & ROUTER_FLAG_START) establish(recv_cb, inst, src, svc_id, reset_id);
hdr.flags = is_ack ? flags : (flags & ~ROUTER_FLAG_START);
hdr.reset_id = reset_id ? reset_id : c->peer_reset_id;
hdr.peer_reset_id = c->reset_id;
size_t total = SVC_ROUTE_HDR_SIZE + (is_ack ? 0 : pl_len);
struct ll_entry* e = queue_entry_new(0);
if (!e) { printf(" inject_epoch: 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; \
fake_conn.send_input_q = queue_new(ua, 0, 0, 0, "test_wire"); \
TESTASSERT(etcp_router_init(&inst) == 0); \
TESTASSERT(etcp_router_bind(&inst, TEST_SVC_ID, test_handler) == 0); \
recv_cb = inject_wire; \
TESTASSERT(recv_cb != NULL); \
} while(0)
#define TEARDOWN() do { \
etcp_router_destroy(&inst); \
struct ll_entry* leftover; \
while ((leftover = queue_data_get(fake_conn.send_input_q))) { queue_dgram_free(leftover); queue_entry_free(leftover); } \
queue_free(fake_conn.send_input_q); fake_conn.send_input_q = NULL; \
uasync_poll(ua, 0); \
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); test_failed++; 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)
// ======================== Sign test infrastructure ========================
static int g_sign_ready = 0;
static struct SC_MYKEYS g_sign_keys;
static sc_context_t g_sign_ctx;
static uint8_t g_sign_ed25519_pubkey[SC_PUBKEY_SIZE];
static uint8_t g_sign_ed25519_privkey[SC_PRIVKEY_SIZE];
static const uint64_t SIGNER_NODE = 0xCCCC000000000001ULL;
static int setup_sign_keys(void) {
if (g_sign_ready) return 0;
if (sc_generate_keypair(&g_sign_keys) != SC_OK) { printf(" setup_sign_keys: generate_keypair failed\n"); return -1; }
sc_init_ctx(&g_sign_ctx, &g_sign_keys);
if (sc_derive_ed25519_pubkey(g_sign_keys.private_key, g_sign_ed25519_pubkey) != SC_OK) { printf(" setup_sign_keys: derive_ed25519_pubkey failed\n"); return -1; }
{ unsigned char h[SHA512_DIGEST_LENGTH]; SHA512(g_sign_keys.private_key, SC_PRIVKEY_SIZE, h); memcpy(g_sign_ed25519_privkey, h, SC_PRIVKEY_SIZE); }
g_sign_ready = 1;
return 0;
}
static void setup_bgp_for_sign_test(struct UTUN_INSTANCE* inst, int with_ed25519_key) {
inst->topo_groups = u_calloc(1, sizeof(struct TOPO_GROUPS));
if (!inst->topo_groups) { printf(" setup_bgp: topo_groups alloc failed\n"); return; }
inst->topo_groups->group_list = queue_new(inst->ua, 16, 0, 8, "group_list_test");
inst->topo_groups->node_registry = queue_new(inst->ua, BGP_NODES_HASH_SIZE, 0, 8, "node_registry_test");
struct ll_entry* qe = queue_entry_new(sizeof(struct TOPO_GROUP));
if (!qe) { printf(" setup_bgp: queue_entry_new failed\n"); return; }
struct TOPO_GROUP* group = (struct TOPO_GROUP*)qe;
memset((uint8_t*)group + sizeof(struct ll_entry), 0, sizeof(*group) - sizeof(struct ll_entry));
group->instance = inst;
group->group_id = TOPO_GROUP_UTUN;
group->nodes = queue_new(inst->ua, BGP_NODES_HASH_SIZE,
offsetof(struct TOPO_GROUP_NODE, node_id) - sizeof(struct ll_entry), 8, "group_nodes_test");
if (!group->nodes) { printf(" setup_bgp: queue_new failed\n"); queue_entry_free(qe); return; }
queue_data_put_with_index(inst->topo_groups->group_list, &group->ll);
struct TOPO_GROUP_NODE* nq = u_calloc(1, sizeof(struct TOPO_GROUP_NODE));
if (!nq) { printf(" setup_bgp: nq alloc failed\n"); return; }
nq->ll.size = sizeof(struct TOPO_GROUP_NODE) - sizeof(struct ll_entry);
struct TOPO_NODE* ni = u_calloc(1, sizeof(struct TOPO_NODE));
if (!ni) { printf(" setup_bgp: ni alloc failed\n"); u_free(nq); return; }
ni->group_ref_count = 1; ni->node_id = SIGNER_NODE;
if (with_ed25519_key) memcpy(ni->ed25519_public_key, g_sign_ed25519_pubkey, SC_PUBKEY_SIZE);
topo_node_registry_store(inst->topo_groups, ni);
nq->node_id = ni->node_id;
queue_data_put_with_index(group->nodes, &nq->ll);
}
static void teardown_bgp_for_sign_test(struct UTUN_INSTANCE* inst) {
if (!inst || !inst->topo_groups) return;
struct TOPO_GROUP* group = topo_groups_get_default(inst->topo_groups);
if (group && group->nodes) {
struct ll_entry* e;
while ((e = queue_data_get(group->nodes)) != NULL) queue_entry_free(e);
queue_free(group->nodes);
group->nodes = NULL;
}
struct ll_entry* ge;
while ((ge = queue_data_get(inst->topo_groups->group_list)) != NULL) queue_entry_free(ge);
queue_free(inst->topo_groups->group_list);
u_free(inst->topo_groups);
inst->topo_groups = NULL;
}
static void inject_signed(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, uint8_t flags,
int tamper_sig) {
establish(recv_cb, inst, src, svc_id, src);
flags &= ~ROUTER_FLAG_START;
size_t hdr_pl = SVC_ROUTE_HDR_SIZE + pl_len;
size_t total = hdr_pl + SC_SIGN_SIZE;
uint8_t* dgram = u_malloc(total);
if (!dgram) { printf(" inject_signed: u_malloc failed\n"); return; }
struct SVC_ROUTE_HDR* hdr = (struct SVC_ROUTE_HDR*)dgram;
memset(hdr, 0, SVC_ROUTE_HDR_SIZE);
hdr->cmd = ETCP_RT_ID_SVC_ROUTE;
hdr->group_id = TOPO_GROUP_UTUN;
hdr->dst_node_id = inst->node_id;
hdr->src_node_id = src;
hdr->seq = seq;
hdr->svc_id = svc_id;
hdr->flags = flags | ROUTER_FLAG_SIGNED;
struct ETCP_ROUTER_CONN* c = etcp_router_conn_get(inst, TOPO_GROUP_UTUN, src, svc_id);
hdr->reset_id = c->peer_reset_id; hdr->peer_reset_id = c->reset_id;
if (pl_len > 0) memcpy(dgram + SVC_ROUTE_HDR_SIZE, pl, pl_len);
if (sc_ed25519_sign(g_sign_ed25519_privkey, dgram, hdr_pl, dgram + hdr_pl) != SC_OK) { u_free(dgram); return; }
if (tamper_sig) dgram[hdr_pl] ^= 0x01;
struct ll_entry* e = queue_entry_new(0);
if (!e) { u_free(dgram); return; }
e->dgram = dgram;
e->len = total;
recv_cb(&fake_conn, e);
}
// ======================== 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_RT_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_RT_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, TOPO_GROUP_UTUN, 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, TOPO_GROUP_UTUN, TEST_REMOTE_NODE, TEST_SVC_ID);
if (!c1) FAIL("c1 NULL");
struct ETCP_ROUTER_CONN* c2 = etcp_router_conn_get(&inst, TOPO_GROUP_UTUN, TEST_REMOTE_NODE, TEST_SVC_ID);
if (c1 != c2) FAIL("reuse: different pointers");
struct ETCP_ROUTER_CONN* c3 = etcp_router_conn_get(&inst, TOPO_GROUP_UTUN, TEST_REMOTE_NODE2, TEST_SVC_ID);
if (c3 == c1) FAIL("different remote, same pointer");
struct ETCP_ROUTER_CONN* c4 = etcp_router_conn_get(&inst, TOPO_GROUP_UTUN, 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, TOPO_GROUP_UTUN, 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, TOPO_GROUP_UTUN, 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, TOPO_GROUP_UTUN, 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, ROUTER_FLAG_START);
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, TOPO_GROUP_UTUN, 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, ROUTER_FLAG_START);
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, TOPO_GROUP_UTUN, 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, ROUTER_FLAG_START);
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, TOPO_GROUP_UTUN, 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, ROUTER_FLAG_START);
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, TOPO_GROUP_UTUN, TEST_REMOTE_NODE, TEST_SVC_ID);
if (!c) FAIL("conn_get failed");
rx_reset(0xEE);
uint8_t data[] = { TEST_SVC_ID, 0xEE, 0 };
/* first packet syncs rx_seq (peer_sync_done), then OOB check works */
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, data + 1, 2, 0, ROUTER_FLAG_START);
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 != 1) FAIL("first packet not delivered");
if (c->rx_seq != 1) FAIL("rx_seq should advance to 1 after first");
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, TOPO_GROUP_UTUN, TEST_REMOTE_NODE, TEST_SVC_ID);
if (!c) FAIL("conn_get failed");
c->rx_seq = 0xFFFFFFFE;
c->peer_sync_done = 1;// сессия уже установлена (seq вручную на границе wrap)
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, TOPO_GROUP_UTUN, 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, ROUTER_FLAG_START);
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, TOPO_GROUP_UTUN, TEST_REMOTE_NODE, TEST_SVC_ID);
if (!c) FAIL("conn_get failed");
rx_reset(0x00);
c->tx_seq = 10; c->tx_sent = 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, TOPO_GROUP_UTUN, 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, ROUTER_FLAG_START);
if (c->ack_timer != NULL) FAIL("ack_timer set without BGP route");
if (c->last_sent_ack_seq != 1) FAIL("ACK not sent on receiving connection");
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, TOPO_GROUP_UTUN, TEST_REMOTE_NODE, TEST_SVC_ID);
if (!c) FAIL("conn_get failed");
establish(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, TEST_REMOTE_NODE);
// Заполняем inflight_q до лимита — канал «загружен»
for (int i = 0; i < ROUTER_MAX_INFLIGHT; i++) {
struct ROUTER_INFLIGHT* inf = u_malloc(sizeof(struct ROUTER_INFLIGHT));
if (!inf) FAIL("inflight alloc");
memset(&inf->ll, 0, sizeof(inf->ll));
inf->ll.size = 4; inf->seq = (uint32_t)i; *(uint32_t*)inf->ll.data = (uint32_t)i;
inf->dgram = u_malloc(1); inf->dgram_len = 1;
queue_data_put_with_index(c->inflight_q, &inf->ll);
}
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), 0);
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->watchdog_timer == NULL) FAIL("watchdog_timer not set");
if (queue_entry_count(c->send_q) != 1) FAIL("send_q count != 1");
if (c->tx_seq != 1) FAIL("tx_seq should advance to 1 at enqueue");
// Cancel timers manually
if (c->watchdog_timer) { uasync_cancel_timeout(ua, c->watchdog_timer); c->watchdog_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, TOPO_GROUP_UTUN, inst.node_id, e, 0, 0) != 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, TOPO_GROUP_UTUN, 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, ROUTER_FLAG_START);
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 buffers with no_route (lossless)");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
struct ETCP_ROUTER_CONN* c = etcp_router_conn_get(&inst, TOPO_GROUP_UTUN, 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), 0);
if (ret != 0) FAIL("conn_send should buffer (return 0) without BGP");
if (!c->handshake_timer) FAIL("handshake retry not scheduled");
if (queue_entry_count(c->send_q) != 1) FAIL("send_q count != 1");
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++) {
uint8_t f = (i == 0) ? (flags | ROUTER_FLAG_START) : flags;
inject(recv_cb, inst, TEST_REMOTE_NODE, TEST_SVC_ID, i, data + 1, 2, 0, f);
}
return count;
}
// ======================== Restart/session tests ========================
static int test_rst_flag(void) {
TEST("RST flag — send-side restart, no service notify");
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");
g_notify_count = 0;
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, NULL, 0, 1, ROUTER_FLAG_RST);
if (g_notify_count != 0) FAIL("RST should NOT notify service");
struct ETCP_ROUTER_CONN* c = etcp_router_conn_get(&inst, TOPO_GROUP_UTUN, TEST_REMOTE_NODE, TEST_SVC_ID);
if (!c) FAIL("rconn should still exist after reset");
if (c->tx_seq != 0 || c->start_sent != 0) FAIL("send side not reset by RST");
if (c->rx_seq != (uint32_t)n) FAIL("rx side should be untouched by RST");
if (c->peer_reset_id != TEST_REMOTE_NODE) FAIL("unsolicited RST changed peer");
etcp_router_conn_close(c);
TEARDOWN();
PASS();
return 0;
}
static int test_start_retransmit_no_restart(void) {
TEST("START retransmit (same epoch) — no restart");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
g_notify_count = 0;
uint64_t epoch = 0x1111111111111111ULL;
rx_reset(0xC1);
uint8_t data[] = { TEST_SVC_ID, 0xC1, 0 };
inject_epoch(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, data + 1, 2, 0, ROUTER_FLAG_START, epoch);
inject_epoch(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 1, data + 1, 2, 0, 0, 0);
inject_epoch(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 2, data + 1, 2, 0, 0, 0);
if (rx.delivered != 3) FAIL("initial delivery failed");
g_notify_count = 0;
// ретрансмит первого пакета (seq=0, START, та же эпоха) — не должен рестартовать
inject_epoch(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, data + 1, 2, 0, ROUTER_FLAG_START, epoch);
if (g_notify_count != 0) FAIL("START retransmit (same epoch) should NOT restart");
if (rx.delivered != 3) FAIL("duplicate START should not deliver");
TEARDOWN();
PASS();
return 0;
}
static int test_client_restart(void) {
TEST("client restart — new epoch after sync");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
g_notify_count = 0;
rx_reset(0xB1);
uint8_t data[] = { TEST_SVC_ID, 0xB1, 0 };
inject_epoch(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, data + 1, 2, 0, ROUTER_FLAG_START, 0xAAAA000000000001ULL);
inject_epoch(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 1, data + 1, 2, 0, 0, 0);
inject_epoch(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 2, data + 1, 2, 0, 0, 0);
if (rx.delivered != 3) FAIL("initial delivery failed");
struct ETCP_ROUTER_CONN* c = etcp_router_conn_get(&inst, TOPO_GROUP_UTUN, TEST_REMOTE_NODE, TEST_SVC_ID);
if (!c) FAIL("rconn not found");
rx_reset(0xB2);
uint8_t data2[] = { TEST_SVC_ID, 0xB2, 0 };
inject_epoch(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, data2 + 1, 2, 0, ROUTER_FLAG_START, 0xAAAA000000000002ULL);
if (g_notify_count != 1) FAIL("restart not notified");
if (rx.delivered != 1) FAIL("data after restart not delivered");
// slave (мы 0xBBBB > пир 0xAAAA) должен принять эпоху пира
if (c->reset_id == c->peer_reset_id) FAIL("local identity must be independent of peer");
if (c->peer_reset_id != 0xAAAA000000000002ULL) FAIL("peer_reset_id not set to adopted epoch");
etcp_router_conn_close(c);
TEARDOWN();
PASS();
return 0;
}
static int test_master_keeps_epoch(void) {
TEST("confirmed peer restart preserves local identity");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
g_notify_count = 0;
// пир 0xCCCC > мы 0xBBBB → мы master.
uint64_t big_peer = 0xCCCC000000000001ULL;
rx_reset(0xD1);
uint8_t data[] = { TEST_SVC_ID, 0xD1, 0 };
inject_epoch(recv_cb, &inst, big_peer, TEST_SVC_ID, 0, data + 1, 2, 0, ROUTER_FLAG_START, 0x1111111111111111ULL);
if (rx.delivered != 1) FAIL("initial delivery failed");
struct ETCP_ROUTER_CONN* c = etcp_router_conn_get(&inst, TOPO_GROUP_UTUN, big_peer, TEST_SVC_ID);
if (!c) FAIL("rconn not found");
uint64_t my_rid = c->reset_id;
g_notify_count = 0;
// пир перезапустился с новой эпохой — master держит свою эпоху (шлёт RST), локального рестарта нет.
inject_epoch(recv_cb, &inst, big_peer, TEST_SVC_ID, 0, data + 1, 2, 0, ROUTER_FLAG_START, 0x2222222222222222ULL);
if (g_notify_count != 1) FAIL("confirmed peer restart should notify once");
c = etcp_router_conn_get(&inst, TOPO_GROUP_UTUN, big_peer, TEST_SVC_ID);
if (!c) FAIL("rconn not found after");
if (c->reset_id != my_rid) FAIL("master reset_id changed");
etcp_router_conn_close(c);
TEARDOWN();
PASS();
return 0;
}
static int test_reset_id(void) {
TEST("local restart generates fresh identity");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
struct ETCP_ROUTER_CONN* c = etcp_router_conn_get(&inst, TOPO_GROUP_UTUN, TEST_REMOTE_NODE, TEST_SVC_ID);
if (!c) FAIL("rconn not found");
uint64_t rid0 = c->reset_id;
if (rid0 == 0) FAIL("reset_id == 0 on fresh conn (should be random)");
if (c->peer_reset_id != 0) FAIL("peer_reset_id != 0 on fresh conn");
etcp_router_conn_restart(&inst, TOPO_GROUP_UTUN, TEST_REMOTE_NODE, TEST_SVC_ID);
c = etcp_router_conn_get(&inst, TOPO_GROUP_UTUN, TEST_REMOTE_NODE, TEST_SVC_ID);
if (!c) FAIL("rconn not found after restart");
if (c->reset_id == rid0) FAIL("reset_id reused after local restart");
if (c->peer_reset_id != 0) FAIL("peer_reset_id not reset after restart");
etcp_router_conn_close(c);
TEARDOWN();
PASS();
return 0;
}
static int test_server_reinit(void) {
TEST("server reinit — new rconn gets start_sent=0 peer_reset_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 = inject_wire;
if (!recv_cb) FAIL("recv_cb not bound");
struct ETCP_ROUTER_CONN* c = etcp_router_conn_get(&inst, TOPO_GROUP_UTUN, 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->peer_reset_id != 0) FAIL("peer_reset_id != 0 after reinit");
if (c->reset_id == 0) FAIL("reset_id == 0 after reinit");
etcp_router_conn_close(c);
TEARDOWN();
PASS();
return 0;
}
// ======================== Signed message tests ========================
static int test_sign_verify_ok(void) {
TEST("signed packet — verify OK, delivered to handler");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
setup_bgp_for_sign_test(&inst, 1);
rx_reset(0xF1);
uint8_t data[] = { TEST_SVC_ID, 0xF1, 0 };
inject_signed(recv_cb, &inst, SIGNER_NODE, TEST_SVC_ID, 0, data + 1, 2, 0, 0);
if (rx.delivered != 1) FAIL("signed packet not delivered");
if (rx.errors != 0) FAIL("data mismatch on signed packet");
teardown_bgp_for_sign_test(&inst);
TEARDOWN();
PASS();
return 0;
}
static int test_sign_bad_signature(void) {
TEST("signed packet — tampered signature → dropped");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
setup_bgp_for_sign_test(&inst, 1);
rx_reset(0xF2);
uint8_t data[] = { TEST_SVC_ID, 0xF2, 0 };
inject_signed(recv_cb, &inst, SIGNER_NODE, TEST_SVC_ID, 0, data + 1, 2, 0, 1 /*tamper*/);
if (rx.delivered != 0) FAIL("tampered signed packet should be dropped");
if (rx.errors != 0) FAIL("handler should not be called for bad signature");
struct ETCP_ROUTER_CONN* c = etcp_router_conn_get(&inst, TOPO_GROUP_UTUN, SIGNER_NODE, TEST_SVC_ID);
if (c->rx_seq != 0) FAIL("bad signature acknowledged");
inject_signed(recv_cb, &inst, SIGNER_NODE, TEST_SVC_ID, 0, data + 1, 2, 0, 0);
if (rx.delivered != 1 || c->rx_seq != 1) FAIL("valid retry lost");
teardown_bgp_for_sign_test(&inst);
TEARDOWN();
PASS();
return 0;
}
static int test_sign_unknown_node(void) {
TEST("signed packet — sender not in routing table → dropped");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
setup_bgp_for_sign_test(&inst, 1);
rx_reset(0xF3);
uint8_t data[] = { TEST_SVC_ID, 0xF3, 0 };
uint64_t unknown = 0xDEAD000000000001ULL;
inject_signed(recv_cb, &inst, unknown, TEST_SVC_ID, 0, data + 1, 2, 0, 0);
if (rx.delivered != 0) FAIL("packet from unknown node should be dropped");
if (rx.errors != 0) FAIL("handler not called for unknown node");
teardown_bgp_for_sign_test(&inst);
TEARDOWN();
PASS();
return 0;
}
static int test_sign_no_ed25519_key(void) {
TEST("signed packet — node in table but no Ed25519 key → dropped");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
setup_bgp_for_sign_test(&inst, 0 /*zero ed25519 key*/);
rx_reset(0xF4);
uint8_t data[] = { TEST_SVC_ID, 0xF4, 0 };
inject_signed(recv_cb, &inst, SIGNER_NODE, TEST_SVC_ID, 0, data + 1, 2, 0, 0);
if (rx.delivered != 0) FAIL("packet with zero ed25519 key should be dropped");
teardown_bgp_for_sign_test(&inst);
TEARDOWN();
PASS();
return 0;
}
static int test_sign_too_short(void) {
TEST("signed packet — SIGNED flag but no signature bytes → dropped");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
setup_bgp_for_sign_test(&inst, 1);
rx_reset(0xF5);
struct SVC_ROUTE_HDR hdr;
memset(&hdr, 0, sizeof(hdr));
hdr.cmd = ETCP_RT_ID_SVC_ROUTE;
hdr.group_id = TOPO_GROUP_UTUN;
hdr.dst_node_id = inst.node_id;
hdr.src_node_id = SIGNER_NODE;
hdr.seq = 0;
hdr.svc_id = TEST_SVC_ID;
hdr.flags = ROUTER_FLAG_SIGNED;
uint8_t payload[] = { TEST_SVC_ID, 0xF5, 0 };
size_t total = SVC_ROUTE_HDR_SIZE + sizeof(payload);
struct ll_entry* e = queue_entry_new(0);
if (!e) FAIL("queue_entry_new");
e->dgram = u_malloc(total);
if (!e->dgram) { queue_entry_free(e); FAIL("u_malloc"); }
memcpy(e->dgram, &hdr, SVC_ROUTE_HDR_SIZE);
memcpy(e->dgram + SVC_ROUTE_HDR_SIZE, payload, sizeof(payload));
e->len = total;
recv_cb(&fake_conn, e);
if (rx.delivered != 0) FAIL("too-short signed packet should be dropped");
teardown_bgp_for_sign_test(&inst);
TEARDOWN();
PASS();
return 0;
}
// ======================== E2E encryption tests ========================
static int g_e2e_ready = 0;
static struct SC_MYKEYS g_e2e_my_keys;
static struct SC_MYKEYS g_e2e_peer_keys;
static sc_context_t g_e2e_tx_ctx; // для шифрования тестовых данных (как отправитель)
static const uint64_t E2E_PEER_NODE = TEST_E2E_PEER;
static int setup_e2e_keys(void) {
if (g_e2e_ready) return 0;
if (sc_generate_keypair(&g_e2e_my_keys) != SC_OK) return -1;
if (sc_generate_keypair(&g_e2e_peer_keys) != SC_OK) return -1;
sc_init_ctx(&g_e2e_tx_ctx, &g_e2e_my_keys);
if (sc_set_peer_public_key(&g_e2e_tx_ctx, g_e2e_peer_keys.public_key, SC_PEER_PUBKEY_BIN) != SC_OK) return -1;
g_e2e_ready = 1;
return 0;
}
static void setup_bgp_for_e2e_test(struct UTUN_INSTANCE* inst, int register_peer) {
inst->topo_groups = u_calloc(1, sizeof(struct TOPO_GROUPS));
if (!inst->topo_groups) return;
inst->topo_groups->instance = inst;
inst->topo_groups->group_list = queue_new(inst->ua, 16, 0, 8, "group_list_e2e");
inst->topo_groups->node_registry = queue_new(inst->ua, BGP_NODES_HASH_SIZE, 0, 8, "node_registry_e2e");
if (register_peer) {
struct TOPO_NODE* ni = u_calloc(1, sizeof(struct TOPO_NODE));
if (!ni) return;
ni->group_ref_count = 1; ni->node_id = E2E_PEER_NODE;
memcpy(ni->public_key, g_e2e_peer_keys.public_key, SC_PUBKEY_SIZE);
topo_node_registry_store(inst->topo_groups, ni);
}
struct ll_entry* qe = queue_entry_new(sizeof(struct TOPO_GROUP));
if (!qe) return;
struct TOPO_GROUP* group = (struct TOPO_GROUP*)qe;
memset((uint8_t*)group + sizeof(struct ll_entry), 0, sizeof(*group) - sizeof(struct ll_entry));
group->instance = inst;
group->group_id = TOPO_GROUP_UTUN;
queue_data_put_with_index(inst->topo_groups->group_list, &group->ll);
}
static void teardown_bgp_for_e2e_test(struct UTUN_INSTANCE* inst) {
if (!inst || !inst->topo_groups) return;
if (inst->topo_groups->node_registry) {
struct ll_entry* e;
while ((e = queue_data_get(inst->topo_groups->node_registry)) != NULL) queue_entry_free(e);
queue_free(inst->topo_groups->node_registry);
inst->topo_groups->node_registry = NULL;
}
if (inst->topo_groups->group_list) {
struct ll_entry* ge;
while ((ge = queue_data_get(inst->topo_groups->group_list)) != NULL) queue_entry_free(ge);
queue_free(inst->topo_groups->group_list);
}
u_free(inst->topo_groups);
inst->topo_groups = NULL;
}
static void inject_encrypted(struct UTUN_INSTANCE* inst, etcp_recv_fn recv_cb,
uint64_t src, uint8_t svc_id, uint32_t seq,
const uint8_t* pt, size_t pt_len, uint8_t extra_flags) {
if (seq == 0) extra_flags |= ROUTER_FLAG_START;
size_t enc_cap = pt_len + SC_NONCE_SIZE + SC_CRC32_SIZE + SC_TAG_SIZE;
uint8_t* enc_buf = u_malloc(enc_cap);
if (!enc_buf) { printf(" inject_enc: u_malloc enc_buf fail\n"); return; }
size_t enc_len = 0;
if (sc_encrypt(&g_e2e_tx_ctx, pt, pt_len, enc_buf, &enc_len) != SC_OK) {
u_free(enc_buf); printf(" inject_enc: sc_encrypt fail\n"); return;
}
struct SVC_ROUTE_HDR hdr;
memset(&hdr, 0, sizeof(hdr));
hdr.cmd = ETCP_RT_ID_SVC_ROUTE;
hdr.group_id = TOPO_GROUP_UTUN;
hdr.dst_node_id = inst->node_id;
hdr.src_node_id = src;
hdr.seq = seq;
hdr.svc_id = svc_id;
establish(recv_cb, inst, src, svc_id, src);
struct ETCP_ROUTER_CONN* c = etcp_router_conn_get(inst, TOPO_GROUP_UTUN, src, svc_id);
hdr.reset_id = c->peer_reset_id; hdr.peer_reset_id = c->reset_id;
hdr.flags = ROUTER_FLAG_ENCRYPTED | (extra_flags & ~ROUTER_FLAG_START);
size_t total = SVC_ROUTE_HDR_SIZE + enc_len;
struct ll_entry* e = queue_entry_new(0);
if (!e) { u_free(enc_buf); return; }
e->dgram = u_malloc(total);
if (!e->dgram) { queue_entry_free(e); u_free(enc_buf); return; }
memcpy(e->dgram, &hdr, SVC_ROUTE_HDR_SIZE);
memcpy(e->dgram + SVC_ROUTE_HDR_SIZE, enc_buf, enc_len);
e->len = total;
u_free(enc_buf);
recv_cb(&fake_conn, e);
}
static int test_e2e_decrypt_basic(void) {
TEST("E2E decrypt — basic round-trip");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
setup_bgp_for_e2e_test(&inst, 1);
memcpy(&inst.my_keys, &g_e2e_my_keys, sizeof(g_e2e_my_keys));
rx_reset(0xE1);
uint8_t pt[] = { TEST_SVC_ID, 0xE1, 0xAA };
inject_encrypted(&inst, recv_cb, E2E_PEER_NODE, TEST_SVC_ID, 0, pt + 1, 2, 0);
if (rx.delivered != 1 || rx.errors != 0) FAIL("decrypt basic");
teardown_bgp_for_e2e_test(&inst);
TEARDOWN();
PASS();
return 0;
}
static int test_e2e_decrypt_no_nodeinfo(void) {
TEST("E2E decrypt — no NODEINFO → dropped");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
setup_bgp_for_e2e_test(&inst, 0);
memcpy(&inst.my_keys, &g_e2e_my_keys, sizeof(g_e2e_my_keys));
rx_reset(0xE2);
uint8_t pt[] = { TEST_SVC_ID, 0xE2, 0xBB };
inject_encrypted(&inst, recv_cb, E2E_PEER_NODE, TEST_SVC_ID, 0, pt + 1, 2, 0);
if (rx.delivered != 0) FAIL("should drop without NODEINFO");
teardown_bgp_for_e2e_test(&inst);
TEARDOWN();
PASS();
return 0;
}
static int test_e2e_decrypt_tampered(void) {
TEST("E2E decrypt — tampered ciphertext → dropped");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
setup_bgp_for_e2e_test(&inst, 1);
memcpy(&inst.my_keys, &g_e2e_my_keys, sizeof(g_e2e_my_keys));
rx_reset(0xE3);
uint8_t pt[] = { TEST_SVC_ID, 0xE3, 0xCC };
size_t enc_cap = sizeof(pt) + SC_NONCE_SIZE + SC_CRC32_SIZE + SC_TAG_SIZE;
uint8_t* enc_buf = u_malloc(enc_cap);
if (!enc_buf) FAIL("alloc");
size_t enc_len = 0;
if (sc_encrypt(&g_e2e_tx_ctx, pt + 1, 2, enc_buf, &enc_len) != SC_OK) { u_free(enc_buf); FAIL("encrypt"); }
enc_buf[SC_NONCE_SIZE + 1] ^= 0xFF; // tamper
struct SVC_ROUTE_HDR hdr;
memset(&hdr, 0, sizeof(hdr));
hdr.cmd = ETCP_RT_ID_SVC_ROUTE; hdr.group_id = TOPO_GROUP_UTUN;
hdr.dst_node_id = inst.node_id; hdr.src_node_id = E2E_PEER_NODE;
hdr.seq = 0; hdr.svc_id = TEST_SVC_ID; hdr.flags = ROUTER_FLAG_ENCRYPTED;
establish(recv_cb, &inst, E2E_PEER_NODE, TEST_SVC_ID, E2E_PEER_NODE);
struct ETCP_ROUTER_CONN* c = etcp_router_conn_get(&inst, TOPO_GROUP_UTUN, E2E_PEER_NODE, TEST_SVC_ID);
hdr.reset_id = c->peer_reset_id; hdr.peer_reset_id = c->reset_id;
size_t total = SVC_ROUTE_HDR_SIZE + enc_len;
struct ll_entry* e = queue_entry_new(0);
if (!e) { u_free(enc_buf); FAIL("entry"); }
e->dgram = u_malloc(total);
if (!e->dgram) { queue_entry_free(e); u_free(enc_buf); FAIL("dgram"); }
memcpy(e->dgram, &hdr, SVC_ROUTE_HDR_SIZE);
memcpy(e->dgram + SVC_ROUTE_HDR_SIZE, enc_buf, enc_len);
e->len = total;
u_free(enc_buf);
recv_cb(&fake_conn, e);
if (rx.delivered != 0) FAIL("tampered should be dropped");
teardown_bgp_for_e2e_test(&inst);
TEARDOWN();
PASS();
return 0;
}
static int test_e2e_cache_reuse(void) {
TEST("E2E cache — second call returns same ctx");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
setup_bgp_for_e2e_test(&inst, 1);
memcpy(&inst.my_keys, &g_e2e_my_keys, sizeof(g_e2e_my_keys));
rx_reset(0xE4);
uint8_t pt[] = { TEST_SVC_ID, 0xE4, 0xDD };
inject_encrypted(&inst, recv_cb, E2E_PEER_NODE, TEST_SVC_ID, 0, pt + 1, 2, 0);
if (rx.delivered != 1) FAIL("first");
int slot = -1;
for (int i = 0; i < E2E_CTX_CACHE_SIZE; i++)
if (inst.e2e_ctx_cache[i].valid && inst.e2e_ctx_cache[i].peer_node_id == E2E_PEER_NODE) { slot = i; break; }
if (slot < 0) FAIL("cache miss after first decrypt");
uint8_t* prev_seskey = inst.e2e_ctx_cache[slot].ctx.session_key;
rx_reset(0xE5);
uint8_t pt2[] = { TEST_SVC_ID, 0xE5, 0xEE };
inject_encrypted(&inst, recv_cb, E2E_PEER_NODE, TEST_SVC_ID, 1, pt2 + 1, 2, 0);
if (rx.delivered != 1) FAIL("second");
if (inst.e2e_ctx_cache[slot].ctx.session_key != prev_seskey) FAIL("ctx recreated");
teardown_bgp_for_e2e_test(&inst);
TEARDOWN();
PASS();
return 0;
}
static int test_e2e_combined_signed(void) {
TEST("E2E ENCRYPTED+SIGNED — verify+decrypt+deliver");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
setup_bgp_for_sign_test(&inst, 1);
memcpy(&inst.my_keys, &g_sign_keys, sizeof(g_sign_keys));
struct SC_MYKEYS peer_keys;
if (sc_generate_keypair(&peer_keys) != SC_OK) FAIL("peer keygen");
// Set SIGNER_NODE's X25519 pubkey in NODEINFO for E2E ECDH
struct TOPO_NODE* sign_ni = topo_node_registry_find(inst.topo_groups, SIGNER_NODE);
if (!sign_ni) FAIL("SIGNER_NODE not in registry");
memcpy(sign_ni->public_key, peer_keys.public_key, SC_PUBKEY_SIZE);
// E2E tx ctx for encrypting test data (same ECDH as receiver will do)
sc_context_t tx_ctx;
sc_init_ctx(&tx_ctx, &inst.my_keys);
if (sc_set_peer_public_key(&tx_ctx, peer_keys.public_key, SC_PEER_PUBKEY_BIN) != SC_OK) FAIL("tx ECDH");
rx_reset(0xE6);
uint8_t pt[] = { TEST_SVC_ID, 0xE6, 0xFF };
size_t enc_cap = 3 + SC_NONCE_SIZE + SC_CRC32_SIZE + SC_TAG_SIZE;
uint8_t* enc_buf = u_malloc(enc_cap);
size_t enc_len = 0;
if (sc_encrypt(&tx_ctx, pt + 1, 2, enc_buf, &enc_len) != SC_OK) { u_free(enc_buf); FAIL("encrypt"); }
size_t hdr_pl = SVC_ROUTE_HDR_SIZE + enc_len;
size_t total = hdr_pl + SC_SIGN_SIZE;
uint8_t* dgram = u_malloc(total);
struct SVC_ROUTE_HDR* hdr = (struct SVC_ROUTE_HDR*)dgram;
memset(hdr, 0, SVC_ROUTE_HDR_SIZE);
hdr->cmd = ETCP_RT_ID_SVC_ROUTE; hdr->group_id = TOPO_GROUP_UTUN;
hdr->dst_node_id = inst.node_id; hdr->src_node_id = SIGNER_NODE;
hdr->seq = 0; hdr->svc_id = TEST_SVC_ID;
hdr->flags = ROUTER_FLAG_ENCRYPTED | ROUTER_FLAG_SIGNED;
establish(recv_cb, &inst, SIGNER_NODE, TEST_SVC_ID, SIGNER_NODE);
struct ETCP_ROUTER_CONN* c = etcp_router_conn_get(&inst, TOPO_GROUP_UTUN, SIGNER_NODE, TEST_SVC_ID);
hdr->reset_id = c->peer_reset_id; hdr->peer_reset_id = c->reset_id;
memcpy(dgram + SVC_ROUTE_HDR_SIZE, enc_buf, enc_len);
if (sc_ed25519_sign(g_sign_ed25519_privkey, dgram, hdr_pl, dgram + hdr_pl) != SC_OK) { u_free(dgram); u_free(enc_buf); FAIL("sign"); }
struct ll_entry* e = queue_entry_new(0);
if (!e) { u_free(dgram); u_free(enc_buf); FAIL("entry"); }
e->dgram = dgram; e->len = total; u_free(enc_buf);
recv_cb(&fake_conn, e);
if (rx.delivered != 1 || rx.errors != 0) FAIL("combined signed+encrypted");
teardown_bgp_for_sign_test(&inst);
TEARDOWN();
PASS();
return 0;
}
static int receive_capacity;
static int receive_ready(struct UTUN_INSTANCE* inst, uint64_t group, uint64_t peer, const uint8_t* payload, size_t bytes) {
(void)inst; (void)group; (void)peer;
if (bytes != 1 || payload[0] != rx.marker) rx.errors++;
return rx.delivered < receive_capacity;
}
static int test_receive_backpressure(void) {
TEST("receive backpressure preserves order and ACK boundary");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP(); rx_reset(0x52); receive_capacity = 0;
etcp_router_set_receive_ready(&inst, TEST_SVC_ID, receive_ready);
uint8_t byte = 0x52;
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, &byte, 1, 0, ROUTER_FLAG_START);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 2, &byte, 1, 0, 0);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 1, &byte, 1, 0, 0);
for (int i = 0; i < 8; i++) uasync_poll(ua, 0);
struct ETCP_ROUTER_CONN* rc = etcp_router_conn_get(&inst, TOPO_GROUP_UTUN, TEST_REMOTE_NODE, TEST_SVC_ID);
TESTASSERT(rx.delivered == 0 && rc->rx_seq == 0 && rc->recv_q->count == 3 && rc->receive_blocked);
receive_capacity = 1; etcp_router_resume_receive(&inst, TOPO_GROUP_UTUN, TEST_REMOTE_NODE, TEST_SVC_ID);
TESTASSERT(rx.delivered == 0);
for (int i = 0; i < 8; i++) uasync_poll(ua, 0);
TESTASSERT(rx.delivered == 1 && rc->rx_seq == 1 && rc->recv_q->count == 2 && rc->receive_blocked);
receive_capacity = 3; etcp_router_resume_receive(&inst, TOPO_GROUP_UTUN, TEST_REMOTE_NODE, TEST_SVC_ID);
for (int i = 0; i < 8; i++) uasync_poll(ua, 0);
TESTASSERT(rx.delivered == 3 && !rx.errors && rc->rx_seq == 3 && rc->recv_q->count == 0 && !rc->receive_blocked);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, NULL, 0, 1, ROUTER_FLAG_RECEIVE_BUSY);
TESTASSERT(rc->peer_receive_blocked && rc->tx_acked == 0);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, NULL, 0, 1, 0);
TESTASSERT(!rc->peer_receive_blocked && rc->tx_acked == 0);
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 3, &byte, 1, 0, 0);
for (int i = 0; i < 8; i++) uasync_poll(ua, 0);
TESTASSERT(rc->receive_blocked && rx.delivered == 3);
etcp_router_resume_receive(&inst, TOPO_GROUP_UTUN, TEST_REMOTE_NODE, TEST_SVC_ID);
int before = g_notify_count;
etcp_router_conn_restart(&inst, TOPO_GROUP_UTUN, TEST_REMOTE_NODE, TEST_SVC_ID);
for (int i = 0; i < 8; i++) uasync_poll(ua, 0);
TESTASSERT(rx.delivered == 3 && g_notify_count == before + 1 && !rc->receive_pump);
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_receive_backpressure();
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_start_retransmit_no_restart();
test_client_restart();
test_master_keeps_epoch();
test_reset_id();
test_server_reinit();
if (setup_sign_keys() != 0) { printf(" SKIP signed tests — crypto init failed\n"); }
else {
test_sign_verify_ok();
test_sign_bad_signature();
test_sign_unknown_node();
test_sign_no_ed25519_key();
test_sign_too_short();
}
if (setup_e2e_keys() != 0) { printf(" SKIP E2E tests — crypto init failed\n"); }
else {
test_e2e_decrypt_basic();
test_e2e_decrypt_no_nodeinfo();
test_e2e_decrypt_tampered();
test_e2e_cache_reuse();
test_e2e_combined_signed();
}
printf("\n=== Results: %d/%d passed, %d failed ===\n", test_passed, test_total, test_failed);
return test_failed > 0 ? 1 : 0;
}