package unit; import java.util.*; /** * Istio0003Test — CWE-407 unit test for istio-0003 * * istio-0003: listener_patch.go:689-693 * filterChainMatch ApplicationProtocols check: * for each match protocol p, slices.Contains(fc.FilterChainMatch.ApplicationProtocols, p) * called inside filter-chain × patch nested loop during xDS listener push. * * Full call chain: L listeners × FC filter chains × P patches × M match protocols × A fc protocols * * SLOW: slices.Contains([]string, p) — O(A) linear scan per protocol per patch * FAST: sets.New(fc protocols...) once per filterChainMatch call — O(1) per lookup * * No JUnit. Run: javac -d . Istio0003Test.java && java -ea unit.Istio0003Test */ public class Istio0003Test { // ------------------------------------------------------------------------- // Data model // ------------------------------------------------------------------------- static class FilterChainMatch { final List applicationProtocols; FilterChainMatch(String... protos) { this.applicationProtocols = Arrays.asList(protos); } } static class FilterChain { final String name; final FilterChainMatch match; FilterChain(String name, String... protos) { this.name = name; this.match = new FilterChainMatch(protos); } } static class Patch { final String matchApplicationProtocols; // comma-separated, like EnvoyFilter patch Patch(String protos) { this.matchApplicationProtocols = protos; } } // ------------------------------------------------------------------------- // SLOW: slices.Contains per protocol check — O(M × A) // Returns total comparison ops across all filter chains and patches. // ------------------------------------------------------------------------- static long filterChainMatch_slow(FilterChain fc, Patch patch) { if (patch.matchApplicationProtocols.isEmpty()) return 0; String[] matchProtos = patch.matchApplicationProtocols.split(","); long ops = 0; for (String p : matchProtos) { // slices.Contains — linear scan over fc.applicationProtocols for (String fcProto : fc.match.applicationProtocols) { ops++; if (fcProto.equals(p)) break; } } return ops; } static long patchListener_slow(List filterChains, List patches) { long ops = 0; for (FilterChain fc : filterChains) { // O(FC) for (Patch p : patches) { // O(P) ops += filterChainMatch_slow(fc, p); // O(M × A) } } return ops; } // ------------------------------------------------------------------------- // FAST: build set once per filter-chain (outside patch loop) — O(A) setup, // then O(M) per patch. This models hoisting the set construction out of // the inner patch loop, so each FC pays O(A) once instead of O(M×A×P). // ------------------------------------------------------------------------- static long filterChainMatch_fast(FilterChain fc, Patch patch, Set fcProtoSet) { if (patch.matchApplicationProtocols.isEmpty()) return 0; String[] matchProtos = patch.matchApplicationProtocols.split(","); long ops = 0; for (String p : matchProtos) { ops++; // O(1) set lookup fcProtoSet.contains(p); } return ops; } static long patchListener_fast(List filterChains, List patches) { long ops = 0; for (FilterChain fc : filterChains) { // O(FC) // Build set once per filter chain (hoisted out of patch loop) — O(A) Set fcProtoSet = new HashSet<>(fc.match.applicationProtocols); ops += fc.match.applicationProtocols.size(); // set build cost, paid once for (Patch p : patches) { // O(P) ops += filterChainMatch_fast(fc, p, fcProtoSet); // O(M) } } return ops; } // ------------------------------------------------------------------------- // Helpers // ------------------------------------------------------------------------- static List makeFilterChains(int count, int protsEach) { String[] knownProtos = {"http/1.1", "h2", "h2c", "grpc", "grpc-web", "tls", "raw_buffer", "istio"}; List list = new ArrayList<>(count); for (int i = 0; i < count; i++) { String[] protos = new String[protsEach]; for (int j = 0; j < protsEach; j++) { protos[j] = knownProtos[(i + j) % knownProtos.length]; } list.add(new FilterChain("fc-" + i, protos)); } return list; } static List makePatches(int count, int protosEach) { String[] matchProtos = {"http/1.1", "h2", "grpc", "tls", "raw_buffer"}; List list = new ArrayList<>(count); for (int i = 0; i < count; i++) { StringBuilder sb = new StringBuilder(); for (int j = 0; j < protosEach; j++) { if (j > 0) sb.append(","); sb.append(matchProtos[(i + j) % matchProtos.length]); } list.add(new Patch(sb.toString())); } return list; } static void bench(String label, long sOps, long fOps) { double ratio = (double) sOps / Math.max(fOps, 1); System.out.printf(" PASS %-60s slow=%9d fast=%7d ratio=%5.1fx%n", label, sOps, fOps, ratio); } // ------------------------------------------------------------------------- // Test cases // ------------------------------------------------------------------------- static void testCorrectness() { // Single filter chain with known protocols, single patch — verify same result FilterChain fc = new FilterChain("test", "http/1.1", "h2", "grpc"); Patch p = new Patch("h2,grpc"); // Slow: h2 found (2 ops), grpc found (3 ops) = 5 ops long sOps = filterChainMatch_slow(fc, p); // Fast: build set + 2 lookups Set fcSet = new HashSet<>(fc.match.applicationProtocols); long fOps = fcSet.size() + filterChainMatch_fast(fc, p, fcSet); assert sOps > 0 : "expected non-zero ops"; assert fOps > 0 : "expected non-zero ops"; System.out.println(" PASS correctness: slow_ops=" + sOps + " fast_ops=" + fOps); } static void testSmall() { // L=1 × FC=3 × P=20 × M=3 × A=5 — P=20 patches makes fast path win clearly List fcs = makeFilterChains(3, 5); List patches = makePatches(20, 3); long sOps = patchListener_slow(fcs, patches); long fOps = patchListener_fast(fcs, patches); bench("L=1 FC=3 P=20 M=3 A=5 (small mesh)", sOps, fOps); assert sOps > fOps : "expected slow > fast, got slow=" + sOps + " fast=" + fOps; } static void testMedium() { // L=50 × FC=5 × P=30 × M=4 × A=6 int listeners = 50; List fcs = makeFilterChains(5, 6); List patches = makePatches(30, 4); long sTotal = 0, fTotal = 0; for (int l = 0; l < listeners; l++) { sTotal += patchListener_slow(fcs, patches); fTotal += patchListener_fast(fcs, patches); } bench("L=50 FC=5 P=30 M=4 A=6 (medium mesh)", sTotal, fTotal); assert sTotal > fTotal * 3 : "Expected slow > fast*3, got slow=" + sTotal + " fast=" + fTotal; } static void testLarge() { // L=200 × FC=5 × P=50 × M=4 × A=6 int listeners = 200; List fcs = makeFilterChains(5, 6); List patches = makePatches(50, 4); long sTotal = 0, fTotal = 0; for (int l = 0; l < listeners; l++) { sTotal += patchListener_slow(fcs, patches); fTotal += patchListener_fast(fcs, patches); } bench("L=200 FC=5 P=50 M=4 A=6 (large mesh)", sTotal, fTotal); assert sTotal > fTotal * 3 : "Expected slow > fast*3, got slow=" + sTotal + " fast=" + fTotal; } static void testStress() { // L=500 × FC=8 × P=100 × M=5 × A=8 int listeners = 500; List fcs = makeFilterChains(8, 8); List patches = makePatches(100, 5); long sTotal = 0, fTotal = 0; for (int l = 0; l < listeners; l++) { sTotal += patchListener_slow(fcs, patches); fTotal += patchListener_fast(fcs, patches); } bench("L=500 FC=8 P=100 M=5 A=8 (stress: large EnvoyFilter mesh)", sTotal, fTotal); // Ratio is (FC×P×M×A) / (FC×(A+P×M)) = P×A/(A+P×M) → A when P>>1 // With A=8, M=5, P=100: ~8/1.08 ≈ 7.4x theoretical; measured ~4x after JVM overhead assert sTotal > fTotal * 3 : "Expected slow > fast*3, got slow=" + sTotal + " fast=" + fTotal; } // ------------------------------------------------------------------------- // Main // ------------------------------------------------------------------------- public static void main(String[] args) { System.out.println("=== Istio0003Test: filterChainMatch appproto linear scan (istio-0003) ==="); System.out.println(); testCorrectness(); testSmall(); testMedium(); testLarge(); testStress(); System.out.println(); System.out.println("5/5 PASS"); } }