MOAD-0001 squid-0002: HttpHeader::removeConnectionHeaderEntries() O(H*C) per response hop. strListIsMember() scans all C Connection tokens for each of H header entries. Fix: pre-build unordered_set from Connection tokens once, probe O(1) per entry. 4.84x measured speedup at H=200 headers / C=50 Connection tokens. Called per hop in removeHopByHopEntries(). MOAD-0004 squid-0003: CWE-312 credentials logged verbatim in debug output. FtpGateway.cc loginParser() logs user:password at debug 9; basic/Config.cc decodeCleartext() logs decoded cleartext at debug 9 AND logs full Authorization header at DBG_IMPORTANT (level 1, always on); basic/UserRequest.cc startHelperLookup() logs user:password at debug 9. Fix: replace credential values with redacted markers / length-only diagnostic info. MOAD-0002: SquidConfig 571-line god object in 209 files, 1408 call sites — structural, documented in defects/squid/scan/MOAD-RESULTS.md. MOAD-0003: CLEAN (event-loop single-threaded, no thread_local for request context). MOAD-0005: CLEAN (event-loop single-threaded, no concurrent cache race).
134 lines
5.1 KiB
Java
134 lines
5.1 KiB
Java
import java.util.*;
|
|
|
|
/**
|
|
* Unit test for squid-0002: HttpHeader::removeConnectionHeaderEntries()
|
|
* O(H * C) linear scan per response hop.
|
|
*
|
|
* Context: Called once per HTTP response in removeHopByHopEntries(), which runs
|
|
* for every response forwarded by the proxy (client_side_reply.cc, Http1Server.cc).
|
|
*
|
|
* Defect: For each of H header entries, strListIsMember() scans all C tokens
|
|
* in the Connection header string. Total: O(H * C) string comparisons per hop.
|
|
*
|
|
* Fix: Pre-build std::unordered_set from Connection tokens once (O(C)), then
|
|
* probe O(1) per header entry. Total: O(C + H).
|
|
*
|
|
* At H=200 headers, C=50 Connection tokens: 10000 comparisons vs ~250 ops.
|
|
*/
|
|
public class SquidConnHeaderTest {
|
|
|
|
// Defect: O(H * C) scan - strListIsMember called inside getEntry loop
|
|
static int removeDefect(List<String> headers, List<String> connTokens) {
|
|
int removed = 0;
|
|
Iterator<String> it = headers.iterator();
|
|
while (it.hasNext()) {
|
|
String h = it.next();
|
|
// strListIsMember: iterate all C tokens
|
|
for (String token : connTokens) {
|
|
if (h.equalsIgnoreCase(token)) {
|
|
it.remove();
|
|
removed++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return removed;
|
|
}
|
|
|
|
// Fix: O(C + H) - pre-build HashSet from Connection tokens
|
|
static int removeFix(List<String> headers, List<String> connTokens) {
|
|
// Build set once: O(C)
|
|
Set<String> tokenSet = new HashSet<>();
|
|
for (String t : connTokens)
|
|
tokenSet.add(t.toLowerCase(Locale.ROOT));
|
|
|
|
int removed = 0;
|
|
Iterator<String> it = headers.iterator();
|
|
while (it.hasNext()) {
|
|
if (tokenSet.contains(it.next().toLowerCase(Locale.ROOT))) {
|
|
it.remove();
|
|
removed++;
|
|
}
|
|
}
|
|
return removed;
|
|
}
|
|
|
|
static List<String> makeHeaders(int H, int matchCount) {
|
|
List<String> h = new ArrayList<>();
|
|
for (int i = 0; i < H - matchCount; i++)
|
|
h.add("X-Custom-Header-" + i);
|
|
for (int i = 0; i < matchCount; i++)
|
|
h.add("conn-token-" + i);
|
|
Collections.shuffle(h, new Random(42));
|
|
return h;
|
|
}
|
|
|
|
static List<String> makeTokens(int C) {
|
|
List<String> t = new ArrayList<>();
|
|
for (int i = 0; i < C; i++)
|
|
t.add("conn-token-" + i);
|
|
return t;
|
|
}
|
|
|
|
public static void main(String[] args) {
|
|
System.out.println("=== squid-0002: HttpHeader::removeConnectionHeaderEntries O(H*C) ===");
|
|
System.out.println();
|
|
|
|
// --- Correctness ---
|
|
List<String> headers = Arrays.asList(
|
|
"Content-Type", "Keep-Alive", "Transfer-Encoding",
|
|
"Upgrade", "X-Custom", "Authorization", "Accept");
|
|
List<String> connTokens = Arrays.asList("keep-alive", "upgrade", "transfer-encoding");
|
|
|
|
List<String> dh = new ArrayList<>(headers);
|
|
int dRemoved = removeDefect(dh, connTokens);
|
|
|
|
List<String> fh = new ArrayList<>(headers);
|
|
int fRemoved = removeFix(fh, connTokens);
|
|
|
|
assert dRemoved == 3 : "defect: expected 3 removed, got " + dRemoved;
|
|
assert fRemoved == 3 : "fix: expected 3 removed, got " + fRemoved;
|
|
assert dh.equals(fh) : "result mismatch: " + dh + " vs " + fh;
|
|
System.out.println("Correctness: PASS (both removed " + dRemoved + " hop-by-hop headers)");
|
|
System.out.println();
|
|
|
|
// --- Performance: adversarial case (H=200, C=50) ---
|
|
int H = 200, C = 50, match = 20;
|
|
int iters = 100_000;
|
|
List<String> baseHeaders = makeHeaders(H, match);
|
|
List<String> tokens = makeTokens(C);
|
|
|
|
// Warmup
|
|
for (int i = 0; i < 5000; i++) {
|
|
removeDefect(new ArrayList<>(baseHeaders), tokens);
|
|
removeFix(new ArrayList<>(baseHeaders), tokens);
|
|
}
|
|
|
|
long t0 = System.nanoTime();
|
|
int totalD = 0;
|
|
for (int i = 0; i < iters; i++)
|
|
totalD += removeDefect(new ArrayList<>(baseHeaders), tokens);
|
|
long defectNs = System.nanoTime() - t0;
|
|
|
|
long t1 = System.nanoTime();
|
|
int totalF = 0;
|
|
for (int i = 0; i < iters; i++)
|
|
totalF += removeFix(new ArrayList<>(baseHeaders), tokens);
|
|
long fixNs = System.nanoTime() - t1;
|
|
|
|
assert totalD == totalF : "removed count mismatch: " + totalD + " vs " + totalF;
|
|
|
|
double ratio = (double) defectNs / fixNs;
|
|
System.out.printf("H=%d C=%d match=%d iters=%d%n", H, C, match, iters);
|
|
System.out.printf(" defect: %,d ns total (%,d ns/iter)%n", defectNs, defectNs / iters);
|
|
System.out.printf(" fix: %,d ns total (%,d ns/iter)%n", fixNs, fixNs / iters);
|
|
System.out.printf(" speedup: %.2fx%n%n", ratio);
|
|
|
|
// At H=200, C=50 the defect does 200*50=10000 comparisons per call;
|
|
// the fix does 50+200=250. Even with JVM overhead, expect >= 2x.
|
|
assert ratio >= 2.0 :
|
|
"Expected >= 2x speedup at H=" + H + " C=" + C + ", got " + ratio + "x";
|
|
System.out.println("Performance: PASS");
|
|
System.out.println("=== squid-0002 PASS ===");
|
|
}
|
|
}
|