1use std::collections::HashMap;
41
42mod coords;
43mod decode;
44mod encode;
45mod error;
46mod repair;
47mod transforms;
48
49pub use error::ClayError;
50
51const MAX_RS_SHARDS: usize = 32768;
52
53use decode::{decode as decode_chunks, decode_rows as decode_rows_impl, RsCodec};
54use encode::encode as encode_chunks;
55use repair::{minimum_to_repair as min_repair, repair as repair_chunk, repair_rows as repair_rows_impl};
56
57#[derive(Clone, Debug)]
59pub struct ClayCode {
60 pub k: usize,
62 pub m: usize,
64 pub n: usize,
66 pub d: usize,
68 pub q: usize,
70 pub t: usize,
72 pub nu: usize,
74 pub sub_chunk_no: usize,
76 pub beta: usize,
78 original_count: usize,
80 rs: RsCodec,
82}
83
84impl ClayCode {
85 pub fn new(k: usize, m: usize, d: usize) -> Result<Self, ClayError> {
95 if k < 1 {
96 return Err(ClayError::InvalidParameters("k must be at least 1".into()));
97 }
98 if m < 1 {
99 return Err(ClayError::InvalidParameters("m must be at least 1".into()));
100 }
101 if d < k + 1 || d > k + m - 1 {
102 return Err(ClayError::InvalidParameters(format!(
103 "d must be in range [{}, {}], got {}",
104 k + 1,
105 k + m - 1,
106 d
107 )));
108 }
109
110 let q = d - k + 1;
111 let n = k + m;
112
113 let nu = if n % q == 0 { 0 } else { q - (n % q) };
115
116 let t = (n + nu) / q;
117
118 let sub_chunk_no = checked_pow(q, t).ok_or_else(|| {
120 ClayError::Overflow(format!("q^t = {}^{} overflows", q, t))
121 })?;
122
123 let beta = sub_chunk_no / q; let original_count = k + nu;
127 let recovery_count = m;
128 if original_count > MAX_RS_SHARDS || recovery_count > MAX_RS_SHARDS {
129 return Err(ClayError::InvalidParameters(
130 "Total nodes exceeds reed-solomon limit of 32768".into(),
131 ));
132 }
133
134 let rs = RsCodec::new(original_count, recovery_count)
135 .map_err(|e| ClayError::InvalidParameters(format!("RS init failed: {:?}", e)))?;
136
137 Ok(ClayCode {
138 k,
139 m,
140 n,
141 d,
142 q,
143 t,
144 nu,
145 sub_chunk_no,
146 beta,
147 original_count,
148 rs,
149 })
150 }
151
152 pub fn new_default(k: usize, m: usize) -> Result<Self, ClayError> {
154 Self::new(k, m, k + m - 1)
155 }
156
157 fn encode_params(&self) -> encode::EncodeParams {
159 encode::EncodeParams {
160 k: self.k,
161 m: self.m,
162 n: self.n,
163 q: self.q,
164 t: self.t,
165 nu: self.nu,
166 sub_chunk_no: self.sub_chunk_no,
167 original_count: self.original_count,
168 }
169 }
170
171 pub fn encode(&self, data: &[u8]) -> Vec<Vec<u8>> {
179 encode_chunks(&self.encode_params(), &self.rs, data)
180 }
181
182 pub fn decode(
191 &self,
192 available: &HashMap<usize, Vec<u8>>,
193 erasures: &[usize],
194 ) -> Result<Vec<u8>, ClayError> {
195 decode_chunks(&self.encode_params(), &self.rs, available, erasures)
196 }
197
198 pub fn decode_rows(&self, chunks: &[Option<&[u8]>]) -> Result<Vec<u8>, ClayError> {
203 decode_rows_impl(&self.encode_params(), &self.rs, chunks)
204 }
205
206 pub fn minimum_to_repair(
218 &self,
219 lost_node: usize,
220 available: &[usize],
221 ) -> Result<Vec<(usize, Vec<usize>)>, ClayError> {
222 min_repair(&self.encode_params(), lost_node, available)
223 }
224
225 pub fn repair(
237 &self,
238 lost_node: usize,
239 helper_data: &HashMap<usize, Vec<u8>>,
240 chunk_size: usize,
241 ) -> Result<Vec<u8>, ClayError> {
242 repair_chunk(&self.encode_params(), &self.rs, lost_node, helper_data, chunk_size)
243 }
244
245 pub fn repair_rows(
251 &self,
252 lost_node: usize,
253 helpers: &[Option<&[u8]>],
254 chunk_size: usize,
255 ) -> Result<Vec<u8>, ClayError> {
256 repair_rows_impl(&self.encode_params(), &self.rs, lost_node, helpers, chunk_size)
257 }
258
259 pub fn normalized_repair_bandwidth(&self) -> f64 {
264 (self.d as f64) / ((self.k as f64) * (self.d - self.k + 1) as f64)
265 }
266}
267
268fn checked_pow(base: usize, exp: usize) -> Option<usize> {
270 let mut result: usize = 1;
271 let mut b = base;
272 let mut e = exp;
273 while e > 0 {
274 if e & 1 == 1 {
275 result = result.checked_mul(b)?;
276 }
277 e >>= 1;
278 if e > 0 {
279 b = b.checked_mul(b)?;
280 }
281 }
282 Some(result)
283}
284
285#[cfg(test)]
286mod tests {
287 use super::*;
288
289 #[test]
290 fn test_basic_encode_decode() {
291 let clay = ClayCode::new(4, 2, 5).unwrap();
292 let data = b"Test data for Clay codes - not empty!";
293 let chunks = clay.encode(data);
294 assert_eq!(chunks.len(), 6); let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
298 for (i, chunk) in chunks.iter().enumerate() {
299 available.insert(i, chunk.clone());
300 }
301 let decoded = clay.decode(&available, &[]).unwrap();
302
303 assert_eq!(&decoded[..data.len()], &data[..]);
305 }
306
307 #[test]
308 fn test_decode_with_erasures() {
309 let clay = ClayCode::new(4, 2, 5).unwrap();
310 let data = b"Test data for Clay codes - testing erasure recovery!";
311 let chunks = clay.encode(data);
312
313 let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
315 for (i, chunk) in chunks.iter().enumerate() {
316 if i != 0 {
317 available.insert(i, chunk.clone());
318 }
319 }
320 let decoded = clay.decode(&available, &[0]).unwrap();
321 assert_eq!(&decoded[..data.len()], &data[..]);
322
323 let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
325 for (i, chunk) in chunks.iter().enumerate() {
326 if i != 5 {
327 available.insert(i, chunk.clone());
328 }
329 }
330 let decoded = clay.decode(&available, &[5]).unwrap();
331 assert_eq!(&decoded[..data.len()], &data[..]);
332
333 let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
335 for (i, chunk) in chunks.iter().enumerate() {
336 if i != 0 && i != 5 {
337 available.insert(i, chunk.clone());
338 }
339 }
340 let decoded = clay.decode(&available, &[0, 5]).unwrap();
341 assert_eq!(&decoded[..data.len()], &data[..]);
342 }
343
344 #[test]
345 fn test_parameters() {
346 let clay = ClayCode::new(4, 2, 5).unwrap();
348 assert_eq!(clay.q, 2);
349 assert_eq!(clay.t, 3);
350 assert_eq!(clay.sub_chunk_no, 8); assert_eq!(clay.beta, 4); let clay2 = ClayCode::new(10, 4, 13).unwrap();
355 assert_eq!(clay2.q, 4);
356 assert_eq!(clay2.t, 4);
357 assert_eq!(clay2.sub_chunk_no, 256); assert_eq!(clay2.beta, 64); }
360
361 #[test]
362 fn test_minimum_to_repair() {
363 let clay = ClayCode::new(4, 2, 5).unwrap();
364 let available: Vec<usize> = vec![1, 2, 3, 4, 5];
365 let helper_info = clay.minimum_to_repair(0, &available).unwrap();
366
367 assert_eq!(helper_info.len(), 5);
369
370 for (_, indices) in &helper_info {
372 assert_eq!(indices.len(), 4);
373 }
374 }
375
376 #[test]
377 fn test_repair_bandwidth_verification() {
378 let clay = ClayCode::new(4, 2, 5).unwrap();
380 let data = b"Test data for bandwidth verification of Clay codes repair!";
381 let chunks = clay.encode(data);
382 let chunk_size = chunks[0].len();
383
384 let available: Vec<usize> = vec![1, 2, 3, 4, 5];
386 let helper_info = clay.minimum_to_repair(0, &available).unwrap();
387
388 let sub_chunk_size = chunk_size / clay.sub_chunk_no;
390 let total_repair_subchunks: usize = helper_info
391 .iter()
392 .map(|(_, indices)| indices.len())
393 .sum();
394 let total_repair_bytes = total_repair_subchunks * sub_chunk_size;
395
396 let full_decode_bytes = clay.k * chunk_size;
397
398 let ratio = total_repair_bytes as f64 / full_decode_bytes as f64;
400 println!(
401 "Repair bandwidth: {} bytes, Full decode: {} bytes, Ratio: {:.3}",
402 total_repair_bytes, full_decode_bytes, ratio
403 );
404
405 assert!(
406 total_repair_bytes < full_decode_bytes * 7 / 10,
407 "Repair bandwidth {} should be < 70% of full decode {}",
408 total_repair_bytes,
409 full_decode_bytes
410 );
411 }
412
413 #[test]
414 fn test_repair_correctness() {
415 let clay = ClayCode::new(4, 2, 5).unwrap();
416 let data = b"Test data for repair correctness verification!!!!";
417 let chunks = clay.encode(data);
418 let chunk_size = chunks[0].len();
419 let sub_chunk_size = chunk_size / clay.sub_chunk_no;
420
421 for lost_node in 0..clay.n {
423 let available: Vec<usize> = (0..clay.n).filter(|&i| i != lost_node).collect();
424 let helper_info = clay.minimum_to_repair(lost_node, &available).unwrap();
425
426 let mut partial_data: HashMap<usize, Vec<u8>> = HashMap::new();
428 for (helper_idx, indices) in &helper_info {
429 let mut helper_partial = Vec::new();
430 for &sc_idx in indices {
431 let start_byte = sc_idx * sub_chunk_size;
432 let end_byte = (sc_idx + 1) * sub_chunk_size;
433 helper_partial.extend_from_slice(&chunks[*helper_idx][start_byte..end_byte]);
434 }
435 partial_data.insert(*helper_idx, helper_partial);
436 }
437
438 let recovered = clay.repair(lost_node, &partial_data, chunk_size).unwrap();
440
441 assert_eq!(
443 recovered, chunks[lost_node],
444 "Repair failed for node {}",
445 lost_node
446 );
447 }
448 }
449
450 #[test]
451 fn test_various_parameters() {
452 let params = vec![
454 (4, 2, 5), (9, 3, 11), (10, 4, 13), ];
458
459 for (k, m, d) in params {
460 let clay = ClayCode::new(k, m, d).unwrap();
461 let data_size = k * clay.sub_chunk_no * 2;
462 let data: Vec<u8> = (0..data_size).map(|i| (i % 256) as u8).collect();
463 let chunks = clay.encode(&data);
464
465 let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
467 for (i, chunk) in chunks.iter().enumerate() {
468 if i != 0 {
469 available.insert(i, chunk.clone());
470 }
471 }
472 let decoded = clay.decode(&available, &[0]).unwrap();
473 assert_eq!(
474 &decoded[..data.len()],
475 &data[..],
476 "Failed for params ({}, {}, {})",
477 k,
478 m,
479 d
480 );
481 }
482 }
483
484 #[test]
485 fn test_repair_all_nodes_various_params() {
486 let params = vec![(4, 2, 5), (9, 3, 11)];
487
488 for (k, m, d) in params {
489 let clay = ClayCode::new(k, m, d).unwrap();
490 let data_size = k * clay.sub_chunk_no;
491 let data: Vec<u8> = (0..data_size).map(|i| ((i * 7 + 13) % 256) as u8).collect();
492 let chunks = clay.encode(&data);
493 let chunk_size = chunks[0].len();
494 let sub_chunk_size = chunk_size / clay.sub_chunk_no;
495
496 for lost_node in 0..clay.n {
497 let available: Vec<usize> = (0..clay.n).filter(|&i| i != lost_node).collect();
498 let helper_info = clay.minimum_to_repair(lost_node, &available).unwrap();
499
500 let mut partial_data: HashMap<usize, Vec<u8>> = HashMap::new();
501 for (helper_idx, indices) in &helper_info {
502 let mut helper_partial = Vec::new();
503 for &sc_idx in indices {
504 let start_byte = sc_idx * sub_chunk_size;
505 let end_byte = (sc_idx + 1) * sub_chunk_size;
506 helper_partial.extend_from_slice(&chunks[*helper_idx][start_byte..end_byte]);
507 }
508 partial_data.insert(*helper_idx, helper_partial);
509 }
510
511 let recovered = clay.repair(lost_node, &partial_data, chunk_size).unwrap();
512 assert_eq!(
513 recovered, chunks[lost_node],
514 "Repair failed for node {} with params ({}, {}, {})",
515 lost_node, k, m, d
516 );
517 }
518 }
519 }
520
521 #[test]
522 fn test_decode_max_erasures() {
523 let clay = ClayCode::new(4, 2, 5).unwrap();
524 let data: Vec<u8> = (0..256).map(|i| (i % 256) as u8).collect();
525 let chunks = clay.encode(&data);
526
527 let patterns = vec![vec![0, 5], vec![0, 1], vec![4, 5], vec![1, 3]];
529
530 for erasures in patterns {
531 let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
532 for (i, chunk) in chunks.iter().enumerate() {
533 if !erasures.contains(&i) {
534 available.insert(i, chunk.clone());
535 }
536 }
537 let decoded = clay.decode(&available, &erasures).unwrap();
538 assert_eq!(
539 &decoded[..data.len()],
540 &data[..],
541 "Failed for erasures {:?}",
542 erasures
543 );
544 }
545 }
546
547 #[test]
548 fn test_normalized_repair_bandwidth() {
549 let test_cases = vec![
550 ((4, 2, 5), 0.625),
551 ((9, 3, 11), 0.407),
552 ((10, 4, 13), 0.325),
553 ];
554
555 for ((k, m, d), expected) in test_cases {
556 let clay = ClayCode::new(k, m, d).unwrap();
557 let actual = clay.normalized_repair_bandwidth();
558 assert!(
559 (actual - expected).abs() < 0.01,
560 "Expected {}, got {} for ({}, {}, {})",
561 expected,
562 actual,
563 k,
564 m,
565 d
566 );
567 }
568 }
569
570 #[test]
571 fn test_random_data() {
572 use rand::Rng;
573 let mut rng = rand::thread_rng();
574
575 let clay = ClayCode::new(4, 2, 5).unwrap();
576 let data_size = clay.k * clay.sub_chunk_no * 4;
577 let data: Vec<u8> = (0..data_size).map(|_| rng.gen()).collect();
578 let chunks = clay.encode(&data);
579
580 let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
582 for (i, chunk) in chunks.iter().enumerate() {
583 available.insert(i, chunk.clone());
584 }
585 let decoded = clay.decode(&available, &[]).unwrap();
586 assert_eq!(&decoded[..data.len()], &data[..]);
587
588 let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
590 for (i, chunk) in chunks.iter().enumerate() {
591 if i != 2 {
592 available.insert(i, chunk.clone());
593 }
594 }
595 let decoded = clay.decode(&available, &[2]).unwrap();
596 assert_eq!(&decoded[..data.len()], &data[..]);
597 }
598
599 #[test]
600 fn test_checked_pow_overflow() {
601 assert!(checked_pow(2, 63).is_some());
603 assert!(checked_pow(2, 64).is_none()); assert!(checked_pow(10, 20).is_none()); }
606
607 #[test]
608 fn test_invalid_parameters() {
609 assert!(ClayCode::new(0, 2, 1).is_err());
611
612 assert!(ClayCode::new(4, 0, 3).is_err());
614
615 assert!(ClayCode::new(4, 2, 4).is_err()); assert!(ClayCode::new(4, 2, 6).is_err()); }
619
620 #[test]
621 fn test_clone_and_debug() {
622 let clay = ClayCode::new(4, 2, 5).unwrap();
623 let clay2 = clay.clone();
624 assert_eq!(clay2.k, clay.k);
625 assert_eq!(clay2.m, clay.m);
626 assert_eq!(clay2.d, clay.d);
627 let debug_str = format!("{:?}", clay);
629 assert!(debug_str.contains("ClayCode"));
630 }
631
632 #[test]
633 fn test_new_default() {
634 let clay_default = ClayCode::new_default(4, 2).unwrap();
635 let clay_explicit = ClayCode::new(4, 2, 4 + 2 - 1).unwrap();
636 assert_eq!(clay_default.k, clay_explicit.k);
637 assert_eq!(clay_default.m, clay_explicit.m);
638 assert_eq!(clay_default.d, clay_explicit.d);
639 assert_eq!(clay_default.q, clay_explicit.q);
640 assert_eq!(clay_default.t, clay_explicit.t);
641 assert_eq!(clay_default.sub_chunk_no, clay_explicit.sub_chunk_no);
642 assert_eq!(clay_default.beta, clay_explicit.beta);
643
644 let clay_default2 = ClayCode::new_default(10, 4).unwrap();
646 let clay_explicit2 = ClayCode::new(10, 4, 13).unwrap();
647 assert_eq!(clay_default2.d, clay_explicit2.d);
648 assert_eq!(clay_default2.sub_chunk_no, clay_explicit2.sub_chunk_no);
649 }
650
651 #[test]
652 fn test_decode_empty_available_with_erasures() {
653 let clay = ClayCode::new(4, 2, 5).unwrap();
654 let available: HashMap<usize, Vec<u8>> = HashMap::new();
655 let result = clay.decode(&available, &[0]);
656 assert!(
657 matches!(result, Err(ClayError::InvalidParameters(_))),
658 "Expected InvalidParameters error when available is empty but erasures is non-empty, got {:?}",
659 result
660 );
661 }
662
663 #[test]
666 fn test_decode_too_many_erasures() {
667 let clay = ClayCode::new(4, 2, 5).unwrap();
668 let data: Vec<u8> = (0..128).map(|i| (i % 256) as u8).collect();
669 let chunks = clay.encode(&data);
670
671 let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
673 for (i, chunk) in chunks.iter().enumerate() {
674 if i > 2 {
675 available.insert(i, chunk.clone());
676 }
677 }
678
679 let result = clay.decode(&available, &[0, 1, 2]);
680 assert!(
681 matches!(result, Err(ClayError::TooManyErasures { max: 2, actual: 3 })),
682 "Expected TooManyErasures error, got {:?}",
683 result
684 );
685 }
686
687 #[test]
688 fn test_decode_inconsistent_chunk_sizes() {
689 let clay = ClayCode::new(4, 2, 5).unwrap();
690 let data: Vec<u8> = (0..128).map(|i| (i % 256) as u8).collect();
691 let chunks = clay.encode(&data);
692
693 let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
694 for (i, chunk) in chunks.iter().enumerate() {
695 if i != 0 {
696 if i == 5 {
697 let mut bad_chunk = chunk.clone();
699 bad_chunk.push(0); available.insert(i, bad_chunk);
701 } else {
702 available.insert(i, chunk.clone());
703 }
704 }
705 }
706
707 let result = clay.decode(&available, &[0]);
708 assert!(
710 matches!(result, Err(ClayError::InconsistentChunkSizes { .. }))
711 || matches!(result, Err(ClayError::InvalidChunkSize { .. })),
712 "Expected InconsistentChunkSizes or InvalidChunkSize error, got {:?}",
713 result
714 );
715 }
716
717 #[test]
718 fn test_decode_invalid_chunk_index() {
719 let clay = ClayCode::new(4, 2, 5).unwrap();
720 let data: Vec<u8> = (0..128).collect();
721 let chunks = clay.encode(&data);
722
723 let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
724 for (i, chunk) in chunks.iter().enumerate() {
725 available.insert(i, chunk.clone());
726 }
727 available.insert(100, vec![0u8; chunks[0].len()]);
729
730 let result = clay.decode(&available, &[]);
731 assert!(
732 matches!(result, Err(ClayError::InvalidParameters(_))),
733 "Expected InvalidParameters error for out-of-range index, got {:?}",
734 result
735 );
736 }
737
738 #[test]
739 fn test_decode_invalid_erasure_index() {
740 let clay = ClayCode::new(4, 2, 5).unwrap();
741 let data: Vec<u8> = (0..128).collect();
742 let chunks = clay.encode(&data);
743
744 let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
745 for (i, chunk) in chunks.iter().enumerate() {
746 if i != 0 {
747 available.insert(i, chunk.clone());
748 }
749 }
750
751 let result = clay.decode(&available, &[100]);
753 assert!(
754 matches!(result, Err(ClayError::InvalidParameters(_))),
755 "Expected InvalidParameters error for out-of-range erasure, got {:?}",
756 result
757 );
758 }
759
760 #[test]
761 fn test_decode_available_erasure_overlap() {
762 let clay = ClayCode::new(4, 2, 5).unwrap();
763 let data: Vec<u8> = (0..128).collect();
764 let chunks = clay.encode(&data);
765
766 let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
768 for (i, chunk) in chunks.iter().enumerate() {
769 available.insert(i, chunk.clone());
770 }
771
772 let result = clay.decode(&available, &[0]);
773 assert!(
774 matches!(result, Err(ClayError::InvalidParameters(ref msg)) if msg.contains("both")),
775 "Expected InvalidParameters error for overlap, got {:?}",
776 result
777 );
778 }
779
780 #[test]
781 fn test_decode_wrong_available_count() {
782 let clay = ClayCode::new(4, 2, 5).unwrap();
783 let data: Vec<u8> = (0..128).collect();
784 let chunks = clay.encode(&data);
785
786 let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
788 for (i, chunk) in chunks.iter().enumerate() {
789 if i > 1 {
790 available.insert(i, chunk.clone());
791 }
792 }
793
794 let result = clay.decode(&available, &[0]);
796 assert!(
797 matches!(result, Err(ClayError::InvalidParameters(ref msg)) if msg.contains("Expected")),
798 "Expected InvalidParameters error for wrong count, got {:?}",
799 result
800 );
801 }
802}