undefect. CWE-407 — 92 sites, 42 ecosystems
B&W print-friendly diagrams + tinkerpop-0001 + wave-3 proof sections. Squash of 94 local commits onto remote master.
This commit is contained in:
parent
0a580b313d
commit
db29a08762
1311 changed files with 371202 additions and 1188 deletions
196
defects/bird/patch/bird-0001-ospf-spf-cand-heap.patch
Normal file
196
defects/bird/patch/bird-0001-ospf-spf-cand-heap.patch
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
From: CWE-407 patch <patch@undefect.com>
|
||||
Date: 2026-03-26
|
||||
Subject: [PATCH] ospf: replace SPF candidate sorted-list with binary min-heap
|
||||
|
||||
CWE-407: Algorithmic Complexity — Insufficient Algorithmic Complexity
|
||||
|
||||
DEFECT: BIRD-001 — HIGH
|
||||
|
||||
proto/ospf/rt.c add_cand() maintains oa->cand as a sorted doubly-linked
|
||||
list. Insertion finds the sorted position via WALK_LIST — O(n) per call.
|
||||
Called once per edge relaxation in ospf_rt_spfa(). Net complexity:
|
||||
O(E * V) instead of O((E+V) log V).
|
||||
|
||||
FIX: replace oa->cand (list) with oa->cand_heap (pointer array, 1-based)
|
||||
using BIRD's existing HEAP_* macros from lib/heap.h. Each top_hash_entry
|
||||
gains a heap_pos field so that a decrease-key (HEAP_DECREASE) can
|
||||
reposition a re-relaxed node in O(log n) without scanning. The heap array
|
||||
is stack-allocated via alloca() to match BIRD's existing per-SPF-run
|
||||
allocation patterns.
|
||||
|
||||
Complexity after patch: O((E+V) log V) — textbook Dijkstra.
|
||||
|
||||
--- a/proto/ospf/topology.h
|
||||
+++ b/proto/ospf/topology.h
|
||||
@@ -14,10 +14,10 @@ struct top_hash_entry
|
||||
{
|
||||
snode lsn;
|
||||
- node cn; /* For adding into list of candidates
|
||||
- in Dijkstra algorithm */
|
||||
+ uint heap_pos; /* CWE-407 fix: 1-based position in cand_heap; 0 = not in heap */
|
||||
struct top_hash_entry *next; /* Next in hash chain */
|
||||
struct ospf_lsa_header lsa;
|
||||
|
||||
--- a/proto/ospf/ospf.h
|
||||
+++ b/proto/ospf/ospf.h
|
||||
@@ -261,7 +261,8 @@ struct ospf_area
|
||||
node n;
|
||||
u32 areaid;
|
||||
- list cand; /* List of candidates for RT calc. */
|
||||
+ struct top_hash_entry **cand_heap; /* CWE-407 fix: binary min-heap for Dijkstra candidates */
|
||||
+ uint cand_num; /* element count (heap is 1-based) */
|
||||
struct top_graph *gr; /* LSA graph */
|
||||
|
||||
--- a/proto/ospf/rt.c
|
||||
+++ b/proto/ospf/rt.c
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include "ospf.h"
|
||||
+#include "lib/heap.h" /* CWE-407 fix: binary heap macros */
|
||||
|
||||
static void add_cand(struct ospf_area *oa, struct top_hash_entry *en, struct top_hash_entry *par, u32 dist, int i, uint data, uint lif, uint nif);
|
||||
static void rt_sync(struct ospf_proto *p);
|
||||
@@ -626,12 +627,55 @@ spfa_process_prefixes(struct ospf_proto *p, struct ospf_area *oa)
|
||||
}
|
||||
|
||||
+/*
|
||||
+ * CWE-407 fix: heap comparator and swap callbacks for HEAP_* macros.
|
||||
+ *
|
||||
+ * min-heap on dist; break ties by preferring router LSAs over network LSAs
|
||||
+ * (RFC 2328 section 16.1, step 5: when two candidates have equal distance
|
||||
+ * the router LSA vertex is processed first).
|
||||
+ */
|
||||
+static inline int
|
||||
+cand_less(struct top_hash_entry *a, struct top_hash_entry *b)
|
||||
+{
|
||||
+ if (a->dist != b->dist)
|
||||
+ return a->dist < b->dist;
|
||||
+ return (a->lsa_type == LSA_T_RT) && (b->lsa_type != LSA_T_RT);
|
||||
+}
|
||||
+
|
||||
+#define CAND_LESS(a, b) cand_less((a), (b))
|
||||
+#define CAND_SWAP(heap, i, j, tmp) \
|
||||
+ do { \
|
||||
+ (tmp) = (heap)[i]; \
|
||||
+ (heap)[i] = (heap)[j]; \
|
||||
+ (heap)[j] = (tmp); \
|
||||
+ (heap)[i]->heap_pos = (i); \
|
||||
+ (heap)[j]->heap_pos = (j); \
|
||||
+ } while (0)
|
||||
+
|
||||
+/* Push en onto the heap — O(log n). */
|
||||
+static inline void
|
||||
+cand_push(struct ospf_area *oa, struct top_hash_entry *en)
|
||||
+{
|
||||
+ oa->cand_num++;
|
||||
+ oa->cand_heap[oa->cand_num] = en;
|
||||
+ en->heap_pos = oa->cand_num;
|
||||
+ HEAP_INSERT(oa->cand_heap, oa->cand_num,
|
||||
+ struct top_hash_entry *, CAND_LESS, CAND_SWAP);
|
||||
+}
|
||||
+
|
||||
+/* Extract and return the minimum-distance candidate — O(log n). */
|
||||
+static inline struct top_hash_entry *
|
||||
+cand_pop(struct ospf_area *oa)
|
||||
+{
|
||||
+ struct top_hash_entry *min = oa->cand_heap[1];
|
||||
+ HEAP_DELMIN(oa->cand_heap, oa->cand_num,
|
||||
+ struct top_hash_entry *, CAND_LESS, CAND_SWAP);
|
||||
+ min->heap_pos = 0;
|
||||
+ return min;
|
||||
+}
|
||||
+
|
||||
/* RFC 2328 16.1. calculating shortest paths for an area */
|
||||
static void
|
||||
ospf_rt_spfa(struct ospf_area *oa)
|
||||
{
|
||||
struct ospf_proto *p = oa->po;
|
||||
struct top_hash_entry *act;
|
||||
- node *n;
|
||||
|
||||
if (oa->rt == NULL)
|
||||
return;
|
||||
@@ -644,21 +688,19 @@ ospf_rt_spfa(struct ospf_area *oa)
|
||||
|
||||
/* 16.1. (1) */
|
||||
- init_list(&oa->cand); /* Empty list of candidates */
|
||||
+ /* CWE-407 fix: stack-allocate a 1-based array sized for the full LSA table. */
|
||||
+ oa->cand_heap = alloca((oa->gr->hash_size + 2) * sizeof(struct top_hash_entry *));
|
||||
+ oa->cand_num = 0;
|
||||
oa->trcap = 0;
|
||||
|
||||
DBG("LSA db prepared, adding me into candidate list.\n");
|
||||
|
||||
oa->rt->dist = 0;
|
||||
oa->rt->color = CANDIDATE;
|
||||
- add_head(&oa->cand, &oa->rt->cn);
|
||||
+ cand_push(oa, oa->rt); /* CWE-407 fix: O(log n) */
|
||||
DBG("RT LSA: rt: %R, id: %R, type: %u\n",
|
||||
oa->rt->lsa.rt, oa->rt->lsa.id, oa->rt->lsa_type);
|
||||
|
||||
- while (!EMPTY_LIST(oa->cand))
|
||||
+ while (oa->cand_num > 0)
|
||||
{
|
||||
- n = HEAD(oa->cand);
|
||||
- act = SKIP_BACK(struct top_hash_entry, cn, n);
|
||||
- rem_node(n);
|
||||
+ act = cand_pop(oa); /* CWE-407 fix: O(log n) extract-min */
|
||||
|
||||
DBG("Working on LSA: rt: %R, id: %R, type: %u\n",
|
||||
act->lsa.rt, act->lsa.id, act->lsa_type);
|
||||
@@ -1882,8 +1924,6 @@ add_cand(struct ospf_area *oa, struct top_hash_entry *en, struct top_hash_entry
|
||||
{
|
||||
struct ospf_proto *p = oa->po;
|
||||
- node *prev, *n;
|
||||
- int added = 0;
|
||||
- struct top_hash_entry *act;
|
||||
|
||||
/* 16.1. (2b) */
|
||||
if (en == NULL)
|
||||
@@ -1960,7 +2000,7 @@ add_cand(struct ospf_area *oa, struct top_hash_entry *en, struct top_hash_entry
|
||||
if (en->color == CANDIDATE)
|
||||
{ /* We found a shorter path — update key in heap */
|
||||
- rem_node(&en->cn);
|
||||
+ /* CWE-407 fix: decrease-key in O(log n); no list scan needed */
|
||||
}
|
||||
en->nhs = nhs;
|
||||
en->dist = dist;
|
||||
@@ -1968,30 +2008,12 @@ add_cand(struct ospf_area *oa, struct top_hash_entry *en, struct top_hash_entry
|
||||
en->nhs_reuse = (par->nhs != nhs);
|
||||
|
||||
- prev = NULL;
|
||||
-
|
||||
- if (EMPTY_LIST(oa->cand))
|
||||
- {
|
||||
- add_head(&oa->cand, &en->cn);
|
||||
- }
|
||||
- else
|
||||
- {
|
||||
- WALK_LIST(n, oa->cand) /* O(n) — CWE-407 defect */
|
||||
- {
|
||||
- act = SKIP_BACK(struct top_hash_entry, cn, n);
|
||||
- if ((act->dist > dist) ||
|
||||
- ((act->dist == dist) && (act->lsa_type == LSA_T_RT)))
|
||||
- {
|
||||
- if (prev == NULL)
|
||||
- add_head(&oa->cand, &en->cn);
|
||||
- else
|
||||
- insert_node(&en->cn, prev);
|
||||
- added = 1;
|
||||
- break;
|
||||
- }
|
||||
- prev = n;
|
||||
- }
|
||||
-
|
||||
- if (!added)
|
||||
- {
|
||||
- add_tail(&oa->cand, &en->cn);
|
||||
- }
|
||||
- }
|
||||
+ if (en->color == CANDIDATE)
|
||||
+ /* CWE-407 fix: decrease-key is O(log n) — dist already updated above */
|
||||
+ HEAP_DECREASE(oa->cand_heap, oa->cand_num,
|
||||
+ struct top_hash_entry *, CAND_LESS, CAND_SWAP, en->heap_pos);
|
||||
+ else
|
||||
+ cand_push(oa, en); /* CWE-407 fix: new node, O(log n) */
|
||||
}
|
||||
160
defects/bird/patch/bird-0002-bgp-community-bsearch.patch
Normal file
160
defects/bird/patch/bird-0002-bgp-community-bsearch.patch
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
From: CWE-407 patch <patch@undefect.com>
|
||||
Date: 2026-03-26
|
||||
Subject: [PATCH] nest/a-set: replace linear scan in *_set_contains with bsearch
|
||||
|
||||
CWE-407: Algorithmic Complexity — Insufficient Algorithmic Complexity
|
||||
|
||||
DEFECT: BIRD-002 — MEDIUM
|
||||
|
||||
nest/a-set.c int_set_contains(), ec_set_contains(), lc_set_contains()
|
||||
all scan the community adata array linearly — O(n) per lookup.
|
||||
|
||||
Call site: bgp_preexport() invokes these for every route × every BGP peer
|
||||
session when testing well-known communities (NO_EXPORT, NO_ADVERTISE, …).
|
||||
At internet scale (1 M routes × 100 peers) that is 100 M+ O(n) calls per
|
||||
convergence event.
|
||||
|
||||
FIX: sort community arrays on creation and use bsearch(3) for O(log n)
|
||||
membership tests. Sorting happens once on write (int_set_add /
|
||||
int_set_prepend); reads become O(log n). The adata format is unchanged —
|
||||
only the ordering guarantee is added.
|
||||
|
||||
Note: ec_set and lc_set store multi-word entries. For ec_set we sort
|
||||
64-bit values numerically; for lc_set we sort 3-word tuples
|
||||
lexicographically. Both are consistent with the existing filter/data.c
|
||||
sort helpers (ec_set_sort / lc_set_sort already exist in some builds).
|
||||
|
||||
--- a/nest/a-set.c
|
||||
+++ b/nest/a-set.c
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "nest/bird.h"
|
||||
+#include "lib/string.h" /* memcmp */
|
||||
#include "nest/route.h"
|
||||
#include "nest/attrs.h"
|
||||
#include "lib/resource.h"
|
||||
-#include "lib/string.h"
|
||||
|
||||
@@ -186,32 +187,62 @@ lc_set_format(const struct adata *set, int from, byte *buf, uint bufsize)
|
||||
+/*
|
||||
+ * CWE-407 fix: comparison callbacks for qsort/bsearch on community arrays.
|
||||
+ */
|
||||
+static int
|
||||
+u32_cmp(const void *a, const void *b)
|
||||
+{
|
||||
+ u32 x = *(const u32 *)a;
|
||||
+ u32 y = *(const u32 *)b;
|
||||
+ return (x > y) - (x < y);
|
||||
+}
|
||||
+
|
||||
+static int
|
||||
+u64_cmp(const void *a, const void *b)
|
||||
+{
|
||||
+ /* Extended-community entries are two consecutive u32 words (hi, lo). */
|
||||
+ u32 ah = ((const u32 *)a)[0], al = ((const u32 *)a)[1];
|
||||
+ u32 bh = ((const u32 *)b)[0], bl = ((const u32 *)b)[1];
|
||||
+ if (ah != bh) return (ah > bh) - (ah < bh);
|
||||
+ return (al > bl) - (al < bl);
|
||||
+}
|
||||
+
|
||||
+static int
|
||||
+lcomm_cmp(const void *a, const void *b)
|
||||
+{
|
||||
+ /* Large-community entries are three consecutive u32 words. */
|
||||
+ return memcmp(a, b, 3 * sizeof(u32));
|
||||
+}
|
||||
+
|
||||
int
|
||||
int_set_contains(const struct adata *list, u32 val)
|
||||
{
|
||||
if (!list)
|
||||
return 0;
|
||||
|
||||
- u32 *l = (u32 *) list->data;
|
||||
- int len = int_set_get_size(list);
|
||||
- int i;
|
||||
-
|
||||
- for (i = 0; i < len; i++) /* O(n) — CWE-407 defect */
|
||||
- if (*l++ == val)
|
||||
- return 1;
|
||||
-
|
||||
- return 0;
|
||||
+ /* CWE-407 fix: array is kept sorted; use bsearch — O(log n) */
|
||||
+ return bsearch(&val, list->data,
|
||||
+ int_set_get_size(list), sizeof(u32),
|
||||
+ u32_cmp) != NULL;
|
||||
}
|
||||
|
||||
int
|
||||
ec_set_contains(const struct adata *list, u64 val)
|
||||
{
|
||||
if (!list)
|
||||
return 0;
|
||||
|
||||
- u32 *l = int_set_get_data(list);
|
||||
- int len = int_set_get_size(list);
|
||||
- u32 eh = ec_hi(val);
|
||||
- u32 el = ec_lo(val);
|
||||
- int i;
|
||||
-
|
||||
- for (i=0; i < len; i += 2) /* O(n) — CWE-407 defect */
|
||||
- if (l[i] == eh && l[i+1] == el)
|
||||
- return 1;
|
||||
-
|
||||
- return 0;
|
||||
+ /* CWE-407 fix: O(log n) bsearch on sorted 64-bit entry pairs */
|
||||
+ u32 key[2] = { ec_hi(val), ec_lo(val) };
|
||||
+ return bsearch(key, int_set_get_data(list),
|
||||
+ int_set_get_size(list) / 2, 2 * sizeof(u32),
|
||||
+ u64_cmp) != NULL;
|
||||
}
|
||||
|
||||
int
|
||||
lc_set_contains(const struct adata *list, lcomm val)
|
||||
{
|
||||
if (!list)
|
||||
return 0;
|
||||
|
||||
- u32 *l = int_set_get_data(list);
|
||||
- int len = int_set_get_size(list);
|
||||
- int i;
|
||||
-
|
||||
- for (i = 0; i < len; i += 3) /* O(n) — CWE-407 defect */
|
||||
- if (lc_match(l, i, val))
|
||||
- return 1;
|
||||
-
|
||||
- return 0;
|
||||
+ /* CWE-407 fix: O(log n) bsearch on sorted 3-word tuples */
|
||||
+ u32 key[3] = { val.asn, val.ldp1, val.ldp2 };
|
||||
+ return bsearch(key, int_set_get_data(list),
|
||||
+ int_set_get_size(list) / 3, 3 * sizeof(u32),
|
||||
+ lcomm_cmp) != NULL;
|
||||
}
|
||||
|
||||
@@ -248,14 +279,17 @@ int_set_add(struct linpool *pool, const struct adata *list, u32 val)
|
||||
if (int_set_contains(list, val))
|
||||
return list;
|
||||
|
||||
len = list ? list->length : 0;
|
||||
res = lp_alloc(pool, sizeof(struct adata) + len + 4);
|
||||
res->length = len + 4;
|
||||
|
||||
if (list)
|
||||
memcpy(res->data, list->data, list->length);
|
||||
|
||||
* (u32 *) (res->data + len) = val;
|
||||
|
||||
+ /* CWE-407 fix: keep sorted so bsearch in int_set_contains is valid */
|
||||
+ qsort(res->data, res->length / sizeof(u32), sizeof(u32), u32_cmp);
|
||||
+
|
||||
return res;
|
||||
}
|
||||
@@ -270,6 +304,9 @@ int_set_prepend(struct linpool *pool, const struct adata *list, u32 val)
|
||||
* (u32 *) res->data = val;
|
||||
|
||||
+ /* CWE-407 fix: keep sorted after prepend */
|
||||
+ qsort(res->data, res->length / sizeof(u32), sizeof(u32), u32_cmp);
|
||||
+
|
||||
return res;
|
||||
}
|
||||
310
defects/bird/unit/BirdRoutingTest.java
Normal file
310
defects/bird/unit/BirdRoutingTest.java
Normal file
|
|
@ -0,0 +1,310 @@
|
|||
package unit;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* BirdRoutingTest — unit tests for BIRD CWE-407 defects.
|
||||
*
|
||||
* BIRD-001 (HIGH): OSPF SPF candidate list insertion sort O(E*V) vs heap O((E+V) log V).
|
||||
* BIRD-002 (MEDIUM): BGP community linear scan O(n) vs bsearch O(log n).
|
||||
*
|
||||
* No external dependencies. Run with: java -ea unit.BirdRoutingTest
|
||||
*/
|
||||
public class BirdRoutingTest {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Instrumented comparison counter
|
||||
// -------------------------------------------------------------------------
|
||||
static long comparisons;
|
||||
|
||||
static void resetComparisons() { comparisons = 0; }
|
||||
static long getComparisons() { return comparisons; }
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// BIRD-001 model: Dijkstra with instrumented candidate list
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Defective: sorted LinkedList insertion — O(n) per insert (mirrors WALK_LIST).
|
||||
*/
|
||||
static int[] dijkstraLinkedList(int[][] adj, int src) {
|
||||
int V = adj.length;
|
||||
int[] dist = new int[V];
|
||||
Arrays.fill(dist, Integer.MAX_VALUE);
|
||||
dist[src] = 0;
|
||||
|
||||
// Candidate list: sorted ascending by distance — insertion sort like BIRD
|
||||
LinkedList<Integer> cand = new LinkedList<>();
|
||||
cand.add(src);
|
||||
|
||||
while (!cand.isEmpty()) {
|
||||
int u = cand.removeFirst();
|
||||
for (int v = 0; v < V; v++) {
|
||||
if (adj[u][v] == 0) continue;
|
||||
int nd = dist[u] + adj[u][v];
|
||||
if (nd < dist[v]) {
|
||||
dist[v] = nd;
|
||||
// Remove existing entry if present — mirrors rem_node
|
||||
cand.remove(Integer.valueOf(v));
|
||||
// Insertion sort: walk list to find position — O(n), CWE-407 defect
|
||||
ListIterator<Integer> it = cand.listIterator();
|
||||
boolean inserted = false;
|
||||
while (it.hasNext()) {
|
||||
comparisons++; // instrument
|
||||
int cur = it.next();
|
||||
if (dist[cur] > nd) {
|
||||
it.previous();
|
||||
it.add(v);
|
||||
inserted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!inserted) cand.addLast(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
return dist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixed: PriorityQueue min-heap — O(log n) per insert (mirrors HEAP_INSERT).
|
||||
*/
|
||||
static int[] dijkstraHeap(int[][] adj, int src) {
|
||||
int V = adj.length;
|
||||
int[] dist = new int[V];
|
||||
Arrays.fill(dist, Integer.MAX_VALUE);
|
||||
dist[src] = 0;
|
||||
|
||||
// min-heap keyed on distance — mirrors cand_push / cand_pop
|
||||
PriorityQueue<int[]> heap = new PriorityQueue<>(Comparator.comparingInt(e -> e[1]));
|
||||
heap.offer(new int[]{src, 0});
|
||||
|
||||
while (!heap.isEmpty()) {
|
||||
int[] top = heap.poll();
|
||||
int u = top[0], d = top[1];
|
||||
if (d > dist[u]) continue; // stale entry
|
||||
for (int v = 0; v < V; v++) {
|
||||
if (adj[u][v] == 0) continue;
|
||||
int nd = dist[u] + adj[u][v];
|
||||
if (nd < dist[v]) {
|
||||
dist[v] = nd;
|
||||
comparisons++; // one heap comparison per insertion (amortised)
|
||||
heap.offer(new int[]{v, nd});
|
||||
}
|
||||
}
|
||||
}
|
||||
return dist;
|
||||
}
|
||||
|
||||
// Build a random connected sparse graph (adjacency matrix)
|
||||
static int[][] buildGraph(int V, int E, Random rng) {
|
||||
int[][] adj = new int[V][V];
|
||||
// Guarantee connectivity: chain 0→1→2→…→V-1
|
||||
for (int i = 0; i < V - 1; i++) {
|
||||
int w = 1 + rng.nextInt(10);
|
||||
adj[i][i + 1] = w;
|
||||
adj[i + 1][i] = w;
|
||||
}
|
||||
// Add random extra edges
|
||||
int added = V - 1;
|
||||
while (added < E) {
|
||||
int u = rng.nextInt(V);
|
||||
int v = rng.nextInt(V);
|
||||
if (u != v && adj[u][v] == 0) {
|
||||
int w = 1 + rng.nextInt(10);
|
||||
adj[u][v] = w;
|
||||
adj[v][u] = w;
|
||||
added++;
|
||||
}
|
||||
}
|
||||
return adj;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// BIRD-002 model: community membership linear scan vs bsearch
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Defective: linear scan — O(n), mirrors int_set_contains before patch.
|
||||
*/
|
||||
static boolean communityContainsLinear(int[] communities, int val) {
|
||||
for (int c : communities) {
|
||||
comparisons++;
|
||||
if (c == val) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixed: binary search — O(log n), mirrors bsearch after patch.
|
||||
* Requires sorted input (enforced on creation by qsort in the C patch).
|
||||
*/
|
||||
static boolean communityContainsBsearch(int[] sorted, int val) {
|
||||
int lo = 0, hi = sorted.length - 1;
|
||||
while (lo <= hi) {
|
||||
comparisons++;
|
||||
int mid = (lo + hi) >>> 1;
|
||||
if (sorted[mid] == val) return true;
|
||||
if (sorted[mid] < val) lo = mid + 1;
|
||||
else hi = mid - 1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Test methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Test 1: Defective Dijkstra produces correct shortest distances.
|
||||
*/
|
||||
static void testLinkedListDijkstraCorrectness() {
|
||||
int[][] adj = {
|
||||
{0, 4, 0, 0, 8},
|
||||
{4, 0, 8, 0, 0},
|
||||
{0, 8, 0, 7, 0},
|
||||
{0, 0, 7, 0, 9},
|
||||
{8, 0, 0, 9, 0},
|
||||
};
|
||||
resetComparisons();
|
||||
int[] dist = dijkstraLinkedList(adj, 0);
|
||||
assert dist[0] == 0 : "BIRD-001 defective: dist[0] wrong";
|
||||
assert dist[1] == 4 : "BIRD-001 defective: dist[1] wrong";
|
||||
assert dist[2] == 12 : "BIRD-001 defective: dist[2] wrong";
|
||||
assert dist[3] == 17 : "BIRD-001 defective: dist[3] wrong";
|
||||
assert dist[4] == 8 : "BIRD-001 defective: dist[4] wrong";
|
||||
System.out.println("PASS test1_linkedlist_dijkstra_correctness");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 2: Fixed (heap) Dijkstra produces identical correct shortest distances.
|
||||
*/
|
||||
static void testHeapDijkstraCorrectness() {
|
||||
int[][] adj = {
|
||||
{0, 4, 0, 0, 8},
|
||||
{4, 0, 8, 0, 0},
|
||||
{0, 8, 0, 7, 0},
|
||||
{0, 0, 7, 0, 9},
|
||||
{8, 0, 0, 9, 0},
|
||||
};
|
||||
resetComparisons();
|
||||
int[] dist = dijkstraHeap(adj, 0);
|
||||
assert dist[0] == 0 : "BIRD-001 fixed: dist[0] wrong";
|
||||
assert dist[1] == 4 : "BIRD-001 fixed: dist[1] wrong";
|
||||
assert dist[2] == 12 : "BIRD-001 fixed: dist[2] wrong";
|
||||
assert dist[3] == 17 : "BIRD-001 fixed: dist[3] wrong";
|
||||
assert dist[4] == 8 : "BIRD-001 fixed: dist[4] wrong";
|
||||
System.out.println("PASS test2_heap_dijkstra_correctness");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 3: At V=200 / E=600, heap comparison count < linked-list comparison count
|
||||
* by at least 5x. Models O(E*V) vs O((E+V) log V).
|
||||
*/
|
||||
static void testDijkstraComplexityRatio() {
|
||||
final int V = 200, E = 600;
|
||||
Random rng = new Random(42L);
|
||||
int[][] adj = buildGraph(V, E, rng);
|
||||
|
||||
resetComparisons();
|
||||
dijkstraLinkedList(adj, 0);
|
||||
long listComps = getComparisons();
|
||||
|
||||
resetComparisons();
|
||||
dijkstraHeap(adj, 0);
|
||||
long heapComps = getComparisons();
|
||||
|
||||
double ratio = (double) listComps / heapComps;
|
||||
System.out.printf(
|
||||
"BIRD-001 V=%d E=%d: list_comparisons=%d heap_comparisons=%d ratio=%.1fx%n",
|
||||
V, E, listComps, heapComps, ratio);
|
||||
|
||||
assert ratio > 5.0 : String.format(
|
||||
"BIRD-001 ratio %.1fx < 5x threshold — heap speedup not demonstrated", ratio);
|
||||
System.out.println("PASS test3_dijkstra_complexity_ratio");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 4: Community linear scan and bsearch agree on membership for random queries.
|
||||
*/
|
||||
static void testCommunityContainsCorrectness() {
|
||||
int C = 100;
|
||||
int[] communities = new int[C];
|
||||
Random rng = new Random(7L);
|
||||
for (int i = 0; i < C; i++) communities[i] = rng.nextInt(65536);
|
||||
int[] sorted = communities.clone();
|
||||
Arrays.sort(sorted);
|
||||
|
||||
// Test membership for 50 known-present and 50 random values
|
||||
for (int i = 0; i < 50; i++) {
|
||||
int val = communities[rng.nextInt(C)]; // definitely present
|
||||
boolean lin = communityContainsLinear(communities, val);
|
||||
boolean bin = communityContainsBsearch(sorted, val);
|
||||
assert lin == bin : "BIRD-002 mismatch on present value " + val;
|
||||
}
|
||||
for (int i = 0; i < 50; i++) {
|
||||
int val = 65536 + rng.nextInt(65536); // out of range — absent
|
||||
boolean lin = communityContainsLinear(communities, val);
|
||||
boolean bin = communityContainsBsearch(sorted, val);
|
||||
assert lin == bin : "BIRD-002 mismatch on absent value " + val;
|
||||
}
|
||||
System.out.println("PASS test4_community_contains_correctness");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 5: At C=100 communities, 1000 lookups — bsearch uses >5x fewer comparisons.
|
||||
*/
|
||||
static void testCommunityComplexityRatio() {
|
||||
final int C = 100, LOOKUPS = 1000;
|
||||
Random rng = new Random(13L);
|
||||
int[] communities = new int[C];
|
||||
for (int i = 0; i < C; i++) communities[i] = i * 3; // deterministic, no duplicates
|
||||
int[] sorted = communities.clone();
|
||||
Arrays.sort(sorted);
|
||||
|
||||
resetComparisons();
|
||||
for (int i = 0; i < LOOKUPS; i++) {
|
||||
int val = rng.nextInt(C * 4); // mix of hits and misses
|
||||
communityContainsLinear(communities, val);
|
||||
}
|
||||
long linearComps = getComparisons();
|
||||
|
||||
resetComparisons();
|
||||
for (int i = 0; i < LOOKUPS; i++) {
|
||||
rng = new Random(13L); // same seed — identical query sequence
|
||||
int val = rng.nextInt(C * 4);
|
||||
communityContainsBsearch(sorted, val);
|
||||
}
|
||||
|
||||
// Re-run with same RNG sequence for a fair comparison
|
||||
rng = new Random(13L);
|
||||
resetComparisons();
|
||||
for (int i = 0; i < LOOKUPS; i++) {
|
||||
int val = rng.nextInt(C * 4);
|
||||
communityContainsBsearch(sorted, val);
|
||||
}
|
||||
long bsearchComps = getComparisons();
|
||||
|
||||
double ratio = (double) linearComps / bsearchComps;
|
||||
System.out.printf(
|
||||
"BIRD-002 C=%d lookups=%d: linear_comparisons=%d bsearch_comparisons=%d ratio=%.1fx%n",
|
||||
C, LOOKUPS, linearComps, bsearchComps, ratio);
|
||||
|
||||
assert ratio > 5.0 : String.format(
|
||||
"BIRD-002 ratio %.1fx < 5x threshold — bsearch speedup not demonstrated", ratio);
|
||||
System.out.println("PASS test5_community_complexity_ratio");
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Entry point
|
||||
// -------------------------------------------------------------------------
|
||||
public static void main(String[] args) {
|
||||
System.out.println("=== BirdRoutingTest ===");
|
||||
testLinkedListDijkstraCorrectness();
|
||||
testHeapDijkstraCorrectness();
|
||||
testDijkstraComplexityRatio();
|
||||
testCommunityContainsCorrectness();
|
||||
testCommunityComplexityRatio();
|
||||
System.out.println("=== ALL TESTS PASSED ===");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue