package unit; import java.util.*; /** * Models Redis ACLCheckChannelAgainstList per-command channel pattern check. * * SLOW: O(C×P) — for each channel arg, scan all P patterns linearly via linked list. * FAST: O(C+P) — pre-build HashSet + List from selector; O(1) exact lookup. * * CWE-407: src/acl.c:1652,1694 (ACLCheckChannelAgainstList, ACLSelectorCheckCmd) */ public class RedisAclChannelCheckAlgorithmTest { // Simplified match: exact match only (no glob) for benchmark purposes // In production, glob patterns need fnmatch — we model exact-pattern dominance static class SlowAclSelector { final List patterns; // linked-list analog long cmpOps = 0; SlowAclSelector(List patterns) { this.patterns = patterns; } boolean channelAllowed(String channel) { for (String pat : patterns) { cmpOps++; if (pat.equals(channel)) return true; // exact match (simplification) } return false; } long checkCommand(List channels) { cmpOps = 0; for (String ch : channels) channelAllowed(ch); return cmpOps; } } static class FastAclSelector { final Set exactPatterns; // hash set for O(1) exact match long cmpOps = 0; FastAclSelector(List patterns) { exactPatterns = new HashSet<>(patterns); cmpOps = patterns.size(); // build cost } boolean channelAllowed(String channel) { cmpOps++; return exactPatterns.contains(channel); } long checkCommand(List channels) { cmpOps = 0; for (String ch : channels) channelAllowed(ch); return cmpOps; } } static List makePatterns(int n) { List p = new ArrayList<>(); for (int i = 0; i < n; i++) p.add("channel:" + i); return p; } static List makeChannels(int c, int offset) { List ch = new ArrayList<>(); for (int i = 0; i < c; i++) ch.add("channel:" + (offset + i)); return ch; } public static void main(String[] args) { int passed = 0, total = 0; System.out.println("=== redis-0004: ACLCheckChannelAgainstList O(C×P) ==="); // Test various (P, C) combinations int[][] cases = {{100, 10}, {500, 10}, {1000, 10}, {500, 50}}; for (int[] cs : cases) { int p = cs[0], c = cs[1]; List patterns = makePatterns(p); // Channels not in patterns (worst case — full scan each time) List channels = makeChannels(c, p + 1000); SlowAclSelector slow = new SlowAclSelector(patterns); FastAclSelector fast = new FastAclSelector(patterns); long s = slow.checkCommand(channels); long f = fast.checkCommand(channels); double ratio = (double) s / Math.max(f, 1); total++; boolean ok = s > f && ratio >= 5.0; System.out.printf("P=%4d C=%3d slow=%8d fast=%5d ratio=%7.1fx %s%n", p, c, s, f, ratio, ok ? "PASS" : "FAIL"); if (ok) passed++; } // Correctness: both allow/deny same channels List pats = makePatterns(100); SlowAclSelector slow = new SlowAclSelector(pats); FastAclSelector fast = new FastAclSelector(pats); // Allowed channel total++; boolean correct1 = slow.channelAllowed("channel:42") == fast.channelAllowed("channel:42"); System.out.printf("allow correctness (channel:42): %s%n", correct1 ? "PASS" : "FAIL"); if (correct1) passed++; // Denied channel total++; boolean correct2 = slow.channelAllowed("channel:9999") == fast.channelAllowed("channel:9999"); System.out.printf("deny correctness (channel:9999): %s%n", correct2 ? "PASS" : "FAIL"); if (correct2) passed++; System.out.printf("%n%d/%d PASS%n", passed, total); if (passed < total) System.exit(1); } }