1use std::sync::OnceLock;
16
17struct Gf {
20 exp: [u8; 512],
21 log: [u8; 256],
22}
23
24fn gf() -> &'static Gf {
25 static GF: OnceLock<Gf> = OnceLock::new();
26 GF.get_or_init(|| {
27 let mut exp = [0u8; 512];
28 let mut log = [0u8; 256];
29 let mut x: u16 = 1;
30 for i in 0..255 {
31 exp[i] = x as u8;
32 log[x as usize] = i as u8;
33 x <<= 1;
34 if x & 0x100 != 0 {
35 x ^= 0x11d;
36 }
37 }
38 for i in 255..512 {
39 exp[i] = exp[i - 255];
40 }
41 Gf { exp, log }
42 })
43}
44
45fn mul(a: u8, b: u8) -> u8 {
46 if a == 0 || b == 0 {
47 return 0;
48 }
49 let g = gf();
50 g.exp[g.log[a as usize] as usize + g.log[b as usize] as usize]
51}
52
53fn div(a: u8, b: u8) -> u8 {
55 if a == 0 {
56 return 0;
57 }
58 let g = gf();
59 g.exp[g.log[a as usize] as usize + 255 - g.log[b as usize] as usize]
60}
61
62fn pow(base: u8, exp: usize) -> u8 {
64 let mut r = 1u8;
65 for _ in 0..exp {
66 r = mul(r, base);
67 }
68 r
69}
70
71fn invert(m: &[Vec<u8>]) -> Option<Vec<Vec<u8>>> {
73 let k = m.len();
74 let mut a: Vec<Vec<u8>> = m
76 .iter()
77 .enumerate()
78 .map(|(i, row)| {
79 let mut r = row.clone();
80 r.extend((0..k).map(|j| if i == j { 1 } else { 0 }));
81 r
82 })
83 .collect();
84 for col in 0..k {
85 let mut piv = col;
86 while piv < k && a[piv][col] == 0 {
87 piv += 1;
88 }
89 if piv == k {
90 return None; }
92 a.swap(col, piv);
93 let inv_p = div(1, a[col][col]);
94 for j in 0..2 * k {
95 a[col][j] = mul(a[col][j], inv_p);
96 }
97 for row in 0..k {
98 if row == col {
99 continue;
100 }
101 let f = a[row][col];
102 if f != 0 {
103 for j in 0..2 * k {
104 a[row][j] ^= mul(f, a[col][j]);
105 }
106 }
107 }
108 }
109 Some(a.iter().map(|r| r[k..2 * k].to_vec()).collect())
110}
111
112fn matmul(a: &[Vec<u8>], b: &[Vec<u8>]) -> Vec<Vec<u8>> {
114 let inner = b.len();
115 let cols = b[0].len();
116 a.iter()
117 .map(|row| {
118 (0..cols)
119 .map(|j| {
120 let mut acc = 0u8;
121 for t in 0..inner {
122 acc ^= mul(row[t], b[t][j]);
123 }
124 acc
125 })
126 .collect()
127 })
128 .collect()
129}
130
131fn encode_matrix(n: usize, k: usize) -> Option<Vec<Vec<u8>>> {
136 let vander: Vec<Vec<u8>> = (0..n)
137 .map(|i| (0..k).map(|j| pow(i as u8, j)).collect())
138 .collect();
139 let top = vander[0..k].to_vec();
140 let inv = invert(&top)?;
141 Some(matmul(&vander, &inv))
142}
143
144pub fn encode(data: &[u8], k: usize, n: usize) -> Option<(usize, Vec<Vec<u8>>)> {
148 if k == 0 || n < k || n > 256 {
149 return None;
150 }
151 let shard_len = data.len().div_ceil(k).max(1);
152 let mut padded = data.to_vec();
153 padded.resize(k * shard_len, 0);
154 let e = encode_matrix(n, k)?;
155 let shards = (0..n)
156 .map(|i| {
157 (0..shard_len)
158 .map(|b| {
159 let mut acc = 0u8;
160 for j in 0..k {
161 acc ^= mul(e[i][j], padded[j * shard_len + b]);
162 }
163 acc
164 })
165 .collect()
166 })
167 .collect();
168 Some((data.len(), shards))
169}
170
171pub fn decode(orig_len: usize, k: usize, n: usize, have: &[(usize, Vec<u8>)]) -> Option<Vec<u8>> {
174 if k == 0 || n < k || n > 256 || have.is_empty() {
175 return None;
176 }
177 let shard_len = have[0].1.len();
178 let e = encode_matrix(n, k)?;
179 let mut idxs: Vec<usize> = Vec::with_capacity(k);
180 let mut rows: Vec<Vec<u8>> = Vec::with_capacity(k);
181 let mut vals: Vec<&Vec<u8>> = Vec::with_capacity(k);
182 for (idx, shard) in have {
183 if *idx >= n || shard.len() != shard_len || idxs.contains(idx) {
184 continue;
185 }
186 idxs.push(*idx);
187 rows.push(e[*idx].clone());
188 vals.push(shard);
189 if idxs.len() == k {
190 break;
191 }
192 }
193 if idxs.len() < k {
194 return None;
195 }
196 let minv = invert(&rows)?;
197 let mut data = vec![0u8; k * shard_len];
198 for b in 0..shard_len {
199 for j in 0..k {
200 let mut acc = 0u8;
201 for r in 0..k {
202 acc ^= mul(minv[j][r], vals[r][b]);
203 }
204 data[j * shard_len + b] = acc;
205 }
206 }
207 if orig_len > data.len() {
208 return None;
209 }
210 data.truncate(orig_len);
211 Some(data)
212}
213
214const FEC_MAGIC: u8 = 0xFE;
221const FEC_HEADER_LEN: usize = 1 + 8 + 2 + 2 + 4 + 2;
223
224fn frame_shard(msg_id: u64, k: usize, n: usize, orig_len: usize, index: usize, shard: &[u8]) -> Vec<u8> {
225 let mut out = Vec::with_capacity(FEC_HEADER_LEN + shard.len());
226 out.push(FEC_MAGIC);
227 out.extend_from_slice(&msg_id.to_le_bytes());
228 out.extend_from_slice(&(k as u16).to_le_bytes());
229 out.extend_from_slice(&(n as u16).to_le_bytes());
230 out.extend_from_slice(&(orig_len as u32).to_le_bytes());
231 out.extend_from_slice(&(index as u16).to_le_bytes());
232 out.extend_from_slice(shard);
233 out
234}
235
236struct ShardHeader {
237 msg_id: u64,
238 k: usize,
239 n: usize,
240 orig_len: usize,
241 index: usize,
242}
243
244fn parse_shard(bytes: &[u8]) -> Option<(ShardHeader, &[u8])> {
245 if bytes.len() < FEC_HEADER_LEN || bytes[0] != FEC_MAGIC {
246 return None;
247 }
248 let h = ShardHeader {
249 msg_id: u64::from_le_bytes(bytes[1..9].try_into().ok()?),
250 k: u16::from_le_bytes(bytes[9..11].try_into().ok()?) as usize,
251 n: u16::from_le_bytes(bytes[11..13].try_into().ok()?) as usize,
252 orig_len: u32::from_le_bytes(bytes[13..17].try_into().ok()?) as usize,
253 index: u16::from_le_bytes(bytes[17..19].try_into().ok()?) as usize,
254 };
255 Some((h, &bytes[FEC_HEADER_LEN..]))
256}
257
258pub fn shard_header(bytes: &[u8]) -> Option<(u64, usize, usize)> {
262 parse_shard(bytes).map(|(h, _)| (h.msg_id, h.k, h.n))
263}
264
265pub fn frame_redundant(msg_id: u64, payload: &[u8], k: usize, n: usize) -> Option<Vec<Vec<u8>>> {
269 let (orig_len, shards) = encode(payload, k, n)?;
270 Some(
271 shards
272 .iter()
273 .enumerate()
274 .map(|(i, s)| frame_shard(msg_id, k, n, orig_len, i, s))
275 .collect(),
276 )
277}
278
279pub fn reconstruct_redundant(received: &[Vec<u8>]) -> Option<(u64, Vec<u8>)> {
285 use std::collections::HashMap;
286 let mut groups: HashMap<u64, (usize, usize, usize, Vec<(usize, Vec<u8>)>)> = HashMap::new();
288 for bytes in received {
289 let Some((h, shard)) = parse_shard(bytes) else { continue };
290 let entry = groups.entry(h.msg_id).or_insert((h.k, h.n, h.orig_len, Vec::new()));
291 if entry.0 == h.k
293 && entry.1 == h.n
294 && entry.2 == h.orig_len
295 && !entry.3.iter().any(|(i, _)| *i == h.index)
296 {
297 entry.3.push((h.index, shard.to_vec()));
298 }
299 }
300 for (msg_id, (k, n, orig_len, shards)) in groups {
301 if shards.len() >= k {
302 if let Some(data) = decode(orig_len, k, n, &shards) {
303 return Some((msg_id, data));
304 }
305 }
306 }
307 None
308}
309
310#[cfg(test)]
311mod tests {
312 use super::*;
313
314 struct R(u64);
315 impl R {
316 fn next(&mut self) -> u64 {
317 self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
318 let mut z = self.0;
319 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
320 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
321 z ^ (z >> 31)
322 }
323 }
324
325 #[test]
326 fn gf_mul_div_are_inverse_across_the_field() {
327 for a in 1u8..=255 {
328 for b in 1u8..=255 {
329 assert_eq!(div(mul(a, b), b), a, "div(mul(a,b),b)=a for a={a} b={b}");
330 }
331 }
332 }
333
334 #[test]
335 fn systematic_first_k_shards_are_the_data() {
336 let data: Vec<u8> = (0..40u8).collect();
337 let (k, n) = (5usize, 8usize);
338 let (_, shards) = encode(&data, k, n).unwrap();
339 let shard_len = 8;
340 for j in 0..k {
341 assert_eq!(
342 &shards[j][..],
343 &data[j * shard_len..(j + 1) * shard_len],
344 "data shard {j} must pass through unchanged (systematic)"
345 );
346 }
347 }
348
349 #[test]
350 fn reconstructs_from_any_k_of_n_sliding_window() {
351 let mut rng = R(0x1234_5678);
352 for &(k, n) in &[(4usize, 6usize), (6, 10), (3, 5), (8, 12), (1, 3), (10, 16), (2, 9)] {
353 let len = (rng.next() % 600) as usize + 1;
354 let data: Vec<u8> = (0..len).map(|_| rng.next() as u8).collect();
355 let (orig, shards) = encode(&data, k, n).unwrap();
356 for drop_start in 0..n {
358 let dropped: Vec<usize> = (0..(n - k)).map(|d| (drop_start + d) % n).collect();
359 let have: Vec<(usize, Vec<u8>)> = (0..n)
360 .filter(|i| !dropped.contains(i))
361 .map(|i| (i, shards[i].clone()))
362 .collect();
363 let got = decode(orig, k, n, &have).expect("reconstruct from k survivors");
364 assert_eq!(got, data, "k={k} n={n} dropped={dropped:?}");
365 }
366 }
367 }
368
369 #[test]
370 fn reconstructs_from_random_k_subsets() {
371 let mut rng = R(0xC0FF_EE00);
372 for &(k, n) in &[(5usize, 9usize), (7, 11), (4, 13)] {
373 let len = (rng.next() % 400) as usize + 1;
374 let data: Vec<u8> = (0..len).map(|_| rng.next() as u8).collect();
375 let (orig, shards) = encode(&data, k, n).unwrap();
376 for _ in 0..40 {
377 let mut all: Vec<usize> = (0..n).collect();
379 for s in 0..k {
381 let pick = s + (rng.next() as usize) % (n - s);
382 all.swap(s, pick);
383 }
384 let have: Vec<(usize, Vec<u8>)> =
385 all[0..k].iter().map(|&i| (i, shards[i].clone())).collect();
386 let got = decode(orig, k, n, &have).expect("reconstruct from random k-subset");
387 assert_eq!(got, data, "k={k} n={n} survivors={:?}", &all[0..k]);
388 }
389 }
390 }
391
392 #[test]
393 fn fewer_than_k_shards_is_unrecoverable() {
394 let data: Vec<u8> = (0..30u8).collect();
395 let (orig, shards) = encode(&data, 5, 8).unwrap();
396 let have: Vec<(usize, Vec<u8>)> = (0..4).map(|i| (i, shards[i].clone())).collect();
397 assert!(decode(orig, 5, 8, &have).is_none(), "k-1 shards must not reconstruct");
398 }
399
400 #[test]
401 fn reordered_and_duplicated_shards_still_reconstruct() {
402 let data: Vec<u8> = (0..100u8).collect();
403 let (k, n) = (6usize, 10usize);
404 let (orig, shards) = encode(&data, k, n).unwrap();
405 let mut have: Vec<(usize, Vec<u8>)> = vec![
407 (9, shards[9].clone()),
408 (2, shards[2].clone()),
409 (2, shards[2].clone()), (7, shards[7].clone()),
411 (0, shards[0].clone()),
412 (5, shards[5].clone()),
413 (5, shards[5].clone()), (3, shards[3].clone()),
415 ];
416 have.reverse();
417 let got = decode(orig, k, n, &have).expect("reorder + dup tolerant");
418 assert_eq!(got, data);
419 }
420
421 #[test]
422 fn degenerate_parameters_are_rejected() {
423 assert!(encode(b"x", 0, 3).is_none());
424 assert!(encode(b"x", 4, 2).is_none()); assert!(encode(b"x", 1, 257).is_none()); }
427
428 #[test]
431 fn framed_redundant_reconstructs_from_any_k_after_loss() {
432 let payload: Vec<u8> = (0..250u8).cycle().take(777).collect();
433 let (k, n) = (5usize, 8usize);
434 let shards = frame_redundant(0xABCD, &payload, k, n).unwrap();
435 assert_eq!(shards.len(), n, "one framed shard per n");
436 let delivered: Vec<Vec<u8>> = vec![
438 shards[7].clone(),
439 shards[1].clone(),
440 shards[4].clone(),
441 shards[0].clone(),
442 shards[6].clone(),
443 ];
444 let (id, got) = reconstruct_redundant(&delivered).expect("reconstruct from k framed shards");
445 assert_eq!(id, 0xABCD);
446 assert_eq!(got, payload);
447 }
448
449 #[test]
450 fn framed_redundant_below_k_does_not_reconstruct() {
451 let payload = b"the quick brown fox".to_vec();
452 let shards = frame_redundant(7, &payload, 4, 7).unwrap();
453 let delivered: Vec<Vec<u8>> = shards[0..3].to_vec(); assert!(reconstruct_redundant(&delivered).is_none());
455 }
456
457 #[test]
458 fn framed_redundant_ignores_other_messages_and_garbage() {
459 let a = frame_redundant(1, b"message A payload here", 3, 5).unwrap();
460 let b = frame_redundant(2, b"message B totally different", 3, 5).unwrap();
461 let mut bag = vec![a[0].clone(), a[1].clone(), vec![0u8, 1, 2, 3], Vec::new()];
463 bag.extend(b.iter().cloned());
464 let (id, got) = reconstruct_redundant(&bag).unwrap();
465 assert_eq!(id, 2);
466 assert_eq!(got, b"message B totally different");
467 }
468
469 #[test]
470 fn framed_redundant_tolerates_duplicate_shards() {
471 let payload: Vec<u8> = (0..200u8).collect();
472 let shards = frame_redundant(99, &payload, 6, 10).unwrap();
473 let mut bag = vec![
475 shards[0].clone(),
476 shards[0].clone(),
477 shards[1].clone(),
478 shards[1].clone(),
479 shards[2].clone(),
480 shards[3].clone(),
481 shards[4].clone(),
482 ];
483 assert!(reconstruct_redundant(&bag).is_none(), "5 distinct (with dups) < k=6");
484 bag.push(shards[8].clone()); let (_, got) = reconstruct_redundant(&bag).expect("6 distinct shards reconstruct");
486 assert_eq!(got, payload);
487 }
488}