package unit; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; /** * traefik-0001/0002/0003: three CWE-407 defects — slices.Contains in hot loops. * * Test 1 (traefik-0001): removeConnectionHeaders * slow: for each Connection token, scan xHeaders(11) + connectionHeaders(C) linearly. * fast: pre-built HashSet, O(1) per lookup. * Assert: slowOps > fastOps * 5 (C=50 connectionHeaders). * * Test 2 (traefik-0002): safeURL query param redaction * slow: for each query param (Q), scan safeQueryParams(P) linearly. * fast: pre-built HashSet, O(1) per lookup. * Assert: slowOps > fastOps * 5 (Q=30, P=20). * * Test 3 (traefik-0003): PopulateUsedBy entryPoints validation * slow: for each (router × entryPoint), scan entryPoints slice linearly. * fast: pre-built HashSet, O(1) per lookup. * Assert: slowOps > fastOps * 5 (R=500 routers, M=3 EPs each, E=20 entry points). */ public class TraefikAlgorithmTest { static long slowOps; static long fastOps; // ========================================================================= // Test 1: traefik-0001 — removeConnectionHeaders slices.Contains // ========================================================================= /** * Slow: O(H × (F1 + F2)) — linear scan of xHeaders then connectionHeaders * for each connection header token. */ static boolean slowContains(List list, String key) { for (String s : list) { slowOps++; if (s.equals(key)) return true; } return false; } static void slowRemoveConnectionHeaders( List connectionTokens, List xHeaders, List connectionHeaders) { for (String token : connectionTokens) { if (slowContains(xHeaders, token)) continue; if (slowContains(connectionHeaders, token)) continue; // else: delete header (no-op in simulation) } } static void fastRemoveConnectionHeaders( List connectionTokens, Set xHeadersSet, Set connectionHeadersSet) { for (String token : connectionTokens) { fastOps++; if (xHeadersSet.contains(token)) continue; fastOps++; if (connectionHeadersSet.contains(token)) continue; } } static boolean test1() { // 11 standard X-Forwarded headers (matches traefik xHeaders) List xHeaders = new ArrayList<>(); xHeaders.add("X-Forwarded-Proto"); xHeaders.add("X-Forwarded-For"); xHeaders.add("X-Forwarded-Host"); xHeaders.add("X-Forwarded-Port"); xHeaders.add("X-Forwarded-Server"); xHeaders.add("X-Forwarded-Uri"); xHeaders.add("X-Forwarded-Method"); xHeaders.add("X-Forwarded-Prefix"); xHeaders.add("X-Forwarded-Tls-Client-Cert"); xHeaders.add("X-Forwarded-Tls-Client-Cert-Info"); xHeaders.add("X-Real-Ip"); // 50 user-configured connectionHeaders (worst-case deployment) final int C = 50; List connectionHeaders = new ArrayList<>(); for (int i = 0; i < C; i++) { connectionHeaders.add("X-Custom-Header-" + i); } // Fast versions use HashSet Set xHeadersSet = new HashSet<>(xHeaders); Set connectionHeadersSet = new HashSet<>(connectionHeaders); // 3 Connection tokens per request — last one hits connectionHeaders list (worst case) List tokens = new ArrayList<>(); tokens.add("keep-alive"); tokens.add("upgrade"); tokens.add("X-Custom-Header-" + (C - 1)); // worst case: match at end of list final int REQUESTS = 50_000; slowOps = 0; fastOps = 0; for (int r = 0; r < REQUESTS; r++) { slowRemoveConnectionHeaders(tokens, xHeaders, connectionHeaders); } long slowResult = slowOps; for (int r = 0; r < REQUESTS; r++) { fastRemoveConnectionHeaders(tokens, xHeadersSet, connectionHeadersSet); } long fastResult = fastOps; long ratio = slowResult / Math.max(fastResult, 1); boolean pass = slowResult > fastResult * 5; System.out.printf("traefik-0001 slow=%d fast=%d ratio=%dx %s%n", slowResult, fastResult, ratio, pass ? "PASS" : "FAIL"); return pass; } // ========================================================================= // Test 2: traefik-0002 — safeURL slices.Contains O(Q×P) // ========================================================================= static void slowSafeURL(List queryParams, List safeParams) { for (String param : queryParams) { // O(Q) for (String safe : safeParams) { // O(P) slowOps++; if (safe.equals(param)) break; } } } static void fastSafeURL(List queryParams, Set safeParamsSet) { for (String param : queryParams) { // O(Q) fastOps++; // O(1) set lookup safeParamsSet.contains(param); } } static boolean test2() { final int P = 20; // safe query params configured final int Q = 30; // query params in URL List safeParams = new ArrayList<>(); for (int i = 0; i < P; i++) safeParams.add("safe_param_" + i); List queryParams = new ArrayList<>(); for (int i = 0; i < Q; i++) queryParams.add("param_" + i); // none safe = worst case Set safeParamsSet = new HashSet<>(safeParams); final int REQUESTS = 50_000; slowOps = 0; fastOps = 0; for (int r = 0; r < REQUESTS; r++) slowSafeURL(queryParams, safeParams); long slowResult = slowOps; for (int r = 0; r < REQUESTS; r++) fastSafeURL(queryParams, safeParamsSet); long fastResult = fastOps; long ratio = slowResult / Math.max(fastResult, 1); boolean pass = slowResult > fastResult * (P - 1); System.out.printf("traefik-0002 slow=%d fast=%d ratio=%dx %s%n", slowResult, fastResult, ratio, pass ? "PASS" : "FAIL"); return pass; } // ========================================================================= // Test 3: traefik-0003 — PopulateUsedBy entryPoints slices.Contains O(R×M×E) // ========================================================================= static void slowPopulateUsedBy( int numRouters, int entryPointsPerRouter, List allEntryPoints) { int E = allEntryPoints.size(); for (int r = 0; r < numRouters; r++) { for (int m = 0; m < entryPointsPerRouter; m++) { // Each router uses the LAST entry point — worst case for linear scan String ep = allEntryPoints.get(E - 1); // linear scan of allEntryPoints slice (must walk all E before match) for (String validEp : allEntryPoints) { slowOps++; if (validEp.equals(ep)) break; } } } } static void fastPopulateUsedBy( int numRouters, int entryPointsPerRouter, List allEntryPoints, Set entryPointsSet) { int E = allEntryPoints.size(); for (int r = 0; r < numRouters; r++) { for (int m = 0; m < entryPointsPerRouter; m++) { String ep = allEntryPoints.get(E - 1); fastOps++; entryPointsSet.contains(ep); // O(1) } } } static boolean test3() { final int R = 500; // routers final int M = 3; // entry points per router final int E = 20; // configured entry points List allEntryPoints = new ArrayList<>(); for (int i = 0; i < E; i++) allEntryPoints.add("web-" + i); Set entryPointsSet = new HashSet<>(allEntryPoints); slowOps = 0; fastOps = 0; slowPopulateUsedBy(R, M, allEntryPoints); long slowResult = slowOps; fastPopulateUsedBy(R, M, allEntryPoints, entryPointsSet); long fastResult = fastOps; long ratio = slowResult / Math.max(fastResult, 1); // Worst case: scan all E entries before match. slowOps = R*M*E, fastOps = R*M boolean pass = slowResult > fastResult * (E - 1); System.out.printf("traefik-0003 slow=%d fast=%d ratio=%dx %s%n", slowResult, fastResult, ratio, pass ? "PASS" : "FAIL"); return pass; } // ========================================================================= // Main // ========================================================================= public static void main(String[] args) { boolean p1 = test1(); boolean p2 = test2(); boolean p3 = test3(); boolean allPass = p1 && p2 && p3; if (!allPass) { System.err.println("FAIL: one or more traefik CWE-407 tests failed"); System.exit(1); } System.out.println("ALL PASS"); } }