rusty_erasure_core/
raid.rs1use crate::error::CodeError;
15
16const NOTBIT0: u64 = 0xfefe_fefe_fefe_fefe;
17const BIT7: u64 = 0x8080_8080_8080_8080;
18const GF8POLY: u64 = 0x1d1d_1d1d_1d1d_1d1d;
19
20#[inline]
23const fn gf2_mul2_lanes(q: u64) -> u64 {
24 let m = q & BIT7;
25 ((q << 1) & NOTBIT0) ^ (((m << 1).wrapping_sub(m >> 7)) & GF8POLY)
26}
27
28#[inline]
29const fn gf2_mul2_byte(q: u8) -> u8 {
30 (q << 1) ^ (if q & 0x80 != 0 { 0x1d } else { 0 })
31}
32
33fn check_lens(sources: &[&[u8]], len: usize, min_sources: usize) -> Result<(), CodeError> {
34 if sources.len() < min_sources {
35 return Err(CodeError::ShardCount {
36 expected: min_sources,
37 got: sources.len(),
38 });
39 }
40 for (index, s) in sources.iter().enumerate() {
41 if s.len() != len {
42 return Err(CodeError::ShardLength {
43 index,
44 expected: len,
45 got: s.len(),
46 });
47 }
48 }
49 Ok(())
50}
51
52pub fn xor_gen(sources: &[&[u8]], parity: &mut [u8]) -> Result<(), CodeError> {
61 check_lens(sources, parity.len(), 2)?;
62 crate::kernel::SCALAR_CENSUS_BYTES.fetch_add(
63 (sources.len() * parity.len()) as u64,
64 core::sync::atomic::Ordering::Relaxed,
65 );
66 let (first, rest) = sources.split_first().expect("count checked");
67 parity.copy_from_slice(first);
68 for src in rest {
69 for (d, &s) in parity.iter_mut().zip(*src) {
70 *d ^= s;
71 }
72 }
73 Ok(())
74}
75
76pub fn xor_check(vects: &[&[u8]]) -> Result<bool, CodeError> {
79 let len = vects.first().map_or(0, |v| v.len());
80 check_lens(vects, len, 2)?;
81 let words = len / 8;
82 for i in 0..words {
83 let o = i * 8;
84 let mut acc = 0u64;
85 for v in vects {
86 acc ^= u64::from_ne_bytes(v[o..o + 8].try_into().expect("in range"));
87 }
88 if acc != 0 {
89 return Ok(false);
90 }
91 }
92 for i in words * 8..len {
93 let mut acc = 0u8;
94 for v in vects {
95 acc ^= v[i];
96 }
97 if acc != 0 {
98 return Ok(false);
99 }
100 }
101 Ok(true)
102}
103
104pub fn pq_gen(sources: &[&[u8]], p: &mut [u8], q: &mut [u8]) -> Result<(), CodeError> {
106 let len = p.len();
107 if q.len() != len {
108 return Err(CodeError::ShardLength {
109 index: 1,
110 expected: len,
111 got: q.len(),
112 });
113 }
114 check_lens(sources, len, 2)?;
115 crate::kernel::SCALAR_CENSUS_BYTES.fetch_add(
116 (sources.len() * len) as u64,
117 core::sync::atomic::Ordering::Relaxed,
118 );
119
120 let last = sources.len() - 1;
125 let blocks = len / 32;
126 for i in 0..blocks {
127 let o = i * 32;
128 let load = |s: &[u8], w: usize| {
129 u64::from_ne_bytes(s[o + w * 8..o + w * 8 + 8].try_into().expect("in range"))
130 };
131 let mut pw = [0u64; 4];
132 let mut qw = [0u64; 4];
133 for w in 0..4 {
134 pw[w] = load(sources[last], w);
135 qw[w] = pw[w];
136 }
137 for j in (0..last).rev() {
138 for w in 0..4 {
139 let s = load(sources[j], w);
140 pw[w] ^= s;
141 qw[w] = s ^ gf2_mul2_lanes(qw[w]);
142 }
143 }
144 for w in 0..4 {
145 p[o + w * 8..o + w * 8 + 8].copy_from_slice(&pw[w].to_ne_bytes());
146 q[o + w * 8..o + w * 8 + 8].copy_from_slice(&qw[w].to_ne_bytes());
147 }
148 }
149 for i in blocks * 32..len {
151 let last = sources.len() - 1;
152 let mut pb = sources[last][i];
153 let mut qb = pb;
154 for j in (0..last).rev() {
155 let s = sources[j][i];
156 pb ^= s;
157 qb = s ^ gf2_mul2_byte(qb);
158 }
159 p[i] = pb;
160 q[i] = qb;
161 }
162 Ok(())
163}
164
165#[derive(Debug, Clone, Copy, PartialEq, Eq)]
167pub enum PqParity {
168 P,
170 Q,
172}
173
174#[derive(Debug, Clone, Copy, PartialEq, Eq)]
177pub struct PqMismatch {
178 pub index: usize,
180 pub parity: PqParity,
182}
183
184pub fn pq_check(sources: &[&[u8]], p: &[u8], q: &[u8]) -> Result<Option<PqMismatch>, CodeError> {
187 let len = p.len();
188 if q.len() != len {
189 return Err(CodeError::ShardLength {
190 index: 1,
191 expected: len,
192 got: q.len(),
193 });
194 }
195 check_lens(sources, len, 2)?;
196 let last = sources.len() - 1;
197
198 let byte_scan = |from: usize, to: usize| -> Option<PqMismatch> {
201 for i in from..to {
202 let mut pb = sources[last][i];
203 let mut qb = pb;
204 for j in (0..last).rev() {
205 let s = sources[j][i];
206 pb ^= s;
207 qb = s ^ gf2_mul2_byte(qb);
208 }
209 if p[i] != pb {
210 return Some(PqMismatch {
211 index: i,
212 parity: PqParity::P,
213 });
214 }
215 if q[i] != qb {
216 return Some(PqMismatch {
217 index: i,
218 parity: PqParity::Q,
219 });
220 }
221 }
222 None
223 };
224
225 let words = len / 8;
229 for i in 0..words {
230 let o = i * 8;
231 let load = |s: &[u8]| u64::from_ne_bytes(s[o..o + 8].try_into().expect("in range"));
232 let mut pw = load(sources[last]);
233 let mut qw = pw;
234 for j in (0..last).rev() {
235 let s = load(sources[j]);
236 pw ^= s;
237 qw = s ^ gf2_mul2_lanes(qw);
238 }
239 if pw != load(p) || qw != load(q) {
240 return Ok(byte_scan(o, o + 8));
241 }
242 }
243 Ok(byte_scan(words * 8, len))
244}