package unit; /** * openssl-0003 unit test * * Models the O(C×S) SRTP profile matching in tls_parse_ctos_use_srtp() * and the O(C+S) fixed version using a hash set. * * Compile: javac -d . OpenSslSrtpProfileTest.java * Run: java unit.OpenSslSrtpProfileTest */ public class OpenSslSrtpProfileTest { // ------------------------------------------------------------------ // // DEFECTIVE: O(C x S) — linear scan for every client profile id // // ------------------------------------------------------------------ // static int defectiveMatch(int[] serverProfiles, int[] clientProfiles) { int srtp_pref = serverProfiles.length; int bestIdx = -1; for (int clientId : clientProfiles) { // inner: linear scan over server list up to srtp_pref for (int i = 0; i < srtp_pref; i++) { if (serverProfiles[i] == clientId) { bestIdx = i; srtp_pref = i; // shrink next scan bound (still O(C*S) worst) break; } } } return bestIdx; } // ------------------------------------------------------------------ // // FIXED: O(C + S) — hash set for O(1) membership, then rank lookup // // ------------------------------------------------------------------ // static int fixedMatch(int[] serverProfiles, int[] clientProfiles) { final int HS = 32; int[] set = new int[HS]; // 0 = empty, otherwise server profile id + 1 int[] setIdx = new int[HS]; // server index for each slot // Build hash set of server ids — O(S) for (int i = 0; i < serverProfiles.length; i++) { int sid = serverProfiles[i]; int slot = (sid * 0x9E3779B9) >>> (32 - 5); // Knuth mult hash mod 32 slot &= (HS - 1); while (set[slot] != 0 && set[slot] != sid + 1) slot = (slot + 1) & (HS - 1); set[slot] = sid + 1; // +1 so 0 stays as "empty" setIdx[slot] = i; } int bestServerIdx = serverProfiles.length; // sentinel: no match yet int bestClientId = -1; // Iterate client list once — O(C) with O(1) lookup for (int clientId : clientProfiles) { int slot = (clientId * 0x9E3779B9) >>> (32 - 5); slot &= (HS - 1); while (set[slot] != 0 && set[slot] != clientId + 1) slot = (slot + 1) & (HS - 1); if (set[slot] == clientId + 1) { int serverIdx = setIdx[slot]; if (serverIdx < bestServerIdx) { bestServerIdx = serverIdx; bestClientId = clientId; if (bestServerIdx == 0) break; // can't do better } } } return bestServerIdx < serverProfiles.length ? bestServerIdx : -1; } // ------------------------------------------------------------------ // // Overhead counter to verify algorithmic complexity // // ------------------------------------------------------------------ // static long[] ops = new long[2]; static int defectiveMatchCounting(int[] server, int[] client) { int srtp_pref = server.length; int bestIdx = -1; for (int cid : client) { for (int i = 0; i < srtp_pref; i++) { ops[0]++; if (server[i] == cid) { bestIdx = i; srtp_pref = i; break; } } } return bestIdx; } static int fixedMatchCounting(int[] server, int[] client) { final int HS = 32; int[] set = new int[HS]; int[] setIdx = new int[HS]; for (int i = 0; i < server.length; i++) { ops[1]++; int sid = server[i]; int slot = (sid * 0x9E3779B9) >>> (32 - 5); slot &= (HS - 1); while (set[slot] != 0 && set[slot] != sid + 1) { slot = (slot + 1) & (HS - 1); ops[1]++; } set[slot] = sid + 1; setIdx[slot] = i; } int best = server.length; for (int cid : client) { ops[1]++; int slot = (cid * 0x9E3779B9) >>> (32 - 5); slot &= (HS - 1); while (set[slot] != 0 && set[slot] != cid + 1) { slot = (slot + 1) & (HS - 1); ops[1]++; } if (set[slot] == cid + 1) { int idx = setIdx[slot]; if (idx < best) { best = idx; if (best == 0) break; } } } return best < server.length ? best : -1; } // ------------------------------------------------------------------ // // Tests // // ------------------------------------------------------------------ // static int pass = 0, fail = 0; static void check(String name, boolean cond) { if (cond) { System.out.println(" PASS " + name); pass++; } else { System.out.println(" FAIL " + name); fail++; } } public static void main(String[] args) { System.out.println("=== openssl-0003: SRTP profile O(C×S) defect ===\n"); // Standard IANA SRTP profile IDs int[] server = {0x0001, 0x0007, 0x0005}; // SRTP_AES128_CM_HMAC_SHA1_80, _32, NULL_HMAC int[] clientMatch = {0x0002, 0x0007, 0x0001}; // 0x0007 matches at server index 1 int[] clientNoMatch = {0x0002, 0x0003, 0x0004}; int[] clientPrefer1 = {0x0005, 0x0001}; // prefer index 2, but server has index 0 too // clientMatch = {0x0002, 0x0007, 0x0001}: 0x0007 at server[1], 0x0001 at server[0]. // Algorithm finds the highest-priority (smallest index) server match → index 0. check("defective: finds best server match (idx=0)", defectiveMatch(server, clientMatch) == 0); // Test 2: fixed agrees check("fixed: finds best server match (idx=0)", fixedMatch(server, clientMatch) == 0); // Test 3: no match check("defective: no match returns -1", defectiveMatch(server, clientNoMatch) == -1); check("fixed: no match returns -1", fixedMatch(server, clientNoMatch) == -1); // Test 4: prefer higher-priority server entry // clientPrefer1 = [0x0005(server idx 2), 0x0001(server idx 0)] // server-preference: should pick server idx 0 (0x0001) check("fixed: picks higher server-priority profile", fixedMatch(server, clientPrefer1) == 0); // Test 5: complexity — generate adversarial input int S = 6; int C = 200; int[] bigServer = new int[S]; for (int i = 0; i < S; i++) bigServer[i] = 0x1000 + i; // client sends non-matching IDs followed by a hit at the end int[] bigClient = new int[C]; for (int i = 0; i < C - 1; i++) bigClient[i] = 0x2000 + i; // no match bigClient[C - 1] = bigServer[S - 1]; // match at worst position ops[0] = 0; ops[1] = 0; int rd = defectiveMatchCounting(bigServer, bigClient); int rf = fixedMatchCounting(bigServer, bigClient); System.out.println("\n Complexity comparison (S=" + S + ", C=" + C + "):"); System.out.println(" Defective ops: " + ops[0] + " (expected ~" + ((long)(C-1)*S + 1) + ")"); System.out.println(" Fixed ops: " + ops[1] + " (expected ~" + (S + C) + ")"); System.out.printf(" Speedup: %.1fx%n", (double) ops[0] / ops[1]); check("defective found same result as fixed", rd == rf); check("defective ops > fixed ops (quadratic vs linear)", ops[0] > ops[1]); check("fixed ops is O(C+S) not O(C*S)", ops[1] < ops[0] / 3); System.out.println("\n--- " + (pass + fail) + " tests: " + pass + " passed, " + fail + " failed ---"); if (fail > 0) System.exit(1); } }