use sha2::{Digest, Sha256};
use crate::fs::VfsSnapshot;
use crate::interpreter::ShellState;
const SNAPSHOT_VERSION: u32 = 1;
const INTEGRITY_TAG: &[u8; 8] = b"BKSNAP01";
const DIGEST_LEN: usize = 32;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Snapshot {
pub version: u32,
pub shell: ShellState,
pub vfs: Option<VfsSnapshot>,
pub session_commands: u64,
pub session_exec_calls: u64,
}
impl Snapshot {
pub fn to_bytes(&self) -> crate::Result<Vec<u8>> {
let json = serde_json::to_vec(self).map_err(|e| crate::Error::Internal(e.to_string()))?;
let digest = Self::compute_digest(&json);
let mut out = Vec::with_capacity(DIGEST_LEN + json.len());
out.extend_from_slice(&digest);
out.extend_from_slice(&json);
Ok(out)
}
pub fn from_bytes(data: &[u8]) -> crate::Result<Self> {
if data.len() < DIGEST_LEN {
return Err(crate::Error::Internal(
"snapshot too short: missing integrity digest".to_string(),
));
}
let (stored_digest, json) = data.split_at(DIGEST_LEN);
let expected = Self::compute_digest(json);
if stored_digest != expected.as_slice() {
return Err(crate::Error::Internal(
"snapshot integrity check failed: data may have been tampered with".to_string(),
));
}
let snap: Self =
serde_json::from_slice(json).map_err(|e| crate::Error::Internal(e.to_string()))?;
if snap.version != SNAPSHOT_VERSION {
return Err(crate::Error::Internal(format!(
"unsupported snapshot version {} (expected {})",
snap.version, SNAPSHOT_VERSION
)));
}
Ok(snap)
}
fn compute_digest(payload: &[u8]) -> [u8; DIGEST_LEN] {
let mut hasher = Sha256::new();
hasher.update(INTEGRITY_TAG);
hasher.update(payload);
let result = hasher.finalize();
let mut out = [0u8; DIGEST_LEN];
out.copy_from_slice(&result);
out
}
}
impl crate::Bash {
pub fn snapshot(&self) -> crate::Result<Vec<u8>> {
let shell = self.interpreter.shell_state();
let vfs = self.fs.vfs_snapshot();
let counters = self.interpreter.counters();
let snap = Snapshot {
version: SNAPSHOT_VERSION,
shell,
vfs,
session_commands: counters.session_commands,
session_exec_calls: counters.session_exec_calls,
};
snap.to_bytes()
}
pub fn from_snapshot(data: &[u8]) -> crate::Result<Self> {
let snap = Snapshot::from_bytes(data)?;
let mut bash = Self::new();
bash.restore_snapshot_inner(&snap);
Ok(bash)
}
pub fn restore_snapshot(&mut self, data: &[u8]) -> crate::Result<()> {
let snap = Snapshot::from_bytes(data)?;
self.restore_snapshot_inner(&snap);
Ok(())
}
fn restore_snapshot_inner(&mut self, snap: &Snapshot) {
self.interpreter.restore_shell_state(&snap.shell);
if let Some(ref vfs) = snap.vfs {
self.fs.vfs_restore(vfs);
}
self.interpreter
.restore_session_counters(snap.session_commands, snap.session_exec_calls);
}
}