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::{chunk_size as encoded_chunk_size, encode as encode_chunks, encode_into as encode_chunks_into};
55use repair::{plan_repair as plan_repair_impl, repair as repair_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 chunk_size(&self, data_len: usize) -> usize {
184 encoded_chunk_size(&self.encode_params(), data_len)
185 }
186
187 pub fn encode_into(&self, data: &[u8], chunks: &mut [&mut [u8]]) -> Result<(), ClayError> {
193 encode_chunks_into(&self.encode_params(), &self.rs, data, chunks)
194 }
195
196 pub fn decode(
205 &self,
206 available: &HashMap<usize, Vec<u8>>,
207 erasures: &[usize],
208 ) -> Result<Vec<u8>, ClayError> {
209 decode_chunks(&self.encode_params(), &self.rs, available, erasures)
210 }
211
212 pub fn decode_rows(&self, chunks: &[Option<&[u8]>]) -> Result<Vec<u8>, ClayError> {
217 decode_rows_impl(&self.encode_params(), &self.rs, chunks)
218 }
219
220 pub fn plan_repair(
247 &self,
248 lost_node: usize,
249 available: &[usize],
250 ) -> Result<Vec<(usize, Vec<usize>)>, ClayError> {
251 plan_repair_impl(&self.encode_params(), lost_node, available)
252 }
253
254 pub fn repair(
266 &self,
267 lost_node: usize,
268 plan: &[(usize, Vec<usize>)],
269 helpers: &[Option<&[u8]>],
270 chunk_size: usize,
271 ) -> Result<Vec<u8>, ClayError> {
272 repair_impl(&self.encode_params(), &self.rs, lost_node, plan, helpers, chunk_size)
273 }
274
275 pub fn normalized_repair_bandwidth(&self) -> f64 {
280 (self.d as f64) / ((self.k as f64) * (self.d - self.k + 1) as f64)
281 }
282}
283
284fn checked_pow(base: usize, exp: usize) -> Option<usize> {
286 let mut result: usize = 1;
287 let mut b = base;
288 let mut e = exp;
289 while e > 0 {
290 if e & 1 == 1 {
291 result = result.checked_mul(b)?;
292 }
293 e >>= 1;
294 if e > 0 {
295 b = b.checked_mul(b)?;
296 }
297 }
298 Some(result)
299}
300
301#[cfg(test)]
302mod tests {
303 use super::*;
304
305 #[test]
306 fn test_basic_encode_decode() {
307 let clay = ClayCode::new(4, 2, 5).unwrap();
308 let data = b"Test data for Clay codes - not empty!";
309 let chunks = clay.encode(data);
310 assert_eq!(chunks.len(), 6); let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
314 for (i, chunk) in chunks.iter().enumerate() {
315 available.insert(i, chunk.clone());
316 }
317 let decoded = clay.decode(&available, &[]).unwrap();
318
319 assert_eq!(&decoded[..data.len()], &data[..]);
321 }
322
323 #[test]
324 fn test_decode_with_erasures() {
325 let clay = ClayCode::new(4, 2, 5).unwrap();
326 let data = b"Test data for Clay codes - testing erasure recovery!";
327 let chunks = clay.encode(data);
328
329 let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
331 for (i, chunk) in chunks.iter().enumerate() {
332 if i != 0 {
333 available.insert(i, chunk.clone());
334 }
335 }
336 let decoded = clay.decode(&available, &[0]).unwrap();
337 assert_eq!(&decoded[..data.len()], &data[..]);
338
339 let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
341 for (i, chunk) in chunks.iter().enumerate() {
342 if i != 5 {
343 available.insert(i, chunk.clone());
344 }
345 }
346 let decoded = clay.decode(&available, &[5]).unwrap();
347 assert_eq!(&decoded[..data.len()], &data[..]);
348
349 let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
351 for (i, chunk) in chunks.iter().enumerate() {
352 if i != 0 && i != 5 {
353 available.insert(i, chunk.clone());
354 }
355 }
356 let decoded = clay.decode(&available, &[0, 5]).unwrap();
357 assert_eq!(&decoded[..data.len()], &data[..]);
358 }
359
360 #[test]
361 fn test_parameters() {
362 let clay = ClayCode::new(4, 2, 5).unwrap();
364 assert_eq!(clay.q, 2);
365 assert_eq!(clay.t, 3);
366 assert_eq!(clay.sub_chunk_no, 8); assert_eq!(clay.beta, 4); let clay2 = ClayCode::new(10, 4, 13).unwrap();
371 assert_eq!(clay2.q, 4);
372 assert_eq!(clay2.t, 4);
373 assert_eq!(clay2.sub_chunk_no, 256); assert_eq!(clay2.beta, 64); }
376
377 #[test]
379 fn plan_helpers() {
380 let clay = ClayCode::new(4, 2, 5).unwrap();
381 let available: Vec<usize> = vec![1, 2, 3, 4, 5];
382 let helper_info = clay.plan_repair(0, &available).unwrap();
383
384 assert_eq!(helper_info.len(), 5);
386
387 for (_, indices) in &helper_info {
389 assert_eq!(indices.len(), 4);
390 }
391 }
392
393 #[test]
394 fn test_repair_bandwidth_verification() {
395 let clay = ClayCode::new(4, 2, 5).unwrap();
397 let data = b"Test data for bandwidth verification of Clay codes repair!";
398 let chunks = clay.encode(data);
399 let chunk_size = chunks[0].len();
400
401 let available: Vec<usize> = vec![1, 2, 3, 4, 5];
403 let helper_info = clay.plan_repair(0, &available).unwrap();
404
405 let sub_chunk_size = chunk_size / clay.sub_chunk_no;
407 let total_repair_subchunks: usize = helper_info
408 .iter()
409 .map(|(_, indices)| indices.len())
410 .sum();
411 let total_repair_bytes = total_repair_subchunks * sub_chunk_size;
412
413 let full_decode_bytes = clay.k * chunk_size;
414
415 let ratio = total_repair_bytes as f64 / full_decode_bytes as f64;
417 println!(
418 "Repair bandwidth: {} bytes, Full decode: {} bytes, Ratio: {:.3}",
419 total_repair_bytes, full_decode_bytes, ratio
420 );
421
422 assert!(
423 total_repair_bytes < full_decode_bytes * 7 / 10,
424 "Repair bandwidth {} should be < 70% of full decode {}",
425 total_repair_bytes,
426 full_decode_bytes
427 );
428 }
429
430 #[test]
431 fn test_repair_correctness() {
432 let clay = ClayCode::new(4, 2, 5).unwrap();
433 let data = b"Test data for repair correctness verification!!!!";
434 let chunks = clay.encode(data);
435 let chunk_size = chunks[0].len();
436 let sub_chunk_size = chunk_size / clay.sub_chunk_no;
437
438 for lost_node in 0..clay.n {
440 let available: Vec<usize> = (0..clay.n).filter(|&i| i != lost_node).collect();
441 let helper_info = clay.plan_repair(lost_node, &available).unwrap();
442
443 let mut partial_data: Vec<Option<Vec<u8>>> = vec![None; clay.n];
445 for (helper_idx, indices) in &helper_info {
446 let mut helper_partial = Vec::new();
447 for &sc_idx in indices {
448 let start_byte = sc_idx * sub_chunk_size;
449 let end_byte = (sc_idx + 1) * sub_chunk_size;
450 helper_partial.extend_from_slice(&chunks[*helper_idx][start_byte..end_byte]);
451 }
452 partial_data[*helper_idx] = Some(helper_partial);
453 }
454
455 let helpers: Vec<Option<&[u8]>> = partial_data.iter().map(|data| data.as_deref()).collect();
457 let recovered = clay
458 .repair(lost_node, &helper_info, &helpers, chunk_size)
459 .unwrap();
460
461 assert_eq!(
463 recovered, chunks[lost_node],
464 "Repair failed for node {}",
465 lost_node
466 );
467 }
468 }
469
470 #[test]
471 fn test_various_parameters() {
472 let params = vec![
474 (4, 2, 5), (9, 3, 11), (10, 4, 13), ];
478
479 for (k, m, d) in params {
480 let clay = ClayCode::new(k, m, d).unwrap();
481 let data_size = k * clay.sub_chunk_no * 2;
482 let data: Vec<u8> = (0..data_size).map(|i| (i % 256) as u8).collect();
483 let chunks = clay.encode(&data);
484
485 let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
487 for (i, chunk) in chunks.iter().enumerate() {
488 if i != 0 {
489 available.insert(i, chunk.clone());
490 }
491 }
492 let decoded = clay.decode(&available, &[0]).unwrap();
493 assert_eq!(
494 &decoded[..data.len()],
495 &data[..],
496 "Failed for params ({}, {}, {})",
497 k,
498 m,
499 d
500 );
501 }
502 }
503
504 #[test]
505 fn test_repair_all_nodes_various_params() {
506 let params = vec![(4, 2, 5), (9, 3, 11)];
507
508 for (k, m, d) in params {
509 let clay = ClayCode::new(k, m, d).unwrap();
510 let data_size = k * clay.sub_chunk_no;
511 let data: Vec<u8> = (0..data_size).map(|i| ((i * 7 + 13) % 256) as u8).collect();
512 let chunks = clay.encode(&data);
513 let chunk_size = chunks[0].len();
514 let sub_chunk_size = chunk_size / clay.sub_chunk_no;
515
516 for lost_node in 0..clay.n {
517 let available: Vec<usize> = (0..clay.n).filter(|&i| i != lost_node).collect();
518 let helper_info = clay.plan_repair(lost_node, &available).unwrap();
519
520 let mut partial_data: Vec<Option<Vec<u8>>> = vec![None; clay.n];
521 for (helper_idx, indices) in &helper_info {
522 let mut helper_partial = Vec::new();
523 for &sc_idx in indices {
524 let start_byte = sc_idx * sub_chunk_size;
525 let end_byte = (sc_idx + 1) * sub_chunk_size;
526 helper_partial.extend_from_slice(&chunks[*helper_idx][start_byte..end_byte]);
527 }
528 partial_data[*helper_idx] = Some(helper_partial);
529 }
530
531 let helpers: Vec<Option<&[u8]>> =
532 partial_data.iter().map(|data| data.as_deref()).collect();
533 let recovered = clay
534 .repair(lost_node, &helper_info, &helpers, chunk_size)
535 .unwrap();
536 assert_eq!(
537 recovered, chunks[lost_node],
538 "Repair failed for node {} with params ({}, {}, {})",
539 lost_node, k, m, d
540 );
541 }
542 }
543 }
544
545 #[test]
546 fn test_decode_max_erasures() {
547 let clay = ClayCode::new(4, 2, 5).unwrap();
548 let data: Vec<u8> = (0..256).map(|i| (i % 256) as u8).collect();
549 let chunks = clay.encode(&data);
550
551 let patterns = vec![vec![0, 5], vec![0, 1], vec![4, 5], vec![1, 3]];
553
554 for erasures in patterns {
555 let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
556 for (i, chunk) in chunks.iter().enumerate() {
557 if !erasures.contains(&i) {
558 available.insert(i, chunk.clone());
559 }
560 }
561 let decoded = clay.decode(&available, &erasures).unwrap();
562 assert_eq!(
563 &decoded[..data.len()],
564 &data[..],
565 "Failed for erasures {:?}",
566 erasures
567 );
568 }
569 }
570
571 #[test]
572 fn test_normalized_repair_bandwidth() {
573 let test_cases = vec![
574 ((4, 2, 5), 0.625),
575 ((9, 3, 11), 0.407),
576 ((10, 4, 13), 0.325),
577 ];
578
579 for ((k, m, d), expected) in test_cases {
580 let clay = ClayCode::new(k, m, d).unwrap();
581 let actual = clay.normalized_repair_bandwidth();
582 assert!(
583 (actual - expected).abs() < 0.01,
584 "Expected {}, got {} for ({}, {}, {})",
585 expected,
586 actual,
587 k,
588 m,
589 d
590 );
591 }
592 }
593
594 #[test]
595 fn test_random_data() {
596 use rand::Rng;
597 let mut rng = rand::thread_rng();
598
599 let clay = ClayCode::new(4, 2, 5).unwrap();
600 let data_size = clay.k * clay.sub_chunk_no * 4;
601 let data: Vec<u8> = (0..data_size).map(|_| rng.gen()).collect();
602 let chunks = clay.encode(&data);
603
604 let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
606 for (i, chunk) in chunks.iter().enumerate() {
607 available.insert(i, chunk.clone());
608 }
609 let decoded = clay.decode(&available, &[]).unwrap();
610 assert_eq!(&decoded[..data.len()], &data[..]);
611
612 let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
614 for (i, chunk) in chunks.iter().enumerate() {
615 if i != 2 {
616 available.insert(i, chunk.clone());
617 }
618 }
619 let decoded = clay.decode(&available, &[2]).unwrap();
620 assert_eq!(&decoded[..data.len()], &data[..]);
621 }
622
623 #[test]
624 fn test_checked_pow_overflow() {
625 assert!(checked_pow(2, 63).is_some());
627 assert!(checked_pow(2, 64).is_none()); assert!(checked_pow(10, 20).is_none()); }
630
631 #[test]
632 fn test_invalid_parameters() {
633 assert!(ClayCode::new(0, 2, 1).is_err());
635
636 assert!(ClayCode::new(4, 0, 3).is_err());
638
639 assert!(ClayCode::new(4, 2, 4).is_err()); assert!(ClayCode::new(4, 2, 6).is_err()); }
643
644 #[test]
645 fn test_clone_and_debug() {
646 let clay = ClayCode::new(4, 2, 5).unwrap();
647 let clay2 = clay.clone();
648 assert_eq!(clay2.k, clay.k);
649 assert_eq!(clay2.m, clay.m);
650 assert_eq!(clay2.d, clay.d);
651 let debug_str = format!("{:?}", clay);
653 assert!(debug_str.contains("ClayCode"));
654 }
655
656 #[test]
657 fn test_new_default() {
658 let clay_default = ClayCode::new_default(4, 2).unwrap();
659 let clay_explicit = ClayCode::new(4, 2, 4 + 2 - 1).unwrap();
660 assert_eq!(clay_default.k, clay_explicit.k);
661 assert_eq!(clay_default.m, clay_explicit.m);
662 assert_eq!(clay_default.d, clay_explicit.d);
663 assert_eq!(clay_default.q, clay_explicit.q);
664 assert_eq!(clay_default.t, clay_explicit.t);
665 assert_eq!(clay_default.sub_chunk_no, clay_explicit.sub_chunk_no);
666 assert_eq!(clay_default.beta, clay_explicit.beta);
667
668 let clay_default2 = ClayCode::new_default(10, 4).unwrap();
670 let clay_explicit2 = ClayCode::new(10, 4, 13).unwrap();
671 assert_eq!(clay_default2.d, clay_explicit2.d);
672 assert_eq!(clay_default2.sub_chunk_no, clay_explicit2.sub_chunk_no);
673 }
674
675 #[test]
676 fn test_decode_empty_available_with_erasures() {
677 let clay = ClayCode::new(4, 2, 5).unwrap();
678 let available: HashMap<usize, Vec<u8>> = HashMap::new();
679 let result = clay.decode(&available, &[0]);
680 assert!(
681 matches!(result, Err(ClayError::InvalidParameters(_))),
682 "Expected InvalidParameters error when available is empty but erasures is non-empty, got {:?}",
683 result
684 );
685 }
686
687 #[test]
690 fn test_decode_too_many_erasures() {
691 let clay = ClayCode::new(4, 2, 5).unwrap();
692 let data: Vec<u8> = (0..128).map(|i| (i % 256) as u8).collect();
693 let chunks = clay.encode(&data);
694
695 let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
697 for (i, chunk) in chunks.iter().enumerate() {
698 if i > 2 {
699 available.insert(i, chunk.clone());
700 }
701 }
702
703 let result = clay.decode(&available, &[0, 1, 2]);
704 assert!(
705 matches!(result, Err(ClayError::TooManyErasures { max: 2, actual: 3 })),
706 "Expected TooManyErasures error, got {:?}",
707 result
708 );
709 }
710
711 #[test]
712 fn test_decode_inconsistent_chunk_sizes() {
713 let clay = ClayCode::new(4, 2, 5).unwrap();
714 let data: Vec<u8> = (0..128).map(|i| (i % 256) as u8).collect();
715 let chunks = clay.encode(&data);
716
717 let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
718 for (i, chunk) in chunks.iter().enumerate() {
719 if i != 0 {
720 if i == 5 {
721 let mut bad_chunk = chunk.clone();
723 bad_chunk.push(0); available.insert(i, bad_chunk);
725 } else {
726 available.insert(i, chunk.clone());
727 }
728 }
729 }
730
731 let result = clay.decode(&available, &[0]);
732 assert!(
734 matches!(result, Err(ClayError::InconsistentChunkSizes { .. }))
735 || matches!(result, Err(ClayError::InvalidChunkSize { .. })),
736 "Expected InconsistentChunkSizes or InvalidChunkSize error, got {:?}",
737 result
738 );
739 }
740
741 #[test]
742 fn test_decode_invalid_chunk_index() {
743 let clay = ClayCode::new(4, 2, 5).unwrap();
744 let data: Vec<u8> = (0..128).collect();
745 let chunks = clay.encode(&data);
746
747 let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
748 for (i, chunk) in chunks.iter().enumerate() {
749 available.insert(i, chunk.clone());
750 }
751 available.insert(100, vec![0u8; chunks[0].len()]);
753
754 let result = clay.decode(&available, &[]);
755 assert!(
756 matches!(result, Err(ClayError::InvalidParameters(_))),
757 "Expected InvalidParameters error for out-of-range index, got {:?}",
758 result
759 );
760 }
761
762 #[test]
763 fn test_decode_invalid_erasure_index() {
764 let clay = ClayCode::new(4, 2, 5).unwrap();
765 let data: Vec<u8> = (0..128).collect();
766 let chunks = clay.encode(&data);
767
768 let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
769 for (i, chunk) in chunks.iter().enumerate() {
770 if i != 0 {
771 available.insert(i, chunk.clone());
772 }
773 }
774
775 let result = clay.decode(&available, &[100]);
777 assert!(
778 matches!(result, Err(ClayError::InvalidParameters(_))),
779 "Expected InvalidParameters error for out-of-range erasure, got {:?}",
780 result
781 );
782 }
783
784 #[test]
785 fn test_decode_available_erasure_overlap() {
786 let clay = ClayCode::new(4, 2, 5).unwrap();
787 let data: Vec<u8> = (0..128).collect();
788 let chunks = clay.encode(&data);
789
790 let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
792 for (i, chunk) in chunks.iter().enumerate() {
793 available.insert(i, chunk.clone());
794 }
795
796 let result = clay.decode(&available, &[0]);
797 assert!(
798 matches!(result, Err(ClayError::InvalidParameters(ref msg)) if msg.contains("both")),
799 "Expected InvalidParameters error for overlap, got {:?}",
800 result
801 );
802 }
803
804 #[test]
805 fn test_decode_wrong_available_count() {
806 let clay = ClayCode::new(4, 2, 5).unwrap();
807 let data: Vec<u8> = (0..128).collect();
808 let chunks = clay.encode(&data);
809
810 let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
812 for (i, chunk) in chunks.iter().enumerate() {
813 if i > 1 {
814 available.insert(i, chunk.clone());
815 }
816 }
817
818 let result = clay.decode(&available, &[0]);
820 assert!(
821 matches!(result, Err(ClayError::InvalidParameters(ref msg)) if msg.contains("Expected")),
822 "Expected InvalidParameters error for wrong count, got {:?}",
823 result
824 );
825 }
826}