Skip to main content

yo_common/
crc.rs

1//! CRC16 for slot placement and CRC32C for integrity.
2//!
3//! Two different polynomials for two different jobs. CRC16 is Redis's XMODEM
4//! variant and it exists here because `slot = crc16(key) & 0x3FFF` is how a key
5//! reaches a shard (`04` section 1). Getting it wrong does not corrupt anything,
6//! it just makes us incompatible with every Redis cluster client, so the hash
7//! tag rules are implemented here too.
8//!
9//! CRC32C is Castagnoli, the same polynomial SSE4.2 and the ARM CRC extension
10//! implement in hardware, and it is what guards pages and superblocks (`07`).
11
12// ---------------------------------------------------------------------------
13// CRC16 / XMODEM, polynomial 0x1021, initial value 0, not reflected.
14// ---------------------------------------------------------------------------
15
16const fn crc16_table() -> [u16; 256] {
17    let mut table = [0u16; 256];
18    let mut i = 0usize;
19    while i < 256 {
20        let mut crc = (i as u16) << 8;
21        let mut bit = 0;
22        while bit < 8 {
23            crc = if crc & 0x8000 != 0 {
24                (crc << 1) ^ 0x1021
25            } else {
26                crc << 1
27            };
28            bit += 1;
29        }
30        table[i] = crc;
31        i += 1;
32    }
33    table
34}
35
36static CRC16_TABLE: [u16; 256] = crc16_table();
37
38/// Redis's CRC16, the XMODEM variant.
39#[inline]
40pub fn crc16(data: &[u8]) -> u16 {
41    let mut crc: u16 = 0;
42    for &b in data {
43        let idx = (((crc >> 8) ^ b as u16) & 0xff) as usize;
44        crc = (crc << 8) ^ CRC16_TABLE[idx];
45    }
46    crc
47}
48
49/// The number of slots, which is Redis's 16384 and is not configurable.
50pub const SLOT_COUNT: u16 = 16384;
51
52/// The cluster slot a key belongs to, hash tags included.
53///
54/// If the key contains `{` followed by a non empty run and then `}`, only the
55/// run between them is hashed. That rule is what lets a caller force two keys
56/// onto one shard, and multi key commands depend on it, so it belongs next to
57/// the CRC rather than in the command layer.
58#[inline]
59pub fn slot_of(key: &[u8]) -> u16 {
60    crc16(hash_tag(key)) & (SLOT_COUNT - 1)
61}
62
63/// The part of a key that decides its slot.
64///
65/// Returns the whole key unless there is a `{...}` with something inside it.
66#[inline]
67pub fn hash_tag(key: &[u8]) -> &[u8] {
68    let Some(open) = key.iter().position(|&b| b == b'{') else {
69        return key;
70    };
71    let rest = &key[open + 1..];
72    let Some(close) = rest.iter().position(|&b| b == b'}') else {
73        return key;
74    };
75    if close == 0 {
76        // `{}` is empty, so the whole key is used. This matches Redis.
77        return key;
78    }
79    &rest[..close]
80}
81
82// ---------------------------------------------------------------------------
83// CRC32C / Castagnoli, polynomial 0x1EDC6F41, reflected as 0x82F63B78.
84// ---------------------------------------------------------------------------
85
86const fn crc32c_table() -> [u32; 256] {
87    let mut table = [0u32; 256];
88    let mut i = 0usize;
89    while i < 256 {
90        let mut crc = i as u32;
91        let mut bit = 0;
92        while bit < 8 {
93            crc = if crc & 1 != 0 {
94                (crc >> 1) ^ 0x82F6_3B78
95            } else {
96                crc >> 1
97            };
98            bit += 1;
99        }
100        table[i] = crc;
101        i += 1;
102    }
103    table
104}
105
106static CRC32C_TABLE: [u32; 256] = crc32c_table();
107
108#[inline]
109fn crc32c_software(mut crc: u32, data: &[u8]) -> u32 {
110    crc = !crc;
111    for &b in data {
112        crc = (crc >> 8) ^ CRC32C_TABLE[((crc ^ b as u32) & 0xff) as usize];
113    }
114    !crc
115}
116
117/// CRC32C over `data`, continuing from `crc`. Start with 0.
118#[inline]
119pub fn crc32c(crc: u32, data: &[u8]) -> u32 {
120    #[cfg(target_arch = "x86_64")]
121    {
122        if is_x86_feature_detected!("sse4.2") {
123            // SAFETY: guarded by the runtime feature check immediately above.
124            return unsafe { crc32c_sse42(crc, data) };
125        }
126    }
127    crc32c_software(crc, data)
128}
129
130#[cfg(target_arch = "x86_64")]
131#[target_feature(enable = "sse4.2")]
132unsafe fn crc32c_sse42(crc: u32, data: &[u8]) -> u32 {
133    use core::arch::x86_64::{_mm_crc32_u8, _mm_crc32_u64};
134
135    let mut c = !crc;
136    // `as_chunks` rather than `chunks_exact`, because the chunk size is a
137    // constant and this way the length is one too. The compiler stops emitting
138    // the bounds check that the eight byte load does not need.
139    let (words, rest) = data.as_chunks::<8>();
140    for chunk in words {
141        // No unsafe block. These intrinsics are safe to call from a function
142        // that carries the matching `#[target_feature]`, and wrapping them
143        // anyway is an unused_unsafe warning on x86, which CI treats as an
144        // error. The unsafety is at the call site in `crc32c`, where the
145        // runtime feature check lives.
146        c = _mm_crc32_u64(c as u64, u64::from_le_bytes(*chunk)) as u32;
147    }
148    for &b in rest {
149        c = _mm_crc32_u8(c, b);
150    }
151    !c
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157
158    /// The slot values Redis documents for its own examples. If these move, we
159    /// are no longer wire compatible with cluster clients.
160    #[test]
161    fn redis_slot_examples() {
162        assert_eq!(crc16(b"123456789"), 0x31C3);
163        assert_eq!(slot_of(b"foo"), 12182);
164        assert_eq!(slot_of(b"bar"), 5061);
165    }
166
167    #[test]
168    fn hash_tags_pick_the_inner_run() {
169        assert_eq!(hash_tag(b"{user1000}.following"), b"user1000");
170        assert_eq!(hash_tag(b"foo{}{bar}"), b"foo{}{bar}");
171        assert_eq!(hash_tag(b"foo{{bar}}zap"), b"{bar");
172        assert_eq!(hash_tag(b"foo{bar}{zap}"), b"bar");
173        assert_eq!(hash_tag(b"nothing"), b"nothing");
174    }
175
176    #[test]
177    fn tagged_keys_land_on_one_slot() {
178        assert_eq!(
179            slot_of(b"{user1000}.following"),
180            slot_of(b"{user1000}.followers")
181        );
182    }
183
184    #[test]
185    fn crc32c_reference() {
186        // The check value every CRC32C implementation publishes.
187        assert_eq!(crc32c(0, b"123456789"), 0xE306_9283);
188        assert_eq!(crc32c(0, b""), 0);
189    }
190
191    /// The hardware and software paths must not disagree, because one machine
192    /// writing a page and another verifying it is the normal case.
193    #[test]
194    fn crc32c_hardware_matches_software() {
195        let buf: Vec<u8> = (0..1000u32).map(|i| (i * 7 % 251) as u8).collect();
196        for n in [0usize, 1, 7, 8, 9, 15, 16, 63, 64, 65, 999, 1000] {
197            assert_eq!(
198                crc32c(0, &buf[..n]),
199                crc32c_software(0, &buf[..n]),
200                "length {n} disagrees between the two paths"
201            );
202        }
203    }
204
205    #[test]
206    fn crc32c_is_resumable() {
207        let buf: Vec<u8> = (0..256u32).map(|i| i as u8).collect();
208        let one_shot = crc32c(0, &buf);
209        let split = crc32c(crc32c(0, &buf[..100]), &buf[100..]);
210        assert_eq!(one_shot, split);
211    }
212}