import java.util.*; /** * CWE-407 unit tests for Conduit (Rust Matrix homeserver) defects. * * conduit-0001: server_server.rs get_missing_events_route() * body.earliest_events.contains() (Vec) inside growing BFS while-loop * O(Q*E) where Q = queued events, E = earliest events. * Fix: convert earliest_events to HashSet for O(1) membership test. * Severity: MEDIUM-HIGH (federation endpoint, remote server controls input) */ public class ConduitTest { // --- conduit-0001: earliest_events Vec::contains in BFS loop --- /** * Simulates the defective path: for each queued event, do a linear scan * of earliest_events (ArrayList.contains -> O(E)). * As BFS expands queued_events, total work = O(Q * E). */ static int getMissingEventsVec(List latestEvents, List earliestEvents, Map> prevEventsMap, int limit) { List queued = new ArrayList<>(latestEvents); List result = new ArrayList<>(); int i = 0; int ops = 0; while (i < queued.size() && result.size() < limit) { String eventId = queued.get(i); // Linear scan of earliestEvents — the defect ops++; if (earliestEvents.contains(eventId)) { // O(E) each time i++; continue; } result.add(eventId); // Expand BFS: add prev_events List prevEvents = prevEventsMap.getOrDefault(eventId, Collections.emptyList()); queued.addAll(prevEvents); i++; } return ops; } /** * Simulates the fixed path: convert earliest_events to HashSet once, * then O(1) membership test per queued event. */ static int getMissingEventsSet(List latestEvents, List earliestEvents, Map> prevEventsMap, int limit) { List queued = new ArrayList<>(latestEvents); Set earliestSet = new HashSet<>(earliestEvents); // O(E) once List result = new ArrayList<>(); int i = 0; int ops = 0; while (i < queued.size() && result.size() < limit) { String eventId = queued.get(i); ops++; if (earliestSet.contains(eventId)) { // O(1) each time i++; continue; } result.add(eventId); List prevEvents = prevEventsMap.getOrDefault(eventId, Collections.emptyList()); queued.addAll(prevEvents); i++; } return ops; } static void testConduit0001() throws Exception { // Build a synthetic DAG: E earliest events, chain of events leading back to them int E = 500; // earliest events count int depth = 50; // BFS depth int limit = 10000; List earliestEvents = new ArrayList<>(); for (int i = 0; i < E; i++) { earliestEvents.add("$earliest_" + i); } // Build a DAG where each event has 2 prev_events, creating a growing BFS frontier Map> prevEventsMap = new HashMap<>(); List latestEvents = new ArrayList<>(); latestEvents.add("$latest_0"); // Create chain: latest -> intermediate events -> eventually hits earliest String current = "$latest_0"; for (int d = 0; d < depth; d++) { List prevs = new ArrayList<>(); prevs.add("$event_" + d + "_a"); prevs.add("$event_" + d + "_b"); prevEventsMap.put(current, prevs); current = "$event_" + d + "_a"; } // Terminal events point to earliest events List terminalPrevs = new ArrayList<>(); terminalPrevs.add(earliestEvents.get(0)); terminalPrevs.add(earliestEvents.get(E / 2)); prevEventsMap.put(current, terminalPrevs); // Correctness: both paths produce the same op count pattern // (they process same events, just with different contains() cost) // Performance long t0 = System.nanoTime(); for (int r = 0; r < 500; r++) { getMissingEventsVec(latestEvents, earliestEvents, prevEventsMap, limit); } long tVec = System.nanoTime() - t0; t0 = System.nanoTime(); for (int r = 0; r < 500; r++) { getMissingEventsSet(latestEvents, earliestEvents, prevEventsMap, limit); } long tSet = System.nanoTime() - t0; double ratio = (double) tVec / tSet; System.out.printf("conduit-0001: vec=%.3fs set=%.3fs ratio=%.1f× (E=%d, depth=%d)%n", tVec / 1e9, tSet / 1e9, ratio, E, depth); assert ratio > 2 : "Expected >2x speedup, got " + ratio; System.out.println("PASS conduit-0001"); } public static void main(String[] args) throws Exception { testConduit0001(); System.out.println("ALL PASS"); } }