Skip to main content

yo_common/
crc.rs

1//! CRC16 for slot placement, CRC32C for integrity, CRC64 for the file format.
2//!
3//! Three different polynomials for three 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//! CRC64 is the Jones polynomial and it is here for one reason only: it is the
13//! eight bytes on the end of an RDB payload, so `DUMP` cannot produce something
14//! a real Redis will accept and `RESTORE` cannot reject a corrupt payload
15//! without it. Nothing else in the engine uses it, and nothing else should,
16//! because CRC32C has hardware behind it and this does not.
17
18// ---------------------------------------------------------------------------
19// CRC16 / XMODEM, polynomial 0x1021, initial value 0, not reflected.
20// ---------------------------------------------------------------------------
21
22const fn crc16_table() -> [u16; 256] {
23    let mut table = [0u16; 256];
24    let mut i = 0usize;
25    while i < 256 {
26        let mut crc = (i as u16) << 8;
27        let mut bit = 0;
28        while bit < 8 {
29            crc = if crc & 0x8000 != 0 {
30                (crc << 1) ^ 0x1021
31            } else {
32                crc << 1
33            };
34            bit += 1;
35        }
36        table[i] = crc;
37        i += 1;
38    }
39    table
40}
41
42static CRC16_TABLE: [u16; 256] = crc16_table();
43
44/// Redis's CRC16, the XMODEM variant.
45#[inline]
46pub fn crc16(data: &[u8]) -> u16 {
47    let mut crc: u16 = 0;
48    for &b in data {
49        let idx = (((crc >> 8) ^ b as u16) & 0xff) as usize;
50        crc = (crc << 8) ^ CRC16_TABLE[idx];
51    }
52    crc
53}
54
55/// The number of slots, which is Redis's 16384 and is not configurable.
56pub const SLOT_COUNT: u16 = 16384;
57
58/// The cluster slot a key belongs to, hash tags included.
59///
60/// If the key contains `{` followed by a non empty run and then `}`, only the
61/// run between them is hashed. That rule is what lets a caller force two keys
62/// onto one shard, and multi key commands depend on it, so it belongs next to
63/// the CRC rather than in the command layer.
64#[inline]
65pub fn slot_of(key: &[u8]) -> u16 {
66    crc16(hash_tag(key)) & (SLOT_COUNT - 1)
67}
68
69/// The part of a key that decides its slot.
70///
71/// Returns the whole key unless there is a `{...}` with something inside it.
72#[inline]
73pub fn hash_tag(key: &[u8]) -> &[u8] {
74    let Some(open) = key.iter().position(|&b| b == b'{') else {
75        return key;
76    };
77    let rest = &key[open + 1..];
78    let Some(close) = rest.iter().position(|&b| b == b'}') else {
79        return key;
80    };
81    if close == 0 {
82        // `{}` is empty, so the whole key is used. This matches Redis.
83        return key;
84    }
85    &rest[..close]
86}
87
88// ---------------------------------------------------------------------------
89// CRC32C / Castagnoli, polynomial 0x1EDC6F41, reflected as 0x82F63B78.
90// ---------------------------------------------------------------------------
91
92const fn crc32c_table() -> [u32; 256] {
93    let mut table = [0u32; 256];
94    let mut i = 0usize;
95    while i < 256 {
96        let mut crc = i as u32;
97        let mut bit = 0;
98        while bit < 8 {
99            crc = if crc & 1 != 0 {
100                (crc >> 1) ^ 0x82F6_3B78
101            } else {
102                crc >> 1
103            };
104            bit += 1;
105        }
106        table[i] = crc;
107        i += 1;
108    }
109    table
110}
111
112static CRC32C_TABLE: [u32; 256] = crc32c_table();
113
114#[inline]
115fn crc32c_software(mut crc: u32, data: &[u8]) -> u32 {
116    crc = !crc;
117    for &b in data {
118        crc = (crc >> 8) ^ CRC32C_TABLE[((crc ^ b as u32) & 0xff) as usize];
119    }
120    !crc
121}
122
123/// CRC32C over `data`, continuing from `crc`. Start with 0.
124#[inline]
125pub fn crc32c(crc: u32, data: &[u8]) -> u32 {
126    #[cfg(target_arch = "x86_64")]
127    {
128        if is_x86_feature_detected!("sse4.2") {
129            // SAFETY: guarded by the runtime feature check immediately above.
130            return unsafe { crc32c_sse42(crc, data) };
131        }
132    }
133    crc32c_software(crc, data)
134}
135
136#[cfg(target_arch = "x86_64")]
137#[target_feature(enable = "sse4.2")]
138unsafe fn crc32c_sse42(crc: u32, data: &[u8]) -> u32 {
139    use core::arch::x86_64::{_mm_crc32_u8, _mm_crc32_u64};
140
141    let mut c = !crc;
142    // `as_chunks` rather than `chunks_exact`, because the chunk size is a
143    // constant and this way the length is one too. The compiler stops emitting
144    // the bounds check that the eight byte load does not need.
145    let (words, rest) = data.as_chunks::<8>();
146    for chunk in words {
147        // No unsafe block. These intrinsics are safe to call from a function
148        // that carries the matching `#[target_feature]`, and wrapping them
149        // anyway is an unused_unsafe warning on x86, which CI treats as an
150        // error. The unsafety is at the call site in `crc32c`, where the
151        // runtime feature check lives.
152        c = _mm_crc32_u64(c as u64, u64::from_le_bytes(*chunk)) as u32;
153    }
154    for &b in rest {
155        c = _mm_crc32_u8(c, b);
156    }
157    !c
158}
159
160// ---------------------------------------------------------------------------
161// CRC64 / Jones, polynomial 0xad93d23594c935a9, reflected as 0x95ac9329ac4bc9b5.
162// ---------------------------------------------------------------------------
163
164const fn crc64_table() -> [u64; 256] {
165    let mut table = [0u64; 256];
166    let mut i = 0usize;
167    while i < 256 {
168        let mut crc = i as u64;
169        let mut bit = 0;
170        while bit < 8 {
171            crc = if crc & 1 != 0 {
172                (crc >> 1) ^ 0x95AC_9329_AC4B_C9B5
173            } else {
174                crc >> 1
175            };
176            bit += 1;
177        }
178        table[i] = crc;
179        i += 1;
180    }
181    table
182}
183
184static CRC64_TABLE: [u64; 256] = crc64_table();
185
186/// CRC64 over `data`, continuing from `crc`. Start with 0.
187///
188/// The variant Redis puts on the end of an RDB payload. Published descriptions
189/// of crc-64-jones give it an initial value of all ones, and Redis's own source
190/// comment says so too, but the function Redis actually calls starts from the
191/// value handed in and that value is zero. Copy the code, not the comment, or
192/// every payload we produce fails somebody else's checksum.
193#[inline]
194pub fn crc64(crc: u64, data: &[u8]) -> u64 {
195    let mut c = crc;
196    for &b in data {
197        c = (c >> 8) ^ CRC64_TABLE[((c ^ b as u64) & 0xff) as usize];
198    }
199    c
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205
206    /// The slot values Redis documents for its own examples. If these move, we
207    /// are no longer wire compatible with cluster clients.
208    #[test]
209    fn redis_slot_examples() {
210        assert_eq!(crc16(b"123456789"), 0x31C3);
211        assert_eq!(slot_of(b"foo"), 12182);
212        assert_eq!(slot_of(b"bar"), 5061);
213    }
214
215    #[test]
216    fn hash_tags_pick_the_inner_run() {
217        assert_eq!(hash_tag(b"{user1000}.following"), b"user1000");
218        assert_eq!(hash_tag(b"foo{}{bar}"), b"foo{}{bar}");
219        assert_eq!(hash_tag(b"foo{{bar}}zap"), b"{bar");
220        assert_eq!(hash_tag(b"foo{bar}{zap}"), b"bar");
221        assert_eq!(hash_tag(b"nothing"), b"nothing");
222    }
223
224    #[test]
225    fn tagged_keys_land_on_one_slot() {
226        assert_eq!(
227            slot_of(b"{user1000}.following"),
228            slot_of(b"{user1000}.followers")
229        );
230    }
231
232    #[test]
233    fn crc32c_reference() {
234        // The check value every CRC32C implementation publishes.
235        assert_eq!(crc32c(0, b"123456789"), 0xE306_9283);
236        assert_eq!(crc32c(0, b""), 0);
237    }
238
239    /// The hardware and software paths must not disagree, because one machine
240    /// writing a page and another verifying it is the normal case.
241    #[test]
242    fn crc32c_hardware_matches_software() {
243        let buf: Vec<u8> = (0..1000u32).map(|i| (i * 7 % 251) as u8).collect();
244        for n in [0usize, 1, 7, 8, 9, 15, 16, 63, 64, 65, 999, 1000] {
245            assert_eq!(
246                crc32c(0, &buf[..n]),
247                crc32c_software(0, &buf[..n]),
248                "length {n} disagrees between the two paths"
249            );
250        }
251    }
252
253    #[test]
254    fn crc32c_is_resumable() {
255        let buf: Vec<u8> = (0..256u32).map(|i| i as u8).collect();
256        let one_shot = crc32c(0, &buf);
257        let split = crc32c(crc32c(0, &buf[..100]), &buf[100..]);
258        assert_eq!(one_shot, split);
259    }
260
261    /// The value Redis prints from its own self test in `crc64.c`. This is the
262    /// whole reason the function is here, so if it moves nothing we produce is
263    /// worth sending anywhere.
264    #[test]
265    fn crc64_matches_the_redis_self_test() {
266        assert_eq!(crc64(0, b"123456789"), 0xe9c6_d914_c4b8_d9ca);
267        assert_eq!(crc64(0, b""), 0);
268    }
269
270    #[test]
271    fn crc64_is_resumable() {
272        let buf: Vec<u8> = (0..256u32).map(|i| i as u8).collect();
273        assert_eq!(crc64(0, &buf), crc64(crc64(0, &buf[..100]), &buf[100..]));
274    }
275}