1379 lines
55 KiB
C
1379 lines
55 KiB
C
// 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;
|
||
}
|