package unit; import java.util.*; /** * Envoy0002Test — CWE-407 unit test for envoy-0002 * * envoy-0002: ext_proc.cc:1640 — std::find over receiving_namespaces (vector) * inside loop over response_metadata keys — O(M × N) per request. * * M = metadata keys in ext_proc gRPC response * N = configured receiving namespaces * * SLOW: for each metadata key, std::find over namespaces vector → O(M × N) * FAST: absl::flat_hash_set::contains → O(M) amortized * * No JUnit. Run: javac -d . Envoy0002Test.java && java -ea unit.Envoy0002Test */ public class Envoy0002Test { // ------------------------------------------------------------------------- // SLOW: linear scan over namespace vector (models std::vector) // ------------------------------------------------------------------------- /** * Returns number of metadata keys that were found in the allowed namespace list. * Models handleDynamicMetadata() ext_proc.cc:1636-1652. * Complexity: O(M × N) where M = metadataKeys.size(), N = namespaces.size() */ static long handleMetadata_slow(List metadataKeys, List namespaces) { long allowed = 0; for (String key : metadataKeys) { // O(M) // std::find — O(N) linear scan if (namespaces.contains(key)) { // ArrayList.contains = O(N) allowed++; } } return allowed; } // ------------------------------------------------------------------------- // FAST: HashSet lookup (models absl::flat_hash_set) // ------------------------------------------------------------------------- /** * Returns number of metadata keys that were found in the allowed namespace set. * Set is built once at config parse time, not per request. * Complexity: O(M) per request. */ static long handleMetadata_fast(List metadataKeys, Set namespaceSet) { long allowed = 0; for (String key : metadataKeys) { // O(M) if (namespaceSet.contains(key)) { // O(1) allowed++; } } return allowed; } // ------------------------------------------------------------------------- // Helpers // ------------------------------------------------------------------------- static List buildNamespaces(int n, String prefix) { List ns = new ArrayList<>(n); for (int i = 0; i < n; i++) ns.add(prefix + i); return ns; } static List buildMetadataKeys(int m, List namespaces, int hitRatio) { // hitRatio: 1-in-hitRatio keys actually appear in namespaces List keys = new ArrayList<>(m); int nsSize = namespaces.size(); for (int i = 0; i < m; i++) { if (i % hitRatio == 0 && nsSize > 0) { keys.add(namespaces.get(i % nsSize)); // known namespace } else { keys.add("unknown-key-" + i); // not in allowed list } } return keys; } static void bench(String label, long sResult, long fResult, long sMs, long fMs) { assert sResult == fResult : "correctness: slow=" + sResult + " fast=" + fResult; System.out.printf(" %-60s slow=%dms fast=%dms result=%d%n", label, sMs, fMs, sResult); } // ------------------------------------------------------------------------- // Tests // ------------------------------------------------------------------------- static void testCorrectness() { List namespaces = Arrays.asList("envoy.lb", "envoy.filters.http.jwt_authn", "custom.ns"); Set nsSet = new HashSet<>(namespaces); List metadataKeys = Arrays.asList( "envoy.lb", // match "envoy.filters.http.jwt_authn", // match "unknown.key", // no match "custom.ns" // match ); long slow = handleMetadata_slow(metadataKeys, namespaces); long fast = handleMetadata_fast(metadataKeys, nsSet); assert slow == 3 : "slow: expected 3 allowed, got " + slow; assert fast == 3 : "fast: expected 3 allowed, got " + fast; assert slow == fast : "results differ: " + slow + " vs " + fast; System.out.println("PASS correctness: " + slow + " allowed metadata keys"); } static void testPerf_M10_N50() { int M = 10, N = 50; List ns = buildNamespaces(N, "envoy.metadata.ns."); List keys = buildMetadataKeys(M, ns, 3); Set nsSet = new HashSet<>(ns); long t0 = System.nanoTime(); // Simulate 100_000 requests (ext_proc is per-request) long slowTotal = 0; for (int r = 0; r < 100_000; r++) slowTotal += handleMetadata_slow(keys, ns); long slowMs = (System.nanoTime() - t0) / 1_000_000; long t1 = System.nanoTime(); long fastTotal = 0; for (int r = 0; r < 100_000; r++) fastTotal += handleMetadata_fast(keys, nsSet); long fastMs = (System.nanoTime() - t1) / 1_000_000; assert slowTotal == fastTotal : "results differ: " + slowTotal + " vs " + fastTotal; System.out.printf("PASS M=%d N=%d 100k requests: slow=%dms fast=%dms ratio=%.1fx%n", M, N, slowMs, fastMs, (double) slowMs / Math.max(fastMs, 1)); assert slowMs > fastMs * 2 || (slowMs < 5 && fastMs < 5) : "expected slow > fast*2, got slow=" + slowMs + " fast=" + fastMs; } static void testPerf_M30_N100() { int M = 30, N = 100; List ns = buildNamespaces(N, "custom.namespace."); List keys = buildMetadataKeys(M, ns, 5); Set nsSet = new HashSet<>(ns); long t0 = System.nanoTime(); long slowTotal = 0; for (int r = 0; r < 50_000; r++) slowTotal += handleMetadata_slow(keys, ns); long slowMs = (System.nanoTime() - t0) / 1_000_000; long t1 = System.nanoTime(); long fastTotal = 0; for (int r = 0; r < 50_000; r++) fastTotal += handleMetadata_fast(keys, nsSet); long fastMs = (System.nanoTime() - t1) / 1_000_000; assert slowTotal == fastTotal : "results differ: " + slowTotal + " vs " + fastTotal; System.out.printf("PASS M=%d N=%d 50k requests: slow=%dms fast=%dms ratio=%.1fx%n", M, N, slowMs, fastMs, (double) slowMs / Math.max(fastMs, 1)); } static void testOpsModel() { // Verify theoretical O(M*N) vs O(M) operation counts int M = 100, N = 80; List ns = buildNamespaces(N, "ns."); List keys = buildMetadataKeys(M, ns, 10); // Count comparisons for slow: worst case each key scans all N namespaces long slowOps = (long) M * N; // Fast: M hash lookups long fastOps = M; assert slowOps > fastOps * 50 : "Expected slowOps >> fastOps, got " + slowOps + " vs " + fastOps; System.out.printf("PASS ops model M=%d N=%d: slow_bound=%d fast_bound=%d ratio=%.0fx%n", M, N, slowOps, fastOps, (double) slowOps / fastOps); } // ------------------------------------------------------------------------- // Main // ------------------------------------------------------------------------- public static void main(String[] args) { System.out.println("=== Envoy0002Test: ext_proc namespace linear scan (envoy-0002) ==="); testCorrectness(); testPerf_M10_N50(); testPerf_M30_N100(); testOpsModel(); System.out.println("4/4 PASS"); } }