1use ipfrs_core::Cid;
9use serde::{Deserialize, Deserializer, Serialize, Serializer};
10use std::collections::HashMap;
11use thiserror::Error;
12
13fn serialize_cid<S>(cid: &Cid, serializer: S) -> Result<S::Ok, S::Error>
15where
16 S: Serializer,
17{
18 serializer.serialize_str(&cid.to_string())
19}
20
21fn deserialize_cid<'de, D>(deserializer: D) -> Result<Cid, D::Error>
23where
24 D: Deserializer<'de>,
25{
26 let s = String::deserialize(deserializer)?;
27 s.parse().map_err(serde::de::Error::custom)
28}
29
30fn serialize_cid_vec<S>(cids: &[Cid], serializer: S) -> Result<S::Ok, S::Error>
32where
33 S: Serializer,
34{
35 use serde::ser::SerializeSeq;
36 let mut seq = serializer.serialize_seq(Some(cids.len()))?;
37 for cid in cids {
38 seq.serialize_element(&cid.to_string())?;
39 }
40 seq.end()
41}
42
43fn deserialize_cid_vec<'de, D>(deserializer: D) -> Result<Vec<Cid>, D::Error>
45where
46 D: Deserializer<'de>,
47{
48 let strings: Vec<String> = Vec::deserialize(deserializer)?;
49 strings
50 .iter()
51 .map(|s| s.parse().map_err(serde::de::Error::custom))
52 .collect()
53}
54
55#[derive(Error, Debug)]
57pub enum ErasureError {
58 #[error("Invalid parameters: {0}")]
59 InvalidParams(String),
60 #[error("Insufficient shards for recovery: have {have}, need {need}")]
61 InsufficientShards { have: usize, need: usize },
62 #[error("Shard size mismatch: expected {expected}, got {got}")]
63 ShardSizeMismatch { expected: usize, got: usize },
64 #[error("Invalid shard index: {0}")]
65 InvalidShardIndex(usize),
66 #[error("Encoding failed: {0}")]
67 EncodingFailed(String),
68 #[error("Decoding failed: {0}")]
69 DecodingFailed(String),
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct ErasureConfig {
75 pub data_shards: usize,
77 pub parity_shards: usize,
79}
80
81impl ErasureConfig {
82 pub fn new(data_shards: usize, parity_shards: usize) -> Result<Self, ErasureError> {
84 if data_shards == 0 {
85 return Err(ErasureError::InvalidParams(
86 "Data shards must be > 0".to_string(),
87 ));
88 }
89 if parity_shards == 0 {
90 return Err(ErasureError::InvalidParams(
91 "Parity shards must be > 0".to_string(),
92 ));
93 }
94 if data_shards + parity_shards > 256 {
95 return Err(ErasureError::InvalidParams(
96 "Total shards must be <= 256".to_string(),
97 ));
98 }
99 Ok(Self {
100 data_shards,
101 parity_shards,
102 })
103 }
104
105 pub fn total_shards(&self) -> usize {
107 self.data_shards + self.parity_shards
108 }
109
110 pub fn min_shards_for_recovery(&self) -> usize {
112 self.data_shards
113 }
114
115 pub fn max_failures(&self) -> usize {
117 self.parity_shards
118 }
119
120 pub fn redundancy_ratio(&self) -> f64 {
122 self.total_shards() as f64 / self.data_shards as f64
123 }
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct Shard {
129 pub index: usize,
131 pub data: Vec<u8>,
133 pub is_parity: bool,
135}
136
137impl Shard {
138 pub fn new(index: usize, data: Vec<u8>, is_parity: bool) -> Self {
140 Self {
141 index,
142 data,
143 is_parity,
144 }
145 }
146
147 pub fn size(&self) -> usize {
149 self.data.len()
150 }
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct ErasureMetadata {
156 #[serde(serialize_with = "serialize_cid", deserialize_with = "deserialize_cid")]
158 pub original_cid: Cid,
159 pub original_size: usize,
161 pub config: ErasureConfig,
163 pub shard_size: usize,
165 #[serde(
167 serialize_with = "serialize_cid_vec",
168 deserialize_with = "deserialize_cid_vec"
169 )]
170 pub shard_cids: Vec<Cid>,
171}
172
173impl ErasureMetadata {
174 pub fn new(
176 original_cid: Cid,
177 original_size: usize,
178 config: ErasureConfig,
179 shard_size: usize,
180 shard_cids: Vec<Cid>,
181 ) -> Self {
182 Self {
183 original_cid,
184 original_size,
185 config,
186 shard_size,
187 shard_cids,
188 }
189 }
190
191 pub fn total_shards(&self) -> usize {
193 self.config.total_shards()
194 }
195
196 pub fn can_recover(&self, available_shards: usize) -> bool {
198 available_shards >= self.config.min_shards_for_recovery()
199 }
200}
201
202pub struct SimpleErasureEncoder {
205 config: ErasureConfig,
206}
207
208impl SimpleErasureEncoder {
209 pub fn new(config: ErasureConfig) -> Self {
211 Self { config }
212 }
213
214 pub fn encode(&self, data: &[u8]) -> Result<Vec<Shard>, ErasureError> {
216 let data_shards = self.config.data_shards;
217 let parity_shards = self.config.parity_shards;
218
219 let shard_size = data.len().div_ceil(data_shards);
221 let padded_size = shard_size * data_shards;
222
223 let mut padded_data = data.to_vec();
225 padded_data.resize(padded_size, 0);
226
227 let mut shards = Vec::new();
228
229 for i in 0..data_shards {
231 let start = i * shard_size;
232 let end = start + shard_size;
233 let shard_data = padded_data[start..end].to_vec();
234 shards.push(Shard::new(i, shard_data, false));
235 }
236
237 for p in 0..parity_shards {
240 let mut parity_data = vec![0u8; shard_size];
241
242 for (i, shard) in shards.iter().enumerate().take(data_shards) {
244 let weight = ((i + p + 1) % 256) as u8;
245 for (j, &byte) in shard.data.iter().enumerate() {
246 parity_data[j] ^= byte.wrapping_mul(weight);
247 }
248 }
249
250 shards.push(Shard::new(data_shards + p, parity_data, true));
251 }
252
253 Ok(shards)
254 }
255
256 pub fn decode(&self, shards: &[Shard], original_size: usize) -> Result<Vec<u8>, ErasureError> {
258 if shards.len() < self.config.data_shards {
259 return Err(ErasureError::InsufficientShards {
260 have: shards.len(),
261 need: self.config.data_shards,
262 });
263 }
264
265 if !shards.is_empty() {
267 let expected_size = shards[0].size();
268 for shard in shards {
269 if shard.size() != expected_size {
270 return Err(ErasureError::ShardSizeMismatch {
271 expected: expected_size,
272 got: shard.size(),
273 });
274 }
275 }
276 }
277
278 let mut data_shards: Vec<_> = shards.iter().filter(|s| !s.is_parity).collect();
280 let parity_shards: Vec<_> = shards.iter().filter(|s| s.is_parity).collect();
281
282 if data_shards.len() == self.config.data_shards {
284 let shard_size = data_shards[0].size();
285 let mut reconstructed = Vec::with_capacity(shard_size * data_shards.len());
286
287 data_shards.sort_by_key(|s| s.index);
289
290 for shard in data_shards {
291 reconstructed.extend_from_slice(&shard.data);
292 }
293
294 reconstructed.truncate(original_size);
296 return Ok(reconstructed);
297 }
298
299 if data_shards.len() + parity_shards.len() < self.config.data_shards {
302 return Err(ErasureError::InsufficientShards {
303 have: data_shards.len() + parity_shards.len(),
304 need: self.config.data_shards,
305 });
306 }
307
308 if data_shards.len() + parity_shards.len() == self.config.data_shards {
311 return Err(ErasureError::DecodingFailed(
313 "Recovery not fully implemented in simple encoder".to_string(),
314 ));
315 }
316
317 Ok(Vec::new())
318 }
319}
320
321pub struct ErasureManager {
323 encoder: SimpleErasureEncoder,
324 metadata_cache: HashMap<Cid, ErasureMetadata>,
326}
327
328impl ErasureManager {
329 pub fn new(config: ErasureConfig) -> Self {
331 Self {
332 encoder: SimpleErasureEncoder::new(config),
333 metadata_cache: HashMap::new(),
334 }
335 }
336
337 pub fn encode_block(&mut self, _cid: Cid, data: &[u8]) -> Result<Vec<Shard>, ErasureError> {
339 self.encoder.encode(data)
340 }
341
342 pub fn decode_shards(
344 &self,
345 shards: &[Shard],
346 original_size: usize,
347 ) -> Result<Vec<u8>, ErasureError> {
348 self.encoder.decode(shards, original_size)
349 }
350
351 pub fn store_metadata(&mut self, metadata: ErasureMetadata) {
353 self.metadata_cache.insert(metadata.original_cid, metadata);
354 }
355
356 pub fn get_metadata(&self, cid: &Cid) -> Option<&ErasureMetadata> {
358 self.metadata_cache.get(cid)
359 }
360
361 pub fn can_recover(&self, cid: &Cid, available_shards: usize) -> bool {
363 if let Some(metadata) = self.get_metadata(cid) {
364 metadata.can_recover(available_shards)
365 } else {
366 false
367 }
368 }
369}
370
371#[cfg(test)]
372mod tests {
373 use super::*;
374
375 fn test_cid() -> Cid {
376 "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi"
377 .parse()
378 .unwrap()
379 }
380
381 #[test]
382 fn test_erasure_config() {
383 let config = ErasureConfig::new(4, 2).unwrap();
384 assert_eq!(config.data_shards, 4);
385 assert_eq!(config.parity_shards, 2);
386 assert_eq!(config.total_shards(), 6);
387 assert_eq!(config.min_shards_for_recovery(), 4);
388 assert_eq!(config.max_failures(), 2);
389 assert_eq!(config.redundancy_ratio(), 1.5);
390 }
391
392 #[test]
393 fn test_erasure_config_invalid() {
394 assert!(ErasureConfig::new(0, 2).is_err());
395 assert!(ErasureConfig::new(2, 0).is_err());
396 assert!(ErasureConfig::new(200, 100).is_err());
397 }
398
399 #[test]
400 fn test_shard_creation() {
401 let shard = Shard::new(0, vec![1, 2, 3], false);
402 assert_eq!(shard.index, 0);
403 assert_eq!(shard.size(), 3);
404 assert!(!shard.is_parity);
405 }
406
407 #[test]
408 fn test_encode_decode() {
409 let config = ErasureConfig::new(3, 2).unwrap();
410 let encoder = SimpleErasureEncoder::new(config);
411
412 let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9];
413 let original_size = data.len();
414
415 let shards = encoder.encode(&data).unwrap();
416 assert_eq!(shards.len(), 5); for (i, shard) in shards.iter().enumerate() {
420 assert_eq!(shard.index, i);
421 if i < 3 {
422 assert!(!shard.is_parity);
423 } else {
424 assert!(shard.is_parity);
425 }
426 }
427
428 let decoded = encoder.decode(&shards[..3], original_size).unwrap();
430 assert_eq!(decoded, data);
431 }
432
433 #[test]
434 fn test_encode_empty_data() {
435 let config = ErasureConfig::new(2, 1).unwrap();
436 let encoder = SimpleErasureEncoder::new(config);
437
438 let data = vec![];
439 let shards = encoder.encode(&data).unwrap();
440 assert_eq!(shards.len(), 3);
441 }
442
443 #[test]
444 fn test_decode_insufficient_shards() {
445 let config = ErasureConfig::new(4, 2).unwrap();
446 let encoder = SimpleErasureEncoder::new(config);
447
448 let data = vec![1, 2, 3, 4, 5, 6, 7, 8];
449 let shards = encoder.encode(&data).unwrap();
450
451 let result = encoder.decode(&shards[..2], data.len());
453 assert!(result.is_err());
454 }
455
456 #[test]
457 fn test_erasure_metadata() {
458 let cid = test_cid();
459 let config = ErasureConfig::new(3, 2).unwrap();
460 let shard_cids = vec![test_cid(); 5];
461
462 let metadata = ErasureMetadata::new(cid, 1000, config, 350, shard_cids);
463
464 assert_eq!(metadata.original_size, 1000);
465 assert_eq!(metadata.total_shards(), 5);
466 assert!(metadata.can_recover(3));
467 assert!(!metadata.can_recover(2));
468 }
469
470 #[test]
471 fn test_erasure_manager() {
472 let config = ErasureConfig::new(3, 2).unwrap();
473 let mut manager = ErasureManager::new(config);
474
475 let cid = test_cid();
476 let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9];
477
478 let shards = manager.encode_block(cid, &data).unwrap();
479 assert_eq!(shards.len(), 5);
480
481 let decoded = manager.decode_shards(&shards[..3], data.len()).unwrap();
482 assert_eq!(decoded, data);
483 }
484
485 #[test]
486 fn test_metadata_caching() {
487 let config = ErasureConfig::new(3, 2).unwrap();
488 let mut manager = ErasureManager::new(config.clone());
489
490 let cid = test_cid();
491 let shard_cids = vec![test_cid(); 5];
492 let metadata = ErasureMetadata::new(cid, 1000, config, 350, shard_cids);
493
494 manager.store_metadata(metadata);
495
496 let retrieved = manager.get_metadata(&cid);
497 assert!(retrieved.is_some());
498 assert_eq!(retrieved.unwrap().original_size, 1000);
499 }
500
501 #[test]
502 fn test_can_recover() {
503 let config = ErasureConfig::new(4, 2).unwrap();
504 let mut manager = ErasureManager::new(config.clone());
505
506 let cid = test_cid();
507 let shard_cids = vec![test_cid(); 6];
508 let metadata = ErasureMetadata::new(cid, 1000, config, 250, shard_cids);
509
510 manager.store_metadata(metadata);
511
512 assert!(manager.can_recover(&cid, 4));
513 assert!(manager.can_recover(&cid, 5));
514 assert!(!manager.can_recover(&cid, 3));
515 }
516}