Skip to main content

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}