use std::path::PathBuf; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use bytes::Bytes; use dav_server::{ davpath::DavPath, fs::{DavFileSystem, OpenOptions}, }; use rand::RngCore; use sanctum::{ crypto::{derive_kek, generate_dek, generate_salt, wrap_dek, KdfParams, FORMAT_VERSION}, storage::Database, verify::verify_container, vfs::SanctumFs, }; /// Live-Crash- und Stresstest: Simuliert harten Verbindungsabbruch und Power-Cut /// während intensiver paralleler Schreibvorgänge im VFS. #[tokio::test] async fn test_live_crash_and_recovery_stress() { let temp_dir = std::env::temp_dir(); let container_path: PathBuf = temp_dir.join(format!("sanctum_live_stress_{}.sanctum", std::process::id())); if container_path.exists() { let _ = std::fs::remove_file(&container_path); } let password = "LiveStressPassword2026!"; let salt = generate_salt(); let kdf_params = KdfParams { memory_cost: 1024, time_cost: 1, parallelism: 1, }; let kek = derive_kek(password, &salt, &kdf_params).expect("KEK derivation"); let dek = generate_dek(); let (wrapped_dek, header_nonce, header_tag) = wrap_dek(&kek, &dek).expect("DEK wrapping"); // 1. Initialisierung des Containers { let db = Database::open(&container_path).expect("Open database"); db.init_schema(&salt, &kdf_params, &wrapped_dek, &header_nonce, &header_tag) .expect("Init schema"); db.checkpoint().expect("Initial Checkpoint"); } // 2. Parallele Schreiblast mit SanctumFs erzeugen let stop_signal = Arc::new(AtomicBool::new(false)); let db = Database::open(&container_path).expect("Open database for VFS"); let fs = SanctumFs::new(db, dek.clone(), FORMAT_VERSION); let mut handles = Vec::new(); // Spawn 4 parallele Schreiber for worker_id in 0..4 { let fs_clone = fs.clone(); let stop_clone = stop_signal.clone(); let handle = tokio::spawn(async move { let mut file_idx = 0; while !stop_clone.load(Ordering::Relaxed) && file_idx < 10 { let file_path_str = format!("/worker_{}_file_{}.dat", worker_id, file_idx); let dav_path = DavPath::new(&file_path_str).unwrap(); let mut opts = OpenOptions::default(); opts.write = true; opts.create = true; opts.truncate = true; // Datei anlegen let mut file = match fs_clone.open(&dav_path, opts).await { Ok(f) => f, Err(_) => break, }; // Mehrere 256-KB Blöcke schreiben (über mehrere Chunks hinweg) let mut payload = vec![0u8; 256 * 1024]; rand::thread_rng().fill_bytes(&mut payload); for _ in 0..6 { if stop_clone.load(Ordering::Relaxed) { break; } let _ = file.write_bytes(Bytes::copy_from_slice(&payload)).await; } let _ = file.flush().await; file_idx += 1; } }); handles.push(handle); } // Lass die Worker 500ms unter Volllast schreiben tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; // 3. Simuliere abrupten Prozessabbruch (Hard Kill / Power Cut) // Wir brechen die Worker hart ab (Cancel) und verwerfen das FS-Handle ohne sauberen Unmount stop_signal.store(true, Ordering::SeqCst); for h in handles { h.abort(); // Simuliert Kill } // FS ohne Checkpoint/Drop-Finalisierung freigeben drop(fs); // 4. Recovery & Integritätsprüfung nach Crash // Das System muss die SQLite WAL-Datei automatisch erkennen und verarbeiten let verify_result = verify_container(&container_path, Some(&dek), false).expect("Verify post-crash"); assert!( verify_result.is_healthy(), "Container muss nach Crash vollkommen konsistent sein! Fehler: {:?}", verify_result.errors ); assert_eq!(verify_result.corrupted_chunks, 0, "Keine korrupten Chunks erlaubt"); // 5. Konsistentes Weiterarbeiten nach dem Absturz let db_recovered = Database::open(&container_path).expect("Open database after crash"); let fs_recovered = SanctumFs::new(db_recovered, dek.clone(), FORMAT_VERSION); // Neue Datei im wiederhergestellten Dateisystem anlegen und lesen let recovery_test_path = DavPath::new("/post_crash_verification.txt").unwrap(); { let mut opts = OpenOptions::default(); opts.write = true; opts.create = true; opts.truncate = true; let mut file = fs_recovered .open(&recovery_test_path, opts) .await .expect("Create post-crash file"); file.write_bytes(Bytes::from_static(b"Sanctum Crash Consistency Verified!")) .await .expect("Write post crash file"); file.flush().await.expect("Flush post crash file"); } // Datei wieder einlesen { let mut opts = OpenOptions::default(); opts.read = true; let mut file = fs_recovered .open(&recovery_test_path, opts) .await .expect("Read post-crash file"); let bytes = file.read_bytes(1024).await.expect("Read bytes"); assert_eq!(&bytes[..], b"Sanctum Crash Consistency Verified!"); } // Sauberes Aufräumen der Testdatei drop(fs_recovered); let _ = std::fs::remove_file(&container_path); }