119 lines
4.1 KiB
Java
119 lines
4.1 KiB
Java
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<exact> + List<glob> 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<String> patterns; // linked-list analog
|
||
long cmpOps = 0;
|
||
|
||
SlowAclSelector(List<String> 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<String> channels) {
|
||
cmpOps = 0;
|
||
for (String ch : channels) channelAllowed(ch);
|
||
return cmpOps;
|
||
}
|
||
}
|
||
|
||
static class FastAclSelector {
|
||
final Set<String> exactPatterns; // hash set for O(1) exact match
|
||
long cmpOps = 0;
|
||
|
||
FastAclSelector(List<String> patterns) {
|
||
exactPatterns = new HashSet<>(patterns);
|
||
cmpOps = patterns.size(); // build cost
|
||
}
|
||
|
||
boolean channelAllowed(String channel) {
|
||
cmpOps++;
|
||
return exactPatterns.contains(channel);
|
||
}
|
||
|
||
long checkCommand(List<String> channels) {
|
||
cmpOps = 0;
|
||
for (String ch : channels) channelAllowed(ch);
|
||
return cmpOps;
|
||
}
|
||
}
|
||
|
||
static List<String> makePatterns(int n) {
|
||
List<String> p = new ArrayList<>();
|
||
for (int i = 0; i < n; i++) p.add("channel:" + i);
|
||
return p;
|
||
}
|
||
|
||
static List<String> makeChannels(int c, int offset) {
|
||
List<String> 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<String> patterns = makePatterns(p);
|
||
// Channels not in patterns (worst case — full scan each time)
|
||
List<String> 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<String> 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);
|
||
}
|
||
}
|