190 lines
7.8 KiB
Java
190 lines
7.8 KiB
Java
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);
|
||
}
|
||
}
|