package unit; import java.util.*; /** * CWE-407 benchmark for freeswitch defects: * freeswitch-0001: mod_conference.c relationship list scan inside per-sample audio mixing * O(S × M × M × R) — relationship linked-list scan per sample per member pair * * Simulates the audio mixing loop in C with Java equivalents: * - S = samples per frame (160 at 8kHz/20ms) * - M = conference members * - R = relationships per member (singly-linked list) */ public class FreeSWITCHTest { static void bench(String label, Runnable slow, Runnable fast, long sOps, long fOps) { slow.run(); fast.run(); long t0 = System.nanoTime(); slow.run(); long sMs = (System.nanoTime() - t0) / 1_000_000; long t1 = System.nanoTime(); fast.run(); long fMs = (System.nanoTime() - t1) / 1_000_000; double r = fOps > 0 ? (double) sOps / fOps : 0; System.out.printf(" %-60s slow:%4dms (%,d ops) fast:%4dms (%,d ops) speedup:%.0fx%n", label, sMs, sOps, fMs, fOps, r); } // Simulated conference relationship (singly-linked list like C struct) static class Rel { int id; // member ID this relationship targets (0 = all) boolean canSpeak; boolean canHear; Rel next; Rel(int id, boolean speak, boolean hear) { this.id = id; canSpeak = speak; canHear = hear; } } static class Member { int id; short[] frame; Rel relationships; // singly-linked list head Member(int id, int samples) { this.id = id; frame = new short[samples]; Arrays.fill(frame, (short) 100); } void addRelationship(int targetId, boolean canSpeak, boolean canHear) { Rel r = new Rel(targetId, canSpeak, canHear); r.next = relationships; relationships = r; } } // ───────────────────────────────────────────────────────────────────────── // freeswitch-0001: relationship scan per sample // ───────────────────────────────────────────────────────────────────────── /** * Slow: O(S × M × M × R) — relationship linked-list scanned for every sample. * Matches the original mod_conference.c code structure. */ static long slowConferenceMix(List members, int samples, boolean hasRelationships) { long ops = 0; int[] mainFrame = new int[samples]; // Build main frame: sum all members' audio for (Member m : members) { for (int x = 0; x < samples; x++) { mainFrame[x] += m.frame[x]; ops++; } } // Per output member: subtract self, subtract excluded members for (Member omember : members) { int[] writeFrame = new int[samples]; for (int x = 0; x < samples; x++) { ops++; int z = mainFrame[x] - omember.frame[x]; if (hasRelationships) { // Inner member loop for (Member imember : members) { if (imember == omember) continue; boolean found = false; // Scan imember->relationships linked list — O(R) for (Rel rel = imember.relationships; rel != null; rel = rel.next) { ops++; if ((rel.id == omember.id || rel.id == 0) && !rel.canSpeak) { z -= imember.frame[x]; found = true; break; } } if (!found) { // Scan omember->relationships — O(R) for (Rel rel = omember.relationships; rel != null; rel = rel.next) { ops++; if ((rel.id == imember.id || rel.id == 0) && !rel.canHear) { z -= imember.frame[x]; break; } } } } } writeFrame[x] = z; } } return ops; } /** * Fast: O(M² × R + S × M²) — pre-compute exclusion matrix outside sample loop. * Relationship scan moved out of the per-sample hot path. */ static long fastConferenceMix(List members, int samples, boolean hasRelationships) { long ops = 0; int[] mainFrame = new int[samples]; // Build main frame for (Member m : members) { for (int x = 0; x < samples; x++) { mainFrame[x] += m.frame[x]; ops++; } } // Per output member: pre-compute exclusion bitmask, then apply per sample for (Member omember : members) { // Pre-compute: which imembers are excluded for this omember? // O(M × R) — done ONCE per omember, not per sample boolean[] excludeAudio = new boolean[members.size()]; if (hasRelationships) { for (int ii = 0; ii < members.size(); ii++) { Member imember = members.get(ii); if (imember == omember) continue; boolean found = false; for (Rel rel = imember.relationships; rel != null; rel = rel.next) { ops++; if ((rel.id == omember.id || rel.id == 0) && !rel.canSpeak) { excludeAudio[ii] = true; found = true; break; } } if (!found) { for (Rel rel = omember.relationships; rel != null; rel = rel.next) { ops++; if ((rel.id == imember.id || rel.id == 0) && !rel.canHear) { excludeAudio[ii] = true; break; } } } } } // Per-sample loop: O(S × M) with O(1) exclusion lookup int[] writeFrame = new int[samples]; for (int x = 0; x < samples; x++) { ops++; int z = mainFrame[x] - omember.frame[x]; if (hasRelationships) { for (int ii = 0; ii < members.size(); ii++) { ops++; if (excludeAudio[ii]) { z -= members.get(ii).frame[x]; } } } writeFrame[x] = z; } } return ops; } // ───────────────────────────────────────────────────────────────────────── public static void main(String[] args) { int M = 20; // conference members int S = 160; // samples per frame (8kHz, 20ms) int R = 4; // relationships per member // Build members List members = new ArrayList<>(); for (int i = 0; i < M; i++) { members.add(new Member(i, S)); } // Add relationships: each member excludes 1-2 others (simulate mute/subconference) Random rng = new Random(42); for (int i = 0; i < M; i++) { for (int r = 0; r < R / 2; r++) { int target = rng.nextInt(M); if (target != i) { members.get(i).addRelationship(target, false, true); // can't speak to target } } } System.out.println("=== freeswitch CWE-407 benchmark ==="); System.out.printf(" M=%d members, S=%d samples/frame, R=%d relationships/member%n%n", M, S, R); long sOps = slowConferenceMix(members, S, true); long fOps = fastConferenceMix(members, S, true); bench("freeswitch-0001 conference mix [M=" + M + ",S=" + S + ",R=" + R + "] with rels", () -> slowConferenceMix(members, S, true), () -> fastConferenceMix(members, S, true), sOps, fOps); // Also benchmark without relationships (baseline) long sOpsNoRel = slowConferenceMix(members, S, false); long fOpsNoRel = fastConferenceMix(members, S, false); bench("freeswitch-0001 conference mix [M=" + M + ",S=" + S + "] no rels (baseline)", () -> slowConferenceMix(members, S, false), () -> fastConferenceMix(members, S, false), sOpsNoRel, fOpsNoRel); // Higher M to show quadratic growth int M2 = 40; List members2 = new ArrayList<>(); for (int i = 0; i < M2; i++) { Member m2 = new Member(i, S); for (int r = 0; r < R / 2; r++) { int target = rng.nextInt(M2); if (target != i) m2.addRelationship(target, false, true); } members2.add(m2); } long sOps2 = slowConferenceMix(members2, S, true); long fOps2 = fastConferenceMix(members2, S, true); bench("freeswitch-0001 conference mix [M=" + M2 + ",S=" + S + ",R=" + R + "] with rels", () -> slowConferenceMix(members2, S, true), () -> fastConferenceMix(members2, S, true), sOps2, fOps2); System.out.println(); // Assertions int pass = 0, total = 0; // slow ops should scale as O(S*M*M*R), fast as O(S*M + M*M*R) // slow / fast > R (relationship scan eliminated from inner loop) total++; long expectedSlowOps = (long) S * M * M; // at minimum (without R) if (sOps > fOps * 2 && sOps >= expectedSlowOps) { System.out.printf(" freeswitch-0001 M=%d: PASS (slow=%,d >= S*M²=%,d, fast=%,d, ratio=%.1fx)%n", M, sOps, expectedSlowOps, fOps, (double)sOps/fOps); pass++; } else { System.out.printf(" freeswitch-0001 M=%d: FAIL (slow=%,d fast=%,d)%n", M, sOps, fOps); } // At M2=40 slow ops should be roughly 4× M=20 (quadratic growth) total++; long expectedSlowOps2 = (long) S * M2 * M2; if (sOps2 > fOps2 * 2 && sOps2 >= expectedSlowOps2) { System.out.printf(" freeswitch-0001 M=%d: PASS (slow=%,d >= S*M²=%,d, fast=%,d, ratio=%.1fx)%n", M2, sOps2, expectedSlowOps2, fOps2, (double)sOps2/fOps2); pass++; } else { System.out.printf(" freeswitch-0001 M=%d: FAIL (slow=%,d fast=%,d)%n", M2, sOps2, fOps2); } // Quadratic growth check: sOps2 / sOps should be roughly (M2/M)² = 4 total++; double growthRatio = (double) sOps2 / sOps; double expectedGrowth = (double)(M2 * M2) / (M * M); if (growthRatio >= expectedGrowth * 0.5) { System.out.printf(" freeswitch-0001 quadratic growth: PASS (ratio=%.1fx, expected~%.1fx)%n", growthRatio, expectedGrowth); pass++; } else { System.out.printf(" freeswitch-0001 quadratic growth: FAIL (ratio=%.1fx, expected~%.1fx)%n", growthRatio, expectedGrowth); } System.out.println(); System.out.println(pass + "/" + total + (pass == total ? " PASS" : " FAIL")); if (pass != total) System.exit(1); } }