btrfs_disk/raid56.rs
1//! # RAID5 / RAID6 parity computation
2//!
3//! Self-contained math for the parity stripes that btrfs's RAID5 and
4//! RAID6 profiles require. Two functions:
5//!
6//! - [`compute_p`]: byte-wise XOR over the data stripes. Used by both
7//! RAID5 and RAID6 (the "P" stripe).
8//! - [`compute_p_q`]: XOR plus a Reed-Solomon code over GF(2^8) using
9//! the generator `x^8 + x^4 + x^3 + x^2 + 1` (0x1D). Used by RAID6.
10//!
11//! Inputs are equal-length byte slices (one per data column of a row).
12//! The output buffers are the same length and represent the parity
13//! columns of that row.
14//!
15//! ## GF(2^8) recipe (Q stripe)
16//!
17//! Q is computed by walking the data stripes in order and accumulating
18//! `q = mul2(q) ^ data_byte`, where `mul2` is multiplication by 2 in
19//! GF(2^8) with reduction by the polynomial 0x1D when the high bit is
20//! set. This matches the kernel's `raid6_call` and produces the same
21//! byte sequence the on-disk format expects.
22//!
23//! The two-times multiplication table is precomputed at first use via
24//! [`std::sync::OnceLock`]; the cost is 256 byte writes once per
25//! process.
26
27use std::sync::OnceLock;
28
29/// Reduction polynomial of GF(2^8) used by the RAID6 Q stripe:
30/// `x^8 + x^4 + x^3 + x^2 + 1`. Only the low 8 bits matter (the
31/// implicit `x^8` term is handled by the conditional XOR after the
32/// shift).
33const RS_POLY: u8 = 0x1D;
34
35/// Lookup table for `x -> 2 * x` in GF(2^8) under [`RS_POLY`].
36///
37/// Built once at first call; subsequent lookups are O(1).
38fn mul2_table() -> &'static [u8; 256] {
39 static TABLE: OnceLock<[u8; 256]> = OnceLock::new();
40 TABLE.get_or_init(|| {
41 let mut t = [0u8; 256];
42 for (i, slot) in t.iter_mut().enumerate() {
43 // i is bounded by 256 (array length), so the cast is exact.
44 let x = u8::try_from(i).expect("table index < 256");
45 // Multiplication by 2 in GF(2^8) is a left shift; if the
46 // pre-shift high bit was set (so the result would overflow
47 // into x^8) reduce by XORing the polynomial.
48 let shifted = x << 1;
49 *slot = if x & 0x80 != 0 {
50 shifted ^ RS_POLY
51 } else {
52 shifted
53 };
54 }
55 t
56 })
57}
58
59/// Multiply `x` by 2 in GF(2^8). Inlined helper around [`mul2_table`].
60#[inline]
61fn mul2(x: u8) -> u8 {
62 mul2_table()[x as usize]
63}
64
65/// Compute the RAID5/RAID6 P (XOR) stripe over `data_stripes`.
66///
67/// Every input slice must have the same length; the returned buffer has
68/// that length too. With zero data stripes, returns an empty buffer.
69///
70/// # Panics
71///
72/// Panics if the data stripes are not all the same length. Callers
73/// inside this crate construct the stripes from a single chunk row, so
74/// equal lengths are an invariant — the panic catches programmer
75/// errors only.
76#[must_use]
77pub fn compute_p(data_stripes: &[&[u8]]) -> Vec<u8> {
78 let Some(first) = data_stripes.first() else {
79 return Vec::new();
80 };
81 let len = first.len();
82 debug_assert!(
83 data_stripes.iter().all(|s| s.len() == len),
84 "compute_p: stripes must have equal length"
85 );
86 let mut p = first.to_vec();
87 for stripe in &data_stripes[1..] {
88 for (out, &b) in p.iter_mut().zip(stripe.iter()) {
89 *out ^= b;
90 }
91 }
92 p
93}
94
95/// Compute the RAID6 (P, Q) parity stripes over `data_stripes`.
96///
97/// P is the XOR of all data stripes. Q is the Reed-Solomon code: walk
98/// the stripes in order and update `q[i] = mul2(q[i]) ^ data[i]` for
99/// every byte position. Returns `(p, q)` with both buffers the same
100/// length as each input stripe.
101///
102/// With zero data stripes, returns two empty buffers.
103///
104/// # Panics
105///
106/// Panics if the data stripes are not all the same length. See
107/// [`compute_p`] for the rationale.
108#[must_use]
109pub fn compute_p_q(data_stripes: &[&[u8]]) -> (Vec<u8>, Vec<u8>) {
110 let Some(first) = data_stripes.first() else {
111 return (Vec::new(), Vec::new());
112 };
113 let len = first.len();
114 debug_assert!(
115 data_stripes.iter().all(|s| s.len() == len),
116 "compute_p_q: stripes must have equal length"
117 );
118
119 let mut p = vec![0u8; len];
120 let mut q = vec![0u8; len];
121
122 // Walk stripes in order; each iteration mixes one column into both
123 // P (xor) and Q (mul2-then-xor).
124 for stripe in data_stripes {
125 for i in 0..len {
126 p[i] ^= stripe[i];
127 q[i] = mul2(q[i]) ^ stripe[i];
128 }
129 }
130 (p, q)
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn mul2_table_basic_values() {
139 // Hand-verifiable entries:
140 // 2*0 = 0
141 // 2*1 = 2
142 // 2*0x40 = 0x80 (high bit not yet set pre-shift)
143 // 2*0x80 = (0x100) reduces to 0x1D (poly XOR)
144 // 2*0x81 = (0x102) reduces to 0x1F
145 // 2*0xFF = (0x1FE) reduces to 0xE3
146 let t = mul2_table();
147 assert_eq!(t[0x00], 0x00);
148 assert_eq!(t[0x01], 0x02);
149 assert_eq!(t[0x40], 0x80);
150 assert_eq!(t[0x80], 0x1D);
151 assert_eq!(t[0x81], 0x1F);
152 assert_eq!(t[0xFF], 0xE3);
153 }
154
155 #[test]
156 fn mul2_doubling_is_associative_to_pow2() {
157 // Doubling x four times equals multiplying by 16 in GF(2^8);
158 // for x = 1 that should reach 16.
159 let mut x = 1u8;
160 for _ in 0..4 {
161 x = mul2(x);
162 }
163 assert_eq!(x, 16);
164 }
165
166 #[test]
167 fn compute_p_empty_input() {
168 let p = compute_p(&[]);
169 assert!(p.is_empty());
170 }
171
172 #[test]
173 fn compute_p_single_stripe_is_copy() {
174 let s = [0x11u8, 0x22, 0x33, 0x44];
175 let p = compute_p(&[&s]);
176 assert_eq!(p, s);
177 }
178
179 #[test]
180 fn compute_p_xor_of_known_pattern() {
181 // P of three stripes should be the byte-wise XOR.
182 let a = [0xFFu8, 0x00, 0xAA, 0x55];
183 let b = [0x0Fu8, 0xF0, 0x55, 0xAA];
184 let c = [0x33u8, 0x33, 0x33, 0x33];
185 let p = compute_p(&[&a, &b, &c]);
186 // Hand-computed:
187 // 0xFF^0x0F^0x33 = 0xC3
188 // 0x00^0xF0^0x33 = 0xC3
189 // 0xAA^0x55^0x33 = 0xCC
190 // 0x55^0xAA^0x33 = 0xCC
191 assert_eq!(p, vec![0xC3, 0xC3, 0xCC, 0xCC]);
192 }
193
194 #[test]
195 fn compute_p_xor_of_self_is_zero() {
196 // Two copies of the same stripe XOR to zero — sanity check that
197 // there's no off-by-one mixing step or extra mul.
198 let s = [0xDEu8, 0xAD, 0xBE, 0xEF];
199 let p = compute_p(&[&s, &s]);
200 assert_eq!(p, vec![0; 4]);
201 }
202
203 #[test]
204 fn compute_p_q_empty_input() {
205 let (p, q) = compute_p_q(&[]);
206 assert!(p.is_empty() && q.is_empty());
207 }
208
209 #[test]
210 fn compute_p_q_zero_data_is_zero() {
211 // Two all-zero stripes -> both P and Q must be all-zero.
212 let z = [0u8; 8];
213 let (p, q) = compute_p_q(&[&z, &z, &z]);
214 assert_eq!(p, vec![0; 8]);
215 assert_eq!(q, vec![0; 8]);
216 }
217
218 #[test]
219 fn compute_p_q_single_stripe_p_is_copy_q_equals_stripe() {
220 // With one stripe, the loop runs once: p[i] = data[i] (since
221 // p starts at 0), q[i] = mul2(0) ^ data[i] = data[i].
222 let s = [1u8, 2, 3, 4, 0xFF];
223 let (p, q) = compute_p_q(&[&s]);
224 assert_eq!(p, s);
225 assert_eq!(q, s);
226 }
227
228 #[test]
229 fn compute_p_q_two_stripes_q_is_2a_xor_b() {
230 // For two stripes A and B walked in order:
231 // P[i] = A[i] ^ B[i]
232 // Q[i] = mul2(mul2(0) ^ A[i]) ^ B[i] = mul2(A[i]) ^ B[i]
233 let a = [0x01u8, 0x02, 0x40, 0x80];
234 let b = [0xFFu8, 0x00, 0xAA, 0x55];
235 let (p, q) = compute_p_q(&[&a, &b]);
236 let expect_p: Vec<u8> =
237 a.iter().zip(b.iter()).map(|(x, y)| x ^ y).collect();
238 let expect_q: Vec<u8> =
239 a.iter().zip(b.iter()).map(|(x, y)| mul2(*x) ^ y).collect();
240 assert_eq!(p, expect_p);
241 assert_eq!(q, expect_q);
242 }
243
244 #[test]
245 fn compute_p_q_three_stripes_explicit() {
246 // Three stripes; reproduce the loop step by step:
247 // q after stripe 0: q1 = A
248 // q after stripe 1: q2 = mul2(A) ^ B
249 // q after stripe 2: q3 = mul2(mul2(A) ^ B) ^ C
250 let s_a = [0x01u8];
251 let s_b = [0x40u8];
252 let s_c = [0x80u8];
253 let (p, q) = compute_p_q(&[&s_a, &s_b, &s_c]);
254 assert_eq!(p, vec![0x01 ^ 0x40 ^ 0x80]);
255 let expected_q = mul2(mul2(0x01) ^ 0x40) ^ 0x80;
256 assert_eq!(q, vec![expected_q]);
257 }
258
259 #[test]
260 fn compute_p_q_p_consistent_with_compute_p() {
261 // For the same data, the P returned by compute_p_q must equal
262 // the standalone compute_p output. Sanity check that the two
263 // helpers agree.
264 let mut a = [0u8; 64];
265 let mut b = [0u8; 64];
266 let mut c = [0u8; 64];
267 for i in 0..64 {
268 a[i] = (i as u8).wrapping_mul(7);
269 b[i] = (i as u8).wrapping_mul(11);
270 c[i] = (i as u8).wrapping_mul(13);
271 }
272 let p_solo = compute_p(&[&a, &b, &c]);
273 let (p_dual, _q) = compute_p_q(&[&a, &b, &c]);
274 assert_eq!(p_solo, p_dual);
275 }
276
277 #[test]
278 fn compute_p_reconstruct_missing_stripe_via_xor() {
279 // Algebra check: in RAID5, given P and all-but-one of the data
280 // stripes, the missing stripe = XOR(P, other stripes). If our P
281 // is correct, this round-trip recovers the input.
282 let a = [0x12u8, 0x34, 0x56, 0x78];
283 let b = [0x9Au8, 0xBC, 0xDE, 0xF0];
284 let c = [0x11u8, 0x22, 0x33, 0x44];
285 let p = compute_p(&[&a, &b, &c]);
286 // Reconstruct b from P, A, and C.
287 let recon: Vec<u8> = p
288 .iter()
289 .zip(a.iter())
290 .zip(c.iter())
291 .map(|((p, x), y)| p ^ x ^ y)
292 .collect();
293 assert_eq!(recon, b);
294 }
295}