package unit; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * varnish-0002: BAN_Reload O(B²) dedup scan. * * During Varnish restart / ban persistence reload, BAN_Reload() calls ban_reload() * once per persisted ban. ban_reload() itself does TWO linear walks over the * existing ban_head list calling ban_equal() (memcmp) on each entry: * * 1. VTAILQ_FOREACH_FROM to detect duplicates at the same timestamp bracket. * 2. A second for-loop that walks ALL older bans to mark them completed. * * Combined: O(B²) comparisons. The code even has an explicit XXX comment: * "This can be optimized by traversing the live ban list together with * the reload list (combining the loops in BAN_Reload and ban_reload)." * * SLOW path: simulates the defect — linear ban_equal scan per loaded ban. * FAST path: simulates the fix — hash pre-filter so only hash-matching bans * need full comparison (typically 0 collisions = 0 full compares). * * Correctness: both paths must produce the same duplicate detection result. * Performance: slow ops >> fast ops, ratio >= 5x at B=500. */ public class VarnishBanReloadAlgorithm { // ---- simulated ban spec ------------------------------------------------- static class BanSpec { final int id; // unique id standing in for full ban content final long hash; // XXH64-equivalent: id*2654435761L boolean completed; BanSpec(int id) { this.id = id; this.hash = id * 2654435761L; // Knuth multiplicative hash this.completed = false; } /** ban_equal equivalent: O(1) here, models memcmp on full spec bytes */ boolean equalSpec(BanSpec other) { return this.id == other.id; } } // ---- Result container --------------------------------------------------- static class Result { long ops; // comparison operations performed int duplicatesFound; Result(long ops, int dups) { this.ops = ops; this.duplicatesFound = dups; } } // ----------------------------------------------------------------------- // SLOW: ban_reload defect — O(B) linear scan per loaded ban // Simulates the "hunt down older duplicates" for-loop in ban_reload() // ----------------------------------------------------------------------- static Result slow(int B) { List banHead = new ArrayList<>(B); long ops = 0; int dups = 0; // Simulate BAN_Reload: load B bans, each doing a full linear dedup scan for (int i = 0; i < B; i++) { BanSpec incoming = new BanSpec(i); // "Hunt down older duplicates" — scan all existing bans for ban_equal for (BanSpec existing : banHead) { ops++; // models one ban_equal call if (existing.equalSpec(incoming)) { existing.completed = true; // mark_completed dups++; } } banHead.add(incoming); } return new Result(ops, dups); } // ----------------------------------------------------------------------- // FAST: hash pre-filter fix — O(1) hash check before ban_equal // Simulates CWE-407 fix: spec_hash field added to struct ban // Each incoming ban gets hash checked; full memcmp only on hash collision // ----------------------------------------------------------------------- static Result fast(int B) { // Map from spec_hash -> list of bans with that hash (collision chain) Map> hashIndex = new HashMap<>(B * 2); long ops = 0; int dups = 0; for (int i = 0; i < B; i++) { BanSpec incoming = new BanSpec(i); // O(1) hash lookup — finds only candidates with matching hash List candidates = hashIndex.get(incoming.hash); if (candidates != null) { for (BanSpec candidate : candidates) { ops++; // models one ban_equal call (after hash match) if (candidate.equalSpec(incoming)) { candidate.completed = true; dups++; } } } // CWE-407 fix: ops for the hash index lookup itself counted as 1 ops++; // hash lookup O(1) // Insert into hash index hashIndex.computeIfAbsent(incoming.hash, k -> new ArrayList<>()).add(incoming); } return new Result(ops, dups); } // ----------------------------------------------------------------------- public static void main(String[] args) { int[] sizes = {100, 500, 1000}; int failures = 0; int tests = 0; for (int B : sizes) { Result s = slow(B); Result f = fast(B); // Correctness: both paths should find same number of duplicates // (With unique IDs: 0 duplicates in both cases) if (s.duplicatesFound != f.duplicatesFound) { System.out.printf("FAIL [B=%d] correctness: slow_dups=%d fast_dups=%d%n", B, s.duplicatesFound, f.duplicatesFound); failures++; } long ratio = s.ops / Math.max(f.ops, 1); // At B=500: slow does ~B²/2 = 124,750 ops, fast does ~B = 500 ops // Expect ratio >= 5x boolean pass = s.ops >= f.ops * 5 && s.duplicatesFound == f.duplicatesFound; tests++; if (!pass) failures++; System.out.printf("varnish-0002 B=%-6d slow=%-10d fast=%-8d ratio=%4dx %s%n", B, s.ops, f.ops, ratio, pass ? "PASS" : "FAIL"); } // Additional test: correctness with actual duplicates // Simulate reloading same ban twice (duplicate scenario) { int B = 200; // Create 200 bans, but first 10 are duplicates of last 10 List banHead = new ArrayList<>(); Map> hashIndex = new HashMap<>(); long slowOps = 0, fastOps = 0; int slowDups = 0, fastDups = 0; // Inject 190 unique bans first for (int i = 0; i < 190; i++) { BanSpec b = new BanSpec(i); // slow: just add banHead.add(b); // fast: index hashIndex.computeIfAbsent(b.hash, k -> new ArrayList<>()).add(b); } // Now reload 10 bans that duplicate IDs 0..9 for (int i = 0; i < 10; i++) { BanSpec incoming = new BanSpec(i); // duplicate of existing ban i // SLOW: linear scan all 190+i bans for (BanSpec existing : banHead) { slowOps++; if (existing.equalSpec(incoming)) { existing.completed = true; slowDups++; } } banHead.add(incoming); // FAST: hash lookup List candidates = hashIndex.get(incoming.hash); if (candidates != null) { for (BanSpec c : candidates) { fastOps++; if (c.equalSpec(incoming)) { c.completed = true; fastDups++; } } } fastOps++; hashIndex.computeIfAbsent(incoming.hash, k -> new ArrayList<>()).add(incoming); } boolean correctness = (slowDups == fastDups && slowDups == 10); boolean perf = slowOps > fastOps * 5; boolean pass = correctness && perf; tests++; if (!pass) failures++; System.out.printf("varnish-0002 dedup_correctness slow_dups=%d fast_dups=%d " + "slow_ops=%d fast_ops=%d %s%n", slowDups, fastDups, slowOps, fastOps, pass ? "PASS" : "FAIL"); } System.out.printf("%n%d/%d PASS%n", tests - failures, tests); if (failures > 0) System.exit(1); } }