package unit; /** * CWE-407 unit test: ONOS RoleInfo.backups() ImmutableList.contains() * Defect: ImmutableList.contains() O(n) on every mastership role event * Fix: ImmutableSet.contains() O(1) * * slow(): ImmutableList.contains() — scanned linearly per call * fast(): ImmutableSet.contains() — O(1) hash lookup per call * Assert: slowOps > fastOps * 5 (C=100 cluster nodes → ~5000 vs ~100 ops) */ public class OnosRoleInfoTest { static class NodeId { final String id; static long slowEqCalls = 0; static long fastEqCalls = 0; final boolean instrumented; NodeId(String id, boolean instrumented) { this.id = id; this.instrumented = instrumented; } @Override public boolean equals(Object o) { if (instrumented) slowEqCalls++; if (!(o instanceof NodeId)) return false; return id.equals(((NodeId) o).id); } @Override public int hashCode() { return id.hashCode(); } } static class NodeIdFast { final String id; NodeIdFast(String id) { this.id = id; } @Override public boolean equals(Object o) { NodeId.fastEqCalls++; if (!(o instanceof NodeIdFast)) return false; return id.equals(((NodeIdFast) o).id); } @Override public int hashCode() { return id.hashCode(); } } /** Slow: ImmutableList — O(n) contains per lookup, called D*C times (D devices, C controllers). */ static long slow(int clusterSize, int lookups) { java.util.List backups = new java.util.ArrayList<>(); for (int i = 1; i < clusterSize; i++) { backups.add(new NodeId("node-" + i, true)); } com.google.common.collect.ImmutableList immList = com.google.common.collect.ImmutableList.copyOf(backups); NodeId.slowEqCalls = 0; NodeId target = new NodeId("node-" + (clusterSize - 1), false); for (int i = 0; i < lookups; i++) { boolean found = false; for (NodeId n : immList) { if (n.equals(target)) { found = true; break; } } } return NodeId.slowEqCalls; } /** Fast: ImmutableSet — O(1) contains per lookup. */ static long fast(int clusterSize, int lookups) { java.util.Set backups = new java.util.HashSet<>(); for (int i = 1; i < clusterSize; i++) { backups.add(new NodeIdFast("node-" + i)); } com.google.common.collect.ImmutableSet immSet = com.google.common.collect.ImmutableSet.copyOf(backups); NodeId.fastEqCalls = 0; NodeIdFast target = new NodeIdFast("node-" + (clusterSize - 1)); for (int i = 0; i < lookups; i++) { immSet.contains(target); } return NodeId.fastEqCalls; } public static void main(String[] args) { int C = 50; // cluster size int D = 100; // number of device role events int MULTIPLIER = 5; long sOps = slow(C, D); long fOps = fast(C, D); System.out.println("C=" + C + " nodes, D=" + D + " events"); System.out.println("slow (ImmutableList.contains): " + sOps + " equals() calls"); System.out.println("fast (ImmutableSet.contains): " + fOps + " equals() calls"); if (sOps > fOps * MULTIPLIER) { System.out.println("1/1 PASS (slow=" + sOps + " > fast*" + MULTIPLIER + "=" + (fOps * MULTIPLIER) + ")"); } else { System.out.println("1/1 FAIL (slow=" + sOps + " not > fast*" + MULTIPLIER + "=" + (fOps * MULTIPLIER) + ")"); System.exit(1); } } }