use std::path::PathBuf; use bytes::Bytes; use dav_server::davpath::DavPath; use dav_server::fs::{DavFileSystem, FsError, OpenOptions, ReadDirMeta}; use rand::rngs::OsRng; use rand::RngCore; use sanctum::crypto::{ derive_kek, generate_dek, generate_salt, wrap_slot0_payload, wrap_slot1_payload, KdfParams, CHUNK_SIZE, MIN_MEMORY_COST_KIB, MIN_TIME_COST, }; use sanctum::storage::Database; use sanctum::verify::verify_container; use sanctum::vfs::SanctumFs; fn temp_db_path(prefix: &str) -> PathBuf { let mut path = std::env::temp_dir(); let id: u64 = OsRng.next_u64(); path.push(format!("sanctum_test_{}_{}.sanctum", prefix, id)); path } #[tokio::test] async fn test_model_a_carrier_filesystem_and_accounting_attack_resistance() { let path = temp_db_path("carrier_accounting"); let carrier_size_bytes = 10 * 1024 * 1024; // 10 MB (10 Blöcke à 1 MB) let carrier_name = "system_backup.dat"; let pass_decoy = "DecoyPassword2026!"; let pass_hidden = "SuperSecretHiddenPassword2026!"; let kdf_params = KdfParams { memory_cost: MIN_MEMORY_COST_KIB, time_cost: MIN_TIME_COST, parallelism: 1, }; let salt_0 = generate_salt(); let kek_0 = derive_kek(pass_decoy, &salt_0, &kdf_params).unwrap(); let dek_0 = generate_dek(); let salt_1 = generate_salt(); let kek_1 = derive_kek(pass_hidden, &salt_1, &kdf_params).unwrap(); let dek_1 = generate_dek(); let carrier_node_id = 3i64; let (wrapped_0, nonce_0, tag_0) = wrap_slot0_payload(&kek_0, &dek_0, carrier_node_id).unwrap(); let (wrapped_1, nonce_1, tag_1) = wrap_slot1_payload(&kek_1, &dek_1, &dek_0, carrier_node_id).unwrap(); let db = Database::open(&path).expect("Open database"); let created_cid = db .init_schema_with_carrier( &salt_0, &kdf_params, &wrapped_0, &nonce_0, &tag_0, Some(( carrier_name, carrier_size_bytes, &salt_1, &kdf_params, &wrapped_1, &nonce_1, &tag_1, &dek_0, &dek_1, )), ) .expect("Init carrier schema"); db.checkpoint().unwrap(); assert_eq!(created_cid, Some(carrier_node_id)); // 1. Authentifizierung beider Passwörter let meta = db.read_meta().unwrap(); let auth_decoy = meta.authenticate(pass_decoy).expect("Auth decoy"); assert_eq!(auth_decoy.slot_id(), 0); assert_eq!(**auth_decoy.dek(), *dek_0); assert_eq!(auth_decoy.carrier_node_id(), Some(carrier_node_id)); let auth_hidden = meta.authenticate(pass_hidden).expect("Auth hidden"); assert_eq!(auth_hidden.slot_id(), 1); assert_eq!(**auth_hidden.dek(), *dek_1); assert_eq!(*auth_hidden.carrier_dek().unwrap(), *dek_0); assert_eq!(auth_hidden.carrier_node_id(), Some(carrier_node_id)); // 2. Decoy Mount: Schutz der Alibi-Datei (system_backup.dat) let decoy_fs = SanctumFs::with_carrier( db.clone(), auth_decoy.dek().clone(), auth_decoy.carrier_dek(), auth_decoy.carrier_node_id(), auth_decoy.version(), true, 0, ); let carrier_path = DavPath::new("/system_backup.dat").unwrap(); // Metadaten der Alibi-Datei im Decoy prüfen let carrier_meta = decoy_fs .metadata(&carrier_path) .await .expect("Carrier meta"); assert_eq!(carrier_meta.len(), carrier_size_bytes); assert!(!carrier_meta.is_dir()); // Alibi-Datei darf im Decoy-Mount NICHT zum Schreiben geöffnet werden let write_opts = OpenOptions { write: true, ..Default::default() }; assert!( matches!( decoy_fs.open(&carrier_path, write_opts).await, Err(FsError::Forbidden) ), "Alibi-Datei darf nicht zum Schreiben geöffnet werden" ); // Alibi-Datei darf im Decoy-Mount NICHT gelöscht werden assert!( matches!( decoy_fs.remove_file(&carrier_path).await, Err(FsError::Forbidden) ), "Alibi-Datei darf nicht gelöscht werden" ); // Alibi-Datei darf im Decoy-Mount NICHT umbenannt werden let new_name = DavPath::new("/renamed.iso").unwrap(); assert!( matches!( decoy_fs.rename(&carrier_path, &new_name).await, Err(FsError::Forbidden) ), "Alibi-Datei darf nicht umbenannt werden" ); // Alibi-Datei KANN im Decoy-Mount gelesen werden let read_opts = OpenOptions { read: true, ..Default::default() }; let mut file_reader = decoy_fs .open(&carrier_path, read_opts) .await .expect("Open read"); let first_mb = file_reader .read_bytes(CHUNK_SIZE) .await .expect("Read first chunk"); assert_eq!(first_mb.len(), CHUNK_SIZE); // 3. Hidden Mount: Dateisystem-Operationen innerhalb des Alibi-Carriers let hidden_fs = SanctumFs::with_carrier( db.clone(), auth_hidden.dek().clone(), auth_hidden.carrier_dek(), auth_hidden.carrier_node_id(), auth_hidden.version(), true, 1, ); // Wurzelverzeichnis des Hidden Vault auflisten (anfangs leer) let root_path = DavPath::new("/").unwrap(); let mut stream = hidden_fs .read_dir(&root_path, ReadDirMeta::None) .await .expect("Read dir root"); use futures_util::StreamExt; let mut entries = Vec::new(); while let Some(item) = stream.next().await { entries.push(item.unwrap().name()); } assert!( entries.is_empty(), "Hidden Vault Wurzelverzeichnis muss anfangs leer sein" ); // Ordner erstellen let secret_dir = DavPath::new("/Classified").unwrap(); hidden_fs .create_dir(&secret_dir) .await .expect("Create Classified dir"); // Datei im Ordner anlegen und schreiben let secret_file_path = DavPath::new("/Classified/passwords.txt").unwrap(); let create_opts = OpenOptions { create: true, write: true, ..Default::default() }; let mut secret_file = hidden_fs .open(&secret_file_path, create_opts) .await .expect("Create secret file"); let secret_content = b"TopSecretCredentials_2026_SanctumCoreSecureVault"; secret_file .write_bytes(Bytes::from_static(secret_content)) .await .expect("Write secret content"); secret_file.flush().await.expect("Flush secret file"); drop(secret_file); // Datei lesen und verifizieren let read_opts = OpenOptions { read: true, ..Default::default() }; let mut read_handle = hidden_fs .open(&secret_file_path, read_opts) .await .expect("Open secret file for read"); let read_data = read_handle .read_bytes(1024) .await .expect("Read secret bytes"); assert_eq!(&read_data[..], secret_content); drop(read_handle); // Größere Binärdatei schreiben (über 2 MB = 2 Blöcke) let big_file_path = DavPath::new("/Classified/payload.bin").unwrap(); let mut big_file = hidden_fs .open( &big_file_path, OpenOptions { create: true, write: true, ..Default::default() }, ) .await .expect("Create big file"); let payload_size = 2 * 1024 * 1024 + 12345; // 2 MB + 12.345 Bytes let mut payload = vec![0u8; payload_size]; OsRng.fill_bytes(&mut payload); big_file .write_bytes(Bytes::copy_from_slice(&payload)) .await .expect("Write big payload"); big_file.flush().await.expect("Flush big file"); drop(big_file); // Datei zurücklesen und Bit-für-Bit verifizieren let mut read_big = hidden_fs .open( &big_file_path, OpenOptions { read: true, ..Default::default() }, ) .await .expect("Open big file"); let read_big_bytes = read_big .read_bytes(payload_size + 100) .await .expect("Read big file bytes"); assert_eq!(read_big_bytes.len(), payload_size); assert_eq!(&read_big_bytes[..], &payload[..]); drop(read_big); // Datei umbenennen let renamed_path = DavPath::new("/Classified/renamed_payload.bin").unwrap(); hidden_fs .rename(&big_file_path, &renamed_path) .await .expect("Rename file"); assert!(hidden_fs.metadata(&big_file_path).await.is_err()); assert!(hidden_fs.metadata(&renamed_path).await.is_ok()); // Datei löschen (Blöcke werden geshreddert und freigegeben) hidden_fs .remove_file(&renamed_path) .await .expect("Remove file"); assert!(hidden_fs.metadata(&renamed_path).await.is_err()); // Checkpoint SQLite db.checkpoint().unwrap(); // 4. CHUNKS-ACCOUNTING-ANGRIFF & INTEGRITÄTSPRÜFUNG // Ein Angreifer besitzt nur das Decoy-Passwort (dek_0). // Er führt eine 100%-ige kryptografische AEAD-Prüfung aller Chunks in der SQLite-Datenbank durch. // ALLE Chunks müssen fehlerfrei unter DEK_0 entschlüsseln! let report = verify_container(&path, Some(&dek_0), true).expect("Verify with DEK_0"); assert!( report.is_healthy(), "Container muss für einen Angreifer mit DEK_0 100% gesund und fehlerfrei sein! Fehler: {:?}", report.errors ); assert_eq!( report.corrupted_chunks, 0, "Chunks-Accounting: Es darf exakt 0 korrupte Chunks unter DEK_0 geben!" ); assert_eq!( report.orphan_nodes, 0, "Es darf keine verwaisten Knoten geben!" ); // Aufräumen let _ = std::fs::remove_file(&path); } #[tokio::test] async fn test_model_a_container_file_size_invariance() { let path = temp_db_path("carrier_size_invariance"); let carrier_size_bytes = 10 * 1024 * 1024; // 10 MB let pass_decoy = "DecoyPass2026!"; let pass_hidden = "HiddenPass2026!"; let kdf_params = KdfParams { memory_cost: MIN_MEMORY_COST_KIB, time_cost: MIN_TIME_COST, parallelism: 1, }; let salt_0 = generate_salt(); let kek_0 = derive_kek(pass_decoy, &salt_0, &kdf_params).unwrap(); let dek_0 = generate_dek(); let salt_1 = generate_salt(); let kek_1 = derive_kek(pass_hidden, &salt_1, &kdf_params).unwrap(); let dek_1 = generate_dek(); let carrier_node_id = 3i64; let (wrapped_0, nonce_0, tag_0) = wrap_slot0_payload(&kek_0, &dek_0, carrier_node_id).unwrap(); let (wrapped_1, nonce_1, tag_1) = wrap_slot1_payload(&kek_1, &dek_1, &dek_0, carrier_node_id).unwrap(); let db = Database::open(&path).expect("Open database"); db.init_schema_with_carrier( &salt_0, &kdf_params, &wrapped_0, &nonce_0, &tag_0, Some(( "virtual_disk.vhdx", carrier_size_bytes, &salt_1, &kdf_params, &wrapped_1, &nonce_1, &tag_1, &dek_0, &dek_1, )), ) .expect("Init carrier schema"); db.checkpoint().unwrap(); // Initiale Dateigröße messen let initial_file_size = std::fs::metadata(&path).unwrap().len(); assert!( initial_file_size >= carrier_size_bytes, "Containergröße muss mindestens 10 MB betragen" ); // Hidden Mount öffnen und 4 MB geheime Daten schreiben let meta = db.read_meta().unwrap(); let auth_hidden = meta.authenticate(pass_hidden).expect("Auth hidden"); let hidden_fs = SanctumFs::with_carrier( db.clone(), auth_hidden.dek().clone(), auth_hidden.carrier_dek(), auth_hidden.carrier_node_id(), auth_hidden.version(), true, 1, ); let test_file = DavPath::new("/large_confidential.pdf").unwrap(); let mut handle = hidden_fs .open( &test_file, OpenOptions { create: true, write: true, ..Default::default() }, ) .await .expect("Open file"); let mut random_data = vec![0u8; 4 * 1024 * 1024]; // 4 MB OsRng.fill_bytes(&mut random_data); handle .write_bytes(Bytes::copy_from_slice(&random_data)) .await .expect("Write 4MB"); handle.flush().await.expect("Flush 4MB"); drop(handle); db.checkpoint().unwrap(); // Dateigröße nach dem Schreiben von 4 MB im Hidden Vault messen let size_after_hidden_writes = std::fs::metadata(&path).unwrap().len(); // Die Dateigröße auf der Festplatte DARF NICHT WACHSEN! // Alle Chunks wurden in vorallokierte Carrier-Blöcke überschrieben. assert_eq!( initial_file_size, size_after_hidden_writes, "Dateigröße auf der Festplatte darf sich beim Schreiben in den Hidden Vault NICHT verändern! Vorher: {}, Nachher: {}", initial_file_size, size_after_hidden_writes ); let _ = std::fs::remove_file(&path); } #[tokio::test] async fn test_carrier_file_drop_and_append_mode() { let path = temp_db_path("carrier_drop_append"); let carrier_size_bytes = 10 * 1024 * 1024; // 10 MB let carrier_name = "test_carrier.iso"; let pass_decoy = "DecoyPassword2026!"; let pass_hidden = "HiddenSecretPassword2026!"; let kdf_params = KdfParams { memory_cost: MIN_MEMORY_COST_KIB, time_cost: MIN_TIME_COST, parallelism: 1, }; let salt_0 = generate_salt(); let kek_0 = derive_kek(pass_decoy, &salt_0, &kdf_params).unwrap(); let dek_0 = generate_dek(); let salt_1 = generate_salt(); let kek_1 = derive_kek(pass_hidden, &salt_1, &kdf_params).unwrap(); let dek_1 = generate_dek(); let carrier_node_id = 3i64; let (wrapped_0, nonce_0, tag_0) = wrap_slot0_payload(&kek_0, &dek_0, carrier_node_id).unwrap(); let (wrapped_1, nonce_1, tag_1) = wrap_slot1_payload(&kek_1, &dek_1, &dek_0, carrier_node_id).unwrap(); let db = Database::open(&path).expect("Open database"); db.init_schema_with_carrier( &salt_0, &kdf_params, &wrapped_0, &nonce_0, &tag_0, Some(( carrier_name, carrier_size_bytes, &salt_1, &kdf_params, &wrapped_1, &nonce_1, &tag_1, &dek_0, &dek_1, )), ) .expect("Init carrier schema"); db.checkpoint().unwrap(); let meta = db.read_meta().unwrap(); let auth_hidden = meta.authenticate(pass_hidden).expect("Auth hidden"); let hidden_fs = SanctumFs::with_carrier( db.clone(), auth_hidden.dek().clone(), auth_hidden.carrier_dek(), auth_hidden.carrier_node_id(), auth_hidden.version(), true, 1, ); // 1. TEST CarrierFile::drop: Write bytes OHNE expliziten flush(), dann drop(handle) let test_file = DavPath::new("/drop_flush_test.txt").unwrap(); let mut write_handle = hidden_fs .open( &test_file, OpenOptions { create: true, write: true, ..Default::default() }, ) .await .expect("Open file for write"); let initial_data = b"Hello from unflushed write!"; write_handle .write_bytes(Bytes::from_static(initial_data)) .await .expect("Write initial data"); // WICHTIG: KEIN write_handle.flush()! Nur drop: drop(write_handle); // Jetzt Datei wieder lesend öffnen und prüfen, ob Daten durch Drop persistiert wurden let mut read_handle = hidden_fs .open( &test_file, OpenOptions { read: true, ..Default::default() }, ) .await .expect("Open file for read"); let read_back = read_handle.read_bytes(100).await.expect("Read data back"); assert_eq!( &read_back[..], initial_data, "Drop muss ungeflushte Datenblöcke und Inode automatisch sichern" ); drop(read_handle); // 2. TEST O_APPEND: Im Append-Modus öffnen und weitere Daten anhängen let append_data = b" - Appended data at EOF!"; let mut append_handle = hidden_fs .open( &test_file, OpenOptions { write: true, append: true, ..Default::default() }, ) .await .expect("Open file for append"); append_handle .write_bytes(Bytes::from_static(append_data)) .await .expect("Write appended data"); drop(append_handle); // Drop sichert auch hier // Prüfe den vollständigen Dateiinhalt nach Append let mut read_handle_2 = hidden_fs .open( &test_file, OpenOptions { read: true, ..Default::default() }, ) .await .expect("Open file for read after append"); let full_content = read_handle_2 .read_bytes(200) .await .expect("Read full content"); let mut expected = Vec::new(); expected.extend_from_slice(initial_data); expected.extend_from_slice(append_data); assert_eq!( &full_content[..], &expected[..], "O_APPEND muss Daten am Dateiende anhängen" ); drop(read_handle_2); let _ = std::fs::remove_file(&path); } #[tokio::test] async fn test_v01_carrier_fs_copy_delegation_and_functionality() { let path = temp_db_path("v01_copy"); let carrier_size_bytes = 10 * 1024 * 1024; // 10 MB let carrier_name = "carrier_for_copy.bin"; let pass_decoy = "DecoyPass2026!"; let pass_hidden = "HiddenPass2026!"; let kdf_params = KdfParams { memory_cost: MIN_MEMORY_COST_KIB, time_cost: MIN_TIME_COST, parallelism: 1, }; let salt_0 = generate_salt(); let kek_0 = derive_kek(pass_decoy, &salt_0, &kdf_params).unwrap(); let dek_0 = generate_dek(); let salt_1 = generate_salt(); let kek_1 = derive_kek(pass_hidden, &salt_1, &kdf_params).unwrap(); let dek_1 = generate_dek(); let carrier_node_id = 3i64; let (wrapped_0, nonce_0, tag_0) = wrap_slot0_payload(&kek_0, &dek_0, carrier_node_id).unwrap(); let (wrapped_1, nonce_1, tag_1) = wrap_slot1_payload(&kek_1, &dek_1, &dek_0, carrier_node_id).unwrap(); let db = Database::open(&path).expect("Open database"); db.init_schema_with_carrier( &salt_0, &kdf_params, &wrapped_0, &nonce_0, &tag_0, Some(( carrier_name, carrier_size_bytes, &salt_1, &kdf_params, &wrapped_1, &nonce_1, &tag_1, &dek_0, &dek_1, )), ) .expect("Init carrier schema"); db.checkpoint().unwrap(); let meta = db.read_meta().unwrap(); let auth_hidden = meta.authenticate(pass_hidden).expect("Auth hidden"); let hidden_fs = SanctumFs::with_carrier( db.clone(), auth_hidden.dek().clone(), auth_hidden.carrier_dek(), auth_hidden.carrier_node_id(), auth_hidden.version(), true, 1, ); // Ordner anlegen let folder = DavPath::new("/work").unwrap(); hidden_fs.create_dir(&folder).await.expect("Create folder"); // Datei anlegen und Inhalt schreiben let src_path = DavPath::new("/work/original.txt").unwrap(); let test_data = b"V01 CarrierFs Copy Functionality Test Payload 2026"; let mut file = hidden_fs .open( &src_path, OpenOptions { create: true, write: true, ..Default::default() }, ) .await .expect("Create source file"); file.write_bytes(Bytes::from_static(test_data)) .await .expect("Write source data"); file.flush().await.expect("Flush source file"); drop(file); // V-01: copy von /work/original.txt nach /work/copy.txt aufrufen let dest_path = DavPath::new("/work/copy.txt").unwrap(); hidden_fs .copy(&src_path, &dest_path) .await .expect("V-01: Copy must succeed on CarrierFs"); // Metadaten der Kopie prüfen let copy_meta = hidden_fs .metadata(&dest_path) .await .expect("Metadata of copied file"); assert_eq!(copy_meta.len(), test_data.len() as u64); assert!(!copy_meta.is_dir()); // Inhalt der Kopie lesen und verifizieren let mut read_copy = hidden_fs .open( &dest_path, OpenOptions { read: true, ..Default::default() }, ) .await .expect("Open copied file"); let copy_bytes = read_copy.read_bytes(1024).await.expect("Read copy bytes"); assert_eq!(©_bytes[..], test_data); drop(read_copy); // Quelldatei löschen: Die Kopie muss unabhängig erhalten bleiben! hidden_fs .remove_file(&src_path) .await .expect("Delete source file"); assert!(hidden_fs.metadata(&src_path).await.is_err()); let mut read_copy_again = hidden_fs .open( &dest_path, OpenOptions { read: true, ..Default::default() }, ) .await .expect("Open copied file after source delete"); let copy_bytes_2 = read_copy_again .read_bytes(1024) .await .expect("Read copy bytes after source delete"); assert_eq!(©_bytes_2[..], test_data); drop(read_copy_again); // Overwrite-Copy testen let src2_path = DavPath::new("/work/source2.txt").unwrap(); let test_data_2 = b"New Overwrite Payload Content"; let mut file2 = hidden_fs .open( &src2_path, OpenOptions { create: true, write: true, ..Default::default() }, ) .await .expect("Create source2"); file2 .write_bytes(Bytes::from_static(test_data_2)) .await .expect("Write source2"); file2.flush().await.expect("Flush source2"); drop(file2); hidden_fs .copy(&src2_path, &dest_path) .await .expect("Overwrite copy must succeed"); let mut read_overwritten = hidden_fs .open( &dest_path, OpenOptions { read: true, ..Default::default() }, ) .await .expect("Open overwritten copy"); let overwritten_bytes = read_overwritten .read_bytes(1024) .await .expect("Read overwritten bytes"); assert_eq!(&overwritten_bytes[..], test_data_2); drop(read_overwritten); // Checkpoint & Verify db.checkpoint().unwrap(); let report = verify_container(&path, Some(&dek_0), true).expect("Verify with DEK_0"); assert!( report.is_healthy(), "Container muss integer und gesund bleiben: {:?}", report.errors ); let _ = std::fs::remove_file(&path); } #[test] fn test_m05_deniability_schema_equality_standard_vs_hidden() { let path_std = temp_db_path("schema_std"); let path_hidden = temp_db_path("schema_hidden"); let kdf_params = KdfParams { memory_cost: MIN_MEMORY_COST_KIB, time_cost: MIN_TIME_COST, parallelism: 1, }; let salt_std = generate_salt(); let kek_std = derive_kek("StandardPass123!", &salt_std, &kdf_params).unwrap(); let dek_std = generate_dek(); let (wrapped_std, nonce_std, tag_std) = wrap_slot0_payload(&kek_std, &dek_std, 0).unwrap(); let db_std = Database::open(&path_std).expect("Open std db"); db_std .init_schema(&salt_std, &kdf_params, &wrapped_std, &nonce_std, &tag_std) .expect("Init std schema"); db_std.checkpoint().unwrap(); let salt_0 = generate_salt(); let kek_0 = derive_kek("DecoyPass123!", &salt_0, &kdf_params).unwrap(); let dek_0 = generate_dek(); let salt_1 = generate_salt(); let kek_1 = derive_kek("HiddenPass123!", &salt_1, &kdf_params).unwrap(); let dek_1 = generate_dek(); let carrier_node_id = 3i64; let (wrapped_0, nonce_0, tag_0) = wrap_slot0_payload(&kek_0, &dek_0, carrier_node_id).unwrap(); let (wrapped_1, nonce_1, tag_1) = wrap_slot1_payload(&kek_1, &dek_1, &dek_0, carrier_node_id).unwrap(); let db_hidden = Database::open(&path_hidden).expect("Open hidden db"); db_hidden .init_schema_with_carrier( &salt_0, &kdf_params, &wrapped_0, &nonce_0, &tag_0, Some(( "carrier.dat", 1024 * 1024, &salt_1, &kdf_params, &wrapped_1, &nonce_1, &tag_1, &dek_0, &dek_1, )), ) .expect("Init carrier schema"); db_hidden.checkpoint().unwrap(); // Vergleiche Schemas via rusqlite let conn_std = rusqlite::Connection::open(&path_std).unwrap(); let conn_hidden = rusqlite::Connection::open(&path_hidden).unwrap(); let get_schema_elements = |conn: &rusqlite::Connection| -> Vec<(String, String, Option)> { let mut stmt = conn .prepare( "SELECT type, name, sql FROM sqlite_master WHERE type IN ('table', 'index') AND name NOT LIKE 'sqlite_%' ORDER BY type, name", ) .unwrap(); let rows = stmt .query_map([], |row| { Ok(( row.get::<_, String>(0)?, row.get::<_, String>(1)?, row.get::<_, Option>(2)?, )) }) .unwrap(); rows.map(|r| r.unwrap()).collect() }; let schema_std = get_schema_elements(&conn_std); let schema_hidden = get_schema_elements(&conn_hidden); assert_eq!( schema_std, schema_hidden, "M-05: SQLite-Schema (Tabellen, Indizes, DDL) muss zwischen Standard- und Hidden-Vault identisch sein" ); // Spalten- und Typinformationen vergleichen for table in &["meta", "nodes", "chunks"] { let get_table_info = |conn: &rusqlite::Connection| -> Vec<(i64, String, String, i64, Option, i64)> { let mut stmt = conn .prepare(&format!("PRAGMA table_info({});", table)) .unwrap(); let rows = stmt .query_map([], |row| { Ok(( row.get::<_, i64>(0)?, row.get::<_, String>(1)?, row.get::<_, String>(2)?, row.get::<_, i64>(3)?, row.get::<_, Option>(4)?, row.get::<_, i64>(5)?, )) }) .unwrap(); rows.map(|r| r.unwrap()).collect() }; let cols_std = get_table_info(&conn_std); let cols_hidden = get_table_info(&conn_hidden); assert_eq!( cols_std, cols_hidden, "M-05: Spalten und Typen für Tabelle '{}' müssen identisch sein", table ); } // Pragmas vergleichen (page_size, auto_vacuum, secure_delete) let get_pragma = |conn: &rusqlite::Connection, pragma: &str| -> i64 { conn.query_row(&format!("PRAGMA {};", pragma), [], |r| r.get(0)) .unwrap() }; assert_eq!( get_pragma(&conn_std, "page_size"), get_pragma(&conn_hidden, "page_size") ); assert_eq!( get_pragma(&conn_std, "auto_vacuum"), get_pragma(&conn_hidden, "auto_vacuum") ); assert_eq!( get_pragma(&conn_std, "secure_delete"), get_pragma(&conn_hidden, "secure_delete") ); drop(conn_std); drop(conn_hidden); drop(db_std); drop(db_hidden); let _ = std::fs::remove_file(&path_std); let _ = std::fs::remove_file(&path_hidden); }