java-topology/defects/envoy/unit/Envoy0002Test.java

187 lines
7.6 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.*;
/**
* 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");
}
}