B&W print-friendly diagrams + tinkerpop-0001 + wave-3 proof sections. Squash of 94 local commits onto remote master.
258 lines
9.6 KiB
Java
258 lines
9.6 KiB
Java
package unit;
|
|
|
|
import java.util.*;
|
|
|
|
/**
|
|
* HttpdProxyBalancerTest — CWE-407 model test for Apache httpd HTTPD-001.
|
|
*
|
|
* Models the defect in mod_proxy_balancer.c where every sticky-session request
|
|
* triggers a linear O(W) strcmp scan over all worker route strings.
|
|
*
|
|
* Defective: List<Worker> scanned with String.equals() per request → O(W·R)
|
|
* Fixed: HashMap<String, Worker> lookup per request → O(R)
|
|
*
|
|
* Parameters:
|
|
* W = 100 workers, R = 1 000 requests
|
|
*
|
|
* Five test methods:
|
|
* 1. testDefectiveCorrectness — defective path returns the right worker
|
|
* 2. testFixedCorrectness — fixed path returns the right worker
|
|
* 3. testDefectiveCompareCount — defective path performs O(W·R) comparisons
|
|
* 4. testFixedCompareCount — fixed path performs O(R) comparisons
|
|
* 5. testSpeedupRatio — ratio > 50x
|
|
*/
|
|
public class HttpdProxyBalancerTest {
|
|
|
|
static final int W = 100; // worker count
|
|
static final int R = 1_000; // request count
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Model classes
|
|
// -----------------------------------------------------------------------
|
|
|
|
/** Instrumented string comparison counter (shared, reset between tests). */
|
|
static long compareCount = 0;
|
|
|
|
static boolean instrumentedEquals(String a, String b) {
|
|
compareCount++;
|
|
return a.equals(b);
|
|
}
|
|
|
|
static class Worker {
|
|
final String route;
|
|
Worker(String route) { this.route = route; }
|
|
}
|
|
|
|
/** Defective balancer: scans all workers linearly per request. */
|
|
static class DefectiveBalancer {
|
|
final List<Worker> workers = new ArrayList<>();
|
|
|
|
Worker findByRoute(String route) {
|
|
for (Worker w : workers) {
|
|
if (instrumentedEquals(w.route, route)) {
|
|
return w;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/** Fixed balancer: O(1) hash lookup per request. */
|
|
static class FixedBalancer {
|
|
final List<Worker> workers = new ArrayList<>();
|
|
// CWE-407 fix: route_index populated at init time
|
|
final Map<String, Worker> routeIndex = new HashMap<>();
|
|
|
|
void addWorker(Worker w) {
|
|
workers.add(w);
|
|
if (!w.route.isEmpty()) {
|
|
routeIndex.put(w.route, w); // O(1) insert
|
|
}
|
|
}
|
|
|
|
Worker findByRoute(String route) {
|
|
compareCount++; // count each hash probe as 1 op
|
|
return routeIndex.get(route);
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Helpers
|
|
// -----------------------------------------------------------------------
|
|
|
|
/** Build a list of W workers with routes "worker-0" .. "worker-(W-1)". */
|
|
static List<Worker> buildWorkers() {
|
|
List<Worker> list = new ArrayList<>(W);
|
|
for (int i = 0; i < W; i++) {
|
|
list.add(new Worker("worker-" + i));
|
|
}
|
|
return list;
|
|
}
|
|
|
|
/**
|
|
* Return an array of R route strings drawn uniformly from the pool of W
|
|
* worker routes. Every route is valid so both implementations always find
|
|
* a match (worst case for both, fair comparison).
|
|
*/
|
|
static String[] buildRequests() {
|
|
String[] reqs = new String[R];
|
|
// Spread requests evenly across all workers to exercise the full
|
|
// scan depth of the defective implementation.
|
|
for (int i = 0; i < R; i++) {
|
|
reqs[i] = "worker-" + (i % W);
|
|
}
|
|
return reqs;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Assert helper
|
|
// -----------------------------------------------------------------------
|
|
|
|
static void assertTrue(String msg, boolean condition) {
|
|
if (!condition) throw new AssertionError("FAIL: " + msg);
|
|
}
|
|
|
|
static void assertEquals(String msg, Object expected, Object actual) {
|
|
if (!Objects.equals(expected, actual))
|
|
throw new AssertionError("FAIL: " + msg + " expected=" + expected + " actual=" + actual);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Test 1: defective path returns the correct worker
|
|
// -----------------------------------------------------------------------
|
|
|
|
static void testDefectiveCorrectness() {
|
|
DefectiveBalancer balancer = new DefectiveBalancer();
|
|
buildWorkers().forEach(w -> balancer.workers.add(w));
|
|
|
|
compareCount = 0;
|
|
// Look up each worker by its exact route
|
|
for (int i = 0; i < W; i++) {
|
|
String route = "worker-" + i;
|
|
Worker found = balancer.findByRoute(route);
|
|
assertTrue("defective found non-null for route " + route, found != null);
|
|
assertEquals("defective correct worker for route " + route, route, found.route);
|
|
}
|
|
System.out.println("[PASS] testDefectiveCorrectness");
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Test 2: fixed path returns the correct worker
|
|
// -----------------------------------------------------------------------
|
|
|
|
static void testFixedCorrectness() {
|
|
FixedBalancer balancer = new FixedBalancer();
|
|
buildWorkers().forEach(balancer::addWorker);
|
|
|
|
compareCount = 0;
|
|
for (int i = 0; i < W; i++) {
|
|
String route = "worker-" + i;
|
|
Worker found = balancer.findByRoute(route);
|
|
assertTrue("fixed found non-null for route " + route, found != null);
|
|
assertEquals("fixed correct worker for route " + route, route, found.route);
|
|
}
|
|
System.out.println("[PASS] testFixedCorrectness");
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Test 3: defective comparison count is O(W·R)
|
|
// -----------------------------------------------------------------------
|
|
|
|
static void testDefectiveCompareCount() {
|
|
DefectiveBalancer balancer = new DefectiveBalancer();
|
|
buildWorkers().forEach(w -> balancer.workers.add(w));
|
|
String[] reqs = buildRequests();
|
|
|
|
compareCount = 0;
|
|
for (String route : reqs) {
|
|
balancer.findByRoute(route);
|
|
}
|
|
long defectiveCount = compareCount;
|
|
|
|
// Each request scans until it finds the worker. Requests are spread
|
|
// across all workers so on average the scan length is W/2; the minimum
|
|
// bound we assert is R (every request matches at position 1 or later).
|
|
assertTrue(
|
|
"defective compare count (" + defectiveCount + ") >= R (" + R + ")",
|
|
defectiveCount >= R
|
|
);
|
|
// And we expect roughly W/2 * R comparisons on average
|
|
long expected = (long) W / 2 * R;
|
|
assertTrue(
|
|
"defective compare count (" + defectiveCount + ") is close to W/2*R (" + expected + ")",
|
|
defectiveCount >= expected / 2 && defectiveCount <= expected * 3
|
|
);
|
|
System.out.println("[PASS] testDefectiveCompareCount comparisons=" + defectiveCount);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Test 4: fixed comparison count is O(R)
|
|
// -----------------------------------------------------------------------
|
|
|
|
static void testFixedCompareCount() {
|
|
FixedBalancer balancer = new FixedBalancer();
|
|
buildWorkers().forEach(balancer::addWorker);
|
|
String[] reqs = buildRequests();
|
|
|
|
compareCount = 0;
|
|
for (String route : reqs) {
|
|
balancer.findByRoute(route);
|
|
}
|
|
long fixedCount = compareCount;
|
|
|
|
// Each request costs exactly 1 hash probe (we count that as 1 in
|
|
// findByRoute), so fixedCount should equal R exactly.
|
|
assertEquals("fixed compare count equals R", (long) R, fixedCount);
|
|
System.out.println("[PASS] testFixedCompareCount comparisons=" + fixedCount);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Test 5: speedup ratio > 50x
|
|
// -----------------------------------------------------------------------
|
|
|
|
static void testSpeedupRatio() {
|
|
// Measure defective
|
|
DefectiveBalancer defBalancer = new DefectiveBalancer();
|
|
buildWorkers().forEach(w -> defBalancer.workers.add(w));
|
|
String[] reqs = buildRequests();
|
|
|
|
compareCount = 0;
|
|
for (String route : reqs) {
|
|
defBalancer.findByRoute(route);
|
|
}
|
|
long defectiveCount = compareCount;
|
|
|
|
// Measure fixed
|
|
FixedBalancer fixBalancer = new FixedBalancer();
|
|
buildWorkers().forEach(fixBalancer::addWorker);
|
|
|
|
compareCount = 0;
|
|
for (String route : reqs) {
|
|
fixBalancer.findByRoute(route);
|
|
}
|
|
long fixedCount = compareCount;
|
|
|
|
double ratio = (double) defectiveCount / fixedCount;
|
|
System.out.printf("[INFO] speedup ratio = %.1fx (defective=%d fixed=%d)%n",
|
|
ratio, defectiveCount, fixedCount);
|
|
assertTrue(
|
|
"speedup ratio " + ratio + " > 50x (W=" + W + ", R=" + R + ")",
|
|
ratio > 50.0
|
|
);
|
|
System.out.printf("[PASS] testSpeedupRatio ratio=%.1fx%n", ratio);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Main
|
|
// -----------------------------------------------------------------------
|
|
|
|
public static void main(String[] args) {
|
|
System.out.println("=== HttpdProxyBalancerTest W=" + W + " R=" + R + " ===");
|
|
testDefectiveCorrectness();
|
|
testFixedCorrectness();
|
|
testDefectiveCompareCount();
|
|
testFixedCompareCount();
|
|
testSpeedupRatio();
|
|
System.out.println("=== ALL TESTS PASSED ===");
|
|
}
|
|
}
|