194 lines
7.4 KiB
Java
194 lines
7.4 KiB
Java
package unit;
|
|
|
|
/**
|
|
* openssl-0004 unit test
|
|
*
|
|
* Models the O(N²) CA subject name dedup in add_uris_recursive()
|
|
* (called from SSL_add_store_cert_subjects_to_stack) and the O(N) fixed
|
|
* version that uses a hash set.
|
|
*
|
|
* The defect: sk_X509_NAME_find() on an unsorted stack is O(N) linear scan.
|
|
* Loading N certs from a store calls it N times → O(N²).
|
|
* The existing SSL_add_dir/file functions already use LHASH for O(1) dedup;
|
|
* add_uris_recursive was left behind.
|
|
*
|
|
* Compile: javac -d . OpenSslStoreCertSubjectsTest.java
|
|
* Run: java unit.OpenSslStoreCertSubjectsTest
|
|
*/
|
|
public class OpenSslStoreCertSubjectsTest {
|
|
|
|
// ------------------------------------------------------------------ //
|
|
// DEFECTIVE: O(N²) — linear scan for each new cert (unsorted stack) //
|
|
// ------------------------------------------------------------------ //
|
|
|
|
/** Models an X509_NAME as a unique integer DN string. */
|
|
static final class Name {
|
|
final String dn;
|
|
Name(String dn) { this.dn = dn; }
|
|
@Override public boolean equals(Object o) {
|
|
return o instanceof Name && ((Name) o).dn.equals(dn);
|
|
}
|
|
@Override public int hashCode() { return dn.hashCode(); }
|
|
}
|
|
|
|
/**
|
|
* SLOW — simulates add_uris_recursive with sk_X509_NAME_find (linear).
|
|
* Returns number of comparison operations performed.
|
|
*/
|
|
static long defectiveAddToStack(Name[] incoming, java.util.List<Name> stack) {
|
|
long ops = 0;
|
|
for (Name xn : incoming) {
|
|
// sk_X509_NAME_find: linear scan when stack is unsorted
|
|
boolean found = false;
|
|
for (Name existing : stack) {
|
|
ops++;
|
|
if (existing.equals(xn)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
stack.add(xn);
|
|
}
|
|
}
|
|
return ops;
|
|
}
|
|
|
|
// ------------------------------------------------------------------ //
|
|
// FIXED: O(N) — hash set for O(1) dedup (mirrors LHASH_OF fix) //
|
|
// ------------------------------------------------------------------ //
|
|
|
|
/**
|
|
* FAST — simulates add_uris_recursive with lh_X509_NAME_retrieve (hash).
|
|
* Returns number of hash operations performed.
|
|
*/
|
|
static long fixedAddToStack(Name[] incoming, java.util.List<Name> stack) {
|
|
long ops = 0;
|
|
java.util.HashSet<Name> nameHash = new java.util.HashSet<>(stack);
|
|
ops += stack.size(); // pre-populate cost (O(N_existing))
|
|
for (Name xn : incoming) {
|
|
ops++;
|
|
if (!nameHash.contains(xn)) {
|
|
stack.add(xn);
|
|
nameHash.add(xn);
|
|
}
|
|
}
|
|
return ops;
|
|
}
|
|
|
|
// ------------------------------------------------------------------ //
|
|
// 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-0004: SSL_add_store_cert_subjects O(N²) defect ===\n");
|
|
|
|
// --- Correctness tests ---
|
|
|
|
// Test 1: dedup - distinct names all added
|
|
{
|
|
Name[] certs = {new Name("CN=A"), new Name("CN=B"), new Name("CN=C")};
|
|
java.util.List<Name> slow = new java.util.ArrayList<>();
|
|
java.util.List<Name> fast = new java.util.ArrayList<>();
|
|
defectiveAddToStack(certs, slow);
|
|
fixedAddToStack(certs, fast);
|
|
check("all distinct names added (slow)", slow.size() == 3);
|
|
check("all distinct names added (fast)", fast.size() == 3);
|
|
}
|
|
|
|
// Test 2: duplicates are suppressed
|
|
{
|
|
Name a1 = new Name("CN=A");
|
|
Name a2 = new Name("CN=A"); // same DN, different object
|
|
Name b = new Name("CN=B");
|
|
Name[] certs = {a1, a2, b, a1};
|
|
java.util.List<Name> slow = new java.util.ArrayList<>();
|
|
java.util.List<Name> fast = new java.util.ArrayList<>();
|
|
defectiveAddToStack(certs, slow);
|
|
fixedAddToStack(certs, fast);
|
|
check("duplicates suppressed (slow)", slow.size() == 2);
|
|
check("duplicates suppressed (fast)", fast.size() == 2);
|
|
}
|
|
|
|
// Test 3: pre-existing stack entries are not duplicated
|
|
{
|
|
Name pre = new Name("CN=Pre");
|
|
Name[] certs = {new Name("CN=Pre"), new Name("CN=New")};
|
|
java.util.List<Name> slow = new java.util.ArrayList<>();
|
|
java.util.List<Name> fast = new java.util.ArrayList<>();
|
|
slow.add(pre);
|
|
fast.add(pre);
|
|
defectiveAddToStack(certs, slow);
|
|
fixedAddToStack(certs, fast);
|
|
check("pre-existing not duplicated (slow)", slow.size() == 2);
|
|
check("pre-existing not duplicated (fast)", fast.size() == 2);
|
|
}
|
|
|
|
// Test 4: empty store — no names added
|
|
{
|
|
Name[] empty = {};
|
|
java.util.List<Name> slow = new java.util.ArrayList<>();
|
|
java.util.List<Name> fast = new java.util.ArrayList<>();
|
|
defectiveAddToStack(empty, slow);
|
|
fixedAddToStack(empty, fast);
|
|
check("empty store → empty stack (slow)", slow.size() == 0);
|
|
check("empty store → empty stack (fast)", fast.size() == 0);
|
|
}
|
|
|
|
// Test 5: single cert, no existing stack — added once
|
|
{
|
|
Name[] certs = {new Name("CN=Solo")};
|
|
java.util.List<Name> slow = new java.util.ArrayList<>();
|
|
java.util.List<Name> fast = new java.util.ArrayList<>();
|
|
defectiveAddToStack(certs, slow);
|
|
fixedAddToStack(certs, fast);
|
|
check("single cert added (slow)", slow.size() == 1);
|
|
check("single cert added (fast)", fast.size() == 1);
|
|
}
|
|
|
|
// --- Complexity comparison ---
|
|
System.out.println();
|
|
|
|
int[] sizes = {100, 500, 1000};
|
|
for (int N : sizes) {
|
|
// N unique certs + N duplicate certs (every cert appears twice)
|
|
Name[] incoming = new Name[N * 2];
|
|
for (int i = 0; i < N; i++) {
|
|
incoming[i] = new Name("CN=Issuer-" + i);
|
|
incoming[N + i] = new Name("CN=Issuer-" + i); // duplicate
|
|
}
|
|
|
|
java.util.List<Name> slowStack = new java.util.ArrayList<>();
|
|
java.util.List<Name> fastStack = new java.util.ArrayList<>();
|
|
|
|
long slowOps = defectiveAddToStack(incoming, slowStack);
|
|
long fastOps = fixedAddToStack(incoming, fastStack);
|
|
|
|
double ratio = (double) slowOps / Math.max(fastOps, 1);
|
|
|
|
System.out.printf(
|
|
" N=%4d slow=%7d ops fast=%5d ops ratio=%.1fx%n",
|
|
N, slowOps, fastOps, ratio);
|
|
|
|
check("N=" + N + ": both stacks have " + N + " names",
|
|
slowStack.size() == N && fastStack.size() == N);
|
|
check("N=" + N + ": slow uses at least 5x more ops than fast",
|
|
ratio >= 5.0);
|
|
}
|
|
|
|
System.out.println("\n--- " + (pass + fail) + " tests: " + pass
|
|
+ " passed, " + fail + " failed ---");
|
|
if (fail > 0) System.exit(1);
|
|
}
|
|
}
|