saorsa_core/storage/
mod.rs

1//! DHT-based storage module for multi-device synchronization
2//! 
3//! All user data is stored in the DHT with proper encryption for privacy
4//! and multi-device access.
5
6use crate::dht::{DHT, Key};
7use crate::identity::enhanced::{EnhancedIdentity, DeviceId};
8use serde::{Deserialize, Serialize};
9use std::time::{SystemTime, Duration};
10use thiserror::Error;
11use aes_gcm::{
12    aead::{Aead, KeyInit},
13    Aes256Gcm, Nonce, Key as AesKey,
14};
15use sha2::{Sha256, Digest};
16
17/// Storage errors
18#[derive(Debug, Error)]
19pub enum StorageError {
20    #[error("DHT error: {0}")]
21    DhtError(String),
22    
23    #[error("Encryption error: {0}")]
24    EncryptionError(String),
25    
26    #[error("Serialization error: {0}")]
27    SerializationError(#[from] bincode::Error),
28    
29    #[error("Key not found: {0}")]
30    KeyNotFound(String),
31    
32    #[error("Invalid data format")]
33    InvalidFormat,
34}
35
36type Result<T> = std::result::Result<T, StorageError>;
37
38/// DHT key patterns for different data types
39pub mod keys {
40    /// User profile key pattern
41    pub fn profile(user_id: &str) -> String {
42        format!("profile:{}", user_id)
43    }
44    
45    /// Device registry key
46    pub fn devices(user_id: &str) -> String {
47        format!("devices:{}", user_id)
48    }
49    
50    /// Chat channel key
51    pub fn chat_channel(channel_id: &str) -> String {
52        format!("chat:channel:{}", channel_id)
53    }
54    
55    /// Chat message key
56    pub fn chat_message(channel_id: &str, msg_id: &str) -> String {
57        format!("chat:msg:{}:{}", channel_id, msg_id)
58    }
59    
60    /// Chat message index (for pagination)
61    pub fn chat_index(channel_id: &str, timestamp: u64) -> String {
62        format!("chat:idx:{}:{}", channel_id, timestamp)
63    }
64    
65    /// Discussion topic key
66    pub fn discuss_topic(topic_id: &str) -> String {
67        format!("discuss:topic:{}", topic_id)
68    }
69    
70    /// Discussion reply key
71    pub fn discuss_reply(topic_id: &str, reply_id: &str) -> String {
72        format!("discuss:reply:{}:{}", topic_id, reply_id)
73    }
74    
75    /// Project key
76    pub fn project(project_id: &str) -> String {
77        format!("project:{}", project_id)
78    }
79    
80    /// Document metadata key
81    pub fn document_meta(doc_id: &str) -> String {
82        format!("doc:meta:{}", doc_id)
83    }
84    
85    /// File chunk key
86    pub fn file_chunk(file_id: &str, chunk_num: u32) -> String {
87        format!("file:chunk:{}:{:08}", file_id, chunk_num)
88    }
89    
90    /// Organization key
91    pub fn organization(org_id: &str) -> String {
92        format!("org:{}", org_id)
93    }
94    
95    /// Public channel discovery
96    pub fn public_channel_list() -> String {
97        "public:channels".to_string()
98    }
99    
100    /// User's joined channels
101    pub fn user_channels(user_id: &str) -> String {
102        format!("user:channels:{}", user_id)
103    }
104}
105
106/// TTL values for different data types
107pub mod ttl {
108    use std::time::Duration;
109    
110    /// Profile data - effectively permanent
111    pub const PROFILE: Duration = Duration::from_secs(365 * 24 * 60 * 60); // 1 year
112    
113    /// Messages - long term storage
114    pub const MESSAGE: Duration = Duration::from_secs(90 * 24 * 60 * 60); // 90 days
115    
116    /// File chunks - permanent until deleted
117    pub const FILE_CHUNK: Duration = Duration::from_secs(365 * 24 * 60 * 60); // 1 year
118    
119    /// Temporary data
120    pub const TEMP: Duration = Duration::from_secs(24 * 60 * 60); // 24 hours
121    
122    /// Presence/status updates
123    pub const PRESENCE: Duration = Duration::from_secs(5 * 60); // 5 minutes
124}
125
126/// Encrypted data wrapper
127#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct EncryptedData {
129    /// Encrypted payload
130    pub ciphertext: Vec<u8>,
131    
132    /// Nonce used for encryption
133    pub nonce: Vec<u8>,
134    
135    /// Key ID (for key rotation)
136    pub key_id: String,
137    
138    /// Timestamp
139    pub timestamp: SystemTime,
140    
141    /// Optional metadata (unencrypted)
142    pub metadata: Option<serde_json::Value>,
143}
144
145/// Storage manager for DHT operations
146pub struct StorageManager {
147    /// DHT instance
148    dht: DHT,
149    
150    /// Encryption keys (in production, use secure key storage)
151    master_key: [u8; 32],
152}
153
154impl StorageManager {
155    /// Create new storage manager
156    pub fn new(dht: DHT, identity: &EnhancedIdentity) -> Result<Self> {
157        // Derive master key from identity (simplified - use proper KDF in production)
158        let mut hasher = Sha256::new();
159        hasher.update(identity.base_identity.user_id.as_bytes()); // Placeholder implementation
160        let master_key: [u8; 32] = hasher.finalize().into();
161        
162        Ok(Self {
163            dht,
164            master_key,
165        })
166    }
167    
168    /// Store encrypted data in DHT
169    pub async fn store_encrypted<T: Serialize>(
170        &mut self,
171        key: &str,
172        data: &T,
173        ttl: Duration,
174        metadata: Option<serde_json::Value>,
175    ) -> Result<()> {
176        // Serialize data
177        let plaintext = bincode::serialize(data)?;
178        
179        // Encrypt data
180        let encrypted = self.encrypt(&plaintext)?;
181        
182        // Create encrypted wrapper
183        let wrapper = EncryptedData {
184            ciphertext: encrypted.0,
185            nonce: encrypted.1.to_vec(),
186            key_id: "v1".to_string(),
187            timestamp: SystemTime::now(),
188            metadata,
189        };
190        
191        // Serialize wrapper
192        let wrapper_bytes = bincode::serialize(&wrapper)?;
193        
194        // Store in DHT
195        let dht_key = Key::new(key.as_bytes());
196        
197        self.dht.put(dht_key, wrapper_bytes).await
198            .map_err(|e| StorageError::DhtError(e.to_string()))?;
199        
200        Ok(())
201    }
202    
203    /// Retrieve and decrypt data from DHT
204    pub async fn get_encrypted<T: for<'de> Deserialize<'de>>(
205        &self,
206        key: &str,
207    ) -> Result<T> {
208        // Get from DHT
209        let dht_key = Key::new(key.as_bytes());
210        let record = self.dht.get(&dht_key).await
211            .ok_or_else(|| StorageError::KeyNotFound(key.to_string()))?;
212        
213        // Deserialize wrapper
214        let wrapper: EncryptedData = bincode::deserialize(&record.value)?;
215        
216        // Decrypt data
217        let plaintext = self.decrypt(&wrapper.ciphertext, &wrapper.nonce)?;
218        
219        // Deserialize data
220        let data = bincode::deserialize(&plaintext)?;
221        
222        Ok(data)
223    }
224    
225    /// Store public (unencrypted) data
226    pub async fn store_public<T: Serialize>(
227        &mut self,
228        key: &str,
229        data: &T,
230        ttl: Duration,
231    ) -> Result<()> {
232        let value = bincode::serialize(data)?;
233        
234        let dht_key = Key::new(key.as_bytes());
235        
236        self.dht.put(dht_key, value).await
237            .map_err(|e| StorageError::DhtError(e.to_string()))?;
238        
239        Ok(())
240    }
241    
242    /// Get public data
243    pub async fn get_public<T: for<'de> Deserialize<'de>>(
244        &self,
245        key: &str,
246    ) -> Result<T> {
247        let dht_key = Key::new(key.as_bytes());
248        let record = self.dht.get(&dht_key).await
249            .ok_or_else(|| StorageError::KeyNotFound(key.to_string()))?;
250        
251        let data = bincode::deserialize(&record.value)?;
252        Ok(data)
253    }
254    
255    /// Delete data from DHT
256    pub async fn delete(&mut self, key: &str) -> Result<()> {
257        // DHT doesn't expose direct delete method, so we'll put an empty value with immediate expiry
258        let dht_key = Key::new(key.as_bytes());
259        self.dht.put(dht_key, vec![]).await
260            .map_err(|e| StorageError::DhtError(e.to_string()))?;
261        Ok(())
262    }
263    
264    /// List keys with prefix (for discovery)
265    pub async fn list_keys(&self, prefix: &str) -> Result<Vec<String>> {
266        // In a real implementation, this would query the DHT for keys with prefix
267        // For now, return empty list
268        Ok(vec![])
269    }
270    
271    /// Encrypt data using AES-256-GCM
272    fn encrypt(&self, plaintext: &[u8]) -> Result<(Vec<u8>, [u8; 12])> {
273        let cipher = Aes256Gcm::new(AesKey::<Aes256Gcm>::from_slice(&self.master_key));
274        
275        // Generate random nonce
276        let nonce_bytes = rand::random::<[u8; 12]>();
277        let nonce = Nonce::from_slice(&nonce_bytes);
278        
279        let ciphertext = cipher.encrypt(nonce, plaintext)
280            .map_err(|e| StorageError::EncryptionError(e.to_string()))?;
281        
282        Ok((ciphertext, nonce_bytes))
283    }
284    
285    /// Decrypt data
286    fn decrypt(&self, ciphertext: &[u8], nonce: &[u8]) -> Result<Vec<u8>> {
287        let cipher = Aes256Gcm::new(AesKey::<Aes256Gcm>::from_slice(&self.master_key));
288        let nonce = Nonce::from_slice(nonce);
289        
290        let plaintext = cipher.decrypt(nonce, ciphertext)
291            .map_err(|e| StorageError::EncryptionError(e.to_string()))?;
292        
293        Ok(plaintext)
294    }
295}
296
297/// Multi-device sync manager (temporarily disabled)
298/*
299pub struct SyncManager {
300    storage: StorageManager,
301    identity: EnhancedIdentity,
302}
303
304impl SyncManager {
305    /// Create new sync manager
306    pub fn new(storage: StorageManager, identity: EnhancedIdentity) -> Self {
307        Self {
308            storage,
309            identity,
310        }
311    }
312    
313    /// Sync identity across devices
314    pub async fn sync_identity(&mut self) -> Result<()> {
315        // Store identity in DHT
316        let key = keys::profile(&self.identity.base_identity.user_id);
317        self.storage.store_encrypted(
318            &key,
319            &self.identity,
320            ttl::PROFILE,
321            None,
322        ).await?;
323        
324        // Store device registry
325        let devices_key = keys::devices(&self.identity.base_identity.user_id);
326        self.storage.store_encrypted(
327            &devices_key,
328            &self.identity.devices,
329            ttl::PROFILE,
330            None,
331        ).await?;
332        
333        // Update last sync time
334        self.identity.last_sync = SystemTime::now();
335        
336        Ok(())
337    }
338    
339    /// Load identity from DHT
340    pub async fn load_identity(&self, user_id: &str) -> Result<EnhancedIdentity> {
341        let key = keys::profile(user_id);
342        self.storage.get_encrypted(&key).await
343    }
344    
345    /// Register new device
346    pub async fn register_device(
347        &mut self,
348        device_id: DeviceId,
349        device_info: crate::identity::enhanced::DeviceInfo,
350    ) -> Result<()> {
351        // Add to local registry
352        self.identity.devices.devices.insert(device_id.clone(), device_info);
353        
354        // Sync to DHT
355        self.sync_identity().await
356    }
357    
358    /// Check for updates from other devices
359    pub async fn check_updates(&mut self) -> Result<bool> {
360        let remote_identity = self.load_identity(&self.identity.base_identity.user_id).await?;
361        
362        if remote_identity.last_sync > self.identity.last_sync {
363            // Remote is newer, update local
364            self.identity = remote_identity;
365            return Ok(true);
366        }
367        
368        Ok(false)
369    }
370}
371*/
372
373/// File chunking for large media
374pub struct FileChunker {
375    chunk_size: usize,
376}
377
378impl FileChunker {
379    /// Create new file chunker
380    pub fn new(chunk_size: usize) -> Self {
381        Self { chunk_size }
382    }
383    
384    /// Split file into chunks
385    pub fn chunk_file(&self, data: &[u8]) -> Vec<Vec<u8>> {
386        data.chunks(self.chunk_size)
387            .map(|chunk| chunk.to_vec())
388            .collect()
389    }
390    
391    /// Store chunked file
392    pub async fn store_file(
393        &self,
394        storage: &mut StorageManager,
395        file_id: &str,
396        data: &[u8],
397        metadata: FileMetadata,
398    ) -> Result<()> {
399        let chunks = self.chunk_file(data);
400        let total_chunks = chunks.len() as u32;
401        
402        // Store metadata
403        let meta_with_chunks = FileMetadata {
404            total_chunks,
405            ..metadata
406        };
407        
408        let meta_key = keys::document_meta(file_id);
409        storage.store_encrypted(
410            &meta_key,
411            &meta_with_chunks,
412            ttl::FILE_CHUNK,
413            None,
414        ).await?;
415        
416        // Store chunks
417        for (i, chunk) in chunks.iter().enumerate() {
418            let chunk_key = keys::file_chunk(file_id, i as u32);
419            storage.store_encrypted(
420                &chunk_key,
421                chunk,
422                ttl::FILE_CHUNK,
423                None,
424            ).await?;
425        }
426        
427        Ok(())
428    }
429    
430    /// Retrieve chunked file
431    pub async fn get_file(
432        &self,
433        storage: &StorageManager,
434        file_id: &str,
435    ) -> Result<Vec<u8>> {
436        // Get metadata
437        let meta_key = keys::document_meta(file_id);
438        let metadata: FileMetadata = storage.get_encrypted(&meta_key).await?;
439        
440        // Get chunks
441        let mut data = Vec::new();
442        for i in 0..metadata.total_chunks {
443            let chunk_key = keys::file_chunk(file_id, i);
444            let chunk: Vec<u8> = storage.get_encrypted(&chunk_key).await?;
445            data.extend(chunk);
446        }
447        
448        Ok(data)
449    }
450}
451
452/// File metadata
453#[derive(Debug, Clone, Serialize, Deserialize)]
454pub struct FileMetadata {
455    pub file_id: String,
456    pub name: String,
457    pub size: u64,
458    pub mime_type: String,
459    pub hash: Vec<u8>,
460    pub total_chunks: u32,
461    pub created_at: SystemTime,
462    pub created_by: String,
463}