pub const CLUSTER_SLOTS: u16 = 16384;
pub fn crc16(bytes: &[u8]) -> u16 {
let mut crc: u16 = 0;
for &byte in bytes {
crc ^= u16::from(byte) << 8;
for _ in 0..8 {
crc = if crc & 0x8000 != 0 {
(crc << 1) ^ 0x1021
} else {
crc << 1
};
}
}
crc
}
pub fn hash_tag(key: &[u8]) -> &[u8] {
if let Some(open) = key.iter().position(|&b| b == b'{') {
if let Some(len) = key[open + 1..].iter().position(|&b| b == b'}') {
if len > 0 {
return &key[open + 1..open + 1 + len];
}
}
}
key
}
pub fn key_hash_slot(key: &[u8]) -> u16 {
crc16(hash_tag(key)) & (CLUSTER_SLOTS - 1)
}
pub fn command_keys(args: &[Vec<u8>]) -> Vec<&[u8]> {
let Some(name) = args.first() else {
return Vec::new();
};
let name = String::from_utf8_lossy(name).to_ascii_uppercase();
let rest = &args[1..];
let at = |i: usize| rest.get(i).map(|a| a.as_slice());
let all = |from: usize| rest.iter().skip(from).map(|a| a.as_slice()).collect();
let counted = |count_at: usize| -> Vec<&[u8]> {
let n = rest
.get(count_at)
.and_then(|a| std::str::from_utf8(a).ok())
.and_then(|s| s.parse::<usize>().ok())
.unwrap_or(0);
rest.iter()
.skip(count_at + 1)
.take(n)
.map(|a| a.as_slice())
.collect()
};
let sub = || {
rest.first()
.map(|s| String::from_utf8_lossy(s).to_ascii_uppercase())
.unwrap_or_default()
};
match name.as_str() {
"PING" | "ECHO" | "INFO" | "CLUSTER" | "COMMAND" | "CLIENT" | "CONFIG" | "HELLO"
| "AUTH" | "SELECT" | "DBSIZE" | "FLUSHALL" | "FLUSHDB" | "KEYS" | "SCAN" | "RANDOMKEY"
| "TIME" | "PUBLISH" | "SPUBLISH" | "SUBSCRIBE" | "PSUBSCRIBE" | "SSUBSCRIBE"
| "UNSUBSCRIBE" | "PUNSUBSCRIBE" | "SUNSUBSCRIBE" | "PUBSUB" | "MULTI" | "EXEC"
| "DISCARD" | "UNWATCH" | "SCRIPT" | "FUNCTION" | "ASKING" | "READONLY" | "READWRITE"
| "QUIT" | "RESET" | "WAIT" | "WAITAOF" | "LASTSAVE" | "SAVE" | "BGSAVE"
| "BGREWRITEAOF" | "SHUTDOWN" | "MONITOR" | "SLOWLOG" | "LATENCY" | "DEBUG" | "ACL"
| "ROLE" | "REPLICAOF" | "SLAVEOF" | "SWAPDB" | "FAILOVER" | "MODULE" | "LOLWUT"
| "SYNC" | "PSYNC" | "REPLCONF" | "MIGRATE" => Vec::new(),
"OBJECT" | "XINFO" => at(1).into_iter().collect(),
"MEMORY" if sub() == "USAGE" => at(1).into_iter().collect(),
"MEMORY" => Vec::new(),
"XGROUP" if sub() != "HELP" => at(1).into_iter().collect(),
"XGROUP" => Vec::new(),
"DEL" | "EXISTS" | "UNLINK" | "TOUCH" | "MGET" | "WATCH" | "SINTER" | "SUNION"
| "SDIFF" | "PFCOUNT" | "PFMERGE" | "SINTERSTORE" | "SUNIONSTORE" | "SDIFFSTORE" => all(0),
"RENAME" | "RENAMENX" | "SMOVE" | "RPOPLPUSH" | "BRPOPLPUSH" | "LMOVE" | "BLMOVE"
| "COPY" | "GEOSEARCHSTORE" | "ZRANGESTORE" => {
rest.iter().take(2).map(|a| a.as_slice()).collect()
}
"BITOP" => all(1),
"BLPOP" | "BRPOP" | "BZPOPMIN" | "BZPOPMAX" => rest
.iter()
.take(rest.len().saturating_sub(1))
.map(|a| a.as_slice())
.collect(),
"MSET" | "MSETNX" => rest.iter().step_by(2).map(|a| a.as_slice()).collect(),
"EVAL" | "EVALSHA" | "EVAL_RO" | "EVALSHA_RO" | "FCALL" | "FCALL_RO" => counted(1),
"ZUNIONSTORE" | "ZINTERSTORE" | "ZDIFFSTORE" => {
let mut keys: Vec<&[u8]> = at(0).into_iter().collect();
keys.extend(counted(1));
keys
}
"ZUNION" | "ZINTER" | "ZDIFF" | "ZINTERCARD" | "SINTERCARD" | "LMPOP" | "ZMPOP" => {
counted(0)
}
"BLMPOP" | "BZMPOP" => counted(1),
"XREAD" | "XREADGROUP" => {
let Some(pos) = rest.iter().position(|a| a.eq_ignore_ascii_case(b"STREAMS")) else {
return Vec::new();
};
let tail = &rest[pos + 1..];
tail.iter()
.take(tail.len() / 2)
.map(|a| a.as_slice())
.collect()
}
_ => at(0).into_iter().collect(),
}
}
pub fn slot_keys(args: &[Vec<u8>]) -> Vec<&[u8]> {
let name = args
.first()
.map(|n| String::from_utf8_lossy(n).to_ascii_uppercase())
.unwrap_or_default();
match name.as_str() {
"SPUBLISH" => args.get(1).map(|a| a.as_slice()).into_iter().collect(),
"SSUBSCRIBE" | "SUNSUBSCRIBE" => args.iter().skip(1).map(|a| a.as_slice()).collect(),
_ => command_keys(args),
}
}
#[derive(Debug, Clone, PartialEq)]
enum Token {
Star,
Any,
Byte(u8),
Class { negate: bool, items: Vec<ClassItem> },
}
#[derive(Debug, Clone, PartialEq)]
enum ClassItem {
Byte(u8),
Range(u8, u8),
}
fn tokenize(pattern: &[u8]) -> Vec<Token> {
let mut tokens = Vec::new();
let mut i = 0;
while i < pattern.len() {
match pattern[i] {
b'*' => {
if tokens.last() != Some(&Token::Star) {
tokens.push(Token::Star);
}
i += 1;
}
b'?' => {
tokens.push(Token::Any);
i += 1;
}
b'[' => {
i += 1;
let negate = pattern.get(i) == Some(&b'^');
if negate {
i += 1;
}
let mut items = Vec::new();
loop {
let left = pattern.len() - i;
if left == 0 {
break;
}
let c = pattern[i];
if c == b'\\' && left >= 2 {
items.push(ClassItem::Byte(pattern[i + 1]));
i += 2;
} else if c == b']' {
i += 1;
break;
} else if left >= 3 && pattern[i + 1] == b'-' {
let (a, b) = (pattern[i], pattern[i + 2]);
items.push(ClassItem::Range(a.min(b), a.max(b)));
i += 3;
} else {
items.push(ClassItem::Byte(c));
i += 1;
}
}
tokens.push(Token::Class { negate, items });
}
b'\\' if i + 1 < pattern.len() => {
tokens.push(Token::Byte(pattern[i + 1]));
i += 2;
}
other => {
tokens.push(Token::Byte(other));
i += 1;
}
}
}
tokens
}
fn token_matches(token: &Token, byte: u8) -> bool {
match token {
Token::Star => true,
Token::Any => true,
Token::Byte(b) => *b == byte,
Token::Class { negate, items } => {
let hit = items.iter().any(|item| match item {
ClassItem::Byte(b) => *b == byte,
ClassItem::Range(lo, hi) => (*lo..=*hi).contains(&byte),
});
hit != *negate
}
}
}
pub fn glob_match(pattern: &[u8], string: &[u8]) -> bool {
if string.is_empty() {
return pattern.is_empty();
}
let tokens = tokenize(pattern);
let (mut t, mut s) = (0usize, 0usize);
let mut star: Option<(usize, usize)> = None;
while s < string.len() {
if t < tokens.len() && tokens[t] == Token::Star {
star = Some((t, s));
t += 1;
} else if t < tokens.len() && token_matches(&tokens[t], string[s]) {
t += 1;
s += 1;
} else if let Some((star_t, star_s)) = star {
t = star_t + 1;
s = star_s + 1;
star = Some((star_t, star_s + 1));
} else {
return false;
}
}
tokens[t..].iter().all(|token| *token == Token::Star)
}