use anyhow::Result;
use std::path::{Path, PathBuf};
use tokio::fs;
pub struct FecStorage {
base_path: PathBuf,
#[allow(dead_code)]
default_redundancy: f32,
}
impl FecStorage {
pub async fn new(base_path: &Path, default_redundancy: f32) -> Result<Self> {
let fec_path = base_path.join("fec");
fs::create_dir_all(&fec_path).await?;
Ok(Self {
base_path: fec_path,
default_redundancy,
})
}
pub async fn store_with_fec(
&self,
key: &str,
data: &[u8],
_redundancy: f32,
) -> Result<Vec<PathBuf>> {
let key_dir = self.base_path.join(key);
fs::create_dir_all(&key_dir).await?;
let data_path = key_dir.join("data.enc");
fs::write(&data_path, data).await?;
let metadata = FecMetadata {
original_size: data.len(),
checksum: blake3::hash(data).as_bytes().to_vec(),
};
let metadata_path = key_dir.join("metadata.json");
let metadata_json = serde_json::to_vec(&metadata)?;
fs::write(metadata_path, metadata_json).await?;
Ok(vec![data_path])
}
pub async fn retrieve_from_fec(&self, shard_paths: &[PathBuf]) -> Result<Vec<u8>> {
if shard_paths.is_empty() {
return Err(anyhow::anyhow!("No shard paths provided"));
}
let data_path = &shard_paths[0];
let data = fs::read(data_path).await?;
let key_dir = data_path
.parent()
.ok_or_else(|| anyhow::anyhow!("Invalid shard path"))?;
let metadata_path = key_dir.join("metadata.json");
let metadata_json = fs::read(metadata_path).await?;
let metadata: FecMetadata = serde_json::from_slice(&metadata_json)?;
let checksum = blake3::hash(&data);
if checksum.as_bytes() != metadata.checksum.as_slice() {
return Err(anyhow::anyhow!("Data integrity check failed"));
}
Ok(data)
}
pub async fn repair_shards(&self, _key: &str) -> Result<usize> {
Ok(0)
}
pub async fn delete(&self, key: &str) -> Result<()> {
let key_dir = self.base_path.join(key);
if key_dir.exists() {
fs::remove_dir_all(key_dir).await?;
}
Ok(())
}
pub async fn get_stats(&self, key: &str) -> Result<FecStats> {
let key_dir = self.base_path.join(key);
let metadata_path = key_dir.join("metadata.json");
let metadata_json = fs::read(metadata_path).await?;
let metadata: FecMetadata = serde_json::from_slice(&metadata_json)?;
let data_path = key_dir.join("data.enc");
let data_size = if data_path.exists() {
fs::metadata(&data_path).await?.len() as usize
} else {
0
};
Ok(FecStats {
original_size: metadata.original_size,
total_shards: 1, healthy_shards: 1,
corrupted_shards: 0,
redundancy: 1.0,
storage_overhead: (data_size as f32) / (metadata.original_size as f32),
can_recover: true,
})
}
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct FecMetadata {
original_size: usize,
checksum: Vec<u8>,
}
#[derive(Debug, Clone)]
pub struct FecStats {
pub original_size: usize,
pub total_shards: usize,
pub healthy_shards: usize,
pub corrupted_shards: usize,
pub redundancy: f32,
pub storage_overhead: f32,
pub can_recover: bool,
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[tokio::test]
async fn test_fec_storage_basic() {
let temp_dir = TempDir::new().unwrap();
let fec = FecStorage::new(temp_dir.path(), 1.5).await.unwrap();
let test_data = vec![42u8; 1000];
let shard_paths = fec
.store_with_fec("test_key", &test_data, 1.5)
.await
.unwrap();
assert!(!shard_paths.is_empty());
let recovered = fec.retrieve_from_fec(&shard_paths).await.unwrap();
assert_eq!(recovered, test_data);
let stats = fec.get_stats("test_key").await.unwrap();
assert!(stats.can_recover);
}
}