209 lines
8.3 KiB
Java
209 lines
8.3 KiB
Java
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<BanSpec> 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<Long, List<BanSpec>> 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<BanSpec> 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<BanSpec> banHead = new ArrayList<>();
|
|
Map<Long, List<BanSpec>> 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<BanSpec> 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);
|
|
}
|
|
}
|