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:
796
vmm/src/snapshot/mod.rs
Normal file
796
vmm/src/snapshot/mod.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user