Skip to main content

shadow_storage/
store.rs

1//! Content-addressed storage implementation
2
3use shadow_core::error::{Result, ShadowError};
4use crate::chunking::{Chunker, ChunkInfo};
5use crate::erasure::ErasureEncoder;
6use bytes::Bytes;
7use std::collections::HashMap;
8use std::sync::{Arc, RwLock};
9
10/// Storage configuration
11#[derive(Debug, Clone)]
12pub struct StorageConfig {
13    /// Chunk size in bytes
14    pub chunk_size: usize,
15    /// Number of data shards for erasure coding
16    pub data_shards: usize,
17    /// Number of parity shards
18    pub parity_shards: usize,
19    /// Enable encryption
20    pub encrypt: bool,
21    /// Maximum storage size
22    pub max_storage: usize,
23}
24
25impl Default for StorageConfig {
26    fn default() -> Self {
27        Self {
28            chunk_size: 256 * 1024, // 256 KB
29            data_shards: 3,
30            parity_shards: 2,
31            encrypt: true,
32            max_storage: 1024 * 1024 * 1024, // 1 GB
33        }
34    }
35}
36
37/// Content-addressed storage
38pub struct ContentStore {
39    /// Configuration
40    config: StorageConfig,
41    /// Chunk storage (hash -> data)
42    chunks: Arc<RwLock<HashMap<[u8; 32], Bytes>>>,
43    /// File metadata (content hash -> chunk list)
44    metadata: Arc<RwLock<HashMap<[u8; 32], Vec<ChunkInfo>>>>,
45    /// Current storage size
46    current_size: Arc<RwLock<usize>>,
47}
48
49impl ContentStore {
50    /// Create new content store
51    pub fn new(config: StorageConfig) -> Self {
52        Self {
53            config,
54            chunks: Arc::new(RwLock::new(HashMap::new())),
55            metadata: Arc::new(RwLock::new(HashMap::new())),
56            current_size: Arc::new(RwLock::new(0)),
57        }
58    }
59
60    /// Store data and return content hash
61    pub fn store(&self, data: &[u8]) -> Result<[u8; 32]> {
62        // Check storage limit
63        let current = *self.current_size.read().unwrap();
64        if current + data.len() > self.config.max_storage {
65            return Err(ShadowError::Storage("Storage full".into()));
66        }
67
68        // Chunk the data
69        let chunker = Chunker::new(self.config.chunk_size);
70        let chunks = chunker.chunk(data)?;
71
72        // Store each chunk
73        let mut chunk_infos = Vec::new();
74        let mut chunks_map = self.chunks.write().unwrap();
75        let mut size = self.current_size.write().unwrap();
76
77        for (info, chunk_data) in chunks {
78            // Only store if not already present (deduplication)
79            if !chunks_map.contains_key(&info.hash) {
80                chunks_map.insert(info.hash, chunk_data);
81                *size += info.size;
82            }
83            chunk_infos.push(info);
84        }
85
86        // Calculate content hash
87        let content_hash = Chunker::content_hash(&chunk_infos);
88
89        // Store metadata
90        self.metadata.write().unwrap().insert(content_hash, chunk_infos);
91
92        Ok(content_hash)
93    }
94
95    /// Retrieve data by content hash
96    pub fn retrieve(&self, content_hash: &[u8; 32]) -> Result<Bytes> {
97        // Get metadata
98        let metadata = self.metadata.read().unwrap();
99        let chunk_infos = metadata.get(content_hash)
100            .ok_or_else(|| ShadowError::Storage("Content not found".into()))?
101            .clone();
102
103        // Retrieve chunks
104        let chunks_map = self.chunks.read().unwrap();
105        let mut chunks = Vec::new();
106
107        for info in chunk_infos {
108            let chunk_data = chunks_map.get(&info.hash)
109                .ok_or_else(|| ShadowError::Storage(format!(
110                    "Chunk {} missing", info.index
111                )))?
112                .clone();
113            
114            chunks.push((info, chunk_data));
115        }
116
117        // Reassemble
118        let chunker = Chunker::new(self.config.chunk_size);
119        chunker.reassemble(&chunks)
120    }
121
122    /// Check if content exists
123    pub fn contains(&self, content_hash: &[u8; 32]) -> bool {
124        self.metadata.read().unwrap().contains_key(content_hash)
125    }
126
127    /// Delete content
128    pub fn delete(&self, content_hash: &[u8; 32]) -> Result<()> {
129        let mut metadata = self.metadata.write().unwrap();
130        
131        if let Some(chunk_infos) = metadata.remove(content_hash) {
132            let mut chunks_map = self.chunks.write().unwrap();
133            let mut size = self.current_size.write().unwrap();
134
135            // Remove chunks (check if other files use them first)
136            for info in chunk_infos {
137                // Simple deletion - in production, implement reference counting
138                if let Some(chunk) = chunks_map.remove(&info.hash) {
139                    *size = size.saturating_sub(chunk.len());
140                }
141            }
142        }
143
144        Ok(())
145    }
146
147    /// Get storage statistics
148    pub fn stats(&self) -> StorageStats {
149        let chunks_count = self.chunks.read().unwrap().len();
150        let files_count = self.metadata.read().unwrap().len();
151        let current_size = *self.current_size.read().unwrap();
152
153        StorageStats {
154            chunks: chunks_count,
155            files: files_count,
156            size_bytes: current_size,
157            max_bytes: self.config.max_storage,
158        }
159    }
160
161    /// List all stored content hashes
162    pub fn list(&self) -> Vec<[u8; 32]> {
163        self.metadata.read().unwrap().keys().copied().collect()
164    }
165}
166
167impl Default for ContentStore {
168    fn default() -> Self {
169        Self::new(StorageConfig::default())
170    }
171}
172
173/// Storage statistics
174#[derive(Debug, Clone)]
175pub struct StorageStats {
176    pub chunks: usize,
177    pub files: usize,
178    pub size_bytes: usize,
179    pub max_bytes: usize,
180}
181
182impl StorageStats {
183    pub fn usage_percent(&self) -> f64 {
184        (self.size_bytes as f64 / self.max_bytes as f64) * 100.0
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191
192    #[test]
193    fn test_store_retrieve() {
194        let store = ContentStore::default();
195        let data = b"Hello, Shadow Network Storage!";
196        
197        let hash = store.store(data).unwrap();
198        let retrieved = store.retrieve(&hash).unwrap();
199        
200        assert_eq!(retrieved.as_ref(), data);
201    }
202
203    #[test]
204    fn test_deduplication() {
205        let store = ContentStore::default();
206        let data = b"Same data";
207        
208        let hash1 = store.store(data).unwrap();
209        let initial_size = store.stats().size_bytes;
210        
211        let hash2 = store.store(data).unwrap();
212        let final_size = store.stats().size_bytes;
213        
214        assert_eq!(hash1, hash2);
215        assert_eq!(initial_size, final_size); // Size shouldn't increase
216    }
217
218    #[test]
219    fn test_delete() {
220        let store = ContentStore::default();
221        let data = b"Temporary data";
222        
223        let hash = store.store(data).unwrap();
224        assert!(store.contains(&hash));
225        
226        store.delete(&hash).unwrap();
227        assert!(!store.contains(&hash));
228    }
229
230    #[test]
231    fn test_stats() {
232        let store = ContentStore::default();
233        let stats1 = store.stats();
234        
235        store.store(b"Test data").unwrap();
236        
237        let stats2 = store.stats();
238        
239        assert!(stats2.size_bytes > stats1.size_bytes);
240        assert!(stats2.files > stats1.files);
241    }
242}