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