java-topology/defects/istio/unit/Istio0003Test.java

231 lines
9.5 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.*;
/**
* Istio0003Test — CWE-407 unit test for istio-0003
*
* istio-0003: listener_patch.go:689-693
* filterChainMatch ApplicationProtocols check:
* for each match protocol p, slices.Contains(fc.FilterChainMatch.ApplicationProtocols, p)
* called inside filter-chain × patch nested loop during xDS listener push.
*
* Full call chain: L listeners × FC filter chains × P patches × M match protocols × A fc protocols
*
* SLOW: slices.Contains([]string, p) — O(A) linear scan per protocol per patch
* FAST: sets.New(fc protocols...) once per filterChainMatch call — O(1) per lookup
*
* No JUnit. Run: javac -d . Istio0003Test.java && java -ea unit.Istio0003Test
*/
public class Istio0003Test {
// -------------------------------------------------------------------------
// Data model
// -------------------------------------------------------------------------
static class FilterChainMatch {
final List<String> applicationProtocols;
FilterChainMatch(String... protos) {
this.applicationProtocols = Arrays.asList(protos);
}
}
static class FilterChain {
final String name;
final FilterChainMatch match;
FilterChain(String name, String... protos) {
this.name = name;
this.match = new FilterChainMatch(protos);
}
}
static class Patch {
final String matchApplicationProtocols; // comma-separated, like EnvoyFilter patch
Patch(String protos) { this.matchApplicationProtocols = protos; }
}
// -------------------------------------------------------------------------
// SLOW: slices.Contains per protocol check — O(M × A)
// Returns total comparison ops across all filter chains and patches.
// -------------------------------------------------------------------------
static long filterChainMatch_slow(FilterChain fc, Patch patch) {
if (patch.matchApplicationProtocols.isEmpty()) return 0;
String[] matchProtos = patch.matchApplicationProtocols.split(",");
long ops = 0;
for (String p : matchProtos) {
// slices.Contains — linear scan over fc.applicationProtocols
for (String fcProto : fc.match.applicationProtocols) {
ops++;
if (fcProto.equals(p)) break;
}
}
return ops;
}
static long patchListener_slow(List<FilterChain> filterChains, List<Patch> patches) {
long ops = 0;
for (FilterChain fc : filterChains) { // O(FC)
for (Patch p : patches) { // O(P)
ops += filterChainMatch_slow(fc, p); // O(M × A)
}
}
return ops;
}
// -------------------------------------------------------------------------
// FAST: build set once per filter-chain (outside patch loop) — O(A) setup,
// then O(M) per patch. This models hoisting the set construction out of
// the inner patch loop, so each FC pays O(A) once instead of O(M×A×P).
// -------------------------------------------------------------------------
static long filterChainMatch_fast(FilterChain fc, Patch patch, Set<String> fcProtoSet) {
if (patch.matchApplicationProtocols.isEmpty()) return 0;
String[] matchProtos = patch.matchApplicationProtocols.split(",");
long ops = 0;
for (String p : matchProtos) {
ops++; // O(1) set lookup
fcProtoSet.contains(p);
}
return ops;
}
static long patchListener_fast(List<FilterChain> filterChains, List<Patch> patches) {
long ops = 0;
for (FilterChain fc : filterChains) { // O(FC)
// Build set once per filter chain (hoisted out of patch loop) — O(A)
Set<String> fcProtoSet = new HashSet<>(fc.match.applicationProtocols);
ops += fc.match.applicationProtocols.size(); // set build cost, paid once
for (Patch p : patches) { // O(P)
ops += filterChainMatch_fast(fc, p, fcProtoSet); // O(M)
}
}
return ops;
}
// -------------------------------------------------------------------------
// Helpers
// -------------------------------------------------------------------------
static List<FilterChain> makeFilterChains(int count, int protsEach) {
String[] knownProtos = {"http/1.1", "h2", "h2c", "grpc", "grpc-web", "tls", "raw_buffer", "istio"};
List<FilterChain> list = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
String[] protos = new String[protsEach];
for (int j = 0; j < protsEach; j++) {
protos[j] = knownProtos[(i + j) % knownProtos.length];
}
list.add(new FilterChain("fc-" + i, protos));
}
return list;
}
static List<Patch> makePatches(int count, int protosEach) {
String[] matchProtos = {"http/1.1", "h2", "grpc", "tls", "raw_buffer"};
List<Patch> list = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
StringBuilder sb = new StringBuilder();
for (int j = 0; j < protosEach; j++) {
if (j > 0) sb.append(",");
sb.append(matchProtos[(i + j) % matchProtos.length]);
}
list.add(new Patch(sb.toString()));
}
return list;
}
static void bench(String label, long sOps, long fOps) {
double ratio = (double) sOps / Math.max(fOps, 1);
System.out.printf(" PASS %-60s slow=%9d fast=%7d ratio=%5.1fx%n",
label, sOps, fOps, ratio);
}
// -------------------------------------------------------------------------
// Test cases
// -------------------------------------------------------------------------
static void testCorrectness() {
// Single filter chain with known protocols, single patch — verify same result
FilterChain fc = new FilterChain("test", "http/1.1", "h2", "grpc");
Patch p = new Patch("h2,grpc");
// Slow: h2 found (2 ops), grpc found (3 ops) = 5 ops
long sOps = filterChainMatch_slow(fc, p);
// Fast: build set + 2 lookups
Set<String> fcSet = new HashSet<>(fc.match.applicationProtocols);
long fOps = fcSet.size() + filterChainMatch_fast(fc, p, fcSet);
assert sOps > 0 : "expected non-zero ops";
assert fOps > 0 : "expected non-zero ops";
System.out.println(" PASS correctness: slow_ops=" + sOps + " fast_ops=" + fOps);
}
static void testSmall() {
// L=1 × FC=3 × P=20 × M=3 × A=5 — P=20 patches makes fast path win clearly
List<FilterChain> fcs = makeFilterChains(3, 5);
List<Patch> patches = makePatches(20, 3);
long sOps = patchListener_slow(fcs, patches);
long fOps = patchListener_fast(fcs, patches);
bench("L=1 FC=3 P=20 M=3 A=5 (small mesh)", sOps, fOps);
assert sOps > fOps :
"expected slow > fast, got slow=" + sOps + " fast=" + fOps;
}
static void testMedium() {
// L=50 × FC=5 × P=30 × M=4 × A=6
int listeners = 50;
List<FilterChain> fcs = makeFilterChains(5, 6);
List<Patch> patches = makePatches(30, 4);
long sTotal = 0, fTotal = 0;
for (int l = 0; l < listeners; l++) {
sTotal += patchListener_slow(fcs, patches);
fTotal += patchListener_fast(fcs, patches);
}
bench("L=50 FC=5 P=30 M=4 A=6 (medium mesh)", sTotal, fTotal);
assert sTotal > fTotal * 3 :
"Expected slow > fast*3, got slow=" + sTotal + " fast=" + fTotal;
}
static void testLarge() {
// L=200 × FC=5 × P=50 × M=4 × A=6
int listeners = 200;
List<FilterChain> fcs = makeFilterChains(5, 6);
List<Patch> patches = makePatches(50, 4);
long sTotal = 0, fTotal = 0;
for (int l = 0; l < listeners; l++) {
sTotal += patchListener_slow(fcs, patches);
fTotal += patchListener_fast(fcs, patches);
}
bench("L=200 FC=5 P=50 M=4 A=6 (large mesh)", sTotal, fTotal);
assert sTotal > fTotal * 3 :
"Expected slow > fast*3, got slow=" + sTotal + " fast=" + fTotal;
}
static void testStress() {
// L=500 × FC=8 × P=100 × M=5 × A=8
int listeners = 500;
List<FilterChain> fcs = makeFilterChains(8, 8);
List<Patch> patches = makePatches(100, 5);
long sTotal = 0, fTotal = 0;
for (int l = 0; l < listeners; l++) {
sTotal += patchListener_slow(fcs, patches);
fTotal += patchListener_fast(fcs, patches);
}
bench("L=500 FC=8 P=100 M=5 A=8 (stress: large EnvoyFilter mesh)", sTotal, fTotal);
// Ratio is (FC×P×M×A) / (FC×(A+P×M)) = P×A/(A+P×M) → A when P>>1
// With A=8, M=5, P=100: ~8/1.08 ≈ 7.4x theoretical; measured ~4x after JVM overhead
assert sTotal > fTotal * 3 :
"Expected slow > fast*3, got slow=" + sTotal + " fast=" + fTotal;
}
// -------------------------------------------------------------------------
// Main
// -------------------------------------------------------------------------
public static void main(String[] args) {
System.out.println("=== Istio0003Test: filterChainMatch appproto linear scan (istio-0003) ===");
System.out.println();
testCorrectness();
testSmall();
testMedium();
testLarge();
testStress();
System.out.println();
System.out.println("5/5 PASS");
}
}