java-topology/defects/postfix/unit/PostfixTest.java

201 lines
8.4 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package unit;
import java.util.*;
/**
* PostfixTest — CWE-407 benchmarks for postfix-0001 and postfix-0002
*
* postfix-0001: resolve_addr() calls string_list_match(virt_alias_doms/relay_domains)
* O(K) per RCPT-TO, O(K×M) total for M recipients.
* Fix: pre-build HashMap from inline domain patterns → O(M).
*
* postfix-0002: cleanup_masquerade_external() calls string_list_match(masq_exceptions)
* O(E) per message address, O(N×E) total for N addresses.
* Fix: pre-build HashMap from exceptions → O(N).
*
* Run: javac -d . PostfixTest.java && java -ea unit.PostfixTest
*/
public class PostfixTest {
// ── Shared bench harness ──────────────────────────────────────────────────
static void bench(String label, Runnable slow, Runnable fast, long sOps, long fOps) {
slow.run(); fast.run(); // warm-up
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(" %-56s slow:%4dms (%,d ops) fast:%4dms (%,d ops) speedup:%.0fx%n",
label, sMs, sOps, fMs, fOps, r);
}
// ── postfix-0001: resolve_addr() domain list scan ─────────────────────────
/**
* SLOW: string_list_match — iterate ARGV<String> of K inline domain patterns
* for each of M recipient domains. Models the Postfix ARGV-based match_list.
*
* Returns the exact number of strcmp operations performed.
*/
static long resolveSlow(int domainPatterns, int recipients) {
// Build ARGV-equivalent: list of domain strings
List<String> argv = new ArrayList<>(domainPatterns);
for (int i = 0; i < domainPatterns; i++) argv.add("hosted-" + i + ".example.com");
long ops = 0;
for (int rcpt = 0; rcpt < recipients; rcpt++) {
// Each rcpt is from a different domain; worst-case: not in list
String domain = "sender-" + rcpt + ".example.com";
// match_list_match: linear scan of ARGV
for (String pat : argv) {
ops++;
if (pat.equalsIgnoreCase(domain)) break;
}
}
return ops;
}
/**
* FAST: HashMap lookup — O(1) per recipient domain.
* Models the patched string_cache HTABLE inside match_list_match.
*/
static long resolveFast(int domainPatterns, int recipients) {
// Build hash map from inline patterns
Set<String> hashCache = new HashSet<>(domainPatterns * 2);
for (int i = 0; i < domainPatterns; i++) hashCache.add("hosted-" + i + ".example.com");
long ops = 0;
for (int rcpt = 0; rcpt < recipients; rcpt++) {
String domain = "sender-" + rcpt + ".example.com";
ops++; // O(1) hash lookup
hashCache.contains(domain);
}
return ops;
}
// ── postfix-0002: cleanup_masquerade_external() exception scan ────────────
/**
* SLOW: string_list_match(masq_exceptions, username) — O(E) scan per address.
* Models the ARGV iteration in cleanup_masquerade_external().
*/
static long masqExceptionSlow(int exceptions, int addresses) {
List<String> exceptionList = new ArrayList<>(exceptions);
for (int i = 0; i < exceptions; i++) exceptionList.add("root" + i);
long ops = 0;
for (int addr = 0; addr < addresses; addr++) {
// Each address has a unique username; worst-case: not in exceptions
String username = "user" + addr;
for (String exc : exceptionList) {
ops++;
if (exc.equalsIgnoreCase(username)) break;
}
}
return ops;
}
/**
* FAST: HashMap lookup for masquerade exceptions — O(1) per address.
* Models the patched htable_find() in cleanup_masquerade_external().
*/
static long masqExceptionFast(int exceptions, int addresses) {
Set<String> exceptionSet = new HashSet<>(exceptions * 2);
for (int i = 0; i < exceptions; i++) exceptionSet.add("root" + i);
long ops = 0;
for (int addr = 0; addr < addresses; addr++) {
String username = "user" + addr;
ops++; // O(1) hash lookup
exceptionSet.contains(username);
}
return ops;
}
/**
* SLOW: masquerade domain ARGV scan — O(D) per address.
* Models the for (masqp = masq_domains->argv; ...) loop in
* cleanup_masquerade_external().
*/
static long masqDomainSlow(int masqDomains, int addresses) {
List<String> domainList = new ArrayList<>(masqDomains);
for (int i = 0; i < masqDomains; i++) domainList.add("corp" + i + ".example.com");
long ops = 0;
for (int addr = 0; addr < addresses; addr++) {
// Address is in a sub-domain of one of the masq domains (find last match)
String addrDomain = "mail.corp" + (addr % masqDomains) + ".example.com";
for (String masq : domainList) {
ops++;
if (addrDomain.endsWith("." + masq) || addrDomain.equals(masq)) break;
}
}
return ops;
}
/**
* FAST: HashMap for exact-match masquerade domains — O(1) per address.
* Models the htable_find(masq_domain_cache, domain) fast path.
*/
static long masqDomainFast(int masqDomains, int addresses) {
Map<String, String> domainMap = new HashMap<>(masqDomains * 2);
for (int i = 0; i < masqDomains; i++) {
String d = "corp" + i + ".example.com";
domainMap.put(d, d);
}
long ops = 0;
for (int addr = 0; addr < addresses; addr++) {
String addrDomain = "mail.corp" + (addr % masqDomains) + ".example.com";
ops++; // O(1) hash lookup (exact parent domain)
// Real patch also handles subdomain stripping, but lookup is O(1)
domainMap.containsKey(addrDomain);
}
return ops;
}
// ── main ──────────────────────────────────────────────────────────────────
public static void main(String[] args) {
final int K = 500; // inline domain patterns in virt_alias/relay_domains
final int M = 2000; // recipients per mailing-list message
final int E = 200; // masquerade_exceptions entries
final int N = 1000; // header addresses in a large message
final int D = 100; // masquerade_domains entries
System.out.println("PostfixTest — CWE-407");
System.out.println();
System.out.println("postfix-0001: resolve_addr() domain list scan");
final long[] sOps1 = new long[1], fOps1 = new long[1];
bench(String.format("resolve K=%d patterns, M=%d recipients", K, M),
() -> { sOps1[0] = resolveSlow(K, M); },
() -> { fOps1[0] = resolveFast(K, M); },
resolveSlow(K, M), resolveFast(K, M));
assert sOps1[0] > fOps1[0] * 100 :
"postfix-0001: expected >100x more ops slow vs fast";
System.out.println();
System.out.println("postfix-0002: cleanup_masquerade_external() scans");
final long[] sOps2 = new long[1], fOps2 = new long[1];
bench(String.format("masq-exceptions E=%d, N=%d addresses", E, N),
() -> { sOps2[0] = masqExceptionSlow(E, N); },
() -> { fOps2[0] = masqExceptionFast(E, N); },
masqExceptionSlow(E, N), masqExceptionFast(E, N));
assert sOps2[0] > fOps2[0] * 50 :
"postfix-0002a: expected >50x more ops slow vs fast";
final long[] sOps3 = new long[1], fOps3 = new long[1];
bench(String.format("masq-domains D=%d, N=%d addresses", D, N),
() -> { sOps3[0] = masqDomainSlow(D, N); },
() -> { fOps3[0] = masqDomainFast(D, N); },
masqDomainSlow(D, N), masqDomainFast(D, N));
assert sOps3[0] > fOps3[0] * 10 :
"postfix-0002b: expected >10x more ops slow vs fast";
System.out.println();
System.out.println("All assertions passed.");
}
}