x86-native 0.1.0

Cross-platform native x86 machine library with image, bootloader, and saved-state APIs
Documentation
use crate::error::{Result, X86Error, io_error};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::fs;
use std::path::{Path, PathBuf};

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum ImageKind {
    RawDisk,
    Iso9660,
    Bios,
    VgaBios,
    Kernel,
    Initrd,
    Bootloader,
    SavedState,
    Other,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Image {
    kind: ImageKind,
    name: String,
    bytes: Vec<u8>,
    source: Option<PathBuf>,
}

impl Image {
    pub fn from_bytes(kind: ImageKind, name: impl Into<String>, bytes: impl Into<Vec<u8>>) -> Self {
        Self {
            kind,
            name: name.into(),
            bytes: bytes.into(),
            source: None,
        }
    }

    pub fn from_file(kind: ImageKind, path: impl AsRef<Path>) -> Result<Self> {
        let path = path.as_ref();
        let bytes = fs::read(path).map_err(|source| io_error(path, source))?;
        Ok(Self {
            kind,
            name: path
                .file_name()
                .and_then(|x| x.to_str())
                .unwrap_or("image")
                .to_owned(),
            bytes,
            source: Some(path.to_path_buf()),
        })
    }

    pub fn kind(&self) -> ImageKind {
        self.kind
    }

    pub fn name(&self) -> &str {
        &self.name
    }

    pub fn bytes(&self) -> &[u8] {
        &self.bytes
    }

    pub fn len(&self) -> usize {
        self.bytes.len()
    }

    pub fn is_empty(&self) -> bool {
        self.bytes.is_empty()
    }

    pub fn source(&self) -> Option<&Path> {
        self.source.as_deref()
    }

    pub fn sha256(&self) -> String {
        let mut hasher = Sha256::new();
        hasher.update(&self.bytes);
        hex::encode(hasher.finalize())
    }

    pub fn verify_sha256(&self, expected: &str) -> Result<()> {
        let expected = expected.trim().to_ascii_lowercase();
        let actual = self.sha256();
        if actual != expected {
            return Err(X86Error::InvalidImage(format!(
                "SHA-256 mismatch for {}: expected {}, got {}",
                self.name, expected, actual
            )));
        }
        Ok(())
    }

    pub fn write_to(&self, path: impl AsRef<Path>) -> Result<()> {
        let path = path.as_ref();
        fs::write(path, &self.bytes).map_err(|source| io_error(path, source))
    }
}