Rust · 9738 bytes Raw Blame History
1 use anyhow::{Context, Result};
2 use rocksdb::{DB, Options, WriteBatch};
3 use serde::{Deserialize, Serialize};
4 use std::collections::HashMap;
5 use std::path::Path;
6 use std::sync::Arc;
7 use tokio::sync::RwLock;
8 use tracing::{debug, info, warn};
9
10 /// File metadata with comprehensive tracking
11 ///
12 /// Privacy: Only stores operational metadata, not file contents
13 #[derive(Debug, Clone, Serialize, Deserialize)]
14 pub struct FileMetadata {
15 /// Original filename (encrypted if privacy mode enabled)
16 pub name: String,
17
18 /// File size in bytes
19 pub size: u64,
20
21 /// MIME type detection
22 pub mime_type: Option<String>,
23
24 /// SHA-256 hash of complete file
25 pub file_hash: String,
26
27 /// List of chunk IDs that comprise this file
28 pub chunk_ids: Vec<String>,
29
30 /// Creation timestamp
31 pub created_at: u64,
32
33 /// Last modified timestamp
34 pub modified_at: u64,
35
36 /// Access permissions (future use)
37 pub permissions: u32,
38
39 /// Integrity checksum for metadata verification
40 pub checksum: String,
41 }
42
43 /// Thread-safe metadata storage using RocksDB
44 ///
45 /// Safety: All operations include integrity checks and atomic updates
46 /// Transparency: All metadata operations are logged for audit
47 /// Privacy: Supports encrypted filename storage
48 pub struct MetadataStore {
49 /// RocksDB instance for file metadata
50 db: Arc<DB>,
51
52 /// In-memory cache for frequently accessed metadata
53 metadata_cache: Arc<RwLock<HashMap<String, FileMetadata>>>,
54 }
55
56 impl MetadataStore {
57 /// Create a new MetadataStore with secure configuration
58 ///
59 /// Safety: Creates database with paranoid checks enabled
60 pub fn new<P: AsRef<Path>>(db_path: P) -> Result<Self> {
61 info!("Initializing MetadataStore with security-focused configuration");
62
63 let mut opts = Options::default();
64 opts.create_if_missing(true);
65 opts.set_paranoid_checks(true); // Safety: Enable paranoid consistency checks
66 opts.set_use_fsync(true); // Safety: Force fsync for durability
67
68 let db = DB::open(&opts, db_path)
69 .context("Failed to open metadata database")?;
70
71 let store = Self {
72 db: Arc::new(db),
73 metadata_cache: Arc::new(RwLock::new(HashMap::new())),
74 };
75
76 info!("MetadataStore initialized successfully");
77 Ok(store)
78 }
79
80 /// Store file metadata with integrity verification
81 ///
82 /// Safety: Includes checksum verification and atomic operations
83 /// Transparency: All operations logged with file hashes
84 pub async fn store_metadata(&self, file_id: &str, mut metadata: FileMetadata) -> Result<()> {
85 debug!("Storing metadata for file: {} ({})", file_id, metadata.name);
86
87 // Calculate integrity checksum
88 metadata.checksum = self.calculate_metadata_checksum(&metadata);
89
90 // Update timestamp
91 metadata.modified_at = std::time::SystemTime::now()
92 .duration_since(std::time::UNIX_EPOCH)?
93 .as_secs();
94
95 // Serialize metadata
96 let metadata_bytes = bincode::serialize(&metadata)
97 .context("Failed to serialize file metadata")?;
98
99 // Store in database
100 let key = format!("file:{}", file_id);
101 self.db.put(&key, metadata_bytes)
102 .context("Failed to store metadata in database")?;
103
104 // Update cache
105 {
106 let mut cache = self.metadata_cache.write().await;
107 cache.insert(file_id.to_string(), metadata.clone());
108 }
109
110 info!("Successfully stored metadata for file: {} with hash: {}",
111 file_id, metadata.file_hash);
112 Ok(())
113 }
114
115 /// Retrieve file metadata with integrity verification
116 ///
117 /// Safety: Verifies checksum before returning metadata
118 /// Transparency: Cache hits/misses are tracked and logged
119 pub async fn get_file(&self, file_id: &str) -> Result<Option<FileMetadata>> {
120 self.get_metadata(file_id).await
121 }
122
123 /// Retrieve file metadata with integrity verification (internal method)
124 ///
125 /// Safety: Verifies checksum before returning metadata
126 /// Transparency: Cache hits/misses are tracked and logged
127 pub async fn get_metadata(&self, file_id: &str) -> Result<Option<FileMetadata>> {
128 debug!("Retrieving metadata for file: {}", file_id);
129
130 // Check cache first
131 {
132 let cache = self.metadata_cache.read().await;
133 if let Some(metadata) = cache.get(file_id) {
134 debug!("Cache hit for metadata: {}", file_id);
135 return Ok(Some(metadata.clone()));
136 }
137 }
138
139 // Load from database
140 let key = format!("file:{}", file_id);
141 let metadata_bytes = match self.db.get(&key)? {
142 Some(bytes) => bytes,
143 None => {
144 debug!("Metadata not found for file: {}", file_id);
145 return Ok(None);
146 }
147 };
148
149 // Deserialize metadata
150 let metadata: FileMetadata = bincode::deserialize(&metadata_bytes)
151 .context("Failed to deserialize file metadata")?;
152
153 // Verify integrity
154 if !self.verify_metadata_integrity(&metadata)? {
155 warn!("Metadata integrity verification failed for file: {}", file_id);
156 return Err(anyhow::anyhow!("Metadata integrity verification failed"));
157 }
158
159 // Update cache
160 {
161 let mut cache = self.metadata_cache.write().await;
162 cache.insert(file_id.to_string(), metadata.clone());
163 }
164
165 debug!("Successfully retrieved and verified metadata for file: {}", file_id);
166 Ok(Some(metadata))
167 }
168
169 /// Delete file metadata
170 ///
171 /// Safety: Atomic deletion with comprehensive logging
172 pub async fn delete_metadata(&self, file_id: &str) -> Result<bool> {
173 debug!("Attempting to delete metadata for file: {}", file_id);
174
175 let key = format!("file:{}", file_id);
176
177 // Check if metadata exists
178 if self.db.get(&key)?.is_none() {
179 debug!("Cannot delete non-existent metadata: {}", file_id);
180 return Ok(false);
181 }
182
183 // Delete from database
184 self.db.delete(&key)
185 .context("Failed to delete metadata from database")?;
186
187 // Remove from cache
188 {
189 let mut cache = self.metadata_cache.write().await;
190 cache.remove(file_id);
191 }
192
193 info!("Successfully deleted metadata for file: {}", file_id);
194 Ok(true)
195 }
196
197 /// List all stored files with optional filtering
198 ///
199 /// Transparency: Provides comprehensive file listing for audit
200 pub async fn list_files(&self, limit: Option<usize>) -> Result<Vec<(String, FileMetadata)>> {
201 debug!("Listing stored files (limit: {:?})", limit);
202
203 let mut files = Vec::new();
204 let iter = self.db.iterator(rocksdb::IteratorMode::Start);
205
206 for (i, item) in iter.enumerate() {
207 if let Some(limit) = limit {
208 if i >= limit {
209 break;
210 }
211 }
212
213 let (key, value) = item?;
214 let key_str = String::from_utf8_lossy(&key);
215
216 // Only process file metadata keys
217 if !key_str.starts_with("file:") {
218 continue;
219 }
220
221 let file_id = key_str.strip_prefix("file:").unwrap().to_string();
222
223 match bincode::deserialize::<FileMetadata>(&value) {
224 Ok(metadata) => {
225 if self.verify_metadata_integrity(&metadata)? {
226 files.push((file_id, metadata));
227 } else {
228 warn!("Skipping file with corrupted metadata: {}", file_id);
229 }
230 }
231 Err(e) => {
232 warn!("Failed to deserialize metadata for {}: {}", file_id, e);
233 }
234 }
235 }
236
237 debug!("Retrieved {} files", files.len());
238 Ok(files)
239 }
240
241 /// Check if file metadata exists
242 pub async fn file_exists(&self, file_id: &str) -> Result<bool> {
243 // Check cache first
244 {
245 let cache = self.metadata_cache.read().await;
246 if cache.contains_key(file_id) {
247 return Ok(true);
248 }
249 }
250
251 // Check database
252 let key = format!("file:{}", file_id);
253 Ok(self.db.get(&key)?.is_some())
254 }
255
256 /// Calculate checksum for metadata integrity verification
257 fn calculate_metadata_checksum(&self, metadata: &FileMetadata) -> String {
258 let mut hasher = blake3::Hasher::new();
259 hasher.update(metadata.name.as_bytes());
260 hasher.update(&metadata.size.to_le_bytes());
261 hasher.update(metadata.file_hash.as_bytes());
262 hasher.update(&metadata.created_at.to_le_bytes());
263 hasher.update(&metadata.permissions.to_le_bytes());
264
265 // Include chunk IDs in checksum
266 for chunk_id in &metadata.chunk_ids {
267 hasher.update(chunk_id.as_bytes());
268 }
269
270 hex::encode(hasher.finalize().as_bytes())
271 }
272
273 /// Verify metadata integrity using checksum
274 ///
275 /// Safety: Prevents use of corrupted metadata
276 fn verify_metadata_integrity(&self, metadata: &FileMetadata) -> Result<bool> {
277 let computed_checksum = self.calculate_metadata_checksum(metadata);
278 Ok(computed_checksum == metadata.checksum)
279 }
280 }