Skip to main content

clay_codes/
encode.rs

1//! Encoding logic for Clay codes
2//!
3//! This module handles encoding data into Clay code chunks.
4
5use std::collections::BTreeSet;
6
7use crate::decode::decode_layered;
8
9/// Parameters needed for encoding
10pub struct EncodeParams {
11    pub k: usize,
12    pub m: usize,
13    pub n: usize,
14    pub q: usize,
15    pub t: usize,
16    pub nu: usize,
17    pub sub_chunk_no: usize,
18    pub original_count: usize,
19    pub recovery_count: usize,
20}
21
22/// Encode data into n chunks
23///
24/// # Parameters
25/// - `params`: Encoding parameters from ClayCode
26/// - `data`: Raw data bytes to encode
27///
28/// # Returns
29/// Vector of n chunks, each containing α sub-chunks
30pub fn encode(params: &EncodeParams, data: &[u8]) -> Vec<Vec<u8>> {
31    // Calculate chunk size: must be divisible by (k * sub_chunk_no)
32    // Also ensure sub_chunk_size >= 2 bytes (reed-solomon-erasure requirement)
33    let min_sub_chunk_size = 2;
34    let min_size = params.k * params.sub_chunk_no * min_sub_chunk_size;
35    let padded_len = if data.is_empty() {
36        min_size
37    } else {
38        let aligned = ((data.len() + min_size - 1) / min_size) * min_size;
39        aligned.max(min_size)
40    };
41    let chunk_size = padded_len / params.k;
42    let sub_chunk_size = chunk_size / params.sub_chunk_no;
43
44    // Create padded data
45    let mut padded_data = data.to_vec();
46    padded_data.resize(padded_len, 0);
47
48    // Initialize all chunks (k data + nu shortened + m parity)
49    let total_nodes = params.q * params.t; // k + m + nu
50    let mut chunks: Vec<Vec<u8>> = vec![vec![0u8; chunk_size]; total_nodes];
51
52    // Load data into first k nodes
53    for i in 0..params.k {
54        chunks[i].copy_from_slice(&padded_data[i * chunk_size..(i + 1) * chunk_size]);
55    }
56
57    // Shortened nodes (k to k+nu-1) are already zeros - they are KNOWN zeros,
58    // not erasures. We mark only parity nodes as needing computation.
59    let parity_start = params.k + params.nu;
60    let mut nodes_to_compute: BTreeSet<usize> = BTreeSet::new();
61    for i in parity_start..total_nodes {
62        nodes_to_compute.insert(i);
63    }
64
65    // Encode by treating parity computation as recovery
66    // This should never fail for valid parameters (parity count = m <= m)
67    decode_layered(params, &nodes_to_compute, &mut chunks, sub_chunk_size)
68        .expect("Encode failed: this indicates a bug in ClayCode");
69
70    // Return only the k data + m parity chunks (exclude shortened nodes)
71    let mut result = Vec::with_capacity(params.n);
72    for i in 0..params.k {
73        result.push(chunks[i].clone());
74    }
75    for i in (params.k + params.nu)..total_nodes {
76        result.push(chunks[i].clone());
77    }
78
79    result
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85
86    fn test_params() -> EncodeParams {
87        // (4, 2, 5) configuration
88        EncodeParams {
89            k: 4,
90            m: 2,
91            n: 6,
92            q: 2,
93            t: 3,
94            nu: 0,
95            sub_chunk_no: 8,
96            original_count: 4,
97            recovery_count: 2,
98        }
99    }
100
101    #[test]
102    fn test_encode_produces_correct_chunk_count() {
103        let params = test_params();
104        let data = b"Test data for encoding";
105        let chunks = encode(&params, data);
106        assert_eq!(chunks.len(), params.n);
107    }
108
109    #[test]
110    fn test_encode_empty_data() {
111        let params = test_params();
112        let chunks = encode(&params, &[]);
113        assert_eq!(chunks.len(), params.n);
114        // All chunks should have same size
115        let chunk_size = chunks[0].len();
116        for chunk in &chunks {
117            assert_eq!(chunk.len(), chunk_size);
118        }
119    }
120
121    #[test]
122    fn test_encode_chunk_alignment() {
123        let params = test_params();
124        let data = vec![0xABu8; 100];
125        let chunks = encode(&params, &data);
126
127        // Chunk size should be divisible by sub_chunk_no
128        for chunk in &chunks {
129            assert_eq!(chunk.len() % params.sub_chunk_no, 0);
130        }
131    }
132}