187 lines
7.6 KiB
Java
187 lines
7.6 KiB
Java
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<string>)
|
||
* 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<std::string>)
|
||
// -------------------------------------------------------------------------
|
||
|
||
/**
|
||
* 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<String> metadataKeys, List<String> 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<std::string>)
|
||
// -------------------------------------------------------------------------
|
||
|
||
/**
|
||
* 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<String> metadataKeys, Set<String> namespaceSet) {
|
||
long allowed = 0;
|
||
for (String key : metadataKeys) { // O(M)
|
||
if (namespaceSet.contains(key)) { // O(1)
|
||
allowed++;
|
||
}
|
||
}
|
||
return allowed;
|
||
}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// Helpers
|
||
// -------------------------------------------------------------------------
|
||
|
||
static List<String> buildNamespaces(int n, String prefix) {
|
||
List<String> ns = new ArrayList<>(n);
|
||
for (int i = 0; i < n; i++) ns.add(prefix + i);
|
||
return ns;
|
||
}
|
||
|
||
static List<String> buildMetadataKeys(int m, List<String> namespaces, int hitRatio) {
|
||
// hitRatio: 1-in-hitRatio keys actually appear in namespaces
|
||
List<String> 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<String> namespaces = Arrays.asList("envoy.lb", "envoy.filters.http.jwt_authn", "custom.ns");
|
||
Set<String> nsSet = new HashSet<>(namespaces);
|
||
List<String> 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<String> ns = buildNamespaces(N, "envoy.metadata.ns.");
|
||
List<String> keys = buildMetadataKeys(M, ns, 3);
|
||
Set<String> 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<String> ns = buildNamespaces(N, "custom.namespace.");
|
||
List<String> keys = buildMetadataKeys(M, ns, 5);
|
||
Set<String> 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<String> ns = buildNamespaces(N, "ns.");
|
||
List<String> 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");
|
||
}
|
||
}
|