Volt VMM (Neutron Stardust): source-available under AGPSL v5.0

KVM-based microVMM for the Volt platform:
- Sub-second VM boot times
- Minimal memory footprint
- Landlock LSM + seccomp security
- Virtio device support
- Custom kernel management

Copyright (c) Armored Gates LLC. All rights reserved.
Licensed under AGPSL v5.0
This commit is contained in:
Karl Clinger
2026-03-21 01:04:35 -05:00
commit 40ed108dd5
143 changed files with 50300 additions and 0 deletions

796
vmm/src/snapshot/mod.rs Normal file
View File

@@ -0,0 +1,796 @@
//! Snapshot/Restore for Volt VMM
//!
//! Provides serializable state types and functions to create and restore
//! VM snapshots. The snapshot format consists of:
//!
//! - `state.json`: Serialized VM state (vCPU registers, IRQ chip, devices, metadata)
//! - `memory.snap`: Raw guest memory dump (mmap'd on restore for lazy loading)
//!
//! # Architecture
//!
//! ```text
//! ┌──────────────────────────────────────────────────┐
//! │ Snapshot Files │
//! │ ┌──────────────────┐ ┌───────────────────────┐ │
//! │ │ state.json │ │ memory.snap │ │
//! │ │ - VcpuState[] │ │ (raw memory dump) │ │
//! │ │ - IrqchipState │ │ │ │
//! │ │ - ClockState │ │ Restored via mmap │ │
//! │ │ - DeviceState │ │ MAP_PRIVATE for CoW │ │
//! │ │ - Metadata+CRC │ │ demand-paged by OS │ │
//! │ └──────────────────┘ └───────────────────────┘ │
//! └──────────────────────────────────────────────────┘
//! ```
pub mod cas;
pub mod create;
pub mod inmem;
pub mod restore;
// Re-export CAS types
pub use cas::{CasManifest, CasChunk, CasDumpResult, CAS_CHUNK_SIZE, CAS_MANIFEST_FILENAME};
// Re-export restore types
pub use restore::MemoryMapping;
use serde::{Deserialize, Serialize};
// ============================================================================
// Snapshot Metadata
// ============================================================================
/// Snapshot format version
pub const SNAPSHOT_VERSION: u32 = 1;
/// Snapshot metadata with integrity check
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SnapshotMetadata {
/// Snapshot format version
pub version: u32,
/// Total guest memory size in bytes
pub memory_size: u64,
/// Number of vCPUs
pub vcpu_count: u8,
/// Snapshot creation timestamp (Unix epoch seconds)
pub created_at: u64,
/// CRC-64 of the state JSON (excluding this field)
pub state_crc64: u64,
/// Memory file size (for validation)
pub memory_file_size: u64,
}
// ============================================================================
// vCPU State
// ============================================================================
/// Complete vCPU state captured from KVM
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VcpuState {
/// vCPU index
pub id: u8,
/// General purpose registers (KVM_GET_REGS)
pub regs: SerializableRegs,
/// Special registers (KVM_GET_SREGS)
pub sregs: SerializableSregs,
/// FPU state (KVM_GET_FPU)
pub fpu: SerializableFpu,
/// Model-specific registers (KVM_GET_MSRS)
pub msrs: Vec<SerializableMsr>,
/// CPUID entries (KVM_GET_CPUID2)
pub cpuid_entries: Vec<SerializableCpuidEntry>,
/// Local APIC state (KVM_GET_LAPIC)
pub lapic: SerializableLapic,
/// Extended control registers (KVM_GET_XCRS)
pub xcrs: Vec<SerializableXcr>,
/// Multiprocessor state (KVM_GET_MP_STATE)
pub mp_state: u32,
/// vCPU events (KVM_GET_VCPU_EVENTS)
pub events: SerializableVcpuEvents,
}
/// Serializable general-purpose registers (maps to kvm_regs)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SerializableRegs {
pub rax: u64,
pub rbx: u64,
pub rcx: u64,
pub rdx: u64,
pub rsi: u64,
pub rdi: u64,
pub rsp: u64,
pub rbp: u64,
pub r8: u64,
pub r9: u64,
pub r10: u64,
pub r11: u64,
pub r12: u64,
pub r13: u64,
pub r14: u64,
pub r15: u64,
pub rip: u64,
pub rflags: u64,
}
/// Serializable segment register
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SerializableSegment {
pub base: u64,
pub limit: u32,
pub selector: u16,
pub type_: u8,
pub present: u8,
pub dpl: u8,
pub db: u8,
pub s: u8,
pub l: u8,
pub g: u8,
pub avl: u8,
pub unusable: u8,
}
/// Serializable descriptor table register
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SerializableDtable {
pub base: u64,
pub limit: u16,
}
/// Serializable special registers (maps to kvm_sregs)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SerializableSregs {
pub cs: SerializableSegment,
pub ds: SerializableSegment,
pub es: SerializableSegment,
pub fs: SerializableSegment,
pub gs: SerializableSegment,
pub ss: SerializableSegment,
pub tr: SerializableSegment,
pub ldt: SerializableSegment,
pub gdt: SerializableDtable,
pub idt: SerializableDtable,
pub cr0: u64,
pub cr2: u64,
pub cr3: u64,
pub cr4: u64,
pub cr8: u64,
pub efer: u64,
pub apic_base: u64,
/// Interrupt bitmap (256 bits = 4 x u64)
pub interrupt_bitmap: [u64; 4],
}
/// Serializable FPU state (maps to kvm_fpu)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SerializableFpu {
/// x87 FPU registers (8 x 16 bytes = 128 bytes)
pub fpr: Vec<Vec<u8>>,
/// FPU control word
pub fcw: u16,
/// FPU status word
pub fsw: u16,
/// FPU tag word (abridged)
pub ftwx: u8,
/// Last FPU opcode
pub last_opcode: u16,
/// Last FPU instruction pointer
pub last_ip: u64,
/// Last FPU data pointer
pub last_dp: u64,
/// SSE/AVX registers (16 x 16 bytes = 256 bytes)
pub xmm: Vec<Vec<u8>>,
/// SSE control/status register
pub mxcsr: u32,
}
/// Serializable MSR entry
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SerializableMsr {
pub index: u32,
pub data: u64,
}
/// Serializable CPUID entry
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SerializableCpuidEntry {
pub function: u32,
pub index: u32,
pub flags: u32,
pub eax: u32,
pub ebx: u32,
pub ecx: u32,
pub edx: u32,
}
/// Serializable LAPIC state (256 x 4 = 1024 bytes, base64-encoded)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SerializableLapic {
/// Raw LAPIC register data (1024 bytes)
pub regs: Vec<u8>,
}
/// Serializable XCR entry
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SerializableXcr {
pub xcr: u32,
pub value: u64,
}
/// Serializable vCPU events (maps to kvm_vcpu_events)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SerializableVcpuEvents {
// Exception state
pub exception_injected: u8,
pub exception_nr: u8,
pub exception_has_error_code: u8,
pub exception_error_code: u32,
// Interrupt state
pub interrupt_injected: u8,
pub interrupt_nr: u8,
pub interrupt_soft: u8,
pub interrupt_shadow: u8,
// NMI state
pub nmi_injected: u8,
pub nmi_pending: u8,
pub nmi_masked: u8,
// SMI state
pub smi_smm: u8,
pub smi_pending: u8,
pub smi_smm_inside_nmi: u8,
pub smi_latched_init: u8,
// Flags
pub flags: u32,
}
// ============================================================================
// IRQ Chip State
// ============================================================================
/// Complete IRQ chip state (PIC + IOAPIC + PIT)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IrqchipState {
/// 8259 PIC master (IRQ chip 0)
pub pic_master: SerializablePicState,
/// 8259 PIC slave (IRQ chip 1)
pub pic_slave: SerializablePicState,
/// IOAPIC state (IRQ chip 2)
pub ioapic: SerializableIoapicState,
/// PIT state (KVM_GET_PIT2)
pub pit: SerializablePitState,
}
/// Serializable 8259 PIC state
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SerializablePicState {
/// Raw chip data from KVM_GET_IRQCHIP (512 bytes)
pub raw_data: Vec<u8>,
}
/// Serializable IOAPIC state
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SerializableIoapicState {
/// Raw chip data from KVM_GET_IRQCHIP (512 bytes)
pub raw_data: Vec<u8>,
}
/// Serializable PIT state
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SerializablePitState {
/// PIT counter channels (3 channels)
pub channels: Vec<SerializablePitChannel>,
/// PIT flags
pub flags: u32,
}
/// Serializable PIT channel state
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SerializablePitChannel {
pub count: u32,
pub latched_count: u16,
pub count_latched: u8,
pub status_latched: u8,
pub status: u8,
pub read_state: u8,
pub write_state: u8,
pub write_latch: u8,
pub rw_mode: u8,
pub mode: u8,
pub bcd: u8,
pub gate: u8,
pub count_load_time: i64,
}
// ============================================================================
// Clock State
// ============================================================================
/// KVM clock state
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClockState {
/// KVM clock value (nanoseconds)
pub clock: u64,
/// Flags from kvm_clock_data
pub flags: u32,
}
// ============================================================================
// Device State
// ============================================================================
/// Combined device state for all emulated devices
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeviceState {
/// Serial console state
pub serial: SerializableSerialState,
/// Virtio-blk device state (if present)
pub virtio_blk: Option<SerializableVirtioBlkState>,
/// Virtio-net device state (if present)
pub virtio_net: Option<SerializableVirtioNetState>,
/// MMIO transport state for each device
pub mmio_transports: Vec<SerializableMmioTransportState>,
}
/// Serializable serial console state
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SerializableSerialState {
pub dlab: bool,
pub ier: u8,
pub lcr: u8,
pub mcr: u8,
pub lsr: u8,
pub msr: u8,
pub scr: u8,
pub dll: u8,
pub dlh: u8,
pub thr_interrupt_pending: bool,
pub input_buffer: Vec<u8>,
}
/// Serializable virtio-blk queue state
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SerializableVirtioBlkState {
/// Features acknowledged by the driver
pub acked_features: u64,
/// Whether the device is activated
pub activated: bool,
/// Queue state
pub queues: Vec<SerializableQueueState>,
/// Read-only flag
pub read_only: bool,
/// Backend path (for re-opening on restore)
pub backend_path: Option<String>,
}
/// Serializable virtio-net queue state
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SerializableVirtioNetState {
/// Features acknowledged by the driver
pub acked_features: u64,
/// Whether the device is activated
pub activated: bool,
/// Queue state
pub queues: Vec<SerializableQueueState>,
/// MAC address
pub mac: [u8; 6],
/// TAP device name
pub tap_name: String,
}
/// Serializable virtqueue state
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SerializableQueueState {
pub max_size: u16,
pub size: u16,
pub ready: bool,
pub desc_table: u64,
pub avail_ring: u64,
pub used_ring: u64,
pub next_avail: u16,
pub next_used: u16,
}
/// Serializable MMIO transport state
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SerializableMmioTransportState {
/// Device type
pub device_type: u32,
/// Current device status register
pub device_status: u32,
/// Driver features
pub driver_features: u64,
/// Device features selector
pub device_features_sel: u32,
/// Driver features selector
pub driver_features_sel: u32,
/// Selected queue index
pub queue_sel: u32,
/// Interrupt status
pub interrupt_status: u32,
/// Configuration generation counter
pub config_generation: u32,
/// MMIO base address
pub base_addr: u64,
/// IRQ number
pub irq: u32,
/// Per-queue addresses
pub queue_desc: Vec<u64>,
pub queue_avail: Vec<u64>,
pub queue_used: Vec<u64>,
pub queue_num: Vec<u16>,
pub queue_ready: Vec<bool>,
}
// ============================================================================
// Complete Snapshot
// ============================================================================
/// Complete VM snapshot (serialized to state.json)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VmSnapshot {
/// Snapshot metadata
pub metadata: SnapshotMetadata,
/// Per-vCPU state
pub vcpu_states: Vec<VcpuState>,
/// IRQ chip state
pub irqchip: IrqchipState,
/// KVM clock state
pub clock: ClockState,
/// Device state
pub devices: DeviceState,
/// Memory region layout
pub memory_regions: Vec<SerializableMemoryRegion>,
}
/// Serializable memory region descriptor
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SerializableMemoryRegion {
/// Guest physical address
pub guest_addr: u64,
/// Size in bytes
pub size: u64,
/// Offset into the memory snapshot file
pub file_offset: u64,
}
// ============================================================================
// VmSnapshot Implementation
// ============================================================================
impl VmSnapshot {
/// Deserialize a VmSnapshot from a byte buffer.
///
/// This allows loading snapshot state from memory (e.g., CAS blob cache)
/// instead of reading from a file on disk.
///
/// # Arguments
///
/// * `data` - JSON-encoded snapshot state bytes
///
/// # Returns
///
/// The deserialized `VmSnapshot`, or an error if deserialization fails.
///
/// # Example
///
/// ```ignore
/// // Load state from CAS blob cache
/// let state_bytes = blob_cache.get("vm-snapshot-state")?;
/// let snapshot = VmSnapshot::from_bytes(&state_bytes)?;
/// ```
pub fn from_bytes(data: &[u8]) -> Result<Self> {
let snapshot: VmSnapshot = serde_json::from_slice(data)?;
// Verify version
if snapshot.metadata.version != SNAPSHOT_VERSION {
return Err(SnapshotError::VersionMismatch {
expected: SNAPSHOT_VERSION,
actual: snapshot.metadata.version,
});
}
// Verify CRC-64
let saved_crc = snapshot.metadata.state_crc64;
let mut check_snapshot = snapshot.clone();
check_snapshot.metadata.state_crc64 = 0;
let check_json = serde_json::to_string_pretty(&check_snapshot)?;
let computed_crc = compute_crc64(check_json.as_bytes());
if saved_crc != computed_crc {
return Err(SnapshotError::CrcMismatch {
expected: saved_crc,
actual: computed_crc,
});
}
Ok(snapshot)
}
/// Serialize the VmSnapshot to bytes.
///
/// This is the inverse of `from_bytes()` and allows storing snapshot
/// state in memory (e.g., for CAS blob cache).
///
/// # Returns
///
/// The JSON-encoded snapshot state as bytes.
pub fn to_bytes(&self) -> Result<Vec<u8>> {
// Create a snapshot with zeroed CRC for computation
let mut snapshot = self.clone();
snapshot.metadata.state_crc64 = 0;
let json = serde_json::to_string_pretty(&snapshot)?;
// Compute CRC and update
let crc = compute_crc64(json.as_bytes());
snapshot.metadata.state_crc64 = crc;
// Re-serialize with correct CRC
let final_json = serde_json::to_string_pretty(&snapshot)?;
Ok(final_json.into_bytes())
}
}
// ============================================================================
// Error types
// ============================================================================
/// Snapshot operation errors
#[derive(Debug, thiserror::Error)]
pub enum SnapshotError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Serialization error: {0}")]
Serialization(#[from] serde_json::Error),
#[error("KVM error: {0}")]
Kvm(String),
#[error("CRC mismatch: expected {expected:#x}, got {actual:#x}")]
CrcMismatch { expected: u64, actual: u64 },
#[error("Version mismatch: expected {expected}, got {actual}")]
VersionMismatch { expected: u32, actual: u32 },
#[error("Memory size mismatch: expected {expected}, got {actual}")]
MemorySizeMismatch { expected: u64, actual: u64 },
#[error("Memory file size mismatch: expected {expected}, got {actual}")]
MemoryFileSizeMismatch { expected: u64, actual: u64 },
#[error("Missing snapshot file: {0}")]
MissingFile(String),
#[error("Invalid snapshot: {0}")]
Invalid(String),
#[error("mmap failed: {0}")]
Mmap(String),
}
pub type Result<T> = std::result::Result<T, SnapshotError>;
// ============================================================================
// CRC-64 helper
// ============================================================================
/// Compute CRC-64/ECMA for integrity checking
pub fn compute_crc64(data: &[u8]) -> u64 {
use crc::{Crc, CRC_64_ECMA_182};
const CRC64: Crc<u64> = Crc::<u64>::new(&CRC_64_ECMA_182);
CRC64.checksum(data)
}
#[cfg(test)]
mod tests {
use super::*;
/// Create a minimal valid snapshot for testing
fn create_test_snapshot() -> VmSnapshot {
VmSnapshot {
metadata: SnapshotMetadata {
version: SNAPSHOT_VERSION,
memory_size: 128 * 1024 * 1024,
vcpu_count: 1,
created_at: 1234567890,
state_crc64: 0, // Will be computed
memory_file_size: 128 * 1024 * 1024,
},
vcpu_states: vec![VcpuState {
id: 0,
regs: SerializableRegs {
rax: 0, rbx: 0, rcx: 0, rdx: 0,
rsi: 0, rdi: 0, rsp: 0x7fff_0000, rbp: 0,
r8: 0, r9: 0, r10: 0, r11: 0,
r12: 0, r13: 0, r14: 0, r15: 0,
rip: 0x0010_0000, rflags: 0x0002,
},
sregs: SerializableSregs {
cs: SerializableSegment {
base: 0, limit: 0xffff_ffff, selector: 0x10,
type_: 11, present: 1, dpl: 0, db: 0, s: 1, l: 1, g: 1, avl: 0, unusable: 0,
},
ds: SerializableSegment {
base: 0, limit: 0xffff_ffff, selector: 0x18,
type_: 3, present: 1, dpl: 0, db: 1, s: 1, l: 0, g: 1, avl: 0, unusable: 0,
},
es: SerializableSegment {
base: 0, limit: 0xffff_ffff, selector: 0x18,
type_: 3, present: 1, dpl: 0, db: 1, s: 1, l: 0, g: 1, avl: 0, unusable: 0,
},
fs: SerializableSegment {
base: 0, limit: 0xffff_ffff, selector: 0x18,
type_: 3, present: 1, dpl: 0, db: 1, s: 1, l: 0, g: 1, avl: 0, unusable: 0,
},
gs: SerializableSegment {
base: 0, limit: 0xffff_ffff, selector: 0x18,
type_: 3, present: 1, dpl: 0, db: 1, s: 1, l: 0, g: 1, avl: 0, unusable: 0,
},
ss: SerializableSegment {
base: 0, limit: 0xffff_ffff, selector: 0x18,
type_: 3, present: 1, dpl: 0, db: 1, s: 1, l: 0, g: 1, avl: 0, unusable: 0,
},
tr: SerializableSegment {
base: 0, limit: 0, selector: 0,
type_: 11, present: 1, dpl: 0, db: 0, s: 0, l: 0, g: 0, avl: 0, unusable: 0,
},
ldt: SerializableSegment {
base: 0, limit: 0, selector: 0,
type_: 2, present: 1, dpl: 0, db: 0, s: 0, l: 0, g: 0, avl: 0, unusable: 1,
},
gdt: SerializableDtable { base: 0, limit: 0 },
idt: SerializableDtable { base: 0, limit: 0 },
cr0: 0x8000_0011,
cr2: 0,
cr3: 0x0010_0000,
cr4: 0x20,
cr8: 0,
efer: 0x500,
apic_base: 0xfee0_0900,
interrupt_bitmap: [0; 4],
},
fpu: SerializableFpu {
fpr: vec![vec![0u8; 16]; 8],
fcw: 0x37f,
fsw: 0,
ftwx: 0,
last_opcode: 0,
last_ip: 0,
last_dp: 0,
xmm: vec![vec![0u8; 16]; 16],
mxcsr: 0x1f80,
},
msrs: vec![],
cpuid_entries: vec![],
lapic: SerializableLapic { regs: vec![0u8; 1024] },
xcrs: vec![],
mp_state: 0,
events: SerializableVcpuEvents {
exception_injected: 0,
exception_nr: 0,
exception_has_error_code: 0,
exception_error_code: 0,
interrupt_injected: 0,
interrupt_nr: 0,
interrupt_soft: 0,
interrupt_shadow: 0,
nmi_injected: 0,
nmi_pending: 0,
nmi_masked: 0,
smi_smm: 0,
smi_pending: 0,
smi_smm_inside_nmi: 0,
smi_latched_init: 0,
flags: 0,
},
}],
irqchip: IrqchipState {
pic_master: SerializablePicState { raw_data: vec![0u8; 512] },
pic_slave: SerializablePicState { raw_data: vec![0u8; 512] },
ioapic: SerializableIoapicState { raw_data: vec![0u8; 512] },
pit: SerializablePitState {
channels: vec![
SerializablePitChannel {
count: 0, latched_count: 0, count_latched: 0,
status_latched: 0, status: 0, read_state: 0,
write_state: 0, write_latch: 0, rw_mode: 0,
mode: 0, bcd: 0, gate: 0, count_load_time: 0,
};
3
],
flags: 0,
},
},
clock: ClockState { clock: 1_000_000_000, flags: 0 },
devices: DeviceState {
serial: SerializableSerialState {
dlab: false,
ier: 0,
lcr: 0,
mcr: 0,
lsr: 0x60,
msr: 0,
scr: 0,
dll: 0,
dlh: 0,
thr_interrupt_pending: false,
input_buffer: vec![],
},
virtio_blk: None,
virtio_net: None,
mmio_transports: vec![],
},
memory_regions: vec![SerializableMemoryRegion {
guest_addr: 0,
size: 128 * 1024 * 1024,
file_offset: 0,
}],
}
}
#[test]
fn test_snapshot_to_bytes_from_bytes_roundtrip() {
let original = create_test_snapshot();
// Serialize to bytes
let bytes = original.to_bytes().expect("to_bytes should succeed");
// Deserialize from bytes
let restored = VmSnapshot::from_bytes(&bytes).expect("from_bytes should succeed");
// Verify key fields match
assert_eq!(original.metadata.version, restored.metadata.version);
assert_eq!(original.metadata.memory_size, restored.metadata.memory_size);
assert_eq!(original.metadata.vcpu_count, restored.metadata.vcpu_count);
assert_eq!(original.vcpu_states.len(), restored.vcpu_states.len());
assert_eq!(original.vcpu_states[0].regs.rip, restored.vcpu_states[0].regs.rip);
assert_eq!(original.vcpu_states[0].regs.rsp, restored.vcpu_states[0].regs.rsp);
assert_eq!(original.clock.clock, restored.clock.clock);
}
#[test]
fn test_snapshot_from_bytes_version_mismatch() {
let mut snapshot = create_test_snapshot();
snapshot.metadata.version = 999; // Invalid version
let bytes = serde_json::to_vec(&snapshot).unwrap();
let result = VmSnapshot::from_bytes(&bytes);
assert!(matches!(result, Err(SnapshotError::VersionMismatch { .. })));
}
#[test]
fn test_snapshot_from_bytes_crc_mismatch() {
let mut snapshot = create_test_snapshot();
// Serialize normally first
let bytes = snapshot.to_bytes().unwrap();
// Corrupt the bytes (modify some content while keeping valid JSON)
let mut json: serde_json::Value = serde_json::from_slice(&bytes).unwrap();
json["clock"]["clock"] = serde_json::json!(12345); // Change clock value
let corrupted = serde_json::to_vec(&json).unwrap();
let result = VmSnapshot::from_bytes(&corrupted);
assert!(matches!(result, Err(SnapshotError::CrcMismatch { .. })));
}
#[test]
fn test_snapshot_from_bytes_invalid_json() {
let invalid_bytes = b"{ this is not valid json }";
let result = VmSnapshot::from_bytes(invalid_bytes);
assert!(matches!(result, Err(SnapshotError::Serialization(_))));
}
#[test]
fn test_crc64_consistency() {
let data1 = b"hello world";
let data2 = b"hello world";
let data3 = b"hello worle"; // Different
let crc1 = compute_crc64(data1);
let crc2 = compute_crc64(data2);
let crc3 = compute_crc64(data3);
assert_eq!(crc1, crc2);
assert_ne!(crc1, crc3);
}
}