java-topology/defects/traefik/unit/TraefikAlgorithmTest.java

250 lines
9.2 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<String> list, String key) {
for (String s : list) {
slowOps++;
if (s.equals(key)) return true;
}
return false;
}
static void slowRemoveConnectionHeaders(
List<String> connectionTokens,
List<String> xHeaders,
List<String> 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<String> connectionTokens,
Set<String> xHeadersSet,
Set<String> 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<String> 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<String> connectionHeaders = new ArrayList<>();
for (int i = 0; i < C; i++) {
connectionHeaders.add("X-Custom-Header-" + i);
}
// Fast versions use HashSet
Set<String> xHeadersSet = new HashSet<>(xHeaders);
Set<String> connectionHeadersSet = new HashSet<>(connectionHeaders);
// 3 Connection tokens per request — last one hits connectionHeaders list (worst case)
List<String> 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<String> queryParams, List<String> safeParams) {
for (String param : queryParams) { // O(Q)
for (String safe : safeParams) { // O(P)
slowOps++;
if (safe.equals(param)) break;
}
}
}
static void fastSafeURL(List<String> queryParams, Set<String> 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<String> safeParams = new ArrayList<>();
for (int i = 0; i < P; i++) safeParams.add("safe_param_" + i);
List<String> queryParams = new ArrayList<>();
for (int i = 0; i < Q; i++) queryParams.add("param_" + i); // none safe = worst case
Set<String> 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<String> 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<String> allEntryPoints,
Set<String> 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<String> allEntryPoints = new ArrayList<>();
for (int i = 0; i < E; i++) allEntryPoints.add("web-" + i);
Set<String> 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");
}
}