You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
451 lines
14 KiB
451 lines
14 KiB
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <time.h> |
|
#include <assert.h> |
|
#include "topo_node.h" |
|
#include "route6_lib.h" |
|
#include "../lib/debug_config.h" |
|
#include "../lib/mem.h" |
|
#include "../lib/u_async.h" |
|
|
|
#define STRESS_NODES 30 |
|
#define STRESS_LOOKUPS 50000 |
|
#define STRESS_OPS 300 |
|
#define MAX_SAVED 256 |
|
|
|
static int tests_run = 0; |
|
#define RUN_TEST(fn) do { tests_run++; fn(); } while(0) |
|
|
|
/* ──── helpers ──── */ |
|
|
|
static struct TOPO_NODEQ *make_node(uint64_t node_id, |
|
const uint8_t addrs[][16], |
|
const uint8_t *plens, int N) { |
|
struct TOPO_NODEQ *nq = u_calloc(1, sizeof(*nq)); |
|
struct TOPO_NODE *ni = u_calloc(1, sizeof(struct TOPO_NODE)); |
|
if (!nq || !ni) { u_free(nq); u_free(ni); return NULL; } |
|
ni->ref_count = 1; ni->node_id = node_id; |
|
nq->node = ni; nq->hash_node_id = node_id; |
|
if (N > 0) { |
|
struct TOPO_NODESUBNETS *r = u_calloc(1, sizeof(struct TOPO_NODESUBNETS)); |
|
nq->subnets = r; |
|
struct TOPO_SUBNET6 *head = NULL; |
|
for (int i = N-1; i >= 0; i--) { |
|
struct TOPO_SUBNET6 *s = u_calloc(1, sizeof(struct TOPO_SUBNET6)); |
|
memcpy(s->addr, addrs[i], 16); s->prefix_length = plens[i]; |
|
s->next = head; head = s; |
|
} |
|
r->v6_subnets = head; |
|
} |
|
return nq; |
|
} |
|
|
|
static void free_node(struct TOPO_NODEQ *nq) { |
|
if (nq) { |
|
if (nq->subnets) { |
|
struct TOPO_SUBNET6 *s = nq->subnets->v6_subnets; |
|
while (s) { struct TOPO_SUBNET6 *next = s->next; u_free(s); s = next; } |
|
u_free(nq->subnets); |
|
} |
|
if (nq->node) u_free(nq->node); |
|
u_free(nq); |
|
} |
|
} |
|
|
|
static void make_key(uint8_t out[17], const uint8_t addr[16]) { |
|
out[0] = 17; memcpy(out + 1, addr, 16); |
|
} |
|
|
|
static int addr_cmp(const uint8_t a[16], const uint8_t b[16]) { |
|
return memcmp(a, b, 16); |
|
} |
|
|
|
/* ──── edge tests ──── */ |
|
|
|
static void test_insert_null_args(void) { |
|
struct UASYNC *ua = uasync_create(); |
|
struct ROUTE6_TABLE *t = route6_table_create(ua); |
|
assert(t != NULL); |
|
assert(route6_insert(NULL, NULL) == false); |
|
assert(route6_insert(t, NULL) == false); |
|
assert(route6_insert(NULL, t ? (struct TOPO_NODEQ*)(uintptr_t)t : NULL) == false); |
|
route6_table_destroy(t); |
|
uasync_destroy(ua, 0); |
|
} |
|
|
|
static void test_insert_zero_subnets(void) { |
|
struct UASYNC *ua = uasync_create(); |
|
struct ROUTE6_TABLE *t = route6_table_create(ua); |
|
assert(t != NULL); |
|
|
|
int addr[16] = {0}; |
|
uint8_t zero_plen = 0; |
|
struct TOPO_NODEQ *nq = make_node(1, (const uint8_t(*)[16])addr, &zero_plen, 0); |
|
assert(route6_insert(t, nq) == false); |
|
|
|
const uint8_t any[16] = {0}; |
|
assert(route6_lookup(t, any) == NULL); |
|
|
|
free_node(nq); |
|
route6_table_destroy(t); |
|
uasync_destroy(ua, 0); |
|
} |
|
|
|
static void test_insert_duplicate(void) { |
|
struct UASYNC *ua = uasync_create(); |
|
struct ROUTE6_TABLE *t = route6_table_create(ua); |
|
assert(t != NULL); |
|
|
|
uint8_t addr[16] = {0xfd, 0}; |
|
uint8_t plen = 64; |
|
struct TOPO_NODEQ *nq = make_node(10, &addr, &plen, 1); |
|
assert(route6_insert(t, nq) == true); |
|
assert(route6_insert(t, nq) == true); // re-insert should not crash |
|
|
|
route6_lookup(t, addr); // route still reachable |
|
|
|
free_node(nq); |
|
route6_table_destroy(t); |
|
uasync_destroy(ua, 0); |
|
} |
|
|
|
static void test_insert_delete_reinsert(void) { |
|
struct UASYNC *ua = uasync_create(); |
|
struct ROUTE6_TABLE *t = route6_table_create(ua); |
|
assert(t != NULL); |
|
|
|
uint8_t addrs[2][16] = {{0xfd, 1}, {0xfd, 2}}; |
|
uint8_t plens[2] = {64, 64}; |
|
struct TOPO_NODEQ *nq = make_node(20, addrs, plens, 2); |
|
assert(route6_insert(t, nq) == true); |
|
|
|
assert(route6_lookup(t, addrs[0]) != NULL); |
|
assert(route6_lookup(t, addrs[1]) != NULL); |
|
|
|
route6_delete(t, nq); |
|
|
|
assert(route6_lookup(t, addrs[0]) == NULL); |
|
assert(route6_lookup(t, addrs[1]) == NULL); |
|
|
|
assert(route6_insert(t, nq) == true); |
|
|
|
const struct ROUTE6_DATA *rd = route6_lookup(t, addrs[0]); |
|
assert(rd != NULL); |
|
assert(rd->prefix_length == 64); |
|
|
|
free_node(nq); |
|
route6_table_destroy(t); |
|
uasync_destroy(ua, 0); |
|
} |
|
|
|
static void test_delete_nonexistent(void) { |
|
struct UASYNC *ua = uasync_create(); |
|
struct ROUTE6_TABLE *t = route6_table_create(ua); |
|
assert(t != NULL); |
|
|
|
uint8_t addr[16] = {0xfd, 10}; |
|
uint8_t plen = 64; |
|
struct TOPO_NODEQ *nq1 = make_node(1, &addr, &plen, 1); |
|
struct TOPO_NODEQ *nq2 = make_node(2, &addr, &plen, 1); |
|
assert(route6_insert(t, nq1) == true); |
|
route6_delete(t, nq2); // nq2 not in table — should not crash |
|
|
|
assert(route6_lookup(t, addr) != NULL); |
|
|
|
free_node(nq1); free_node(nq2); |
|
route6_table_destroy(t); |
|
uasync_destroy(ua, 0); |
|
} |
|
|
|
static void test_delete_null_args(void) { |
|
struct UASYNC *ua = uasync_create(); |
|
struct ROUTE6_TABLE *t = route6_table_create(ua); |
|
assert(t != NULL); |
|
|
|
route6_delete(NULL, NULL); |
|
route6_delete(t, NULL); |
|
route6_delete(NULL, (struct TOPO_NODEQ*)(uintptr_t)t); |
|
|
|
route6_table_destroy(t); |
|
uasync_destroy(ua, 0); |
|
} |
|
|
|
static void test_delete_multi_subnet(void) { |
|
struct UASYNC *ua = uasync_create(); |
|
struct ROUTE6_TABLE *t = route6_table_create(ua); |
|
assert(t != NULL); |
|
|
|
uint8_t addrs[3][16] = {{0xfc, 1}, {0xfc, 2}, {0xfc, 3}}; |
|
uint8_t plens[3] = {48, 56, 64}; |
|
struct TOPO_NODEQ *nq = make_node(30, addrs, plens, 3); |
|
assert(route6_insert(t, nq) == true); |
|
|
|
assert(route6_lookup(t, addrs[0]) != NULL); |
|
assert(route6_lookup(t, addrs[1]) != NULL); |
|
assert(route6_lookup(t, addrs[2]) != NULL); |
|
|
|
route6_delete(t, nq); |
|
|
|
assert(route6_lookup(t, addrs[0]) == NULL); |
|
assert(route6_lookup(t, addrs[1]) == NULL); |
|
assert(route6_lookup(t, addrs[2]) == NULL); |
|
|
|
free_node(nq); |
|
route6_table_destroy(t); |
|
uasync_destroy(ua, 0); |
|
} |
|
|
|
static void test_delete_one_of_two(void) { |
|
struct UASYNC *ua = uasync_create(); |
|
struct ROUTE6_TABLE *t = route6_table_create(ua); |
|
assert(t != NULL); |
|
|
|
uint8_t addr_a[16] = {0xfc, 0}; |
|
uint8_t addr_b[16] = {0xfd, 0}; |
|
uint8_t plen = 64; |
|
struct TOPO_NODEQ *nqa = make_node(100, &addr_a, &plen, 1); |
|
struct TOPO_NODEQ *nqb = make_node(200, &addr_b, &plen, 1); |
|
assert(route6_insert(t, nqa) == true); |
|
assert(route6_insert(t, nqb) == true); |
|
|
|
assert(route6_lookup(t, addr_a) != NULL); |
|
assert(route6_lookup(t, addr_b) != NULL); |
|
|
|
route6_delete(t, nqa); |
|
|
|
assert(route6_lookup(t, addr_a) == NULL); |
|
assert(route6_lookup(t, addr_b) != NULL); |
|
|
|
free_node(nqa); free_node(nqb); |
|
route6_table_destroy(t); |
|
uasync_destroy(ua, 0); |
|
} |
|
|
|
static void test_lookup_null_empty(void) { |
|
assert(route6_lookup(NULL, (const uint8_t[16]){0}) == NULL); |
|
|
|
struct UASYNC *ua = uasync_create(); |
|
struct ROUTE6_TABLE *t = route6_table_create(ua); |
|
assert(t != NULL); |
|
assert(route6_lookup(t, (const uint8_t[16]){0}) == NULL); |
|
route6_table_destroy(t); |
|
uasync_destroy(ua, 0); |
|
} |
|
|
|
static void test_lookup_returns_correct_data(void) { |
|
struct UASYNC *ua = uasync_create(); |
|
struct ROUTE6_TABLE *t = route6_table_create(ua); |
|
assert(t != NULL); |
|
|
|
uint8_t addr[16] = {0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; |
|
uint8_t plen = 64; |
|
struct TOPO_NODEQ *nq = make_node(42, &addr, &plen, 1); |
|
assert(route6_insert(t, nq) == true); |
|
|
|
const struct ROUTE6_DATA *rd = route6_lookup(t, addr); |
|
assert(rd != NULL); |
|
assert(rd->v_node_info == nq); |
|
assert(rd->prefix_length == 64); |
|
assert(memcmp(rd->key + 1, addr, 16) == 0); |
|
|
|
free_node(nq); |
|
route6_table_destroy(t); |
|
uasync_destroy(ua, 0); |
|
} |
|
|
|
/* ──── stress test ──── */ |
|
|
|
struct saved_route { |
|
uint8_t key[16]; |
|
uint8_t mask[16]; |
|
uint8_t plen; |
|
uint64_t node_id; |
|
int active; |
|
}; |
|
|
|
static void prefix_to_mask(uint8_t plen, uint8_t mask[16]) { |
|
memset(mask, 0, 16); |
|
if (plen == 0) return; |
|
for (int i = 0; i < 16 && plen > 0; i++) { |
|
if (plen >= 8) { mask[i] = 0xFF; plen -= 8; } |
|
else { mask[i] = (uint8_t)(0xFF00 >> plen); plen = 0; } |
|
} |
|
} |
|
|
|
static int brute_match(const uint8_t addr[16], struct saved_route *sr, int n, struct saved_route **out) { |
|
*out = NULL; |
|
int best_plen = -1; |
|
for (int i = 0; i < n; i++) { |
|
if (!sr[i].active) continue; |
|
int match = 1; |
|
for (int j = 0; j < 16 && match; j++) { |
|
if ((addr[j] & sr[i].mask[j]) != sr[i].key[j]) match = 0; |
|
} |
|
if (match && (int)sr[i].plen > best_plen) { |
|
best_plen = sr[i].plen; |
|
*out = &sr[i]; |
|
} |
|
} |
|
return 0; |
|
} |
|
|
|
static void test_stress(void) { |
|
unsigned seed = (unsigned)time(NULL); |
|
printf(" seed = %u\n", seed); |
|
srand(seed); |
|
|
|
struct UASYNC *ua = uasync_create(); |
|
struct ROUTE6_TABLE *t = route6_table_create(ua); |
|
assert(t != NULL); |
|
|
|
struct saved_route sr[MAX_SAVED]; |
|
int nsr = 0; |
|
struct TOPO_NODEQ *nodes[STRESS_NODES + STRESS_OPS]; |
|
|
|
// create nodes with random subnets |
|
int n_created = 0; |
|
for (int i = 0; i < STRESS_NODES; i++) { |
|
int ns = rand() % 4 + 1; |
|
uint8_t addrs[4][16]; uint8_t plens[4]; |
|
uint64_t nid = (uint64_t)(rand() + 1) << 32 | (uint32_t)rand(); |
|
for (int j = 0; j < ns; j++) { |
|
for (int k = 0; k < 16; k++) addrs[j][k] = (uint8_t)rand(); |
|
plens[j] = (uint8_t)(rand() % 96 + 33); // /33../128 |
|
uint8_t m[16]; prefix_to_mask(plens[j], m); |
|
for (int k = 0; k < 16; k++) addrs[j][k] &= m[k]; |
|
// save |
|
memcpy(sr[nsr].key, addrs[j], 16); |
|
memcpy(sr[nsr].mask, m, 16); |
|
sr[nsr].plen = plens[j]; |
|
sr[nsr].node_id = nid; |
|
sr[nsr].active = 1; |
|
nsr++; |
|
} |
|
nodes[n_created] = make_node(nid, (const uint8_t(*)[16])addrs, plens, ns); |
|
assert(nodes[n_created] != NULL); |
|
assert(route6_insert(t, nodes[n_created]) == true); |
|
n_created++; |
|
} |
|
|
|
// === Phase 1: random insert/delete ops === |
|
for (int op = 0; op < STRESS_OPS; op++) { |
|
if (rand() % 10 < 6 && n_created > 0) { |
|
// delete random active node |
|
int idx = rand() % n_created; |
|
uint64_t nid = nodes[idx]->node->node_id; |
|
route6_delete(t, nodes[idx]); |
|
// mark all routes of this node inactive |
|
for (int j = 0; j < nsr; j++) { |
|
if (sr[j].node_id == nid) sr[j].active = 0; |
|
} |
|
free_node(nodes[idx]); |
|
nodes[idx] = nodes[--n_created]; |
|
} |
|
// insert new random node |
|
int ns = rand() % 4 + 1; |
|
uint8_t addrs[4][16]; uint8_t plens[4]; |
|
uint64_t nid = (uint64_t)(rand() + 1) << 32 | (uint32_t)rand(); |
|
for (int j = 0; j < ns; j++) { |
|
for (int k = 0; k < 16; k++) addrs[j][k] = (uint8_t)rand(); |
|
plens[j] = (uint8_t)(rand() % 96 + 33); |
|
uint8_t m[16]; prefix_to_mask(plens[j], m); |
|
for (int k = 0; k < 16; k++) addrs[j][k] &= m[k]; |
|
if (nsr < MAX_SAVED) { |
|
memcpy(sr[nsr].key, addrs[j], 16); memcpy(sr[nsr].mask, m, 16); |
|
sr[nsr].plen = plens[j]; sr[nsr].node_id = nid; sr[nsr].active = 1; |
|
nsr++; |
|
} |
|
} |
|
nodes[n_created] = make_node(nid, (const uint8_t(*)[16])addrs, plens, ns); |
|
assert(route6_insert(t, nodes[n_created]) == true); |
|
n_created++; |
|
} |
|
|
|
// === Phase 2: random lookups === |
|
for (int iter = 0; iter < STRESS_LOOKUPS; iter++) { |
|
uint8_t addr[16]; |
|
for (int k = 0; k < 16; k++) addr[k] = (uint8_t)rand(); |
|
|
|
const struct ROUTE6_DATA *rd = route6_lookup(t, addr); |
|
struct saved_route *expected = NULL; |
|
brute_match(addr, sr, nsr, &expected); |
|
|
|
if (expected == NULL) { |
|
assert(rd == NULL); |
|
} else { |
|
assert(rd != NULL); |
|
assert(rd->prefix_length == expected->plen); |
|
assert(rd->node_id == expected->node_id); |
|
assert(memcmp(rd->key + 1, expected->key, 16) == 0); |
|
} |
|
} |
|
|
|
// === Phase 3: provoke error paths === |
|
// 3a. duplicate insert on remaining active node |
|
if (n_created > 0) { |
|
int idx = rand() % n_created; |
|
assert(route6_insert(t, nodes[idx]) == true); |
|
} |
|
|
|
// 3b. delete already-deleted node (from a new fake node) |
|
{ |
|
uint8_t addr[16] = {0xfc, 0}; |
|
uint8_t plen = 64; |
|
struct TOPO_NODEQ *ghost = make_node(999999, &addr, &plen, 1); |
|
route6_delete(t, ghost); // should no-op |
|
free_node(ghost); |
|
} |
|
|
|
// 3c. insert zero-subnet node |
|
{ |
|
uint8_t addr[16] = {0}; |
|
uint8_t zero_plen = 0; |
|
struct TOPO_NODEQ *z = make_node(777, (const uint8_t(*)[16])addr, &zero_plen, 0); |
|
assert(route6_insert(t, z) == false); |
|
free_node(z); |
|
} |
|
|
|
// 3d. lookup NULL |
|
assert(route6_lookup(NULL, (const uint8_t[16]){0}) == NULL); |
|
|
|
// 3e. delete all remaining, verify emptiness |
|
while (n_created > 0) { |
|
int idx = 0; |
|
route6_delete(t, nodes[idx]); |
|
free_node(nodes[idx]); |
|
nodes[idx] = nodes[--n_created]; |
|
} |
|
for (int iter = 0; iter < 10; iter++) { |
|
uint8_t addr[16]; |
|
for (int k = 0; k < 16; k++) addr[k] = (uint8_t)rand(); |
|
assert(route6_lookup(t, addr) == NULL); |
|
} |
|
|
|
// 3f. destroy NULL — no crash |
|
route6_table_destroy(NULL); |
|
|
|
route6_table_destroy(t); |
|
uasync_destroy(ua, 0); |
|
} |
|
|
|
int main(void) { |
|
debug_config_init(); |
|
printf("test_route6_lib\n"); |
|
|
|
RUN_TEST(test_insert_null_args); |
|
RUN_TEST(test_insert_zero_subnets); |
|
RUN_TEST(test_insert_duplicate); |
|
RUN_TEST(test_insert_delete_reinsert); |
|
RUN_TEST(test_delete_nonexistent); |
|
RUN_TEST(test_delete_null_args); |
|
RUN_TEST(test_delete_multi_subnet); |
|
RUN_TEST(test_delete_one_of_two); |
|
RUN_TEST(test_lookup_null_empty); |
|
RUN_TEST(test_lookup_returns_correct_data); |
|
|
|
RUN_TEST(test_stress); |
|
|
|
printf("ALL ROUTE6_LIB TESTS PASSED (%d tests)\n", tests_run); |
|
return 0; |
|
}
|
|
|