feat(recovery): implement header backup/restore, BIP-39 recovery key, and integrity verification
This commit is contained in:
+124
@@ -507,6 +507,130 @@ impl Database {
|
||||
conn.execute_batch("PRAGMA wal_checkpoint(TRUNCATE);")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Schreibt oder stellt die Metadaten in der `meta`-Tabelle wieder her (z. B. nach Restore oder Header-Neugenerierung).
|
||||
pub fn restore_meta(&self, meta: &ContainerMeta) -> Result<()> {
|
||||
let conn = self.conn.lock().unwrap();
|
||||
let params_json = serde_json::to_string(&meta.kdf_params)?;
|
||||
|
||||
conn.execute_batch(
|
||||
"CREATE TABLE IF NOT EXISTS meta (
|
||||
magic BLOB NOT NULL,
|
||||
version INTEGER NOT NULL,
|
||||
kdf_salt BLOB NOT NULL,
|
||||
kdf_params TEXT NOT NULL,
|
||||
wrapped_dek BLOB NOT NULL,
|
||||
header_nonce BLOB NOT NULL,
|
||||
header_tag BLOB NOT NULL
|
||||
);",
|
||||
)?;
|
||||
|
||||
conn.execute("DELETE FROM meta", [])?;
|
||||
conn.execute(
|
||||
"INSERT INTO meta (magic, version, kdf_salt, kdf_params, wrapped_dek, header_nonce, header_tag)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
|
||||
params![
|
||||
MAGIC_BYTES.as_slice(),
|
||||
meta.version,
|
||||
meta.kdf_salt.as_slice(),
|
||||
params_json,
|
||||
meta.wrapped_dek,
|
||||
meta.header_nonce.as_slice(),
|
||||
meta.header_tag.as_slice(),
|
||||
],
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Führt SQLite-eigene Integritäts- und Foreign-Key-Prüfungen aus.
|
||||
pub fn run_sqlite_integrity_check(&self) -> Result<Vec<String>> {
|
||||
let conn = self.conn.lock().unwrap();
|
||||
let mut issues = Vec::new();
|
||||
|
||||
// 1. PRAGMA integrity_check
|
||||
let mut stmt = conn.prepare("PRAGMA integrity_check;")?;
|
||||
let rows = stmt.query_map([], |row| row.get::<_, String>(0))?;
|
||||
for r in rows {
|
||||
let msg = r?;
|
||||
if msg.to_lowercase() != "ok" {
|
||||
issues.push(format!("SQLite integrity error: {msg}"));
|
||||
}
|
||||
}
|
||||
|
||||
// 2. PRAGMA foreign_key_check
|
||||
let mut fk_stmt = conn.prepare("PRAGMA foreign_key_check;")?;
|
||||
let fk_rows = fk_stmt.query_map([], |row| {
|
||||
let table: String = row.get(0)?;
|
||||
let rowid: i64 = row.get(1)?;
|
||||
let parent: String = row.get(2)?;
|
||||
let fkid: i64 = row.get(3)?;
|
||||
Ok(format!(
|
||||
"Foreign Key Verletzung in Tabelle '{table}', RowId {rowid}, Ziel '{parent}', FK #{fkid}"
|
||||
))
|
||||
})?;
|
||||
for r in fk_rows {
|
||||
issues.push(r?);
|
||||
}
|
||||
|
||||
Ok(issues)
|
||||
}
|
||||
|
||||
/// Zählt die Anzahl von Verzeichnissen, Dateien und Daten-Chunks im Container.
|
||||
pub fn count_nodes_and_chunks(&self) -> Result<(usize, usize, usize)> {
|
||||
let conn = self.conn.lock().unwrap();
|
||||
let dirs: i64 =
|
||||
conn.query_row("SELECT COUNT(*) FROM nodes WHERE is_dir = 1", [], |r| {
|
||||
r.get(0)
|
||||
})?;
|
||||
let files: i64 =
|
||||
conn.query_row("SELECT COUNT(*) FROM nodes WHERE is_dir = 0", [], |r| {
|
||||
r.get(0)
|
||||
})?;
|
||||
let chunks: i64 = conn.query_row("SELECT COUNT(*) FROM chunks", [], |r| r.get(0))?;
|
||||
Ok((dirs as usize, files as usize, chunks as usize))
|
||||
}
|
||||
|
||||
/// Listet alle Knoten (Dateien und Ordner) im gesamten Baum auf.
|
||||
pub fn list_all_nodes(&self) -> Result<Vec<NodeRecord>> {
|
||||
let conn = self.conn.lock().unwrap();
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT id, parent_id, name, is_dir, size, created_at, modified_at FROM nodes ORDER BY id ASC",
|
||||
)?;
|
||||
let rows = stmt.query_map([], |row| {
|
||||
Ok(NodeRecord {
|
||||
id: row.get(0)?,
|
||||
parent_id: row.get(1)?,
|
||||
name: row.get(2)?,
|
||||
is_dir: row.get::<_, i32>(3)? != 0,
|
||||
size: row.get::<_, i64>(4)? as u64,
|
||||
created_at: row.get::<_, i64>(5)? as u64,
|
||||
modified_at: row.get::<_, i64>(6)? as u64,
|
||||
})
|
||||
})?;
|
||||
let mut result = Vec::new();
|
||||
for r in rows {
|
||||
result.push(r?);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Liefert alle vorhandenen Chunk-Identifikatoren (node_id, chunk_index).
|
||||
pub fn list_all_chunk_headers(&self) -> Result<Vec<(i64, u32)>> {
|
||||
let conn = self.conn.lock().unwrap();
|
||||
let mut stmt =
|
||||
conn.prepare("SELECT node_id, chunk_index FROM chunks ORDER BY node_id, chunk_index")?;
|
||||
let rows = stmt.query_map([], |row| {
|
||||
let nid: i64 = row.get(0)?;
|
||||
let cidx: u32 = row.get(1)?;
|
||||
Ok((nid, cidx))
|
||||
})?;
|
||||
let mut result = Vec::new();
|
||||
for r in rows {
|
||||
result.push(r?);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in New Issue
Block a user