use std::collections::HashMap; use std::fmt::Debug; use std::io::SeekFrom; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::{Arc, Mutex}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use anyhow::{bail, Result}; use bytes::{Buf, Bytes, BytesMut}; use dav_server::{ davpath::DavPath, fs::{ DavDirEntry, DavFile, DavFileSystem, DavMetaData, FsError, FsFuture, FsStream, OpenOptions, ReadDirMeta, }, }; use futures_util::stream; use rand::rngs::OsRng; use rand::RngCore; use serde::{Deserialize, Serialize}; use tracing::{error, warn}; use zeroize::{Zeroize, Zeroizing}; use crate::crypto::{decrypt_chunk, encrypt_chunk, CHUNK_SIZE}; use crate::storage::Database; use crate::vfs::{is_leak_file_with_custom, SanctumDirEntry, SanctumMetaData}; pub const CARRIER_MAGIC: &[u8; 8] = b"SANCTCAR"; pub const CARRIER_VERSION: u32 = 1; /// Manifest für das steganografische Dateisystem innerhalb des Alibi-Carriers (Block 0). #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CarrierManifest { pub magic: [u8; 8], pub version: u32, pub total_blocks: u32, pub free_blocks: Vec, pub next_inode_id: i64, pub inodes: HashMap, } impl CarrierManifest { pub fn new(total_blocks: u32) -> Self { let mut inodes = HashMap::new(); let now = SystemTime::now() .duration_since(UNIX_EPOCH) .map(|d| d.as_secs()) .unwrap_or(0); // Inode 1 ist das Wurzelverzeichnis '/' inodes.insert( 1, CarrierInode { id: 1, parent_id: None, name: String::new(), is_dir: true, size: 0, created_at: now, modified_at: now, blocks: Vec::new(), }, ); // Block 0 ist für das Manifest reserviert. Nutzblöcke sind 1..total_blocks-1. let free_blocks = (1..total_blocks).collect(); Self { magic: *CARRIER_MAGIC, version: CARRIER_VERSION, total_blocks, free_blocks, next_inode_id: 2, inodes, } } } /// Inode-Eintrag für Dateien und Verzeichnisse im Carrier-Dateisystem. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CarrierInode { pub id: i64, pub parent_id: Option, pub name: String, pub is_dir: bool, pub size: u64, pub created_at: u64, pub modified_at: u64, pub blocks: Vec, // Indizes der 1-MB-Blöcke innerhalb der Trägerdatei (1..total_blocks-1) } pub const CARRIER_BLOCK_PAYLOAD_SIZE: usize = CHUNK_SIZE - 64; /// Liest einen Carrier-Block mit Zwei-Schichten-AEAD: /// 1. Äußere Entschlüsselung mit dek_outer (DEK_0) /// 2. Innere Entschlüsselung mit dek_inner (DEK_1) pub fn read_carrier_block( db: &Database, carrier_node_id: i64, block_idx: u32, dek_outer: &[u8; 32], dek_inner: &[u8; 32], format_version: u32, ) -> Result> { let chunk_rec = db .read_chunk(carrier_node_id, block_idx)? .ok_or_else(|| anyhow::anyhow!("Carrier-Chunk #{} nicht gefunden", block_idx))?; // 1. Äußere Schicht entschlüsseln (mit dek_outer = DEK_0) let outer_decrypted = decrypt_chunk( dek_outer, carrier_node_id, block_idx, &chunk_rec.ciphertext, &chunk_rec.nonce, &chunk_rec.tag, format_version, chunk_rec.generation, )?; // Das outer_decrypted enthält: inner_nonce (12B) || inner_tag (16B) || inner_ct_len (4B LE) || inner_ct || CSPRNG-Padding if outer_decrypted.len() < 32 { bail!( "Carrier-Block #{}: Äußere Nutzdaten zu kurz (< 32 Bytes)", block_idx ); } let mut inner_nonce = [0u8; 12]; inner_nonce.copy_from_slice(&outer_decrypted[0..12]); let mut inner_tag = [0u8; 16]; inner_tag.copy_from_slice(&outer_decrypted[12..28]); let inner_ct_len = u32::from_le_bytes(outer_decrypted[28..32].try_into().unwrap()) as usize; if 32 + inner_ct_len > outer_decrypted.len() { bail!( "Carrier-Block #{}: Ungültige innere Ciphertext-Länge {} (verfügbar: {})", block_idx, inner_ct_len, outer_decrypted.len() - 32 ); } let inner_ciphertext = &outer_decrypted[32..32 + inner_ct_len]; // 2. Innere Schicht entschlüsseln (mit dek_inner = DEK_1) let inner_plaintext = decrypt_chunk( dek_inner, carrier_node_id, block_idx, inner_ciphertext, &inner_nonce, &inner_tag, format_version, chunk_rec.generation, )?; Ok(inner_plaintext) } /// Schreibt einen Carrier-Block mit Zwei-Schichten-AEAD: /// 1. Innere Verschlüsselung mit dek_inner (DEK_1) /// 2. Auffüllen auf exakt CHUNK_SIZE mit CSPRNG-Zufallsdaten /// 3. Äußere Verschlüsselung mit dek_outer (DEK_0) pub fn write_carrier_block( db: &Database, carrier_node_id: i64, block_idx: u32, dek_outer: &[u8; 32], dek_inner: &[u8; 32], plaintext: &[u8], format_version: u32, ) -> Result<()> { let gen = 0u64; // 1. Innere Schicht verschlüsseln (mit dek_inner = DEK_1) let (inner_ct, inner_nonce, inner_tag) = encrypt_chunk( dek_inner, carrier_node_id, block_idx, plaintext, format_version, gen, )?; let inner_ct_len = inner_ct.len() as u32; // 2. Äußere Nutzlast vorbereiten: Exakt CHUNK_SIZE (1 MB) // Format: inner_nonce (12B) || inner_tag (16B) || inner_ct_len (4B LE) || inner_ct || CSPRNG-Padding let mut outer_plaintext = vec![0u8; CHUNK_SIZE]; outer_plaintext[0..12].copy_from_slice(&inner_nonce); outer_plaintext[12..28].copy_from_slice(&inner_tag); outer_plaintext[28..32].copy_from_slice(&inner_ct_len.to_le_bytes()); let ct_end = 32 + inner_ct.len(); if ct_end > CHUNK_SIZE { bail!( "Carrier-Block #{}: Payload überschreitet Blockgröße ({} > {})", block_idx, ct_end, CHUNK_SIZE ); } outer_plaintext[32..ct_end].copy_from_slice(&inner_ct); // Nur den ungenutzten Slack-Space mit kryptografischem Zufall auffüllen (~30x schneller als 1MB OsRng) if ct_end < CHUNK_SIZE { OsRng.fill_bytes(&mut outer_plaintext[ct_end..]); } // 3. Äußere Schicht verschlüsseln (mit dek_outer = DEK_0) let (outer_ct, outer_nonce, outer_tag) = encrypt_chunk( dek_outer, carrier_node_id, block_idx, &outer_plaintext, format_version, gen, )?; // 4. In SQLite schreiben (in-place Überschreiben des bestehenden Chunks) db.write_chunk( carrier_node_id, block_idx, gen, &outer_nonce, &outer_tag, &outer_ct, )?; Ok(()) } /// Überschreibt einen freigegebenen Carrier-Block mit frischem kryptografischen Rauschen /// (unter dek_outer verschlüsselt), sodass er weiterhin als 100% gültiger DEK_0-Chunk authentifiziert. pub fn shred_carrier_block( db: &Database, carrier_node_id: i64, block_idx: u32, dek_outer: &[u8; 32], format_version: u32, ) -> Result<()> { let mut noise = vec![0u8; CHUNK_SIZE]; OsRng.fill_bytes(&mut noise); let gen = 0u64; let (outer_ct, outer_nonce, outer_tag) = encrypt_chunk( dek_outer, carrier_node_id, block_idx, &noise, format_version, gen, )?; db.write_chunk( carrier_node_id, block_idx, gen, &outer_nonce, &outer_tag, &outer_ct, )?; Ok(()) } /// Interner Zustand des Carrier-Dateisystems. pub struct CarrierFsInner { pub db: Database, pub carrier_node_id: i64, pub dek_outer: Arc>, pub dek_inner: Arc>, pub format_version: u32, pub anti_leak: bool, pub custom_leak_rules: Arc>, pub leak_counter: Arc, pub manifest: CarrierManifest, pub last_activity: Arc, } impl CarrierFsInner { pub fn is_leak(&self, name: &str) -> bool { self.anti_leak && is_leak_file_with_custom(name, &self.custom_leak_rules) } pub fn save_manifest(&mut self) -> Result<()> { let manifest_bytes = serde_json::to_vec(&self.manifest)?; if manifest_bytes.len() > (crate::crypto::CHUNK_SIZE - 64) { bail!( "Carrier-Manifest überschreitet die maximale Blockgröße ({} > {} Bytes).", manifest_bytes.len(), crate::crypto::CHUNK_SIZE - 64 ); } if manifest_bytes.len() > 800_000 { warn!( "Carrier-Manifest erreicht 80% der Blockkapazität ({}/{} Bytes)", manifest_bytes.len(), crate::crypto::CHUNK_SIZE - 64 ); } write_carrier_block( &self.db, self.carrier_node_id, 0, &self.dek_outer, &self.dek_inner, &manifest_bytes, self.format_version, )?; Ok(()) } pub fn allocate_block(&mut self) -> Result { self.manifest .free_blocks .pop() .ok_or(FsError::InsufficientStorage) } pub fn free_block(&mut self, block_idx: u32) -> Result<(), FsError> { let _ = shred_carrier_block( &self.db, self.carrier_node_id, block_idx, &self.dek_outer, self.format_version, ); self.manifest.free_blocks.push(block_idx); Ok(()) } pub fn resolve_path(&self, raw_path: &str) -> Option { let trimmed = raw_path.trim_matches('/'); if trimmed.is_empty() { return self.manifest.inodes.get(&1).cloned(); } let segments: Vec<&str> = trimmed.split('/').filter(|s| !s.is_empty()).collect(); let mut current_id = 1i64; for (idx, segment) in segments.iter().enumerate() { let child = self.manifest.inodes.values().find(|inode| { inode.parent_id == Some(current_id) && inode.name.as_str() == *segment })?; if idx + 1 < segments.len() && !child.is_dir { return None; } current_id = child.id; } self.manifest.inodes.get(¤t_id).cloned() } pub fn split_parent_and_name<'a>(&self, path: &'a str) -> (&'a str, &'a str) { let trimmed = path.trim_matches('/'); match trimmed.rfind('/') { Some(pos) => (&trimmed[..pos], &trimmed[pos + 1..]), None => ("", trimmed), } } } impl Drop for CarrierFsInner { fn drop(&mut self) { crate::windows::unlock_memory(self.dek_outer.as_ptr(), 32); crate::windows::unlock_memory(self.dek_inner.as_ptr(), 32); } } /// WebDAV-Filesystem-Treiber für den steganografischen Alibi-Carrier (Hidden Vault). #[derive(Clone)] pub struct CarrierFs { inner: Arc>, } impl CarrierFs { /// Lädt ein bestehendes Carrier-Dateisystem mit benutzerdefinierten Anti-Leak-Regeln (V-04). pub fn load_with_leak_rules( db: Database, carrier_node_id: i64, dek_outer: Arc>, dek_inner: Arc>, format_version: u32, anti_leak: bool, custom_leak_rules: Vec, ) -> Result { let manifest_bytes = read_carrier_block( &db, carrier_node_id, 0, &dek_outer, &dek_inner, format_version, )?; let manifest: CarrierManifest = serde_json::from_slice(&manifest_bytes)?; if manifest.magic != *CARRIER_MAGIC { bail!("Ungültige Carrier-Magic-Bytes in Block 0"); } let now = SystemTime::now() .duration_since(UNIX_EPOCH) .map(|d| d.as_secs()) .unwrap_or(0); // Forensischer RAM-Paging-Schutz via VirtualLock crate::windows::lock_memory(dek_outer.as_ptr(), 32); crate::windows::lock_memory(dek_inner.as_ptr(), 32); let inner = CarrierFsInner { db, carrier_node_id, dek_outer, dek_inner, format_version, anti_leak, custom_leak_rules: Arc::new(custom_leak_rules), leak_counter: Arc::new(AtomicU64::new(0)), manifest, last_activity: Arc::new(AtomicU64::new(now)), }; Ok(Self { inner: Arc::new(Mutex::new(inner)), }) } /// Lädt ein bestehendes Carrier-Dateisystem aus Block 0 der Trägerdatei. pub fn load( db: Database, carrier_node_id: i64, dek_outer: Arc>, dek_inner: Arc>, format_version: u32, anti_leak: bool, ) -> Result { Self::load_with_leak_rules( db, carrier_node_id, dek_outer, dek_inner, format_version, anti_leak, Vec::new(), ) } pub fn leak_counter(&self) -> Arc { let inner = self.inner.lock().unwrap_or_else(|e| e.into_inner()); inner.leak_counter.clone() } pub fn last_activity(&self) -> Arc { let inner = self.inner.lock().unwrap_or_else(|e| e.into_inner()); inner.last_activity.clone() } pub fn touch(&self) { let now = SystemTime::now() .duration_since(UNIX_EPOCH) .map(|d| d.as_secs()) .unwrap_or(0); let inner = self.inner.lock().unwrap(); inner.last_activity.store(now, Ordering::Relaxed); } fn path_to_str(path: &DavPath) -> String { String::from_utf8_lossy(path.as_bytes()).to_string() } } impl DavFileSystem for CarrierFs { fn open<'a>( &'a self, path: &'a DavPath, options: OpenOptions, ) -> FsFuture<'a, Box> { Box::pin(async move { self.touch(); let path_str = Self::path_to_str(path); let mut inner = self.inner.lock().unwrap(); let (parent_path, file_name) = inner.split_parent_and_name(&path_str); if inner.is_leak(file_name) { if options.create || options.create_new || options.write || options.append || options.truncate { inner.leak_counter.fetch_add(1, Ordering::Relaxed); return Err(FsError::Forbidden); } } let existing_node = inner.resolve_path(&path_str); let inode = match existing_node { Some(mut n) => { if n.is_dir && (options.write || options.append) { return Err(FsError::Forbidden); } if options.create_new { return Err(FsError::Exists); } if options.truncate { for b in n.blocks.drain(..) { let _ = shred_carrier_block( &inner.db, inner.carrier_node_id, b, &inner.dek_outer, inner.format_version, ); inner.manifest.free_blocks.push(b); } let now = SystemTime::now() .duration_since(UNIX_EPOCH) .map(|d| d.as_secs()) .unwrap_or(0); n.size = 0; n.modified_at = now; inner.manifest.inodes.insert(n.id, n.clone()); let _ = inner.save_manifest(); } n } None => { if options.create || options.create_new { let parent = inner.resolve_path(parent_path).ok_or(FsError::NotFound)?; if !parent.is_dir { return Err(FsError::Forbidden); } let now = SystemTime::now() .duration_since(UNIX_EPOCH) .map(|d| d.as_secs()) .unwrap_or(0); let new_id = inner.manifest.next_inode_id; inner.manifest.next_inode_id += 1; let new_inode = CarrierInode { id: new_id, parent_id: Some(parent.id), name: file_name.to_string(), is_dir: false, size: 0, created_at: now, modified_at: now, blocks: Vec::new(), }; inner.manifest.inodes.insert(new_id, new_inode.clone()); let _ = inner.save_manifest(); new_inode } else { return Err(FsError::NotFound); } } }; drop(inner); let file = CarrierFile::new(inode, self.inner.clone(), options.append); Ok(Box::new(file) as Box) }) } fn read_dir<'a>( &'a self, path: &'a DavPath, _meta: ReadDirMeta, ) -> FsFuture<'a, FsStream>> { Box::pin(async move { let path_str = Self::path_to_str(path); let inner = self.inner.lock().unwrap(); let node = inner.resolve_path(&path_str).ok_or(FsError::NotFound)?; if !node.is_dir { return Err(FsError::Forbidden); } let entries: Vec, FsError>> = inner .manifest .inodes .values() .filter(|child| child.parent_id == Some(node.id)) .filter(|child| { if inner.is_leak(&child.name) { inner.leak_counter.fetch_add(1, Ordering::Relaxed); false } else { true } }) .map(|child| { Ok(Box::new(SanctumDirEntry { name: child.name.clone(), meta: SanctumMetaData { is_dir: child.is_dir, size: child.size, created_at: UNIX_EPOCH + Duration::from_secs(child.created_at), modified_at: UNIX_EPOCH + Duration::from_secs(child.modified_at), }, }) as Box) }) .collect(); Ok(Box::pin(stream::iter(entries)) as FsStream>) }) } fn metadata<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, Box> { Box::pin(async move { let path_str = Self::path_to_str(path); let inner = self.inner.lock().unwrap(); let node = inner.resolve_path(&path_str).ok_or(FsError::NotFound)?; let meta = SanctumMetaData { is_dir: node.is_dir, size: node.size, created_at: UNIX_EPOCH + Duration::from_secs(node.created_at), modified_at: UNIX_EPOCH + Duration::from_secs(node.modified_at), }; Ok(Box::new(meta) as Box) }) } fn symlink_metadata<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, Box> { self.metadata(path) } fn create_dir<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, ()> { Box::pin(async move { self.touch(); let path_str = Self::path_to_str(path); let mut inner = self.inner.lock().unwrap(); let (parent_path, dir_name) = inner.split_parent_and_name(&path_str); if inner.is_leak(dir_name) { inner.leak_counter.fetch_add(1, Ordering::Relaxed); return Err(FsError::Forbidden); } if inner.resolve_path(&path_str).is_some() { return Err(FsError::Exists); } let parent = inner.resolve_path(parent_path).ok_or(FsError::NotFound)?; if !parent.is_dir { return Err(FsError::Forbidden); } let now = SystemTime::now() .duration_since(UNIX_EPOCH) .map(|d| d.as_secs()) .unwrap_or(0); let new_id = inner.manifest.next_inode_id; inner.manifest.next_inode_id += 1; let new_dir = CarrierInode { id: new_id, parent_id: Some(parent.id), name: dir_name.to_string(), is_dir: true, size: 0, created_at: now, modified_at: now, blocks: Vec::new(), }; inner.manifest.inodes.insert(new_id, new_dir); inner.save_manifest().map_err(|_| FsError::GeneralFailure)?; Ok(()) }) } fn remove_dir<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, ()> { Box::pin(async move { self.touch(); let path_str = Self::path_to_str(path); let mut inner = self.inner.lock().unwrap(); let node = inner.resolve_path(&path_str).ok_or(FsError::NotFound)?; if !node.is_dir { return Err(FsError::Forbidden); } if node.id == 1 { // Wurzelknoten darf nicht gelöscht werden return Err(FsError::Forbidden); } // Prüfe, ob das Verzeichnis leer ist let has_children = inner .manifest .inodes .values() .any(|child| child.parent_id == Some(node.id)); if has_children { return Err(FsError::Forbidden); } inner.manifest.inodes.remove(&node.id); inner.save_manifest().map_err(|_| FsError::GeneralFailure)?; Ok(()) }) } fn remove_file<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, ()> { Box::pin(async move { self.touch(); let path_str = Self::path_to_str(path); let mut inner = self.inner.lock().unwrap(); let node = inner.resolve_path(&path_str).ok_or(FsError::NotFound)?; if node.is_dir { return Err(FsError::Forbidden); } for b in node.blocks { let _ = shred_carrier_block( &inner.db, inner.carrier_node_id, b, &inner.dek_outer, inner.format_version, ); inner.manifest.free_blocks.push(b); } inner.manifest.inodes.remove(&node.id); inner.save_manifest().map_err(|_| FsError::GeneralFailure)?; Ok(()) }) } fn rename<'a>(&'a self, from: &'a DavPath, to: &'a DavPath) -> FsFuture<'a, ()> { Box::pin(async move { self.touch(); let from_str = Self::path_to_str(from); let to_str = Self::path_to_str(to); let mut inner = self.inner.lock().unwrap(); let node = inner.resolve_path(&from_str).ok_or(FsError::NotFound)?; let (to_parent_path, to_name) = inner.split_parent_and_name(&to_str); if inner.is_leak(to_name) { inner.leak_counter.fetch_add(1, Ordering::Relaxed); return Err(FsError::Forbidden); } let to_parent = inner .resolve_path(to_parent_path) .ok_or(FsError::NotFound)?; if !to_parent.is_dir { return Err(FsError::Forbidden); } // Falls Zieldatei existiert und kein Verzeichnis ist: überschreiben if let Some(dest) = inner.resolve_path(&to_str) { if dest.is_dir { return Err(FsError::Forbidden); } for b in dest.blocks { let _ = shred_carrier_block( &inner.db, inner.carrier_node_id, b, &inner.dek_outer, inner.format_version, ); inner.manifest.free_blocks.push(b); } inner.manifest.inodes.remove(&dest.id); } let now = SystemTime::now() .duration_since(UNIX_EPOCH) .map(|d| d.as_secs()) .unwrap_or(0); if let Some(inode) = inner.manifest.inodes.get_mut(&node.id) { inode.parent_id = Some(to_parent.id); inode.name = to_name.to_string(); inode.modified_at = now; } inner.save_manifest().map_err(|_| FsError::GeneralFailure)?; Ok(()) }) } fn copy<'a>(&'a self, from: &'a DavPath, to: &'a DavPath) -> FsFuture<'a, ()> { Box::pin(async move { self.touch(); let from_str = Self::path_to_str(from); let to_str = Self::path_to_str(to); if from_str == to_str { return Ok(()); } let mut inner = self.inner.lock().unwrap(); let node = inner.resolve_path(&from_str).ok_or(FsError::NotFound)?; if node.is_dir { return Err(FsError::NotImplemented); } let (to_parent_path, to_name) = inner.split_parent_and_name(&to_str); if inner.is_leak(to_name) { inner.leak_counter.fetch_add(1, Ordering::Relaxed); return Err(FsError::Forbidden); } let to_parent = inner .resolve_path(to_parent_path) .ok_or(FsError::NotFound)?; if !to_parent.is_dir { return Err(FsError::Forbidden); } // Falls Zieldatei existiert und ein Verzeichnis ist: Fehler if let Some(dest) = inner.resolve_path(&to_str) { if dest.is_dir { return Err(FsError::Forbidden); } for b in dest.blocks { let _ = shred_carrier_block( &inner.db, inner.carrier_node_id, b, &inner.dek_outer, inner.format_version, ); inner.manifest.free_blocks.push(b); } inner.manifest.inodes.remove(&dest.id); } if inner.manifest.free_blocks.len() < node.blocks.len() { return Err(FsError::InsufficientStorage); } let mut dest_blocks = Vec::with_capacity(node.blocks.len()); for &src_block_idx in &node.blocks { let dest_block_idx = match inner.allocate_block() { Ok(b) => b, Err(e) => { for b in dest_blocks { let _ = inner.free_block(b); } return Err(e); } }; let data = match read_carrier_block( &inner.db, inner.carrier_node_id, src_block_idx, &inner.dek_outer, &inner.dek_inner, inner.format_version, ) { Ok(d) => d, Err(e) => { error!("Fehler beim Lesen des Quell-Blocks während copy: {e}"); let _ = inner.free_block(dest_block_idx); for b in dest_blocks { let _ = inner.free_block(b); } return Err(FsError::GeneralFailure); } }; if let Err(e) = write_carrier_block( &inner.db, inner.carrier_node_id, dest_block_idx, &inner.dek_outer, &inner.dek_inner, &data, inner.format_version, ) { error!("Fehler beim Schreiben des Ziel-Blocks während copy: {e}"); let _ = inner.free_block(dest_block_idx); for b in dest_blocks { let _ = inner.free_block(b); } return Err(FsError::GeneralFailure); } dest_blocks.push(dest_block_idx); } let now = SystemTime::now() .duration_since(UNIX_EPOCH) .map(|d| d.as_secs()) .unwrap_or(0); let new_id = inner.manifest.next_inode_id; inner.manifest.next_inode_id += 1; let new_inode = CarrierInode { id: new_id, parent_id: Some(to_parent.id), name: to_name.to_string(), is_dir: false, size: node.size, created_at: now, modified_at: now, blocks: dest_blocks, }; inner.manifest.inodes.insert(new_id, new_inode); inner.save_manifest().map_err(|_| FsError::GeneralFailure)?; Ok(()) }) } fn get_quota(&self) -> FsFuture<'_, (u64, Option)> { Box::pin(async move { self.touch(); let inner = self.inner.lock().unwrap_or_else(|e| e.into_inner()); let total_capacity = inner.manifest.total_blocks as u64 * crate::crypto::CHUNK_SIZE as u64; let used_bytes = inner .manifest .inodes .values() .filter(|i| !i.is_dir) .map(|i| i.size) .sum::(); Ok((used_bytes, Some(total_capacity))) }) } } /// Datei-Handle für Dateien innerhalb des Carrier-Dateisystems mit Streaming und Chunk-Pufferung. pub struct CarrierFile { inode_id: i64, file_size: u64, cursor: u64, blocks: Vec, inner_fs: Arc>, meta: SanctumMetaData, // (block_index_in_file, decrypted_payload, is_dirty) cached_block: Option<(usize, Vec, bool)>, } impl Debug for CarrierFile { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("CarrierFile") .field("inode_id", &self.inode_id) .field("file_size", &self.file_size) .field("cursor", &self.cursor) .finish() } } impl CarrierFile { pub fn new(inode: CarrierInode, inner_fs: Arc>, append: bool) -> Self { let meta = SanctumMetaData { is_dir: inode.is_dir, size: inode.size, created_at: UNIX_EPOCH + Duration::from_secs(inode.created_at), modified_at: UNIX_EPOCH + Duration::from_secs(inode.modified_at), }; let cursor = if append { inode.size } else { 0 }; Self { inode_id: inode.id, file_size: inode.size, cursor, blocks: inode.blocks, inner_fs, meta, cached_block: None, } } fn touch(&self) { let now = SystemTime::now() .duration_since(UNIX_EPOCH) .map(|d| d.as_secs()) .unwrap_or(0); let inner = self.inner_fs.lock().unwrap(); inner.last_activity.store(now, Ordering::Relaxed); } fn flush_cached_block(&mut self) -> Result<(), FsError> { if let Some((block_idx_in_file, ref data, true)) = self.cached_block { let mut inner = self.inner_fs.lock().unwrap(); // Allokiere Blöcke bis zum aktuellen Index falls nötig while self.blocks.len() <= block_idx_in_file { let new_block = inner.allocate_block()?; self.blocks.push(new_block); } let carrier_block_idx = self.blocks[block_idx_in_file]; write_carrier_block( &inner.db, inner.carrier_node_id, carrier_block_idx, &inner.dek_outer, &inner.dek_inner, data, inner.format_version, ) .map_err(|e| { error!("Fehler beim Schreiben des Carrier-Blocks: {e}"); FsError::GeneralFailure })?; if let Some((_, _, ref mut dirty)) = self.cached_block { *dirty = false; } } Ok(()) } fn ensure_block_loaded(&mut self, block_idx_in_file: usize) -> Result<&mut Vec, FsError> { if let Some((cached_idx, _, _)) = self.cached_block { if cached_idx == block_idx_in_file { return Ok(&mut self.cached_block.as_mut().unwrap().1); } } self.flush_cached_block()?; if let Some((_, ref mut data, _)) = self.cached_block { data.zeroize(); } let data = if block_idx_in_file < self.blocks.len() { let carrier_block_idx = self.blocks[block_idx_in_file]; let inner = self.inner_fs.lock().unwrap(); match read_carrier_block( &inner.db, inner.carrier_node_id, carrier_block_idx, &inner.dek_outer, &inner.dek_inner, inner.format_version, ) { Ok(bytes) => bytes, Err(e) => { error!("Fehler beim Lesen des Carrier-Blocks: {e}"); return Err(FsError::GeneralFailure); } } } else { Vec::new() }; self.cached_block = Some((block_idx_in_file, data, false)); Ok(&mut self.cached_block.as_mut().unwrap().1) } } impl DavFile for CarrierFile { fn metadata(&mut self) -> FsFuture<'_, Box> { let meta = self.meta.clone(); Box::pin(async move { Ok(Box::new(meta) as Box) }) } fn read_bytes(&mut self, count: usize) -> FsFuture<'_, Bytes> { self.touch(); Box::pin(async move { if self.cursor >= self.file_size { return Ok(Bytes::new()); } let available = (self.file_size - self.cursor) as usize; let to_read = count.min(available); let mut result = BytesMut::with_capacity(to_read); let mut remaining = to_read; while remaining > 0 { let block_idx = (self.cursor / CARRIER_BLOCK_PAYLOAD_SIZE as u64) as usize; let offset_in_block = (self.cursor % CARRIER_BLOCK_PAYLOAD_SIZE as u64) as usize; let space_in_block = CARRIER_BLOCK_PAYLOAD_SIZE - offset_in_block; let bytes_from_block = remaining.min(space_in_block); let block_data = self.ensure_block_loaded(block_idx)?; if offset_in_block < block_data.len() { let end = (offset_in_block + bytes_from_block).min(block_data.len()); result.extend_from_slice(&block_data[offset_in_block..end]); let actually_read = end - offset_in_block; self.cursor += actually_read as u64; remaining -= actually_read; if actually_read < bytes_from_block { break; } } else { break; } } Ok(result.freeze()) }) } fn write_bytes(&mut self, buf: Bytes) -> FsFuture<'_, ()> { self.touch(); Box::pin(async move { // V-06: Sparse Writes — Lücke zwischen bisherigem Dateiende und Cursor mit Nullen füllen if self.cursor > self.file_size { let target = self.cursor; while self.file_size < target { let block_idx = (self.file_size / CARRIER_BLOCK_PAYLOAD_SIZE as u64) as usize; let offset_in_block = (self.file_size % CARRIER_BLOCK_PAYLOAD_SIZE as u64) as usize; let space_in_block = CARRIER_BLOCK_PAYLOAD_SIZE - offset_in_block; let to_pad = ((target - self.file_size) as usize).min(space_in_block); let block_data = self.ensure_block_loaded(block_idx)?; if block_data.len() < offset_in_block + to_pad { block_data.resize(offset_in_block + to_pad, 0); } let mut is_full = false; if let Some((_, ref d, ref mut dirty)) = self.cached_block { *dirty = true; is_full = d.len() >= CARRIER_BLOCK_PAYLOAD_SIZE; } self.file_size += to_pad as u64; if is_full { self.flush_cached_block()?; } } } let mut src = &buf[..]; while !src.is_empty() { let block_idx = (self.cursor / CARRIER_BLOCK_PAYLOAD_SIZE as u64) as usize; let offset_in_block = (self.cursor % CARRIER_BLOCK_PAYLOAD_SIZE as u64) as usize; let space_in_block = CARRIER_BLOCK_PAYLOAD_SIZE - offset_in_block; let to_write = src.len().min(space_in_block); let block_data = self.ensure_block_loaded(block_idx)?; if block_data.len() < offset_in_block { block_data.resize(offset_in_block, 0); } if block_data.len() < offset_in_block + to_write { block_data.resize(offset_in_block + to_write, 0); } block_data[offset_in_block..offset_in_block + to_write] .copy_from_slice(&src[..to_write]); if let Some((_, _, ref mut dirty)) = self.cached_block { *dirty = true; } self.cursor += to_write as u64; if self.cursor > self.file_size { self.file_size = self.cursor; } if self .cached_block .as_ref() .map(|(_, d, _)| d.len() >= CARRIER_BLOCK_PAYLOAD_SIZE) .unwrap_or(false) { self.flush_cached_block()?; } src = &src[to_write..]; } Ok(()) }) } fn write_buf(&mut self, mut buf: Box) -> FsFuture<'_, ()> { let bytes = buf.copy_to_bytes(buf.remaining()); self.write_bytes(bytes) } fn seek(&mut self, pos: SeekFrom) -> FsFuture<'_, u64> { self.touch(); Box::pin(async move { let new_cursor = match pos { SeekFrom::Start(offset) => offset as i64, SeekFrom::End(offset) => self.file_size as i64 + offset, SeekFrom::Current(offset) => self.cursor as i64 + offset, }; if new_cursor < 0 { return Err(FsError::GeneralFailure); } self.cursor = new_cursor as u64; Ok(self.cursor) }) } fn flush(&mut self) -> FsFuture<'_, ()> { self.touch(); Box::pin(async move { self.flush_cached_block()?; let now = SystemTime::now() .duration_since(UNIX_EPOCH) .map(|d| d.as_secs()) .unwrap_or(0); let mut inner = self.inner_fs.lock().unwrap(); if let Some(inode) = inner.manifest.inodes.get_mut(&self.inode_id) { inode.size = self.file_size; inode.blocks = self.blocks.clone(); inode.modified_at = now; } inner.save_manifest().map_err(|_| FsError::GeneralFailure)?; self.meta.size = self.file_size; self.meta.modified_at = UNIX_EPOCH + Duration::from_secs(now); Ok(()) }) } } impl Drop for CarrierFile { fn drop(&mut self) { if let Err(e) = self.flush_cached_block() { tracing::warn!( "Fehler beim automatischen Flush im CarrierFile::drop: {:?}", e ); } if let Some((_, ref mut data, _)) = self.cached_block { data.zeroize(); } let now = SystemTime::now() .duration_since(UNIX_EPOCH) .map(|d| d.as_secs()) .unwrap_or(0); if let Ok(mut inner) = self.inner_fs.lock() { if let Some(inode) = inner.manifest.inodes.get_mut(&self.inode_id) { inode.size = self.file_size; inode.blocks = self.blocks.clone(); inode.modified_at = now; } let _ = inner.save_manifest(); } } }