use std::collections::HashMap;
pub mod coords;
pub mod decode;
pub mod encode;
pub mod error;
pub mod repair;
pub mod transforms;
pub use error::ClayError;
use decode::decode as decode_chunks;
use encode::encode as encode_chunks;
use repair::{minimum_to_repair as min_repair, repair as repair_chunk};
pub struct ClayCode {
pub k: usize,
pub m: usize,
pub n: usize,
pub d: usize,
pub q: usize,
pub t: usize,
pub nu: usize,
pub sub_chunk_no: usize,
pub beta: usize,
original_count: usize,
recovery_count: usize,
}
impl ClayCode {
pub fn new(k: usize, m: usize, d: usize) -> Result<Self, ClayError> {
if k < 1 {
return Err(ClayError::InvalidParameters("k must be at least 1".into()));
}
if m < 1 {
return Err(ClayError::InvalidParameters("m must be at least 1".into()));
}
if d < k + 1 || d > k + m - 1 {
return Err(ClayError::InvalidParameters(format!(
"d must be in range [{}, {}], got {}",
k + 1,
k + m - 1,
d
)));
}
let q = d - k + 1;
let n = k + m;
let nu = if n % q == 0 { 0 } else { q - (n % q) };
let t = (n + nu) / q;
let sub_chunk_no = checked_pow(q, t).ok_or_else(|| {
ClayError::Overflow(format!("q^t = {}^{} overflows", q, t))
})?;
let beta = sub_chunk_no / q;
let original_count = k + nu;
let recovery_count = m;
if original_count > 32768 || recovery_count > 32768 {
return Err(ClayError::InvalidParameters(
"Total nodes exceeds reed-solomon limit of 32768".into(),
));
}
Ok(ClayCode {
k,
m,
n,
d,
q,
t,
nu,
sub_chunk_no,
beta,
original_count,
recovery_count,
})
}
pub fn new_default(k: usize, m: usize) -> Result<Self, ClayError> {
Self::new(k, m, k + m - 1)
}
fn encode_params(&self) -> encode::EncodeParams {
encode::EncodeParams {
k: self.k,
m: self.m,
n: self.n,
q: self.q,
t: self.t,
nu: self.nu,
sub_chunk_no: self.sub_chunk_no,
original_count: self.original_count,
recovery_count: self.recovery_count,
}
}
pub fn encode(&self, data: &[u8]) -> Vec<Vec<u8>> {
encode_chunks(&self.encode_params(), data)
}
pub fn decode(
&self,
available: &HashMap<usize, Vec<u8>>,
erasures: &[usize],
) -> Result<Vec<u8>, ClayError> {
decode_chunks(&self.encode_params(), available, erasures)
}
pub fn minimum_to_repair(
&self,
lost_node: usize,
available: &[usize],
) -> Result<Vec<(usize, Vec<usize>)>, ClayError> {
min_repair(&self.encode_params(), lost_node, available)
}
pub fn repair(
&self,
lost_node: usize,
helper_data: &HashMap<usize, Vec<u8>>,
chunk_size: usize,
) -> Result<Vec<u8>, ClayError> {
repair_chunk(&self.encode_params(), lost_node, helper_data, chunk_size)
}
pub fn normalized_repair_bandwidth(&self) -> f64 {
(self.d as f64) / ((self.k as f64) * (self.d - self.k + 1) as f64)
}
}
fn checked_pow(base: usize, exp: usize) -> Option<usize> {
let mut result: usize = 1;
let mut b = base;
let mut e = exp;
while e > 0 {
if e & 1 == 1 {
result = result.checked_mul(b)?;
}
e >>= 1;
if e > 0 {
b = b.checked_mul(b)?;
}
}
Some(result)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_encode_decode() {
let clay = ClayCode::new(4, 2, 5).unwrap();
let data = b"Test data for Clay codes - not empty!";
let chunks = clay.encode(data);
assert_eq!(chunks.len(), 6);
let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
for (i, chunk) in chunks.iter().enumerate() {
available.insert(i, chunk.clone());
}
let decoded = clay.decode(&available, &[]).unwrap();
assert_eq!(&decoded[..data.len()], &data[..]);
}
#[test]
fn test_decode_with_erasures() {
let clay = ClayCode::new(4, 2, 5).unwrap();
let data = b"Test data for Clay codes - testing erasure recovery!";
let chunks = clay.encode(data);
let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
for (i, chunk) in chunks.iter().enumerate() {
if i != 0 {
available.insert(i, chunk.clone());
}
}
let decoded = clay.decode(&available, &[0]).unwrap();
assert_eq!(&decoded[..data.len()], &data[..]);
let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
for (i, chunk) in chunks.iter().enumerate() {
if i != 5 {
available.insert(i, chunk.clone());
}
}
let decoded = clay.decode(&available, &[5]).unwrap();
assert_eq!(&decoded[..data.len()], &data[..]);
let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
for (i, chunk) in chunks.iter().enumerate() {
if i != 0 && i != 5 {
available.insert(i, chunk.clone());
}
}
let decoded = clay.decode(&available, &[0, 5]).unwrap();
assert_eq!(&decoded[..data.len()], &data[..]);
}
#[test]
fn test_parameters() {
let clay = ClayCode::new(4, 2, 5).unwrap();
assert_eq!(clay.q, 2);
assert_eq!(clay.t, 3);
assert_eq!(clay.sub_chunk_no, 8); assert_eq!(clay.beta, 4);
let clay2 = ClayCode::new(10, 4, 13).unwrap();
assert_eq!(clay2.q, 4);
assert_eq!(clay2.t, 4);
assert_eq!(clay2.sub_chunk_no, 256); assert_eq!(clay2.beta, 64); }
#[test]
fn test_minimum_to_repair() {
let clay = ClayCode::new(4, 2, 5).unwrap();
let available: Vec<usize> = vec![1, 2, 3, 4, 5];
let helper_info = clay.minimum_to_repair(0, &available).unwrap();
assert_eq!(helper_info.len(), 5);
for (_, indices) in &helper_info {
assert_eq!(indices.len(), 4);
}
}
#[test]
fn test_repair_bandwidth_verification() {
let clay = ClayCode::new(4, 2, 5).unwrap();
let data = b"Test data for bandwidth verification of Clay codes repair!";
let chunks = clay.encode(data);
let chunk_size = chunks[0].len();
let available: Vec<usize> = vec![1, 2, 3, 4, 5];
let helper_info = clay.minimum_to_repair(0, &available).unwrap();
let sub_chunk_size = chunk_size / clay.sub_chunk_no;
let total_repair_subchunks: usize = helper_info
.iter()
.map(|(_, indices)| indices.len())
.sum();
let total_repair_bytes = total_repair_subchunks * sub_chunk_size;
let full_decode_bytes = clay.k * chunk_size;
let ratio = total_repair_bytes as f64 / full_decode_bytes as f64;
println!(
"Repair bandwidth: {} bytes, Full decode: {} bytes, Ratio: {:.3}",
total_repair_bytes, full_decode_bytes, ratio
);
assert!(
total_repair_bytes < full_decode_bytes * 7 / 10,
"Repair bandwidth {} should be < 70% of full decode {}",
total_repair_bytes,
full_decode_bytes
);
}
#[test]
fn test_repair_correctness() {
let clay = ClayCode::new(4, 2, 5).unwrap();
let data = b"Test data for repair correctness verification!!!!";
let chunks = clay.encode(data);
let chunk_size = chunks[0].len();
let sub_chunk_size = chunk_size / clay.sub_chunk_no;
for lost_node in 0..clay.n {
let available: Vec<usize> = (0..clay.n).filter(|&i| i != lost_node).collect();
let helper_info = clay.minimum_to_repair(lost_node, &available).unwrap();
let mut partial_data: HashMap<usize, Vec<u8>> = HashMap::new();
for (helper_idx, indices) in &helper_info {
let mut helper_partial = Vec::new();
for &sc_idx in indices {
let start_byte = sc_idx * sub_chunk_size;
let end_byte = (sc_idx + 1) * sub_chunk_size;
helper_partial.extend_from_slice(&chunks[*helper_idx][start_byte..end_byte]);
}
partial_data.insert(*helper_idx, helper_partial);
}
let recovered = clay.repair(lost_node, &partial_data, chunk_size).unwrap();
assert_eq!(
recovered, chunks[lost_node],
"Repair failed for node {}",
lost_node
);
}
}
#[test]
fn test_various_parameters() {
let params = vec![
(4, 2, 5), (9, 3, 11), (10, 4, 13), ];
for (k, m, d) in params {
let clay = ClayCode::new(k, m, d).unwrap();
let data_size = k * clay.sub_chunk_no * 2;
let data: Vec<u8> = (0..data_size).map(|i| (i % 256) as u8).collect();
let chunks = clay.encode(&data);
let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
for (i, chunk) in chunks.iter().enumerate() {
if i != 0 {
available.insert(i, chunk.clone());
}
}
let decoded = clay.decode(&available, &[0]).unwrap();
assert_eq!(
&decoded[..data.len()],
&data[..],
"Failed for params ({}, {}, {})",
k,
m,
d
);
}
}
#[test]
fn test_repair_all_nodes_various_params() {
let params = vec![(4, 2, 5), (9, 3, 11)];
for (k, m, d) in params {
let clay = ClayCode::new(k, m, d).unwrap();
let data_size = k * clay.sub_chunk_no;
let data: Vec<u8> = (0..data_size).map(|i| ((i * 7 + 13) % 256) as u8).collect();
let chunks = clay.encode(&data);
let chunk_size = chunks[0].len();
let sub_chunk_size = chunk_size / clay.sub_chunk_no;
for lost_node in 0..clay.n {
let available: Vec<usize> = (0..clay.n).filter(|&i| i != lost_node).collect();
let helper_info = clay.minimum_to_repair(lost_node, &available).unwrap();
let mut partial_data: HashMap<usize, Vec<u8>> = HashMap::new();
for (helper_idx, indices) in &helper_info {
let mut helper_partial = Vec::new();
for &sc_idx in indices {
let start_byte = sc_idx * sub_chunk_size;
let end_byte = (sc_idx + 1) * sub_chunk_size;
helper_partial.extend_from_slice(&chunks[*helper_idx][start_byte..end_byte]);
}
partial_data.insert(*helper_idx, helper_partial);
}
let recovered = clay.repair(lost_node, &partial_data, chunk_size).unwrap();
assert_eq!(
recovered, chunks[lost_node],
"Repair failed for node {} with params ({}, {}, {})",
lost_node, k, m, d
);
}
}
}
#[test]
fn test_decode_max_erasures() {
let clay = ClayCode::new(4, 2, 5).unwrap();
let data: Vec<u8> = (0..256).map(|i| (i % 256) as u8).collect();
let chunks = clay.encode(&data);
let patterns = vec![vec![0, 5], vec![0, 1], vec![4, 5], vec![1, 3]];
for erasures in patterns {
let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
for (i, chunk) in chunks.iter().enumerate() {
if !erasures.contains(&i) {
available.insert(i, chunk.clone());
}
}
let decoded = clay.decode(&available, &erasures).unwrap();
assert_eq!(
&decoded[..data.len()],
&data[..],
"Failed for erasures {:?}",
erasures
);
}
}
#[test]
fn test_normalized_repair_bandwidth() {
let test_cases = vec![
((4, 2, 5), 0.625),
((9, 3, 11), 0.407),
((10, 4, 13), 0.325),
];
for ((k, m, d), expected) in test_cases {
let clay = ClayCode::new(k, m, d).unwrap();
let actual = clay.normalized_repair_bandwidth();
assert!(
(actual - expected).abs() < 0.01,
"Expected {}, got {} for ({}, {}, {})",
expected,
actual,
k,
m,
d
);
}
}
#[test]
fn test_random_data() {
use rand::Rng;
let mut rng = rand::thread_rng();
let clay = ClayCode::new(4, 2, 5).unwrap();
let data_size = clay.k * clay.sub_chunk_no * 4;
let data: Vec<u8> = (0..data_size).map(|_| rng.gen()).collect();
let chunks = clay.encode(&data);
let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
for (i, chunk) in chunks.iter().enumerate() {
available.insert(i, chunk.clone());
}
let decoded = clay.decode(&available, &[]).unwrap();
assert_eq!(&decoded[..data.len()], &data[..]);
let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
for (i, chunk) in chunks.iter().enumerate() {
if i != 2 {
available.insert(i, chunk.clone());
}
}
let decoded = clay.decode(&available, &[2]).unwrap();
assert_eq!(&decoded[..data.len()], &data[..]);
}
#[test]
fn test_checked_pow_overflow() {
assert!(checked_pow(2, 63).is_some());
assert!(checked_pow(2, 64).is_none()); assert!(checked_pow(10, 20).is_none()); }
#[test]
fn test_invalid_parameters() {
assert!(ClayCode::new(0, 2, 1).is_err());
assert!(ClayCode::new(4, 0, 3).is_err());
assert!(ClayCode::new(4, 2, 4).is_err()); assert!(ClayCode::new(4, 2, 6).is_err()); }
#[test]
fn test_decode_too_many_erasures() {
let clay = ClayCode::new(4, 2, 5).unwrap();
let data: Vec<u8> = (0..128).map(|i| (i % 256) as u8).collect();
let chunks = clay.encode(&data);
let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
for (i, chunk) in chunks.iter().enumerate() {
if i > 2 {
available.insert(i, chunk.clone());
}
}
let result = clay.decode(&available, &[0, 1, 2]);
assert!(
matches!(result, Err(ClayError::TooManyErasures { max: 2, actual: 3 })),
"Expected TooManyErasures error, got {:?}",
result
);
}
#[test]
fn test_decode_inconsistent_chunk_sizes() {
let clay = ClayCode::new(4, 2, 5).unwrap();
let data: Vec<u8> = (0..128).map(|i| (i % 256) as u8).collect();
let chunks = clay.encode(&data);
let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
for (i, chunk) in chunks.iter().enumerate() {
if i != 0 {
if i == 5 {
let mut bad_chunk = chunk.clone();
bad_chunk.push(0); available.insert(i, bad_chunk);
} else {
available.insert(i, chunk.clone());
}
}
}
let result = clay.decode(&available, &[0]);
assert!(
matches!(result, Err(ClayError::InconsistentChunkSizes { .. }))
|| matches!(result, Err(ClayError::InvalidChunkSize { .. })),
"Expected InconsistentChunkSizes or InvalidChunkSize error, got {:?}",
result
);
}
#[test]
fn test_decode_invalid_chunk_index() {
let clay = ClayCode::new(4, 2, 5).unwrap();
let data: Vec<u8> = (0..128).collect();
let chunks = clay.encode(&data);
let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
for (i, chunk) in chunks.iter().enumerate() {
available.insert(i, chunk.clone());
}
available.insert(100, vec![0u8; chunks[0].len()]);
let result = clay.decode(&available, &[]);
assert!(
matches!(result, Err(ClayError::InvalidParameters(_))),
"Expected InvalidParameters error for out-of-range index, got {:?}",
result
);
}
#[test]
fn test_decode_invalid_erasure_index() {
let clay = ClayCode::new(4, 2, 5).unwrap();
let data: Vec<u8> = (0..128).collect();
let chunks = clay.encode(&data);
let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
for (i, chunk) in chunks.iter().enumerate() {
if i != 0 {
available.insert(i, chunk.clone());
}
}
let result = clay.decode(&available, &[100]);
assert!(
matches!(result, Err(ClayError::InvalidParameters(_))),
"Expected InvalidParameters error for out-of-range erasure, got {:?}",
result
);
}
#[test]
fn test_decode_available_erasure_overlap() {
let clay = ClayCode::new(4, 2, 5).unwrap();
let data: Vec<u8> = (0..128).collect();
let chunks = clay.encode(&data);
let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
for (i, chunk) in chunks.iter().enumerate() {
available.insert(i, chunk.clone());
}
let result = clay.decode(&available, &[0]);
assert!(
matches!(result, Err(ClayError::InvalidParameters(ref msg)) if msg.contains("both")),
"Expected InvalidParameters error for overlap, got {:?}",
result
);
}
#[test]
fn test_decode_wrong_available_count() {
let clay = ClayCode::new(4, 2, 5).unwrap();
let data: Vec<u8> = (0..128).collect();
let chunks = clay.encode(&data);
let mut available: HashMap<usize, Vec<u8>> = HashMap::new();
for (i, chunk) in chunks.iter().enumerate() {
if i > 1 {
available.insert(i, chunk.clone());
}
}
let result = clay.decode(&available, &[0]);
assert!(
matches!(result, Err(ClayError::InvalidParameters(ref msg)) if msg.contains("Expected")),
"Expected InvalidParameters error for wrong count, got {:?}",
result
);
}
}