const POLY: u16 = 0x1021;
const TABLE: [u16; 256] = make_table();
const TABLE1: [u16; 256] = next_table(&TABLE);
const TABLE2: [u16; 256] = next_table(&TABLE1);
const TABLE3: [u16; 256] = next_table(&TABLE2);
const fn make_table() -> [u16; 256] {
let mut table = [0u16; 256];
let mut i = 0;
while i < 256 {
let mut crc = (i as u16) << 8;
let mut bit = 0;
while bit < 8 {
crc = if crc & 0x8000 != 0 {
(crc << 1) ^ POLY
} else {
crc << 1
};
bit += 1;
}
table[i] = crc;
i += 1;
}
table
}
const fn next_table(prev: &[u16; 256]) -> [u16; 256] {
let mut table = [0u16; 256];
let mut i = 0;
while i < 256 {
table[i] = (prev[i] << 8) ^ TABLE[(prev[i] >> 8) as usize];
i += 1;
}
table
}
#[inline]
pub fn crc16(bytes: &[u8]) -> u16 {
let mut crc: u16 = 0;
let mut chunks = bytes.chunks_exact(4);
for c in &mut chunks {
crc = TABLE3[(((crc >> 8) as u8) ^ c[0]) as usize]
^ TABLE2[((crc as u8) ^ c[1]) as usize]
^ TABLE1[c[2] as usize]
^ TABLE[c[3] as usize];
}
for &b in chunks.remainder() {
crc = (crc << 8) ^ TABLE[(((crc >> 8) as u8) ^ b) as usize];
}
crc
}
#[cfg(test)]
fn crc16_bytewise(bytes: &[u8]) -> u16 {
let mut crc: u16 = 0;
for &b in bytes {
crc = (crc << 8) ^ TABLE[(((crc >> 8) as u8) ^ b) as usize];
}
crc
}
#[inline]
pub fn key_hash_slot(key: &[u8]) -> u16 {
crc16(hashtag(key)) & 0x3FFF
}
#[inline]
fn hashtag(key: &[u8]) -> &[u8] {
let Some(open) = key.iter().position(|&b| b == b'{') else {
return key;
};
let rest = &key[open + 1..];
match rest.iter().position(|&b| b == b'}') {
Some(close) if close > 0 => &rest[..close],
_ => key,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn xmodem_check_vector() {
assert_eq!(crc16(b"123456789"), 0x31C3);
assert_eq!(crc16(b""), 0);
}
#[test]
fn slice_by_4_matches_bytewise() {
let data: Vec<u8> = (0..64u32)
.map(|i| (i.wrapping_mul(167).wrapping_add(13) % 251) as u8)
.collect();
for len in 0..=data.len() {
assert_eq!(
crc16(&data[..len]),
crc16_bytewise(&data[..len]),
"len={len}"
);
}
assert_eq!(crc16(b"key:000000123456"), crc16_bytewise(b"key:000000123456"));
}
#[test]
fn slot_is_in_range() {
for key in [&b"foo"[..], b"key:000000000042", b"", b"\xff\x00\xfe"] {
assert!(key_hash_slot(key) < 16384);
}
}
#[test]
fn hashtag_extraction_redis_spec_examples() {
assert_eq!(
key_hash_slot(b"{user1000}.following"),
key_hash_slot(b"{user1000}.followers")
);
assert_eq!(key_hash_slot(b"{user1000}.following"), key_hash_slot(b"user1000"));
assert_eq!(hashtag(b"foo{}{bar}"), b"foo{}{bar}");
assert_eq!(hashtag(b"foo{{bar}}zap"), b"{bar");
assert_eq!(hashtag(b"foo{bar}{zap}"), b"bar");
assert_eq!(hashtag(b"foo{bar"), b"foo{bar");
assert_eq!(hashtag(b"no_braces"), b"no_braces");
}
}