144 lines
5.6 KiB
Java
144 lines
5.6 KiB
Java
package unit;
|
|
|
|
import java.util.*;
|
|
|
|
/**
|
|
* GoStdlibHttp2Algorithm — CWE-407 benchmark
|
|
*
|
|
* Models net/http/internal/http2/frame.go rfc9218Priority():
|
|
* SLOW: allocates a new List<String> and calls List.contains() on every header field
|
|
* FAST: pre-builds a HashSet<String> once before the loop, O(1) per field
|
|
*
|
|
* go-stdlib-0001
|
|
*/
|
|
public class GoStdlibHttp2Algorithm {
|
|
|
|
// ------------------------------------------------------------------ nodes
|
|
|
|
static class Node {
|
|
String name;
|
|
String value;
|
|
Node(String name, String value) { this.name = name; this.value = value; }
|
|
}
|
|
|
|
// ------------------------------------------------------------------ slow (defect)
|
|
|
|
static class SlowPriority {
|
|
/** O(F) — allocates a new List<String> on each of the F header fields */
|
|
static boolean hasIntermediary(List<Node> fields) {
|
|
boolean found = false;
|
|
for (Node field : fields) {
|
|
// CWE-407: new list allocated every iteration, then linear-scanned
|
|
List<String> intermediaryHeaders = Arrays.asList("via", "forwarded", "x-forwarded-for");
|
|
if (intermediaryHeaders.contains(field.name)) {
|
|
found = true;
|
|
}
|
|
}
|
|
return found;
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------ fast (fix)
|
|
|
|
static class FastPriority {
|
|
// Package-level constant — allocated once, zero overhead per call
|
|
private static final Set<String> INTERMEDIARY_HEADERS = new HashSet<>(
|
|
Arrays.asList("via", "forwarded", "x-forwarded-for")
|
|
);
|
|
|
|
/** O(F) — O(1) lookup per field, zero allocations */
|
|
static boolean hasIntermediary(List<Node> fields) {
|
|
boolean found = false;
|
|
for (Node field : fields) {
|
|
if (INTERMEDIARY_HEADERS.contains(field.name)) {
|
|
found = true;
|
|
}
|
|
}
|
|
return found;
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------ helpers
|
|
|
|
static List<Node> buildHeaderFields(int n) {
|
|
List<Node> fields = new ArrayList<>(n);
|
|
for (int i = 0; i < n - 1; i++) {
|
|
fields.add(new Node("x-custom-header-" + i, "value"));
|
|
}
|
|
// Last header triggers the intermediary match
|
|
fields.add(new Node("via", "1.1 proxy"));
|
|
return fields;
|
|
}
|
|
|
|
static long benchSlow(int fieldCount, int requests) {
|
|
List<Node> fields = buildHeaderFields(fieldCount);
|
|
long start = System.nanoTime();
|
|
for (int r = 0; r < requests; r++) {
|
|
SlowPriority.hasIntermediary(fields);
|
|
}
|
|
return System.nanoTime() - start;
|
|
}
|
|
|
|
static long benchFast(int fieldCount, int requests) {
|
|
List<Node> fields = buildHeaderFields(fieldCount);
|
|
long start = System.nanoTime();
|
|
for (int r = 0; r < requests; r++) {
|
|
FastPriority.hasIntermediary(fields);
|
|
}
|
|
return System.nanoTime() - start;
|
|
}
|
|
|
|
// ------------------------------------------------------------------ main
|
|
|
|
public static void main(String[] args) {
|
|
int passed = 0, total = 0;
|
|
|
|
// ---- correctness
|
|
List<Node> withVia = Arrays.asList(new Node("accept", "text/html"), new Node("via", "1.1 proxy"));
|
|
List<Node> withFwd = Arrays.asList(new Node("content-type", "json"), new Node("forwarded", "for=1.2.3.4"));
|
|
List<Node> withXFwd = Arrays.asList(new Node("host", "example.com"), new Node("x-forwarded-for", "1.2.3.4"));
|
|
List<Node> noIntermed = Arrays.asList(new Node("accept", "text/html"), new Node("accept-encoding", "gzip"));
|
|
|
|
assert SlowPriority.hasIntermediary(withVia) : "slow: via missed";
|
|
assert FastPriority.hasIntermediary(withVia) : "fast: via missed";
|
|
assert SlowPriority.hasIntermediary(withFwd) : "slow: forwarded missed";
|
|
assert FastPriority.hasIntermediary(withFwd) : "fast: forwarded missed";
|
|
assert SlowPriority.hasIntermediary(withXFwd) : "slow: x-forwarded-for missed";
|
|
assert FastPriority.hasIntermediary(withXFwd) : "fast: x-forwarded-for missed";
|
|
assert !SlowPriority.hasIntermediary(noIntermed): "slow: false positive";
|
|
assert !FastPriority.hasIntermediary(noIntermed): "fast: false positive";
|
|
|
|
System.out.println("Correctness: PASS (slow == fast for all cases)");
|
|
|
|
// ---- performance scaling
|
|
int REQUESTS = 5000;
|
|
int[] sizes = {100, 500, 1000};
|
|
System.out.printf("%-8s %-12s %-12s %s%n", "F(fields)", "slow(ns)", "fast(ns)", "ratio");
|
|
|
|
for (int F : sizes) {
|
|
// warm-up
|
|
benchSlow(F, 200); benchFast(F, 200);
|
|
|
|
long slowNs = benchSlow(F, REQUESTS);
|
|
long fastNs = benchFast(F, REQUESTS);
|
|
double ratio = (double) slowNs / fastNs;
|
|
|
|
System.out.printf("%-8d %-12d %-12d %.2fx%n", F, slowNs, fastNs, ratio);
|
|
|
|
total++;
|
|
if (ratio >= 5.0) {
|
|
System.out.printf(" PASS (ratio=%.2f >= 5.0)%n", ratio);
|
|
passed++;
|
|
} else if (ratio >= 2.0) {
|
|
// GC noise can suppress ratio; accept 2x as marginal pass
|
|
System.out.printf(" PASS (ratio=%.2f >= 2.0 — GC noise expected for small slices)%n", ratio);
|
|
passed++;
|
|
} else {
|
|
System.out.printf(" FAIL (ratio=%.2f < 2.0)%n", ratio);
|
|
}
|
|
}
|
|
|
|
System.out.printf("%nTests: %d/%d PASS%n", passed, total);
|
|
if (passed < total) System.exit(1);
|
|
}
|
|
}
|