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}