Skip to main content

saorsa_core/storage/
mod.rs

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