1pub mod gf;
17
18use std::collections::HashMap;
19use std::sync::{Mutex, OnceLock};
20
21pub const MAX_SYMBOLS: usize = 256;
23
24pub type Matrix = std::sync::Arc<Vec<Vec<u8>>>;
30
31pub fn cauchy_matrix(n: usize, k: usize) -> Matrix {
32 static CACHE: OnceLock<Mutex<HashMap<(usize, usize), Matrix>>> = OnceLock::new();
33 assert!(n + k <= MAX_SYMBOLS, "n+k must be <= 256, got n={n} k={k}");
34 let cache = CACHE.get_or_init(|| Mutex::new(HashMap::new()));
35 let mut guard = cache.lock().unwrap();
36 guard
37 .entry((n, k))
38 .or_insert_with(|| {
39 let m = (0..k)
40 .map(|i| (0..n).map(|j| gf::inv((i ^ (k + j)) as u8)).collect())
41 .collect();
42 std::sync::Arc::new(m)
43 })
44 .clone()
45}
46
47pub fn parity_count(n: usize, pct: u32, cap: usize) -> usize {
52 if pct == 0 || n == 0 {
53 return 0;
54 }
55 let scaled = (n as f64) * (pct as f64) / 100.0;
58 let rounded = round_half_even(scaled);
59 rounded.max(1).min(cap).min(n)
60}
61
62fn round_half_even(x: f64) -> usize {
63 let floor = x.floor();
64 let diff = x - floor;
65 let round_up = diff > 0.5 || (diff == 0.5 && (floor as u64) % 2 == 1);
66 (if round_up { floor + 1.0 } else { floor }) as usize
67}
68
69pub fn encode_parity(chunks: &[&[u8]], k: usize) -> Vec<Vec<u8>> {
74 if k == 0 || chunks.is_empty() {
75 return Vec::new();
76 }
77 let size = chunks[0].len();
78 debug_assert!(
79 chunks.iter().all(|c| c.len() == size),
80 "unequal chunk sizes"
81 );
82 let matrix = cauchy_matrix(chunks.len(), k);
83 matrix
84 .iter()
85 .map(|row| {
86 let mut acc = vec![0u8; size];
87 for (&coeff, chunk) in row.iter().zip(chunks) {
88 gf::madd_into(&mut acc, chunk, coeff);
89 }
90 acc
91 })
92 .collect()
93}
94
95pub fn decode(data: &mut [Option<Vec<u8>>], parity: &[Option<Vec<u8>>]) -> bool {
100 let n = data.len();
101 let k = parity.len();
102 let lost: Vec<usize> = (0..n).filter(|&i| data[i].is_none()).collect();
103 if lost.is_empty() {
104 return true;
105 }
106 let available: Vec<usize> = (0..k).filter(|&p| parity[p].is_some()).collect();
107 if available.len() < lost.len() {
108 return false;
109 }
110 let use_rows = &available[..lost.len()];
111 let size = match data
112 .iter()
113 .chain(parity.iter())
114 .find_map(|c| c.as_ref().map(|v| v.len()))
115 {
116 Some(s) => s,
117 None => return false,
118 };
119 let matrix = cauchy_matrix(n, k);
120
121 let syndromes: Vec<Vec<u8>> = use_rows
124 .iter()
125 .map(|&p| {
126 let mut acc = parity[p].as_ref().unwrap().clone();
127 for j in 0..n {
128 if let Some(chunk) = &data[j] {
129 gf::madd_into(&mut acc, chunk, matrix[p][j]);
130 }
131 }
132 acc
133 })
134 .collect();
135
136 let sub: Vec<Vec<u8>> = use_rows
137 .iter()
138 .map(|&p| lost.iter().map(|&j| matrix[p][j]).collect())
139 .collect();
140 let inverse = match invert(&sub) {
141 Some(m) => m,
142 None => return false,
143 };
144
145 for (r, &idx) in lost.iter().enumerate() {
146 let mut acc = vec![0u8; size];
147 for (c, syndrome) in syndromes.iter().enumerate() {
148 gf::madd_into(&mut acc, syndrome, inverse[r][c]);
149 }
150 data[idx] = Some(acc);
151 }
152 true
153}
154
155fn invert(matrix: &[Vec<u8>]) -> Option<Vec<Vec<u8>>> {
157 let m = matrix.len();
158 let mut aug: Vec<Vec<u8>> = matrix
159 .iter()
160 .enumerate()
161 .map(|(i, row)| {
162 let mut r = row.clone();
163 r.extend((0..m).map(|j| u8::from(i == j)));
164 r
165 })
166 .collect();
167 for col in 0..m {
168 let pivot = (col..m).find(|&r| aug[r][col] != 0)?;
169 aug.swap(col, pivot);
170 let scale = gf::inv(aug[col][col]);
171 for v in aug[col].iter_mut() {
172 *v = gf::mul(*v, scale);
173 }
174 let pivot_row = aug[col].clone();
175 for (r, row) in aug.iter_mut().enumerate() {
176 if r != col && row[col] != 0 {
177 let f = row[col];
178 for (v, p) in row.iter_mut().zip(&pivot_row) {
179 *v ^= gf::mul(f, *p);
180 }
181 }
182 }
183 }
184 Some(aug.into_iter().map(|row| row[m..].to_vec()).collect())
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190
191 struct Rng(u64);
192 impl Rng {
193 fn next(&mut self) -> u64 {
194 self.0 ^= self.0 << 13;
195 self.0 ^= self.0 >> 7;
196 self.0 ^= self.0 << 17;
197 self.0
198 }
199 fn below(&mut self, n: usize) -> usize {
200 (self.next() % n as u64) as usize
201 }
202 }
203
204 fn random_chunks(rng: &mut Rng, n: usize, size: usize) -> Vec<Vec<u8>> {
205 (0..n)
206 .map(|_| (0..size).map(|_| rng.next() as u8).collect())
207 .collect()
208 }
209
210 #[test]
211 fn any_k_erasures_recover() {
212 let mut rng = Rng(0x9e3779b97f4a7c15);
213 for &(n, k) in &[(1, 1), (6, 2), (12, 4), (18, 9), (50, 16), (200, 56)] {
214 let chunks = random_chunks(&mut rng, n, 64);
215 let refs: Vec<&[u8]> = chunks.iter().map(|c| c.as_slice()).collect();
216 let parity = encode_parity(&refs, k);
217 for _trial in 0..20 {
218 let mut data: Vec<Option<Vec<u8>>> = chunks.iter().cloned().map(Some).collect();
219 let mut par: Vec<Option<Vec<u8>>> = parity.iter().cloned().map(Some).collect();
220 let mut erased = 0;
221 while erased < k {
222 let i = rng.below(n + k);
223 let slot = if i < n { &mut data[i] } else { &mut par[i - n] };
224 if slot.is_some() {
225 *slot = None;
226 erased += 1;
227 }
228 }
229 assert!(decode(&mut data, &par), "n={n} k={k}");
230 for (a, b) in data.iter().zip(&chunks) {
231 assert_eq!(a.as_ref().unwrap(), b);
232 }
233 }
234 }
235 }
236
237 #[test]
238 fn too_many_erasures_fail() {
239 let chunks = [vec![1u8; 8], vec![2u8; 8], vec![3u8; 8]];
240 let refs: Vec<&[u8]> = chunks.iter().map(|c| c.as_slice()).collect();
241 let parity = encode_parity(&refs, 1);
242 let mut data = vec![None, None, Some(chunks[2].clone())];
243 let par: Vec<Option<Vec<u8>>> = parity.into_iter().map(Some).collect();
244 assert!(!decode(&mut data, &par));
245 }
246
247 #[test]
248 fn parity_count_matches_python() {
249 assert_eq!(parity_count(6, 25, 16), 2);
250 assert_eq!(parity_count(6, 8, 16), 1);
251 assert_eq!(parity_count(18, 50, 16), 9);
252 assert_eq!(parity_count(61, 30, 16), 16);
253 assert_eq!(parity_count(1, 50, 16), 1);
254 assert_eq!(parity_count(5, 0, 16), 0);
255 assert_eq!(parity_count(10, 25, 16), 2);
257 assert_eq!(parity_count(14, 25, 16), 4);
258 }
259
260 #[test]
261 fn cauchy_matches_reference() {
262 let m = cauchy_matrix(3, 2);
263 assert_eq!(m[0][0], gf::inv(2));
264 assert_eq!(m[1][2], gf::inv(1 ^ 4));
265 }
266}