Skip to main content

x86/
state.rs

1use crate::error::{Result, X86Error, io_error};
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4use sha2::{Digest, Sha256};
5use std::fs;
6use std::path::{Path, PathBuf};
7
8pub const STATE_MAGIC: u32 = 0x8676_8676;
9pub const STATE_VERSION: u32 = 6;
10pub const STATE_HEADER_LEN: usize = 16;
11pub const ZSTD_MAGIC: u32 = 0xFD2F_B528;
12
13#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
14pub struct StateHeader {
15    pub magic: u32,
16    pub version: u32,
17    pub total_length: u32,
18    pub metadata_length: u32,
19}
20
21#[derive(Debug, Clone)]
22pub struct SavedState {
23    encoded: Vec<u8>,
24    decoded: Vec<u8>,
25    metadata: Value,
26    header: StateHeader,
27    compressed: bool,
28    source: Option<PathBuf>,
29}
30
31#[derive(Debug, Clone, Serialize)]
32pub struct StateSummary {
33    pub compressed: bool,
34    pub encoded_bytes: usize,
35    pub decoded_bytes: usize,
36    pub version: u32,
37    pub buffer_count: usize,
38    pub memory_bytes: Option<u64>,
39    pub sha256: String,
40    pub source: Option<PathBuf>,
41}
42
43impl SavedState {
44    pub fn from_bytes(bytes: impl Into<Vec<u8>>) -> Result<Self> {
45        let encoded = bytes.into();
46        let (decoded, compressed) = decode(&encoded)?;
47        let header = parse_header(&decoded)?;
48        let metadata_end = STATE_HEADER_LEN + header.metadata_length as usize;
49        let metadata: Value = serde_json::from_slice(&decoded[STATE_HEADER_LEN..metadata_end])?;
50        if !metadata.get("state").is_some() || !metadata.get("buffer_infos").is_some() {
51            return Err(X86Error::InvalidState(
52                "metadata must contain state and buffer_infos".to_owned(),
53            ));
54        }
55        Ok(Self {
56            encoded,
57            decoded,
58            metadata,
59            header,
60            compressed,
61            source: None,
62        })
63    }
64
65    pub fn from_file(path: impl AsRef<Path>) -> Result<Self> {
66        let path = path.as_ref();
67        let bytes = fs::read(path).map_err(|source| io_error(path, source))?;
68        let mut state = Self::from_bytes(bytes)?;
69        state.source = Some(path.to_path_buf());
70        Ok(state)
71    }
72
73    pub fn header(&self) -> StateHeader {
74        self.header
75    }
76
77    pub fn metadata(&self) -> &Value {
78        &self.metadata
79    }
80
81    pub fn encoded_bytes(&self) -> &[u8] {
82        &self.encoded
83    }
84
85    pub fn decoded_bytes(&self) -> &[u8] {
86        &self.decoded
87    }
88
89    pub fn is_compressed(&self) -> bool {
90        self.compressed
91    }
92
93    pub fn source(&self) -> Option<&Path> {
94        self.source.as_deref()
95    }
96
97    pub fn buffer_count(&self) -> usize {
98        self.metadata
99            .get("buffer_infos")
100            .and_then(Value::as_array)
101            .map_or(0, Vec::len)
102    }
103
104    pub fn memory_bytes(&self) -> Option<u64> {
105        self.metadata
106            .get("state")
107            .and_then(Value::as_array)
108            .and_then(|items| items.first())
109            .and_then(Value::as_u64)
110    }
111
112    /// Return the raw v86 state array and typed buffers in buffer_id order.
113    /// The buffers are copied out of the decoded state so callers can safely
114    /// hand them to a native backend with its own lifetime.
115    pub fn cpu_state_and_buffers(&self) -> Result<(Value, Vec<Vec<u8>>)> {
116        let state = self
117            .metadata
118            .get("state")
119            .cloned()
120            .ok_or_else(|| X86Error::InvalidState("missing state array".to_owned()))?;
121        let infos = self
122            .metadata
123            .get("buffer_infos")
124            .and_then(Value::as_array)
125            .ok_or_else(|| X86Error::InvalidState("missing buffer_infos array".to_owned()))?;
126        let buffer_block_start = (STATE_HEADER_LEN + self.header.metadata_length as usize + 3) & !3;
127        let mut buffers = Vec::with_capacity(infos.len());
128        for (index, info) in infos.iter().enumerate() {
129            let offset = info.get("offset").and_then(Value::as_u64).ok_or_else(|| {
130                X86Error::InvalidState(format!("buffer_infos[{index}] has no offset"))
131            })? as usize;
132            let length = info.get("length").and_then(Value::as_u64).ok_or_else(|| {
133                X86Error::InvalidState(format!("buffer_infos[{index}] has no length"))
134            })? as usize;
135            let start = buffer_block_start
136                .checked_add(offset)
137                .ok_or_else(|| X86Error::InvalidState("buffer offset overflow".to_owned()))?;
138            let end = start
139                .checked_add(length)
140                .ok_or_else(|| X86Error::InvalidState("buffer length overflow".to_owned()))?;
141            let bytes = self.decoded.get(start..end).ok_or_else(|| {
142                X86Error::InvalidState(format!("buffer {index} exceeds decoded state"))
143            })?;
144            buffers.push(bytes.to_vec());
145        }
146        Ok((state, buffers))
147    }
148
149    pub fn sha256(&self) -> String {
150        let mut hasher = Sha256::new();
151        hasher.update(&self.decoded);
152        hex::encode(hasher.finalize())
153    }
154
155    pub fn summary(&self) -> StateSummary {
156        StateSummary {
157            compressed: self.compressed,
158            encoded_bytes: self.encoded.len(),
159            decoded_bytes: self.decoded.len(),
160            version: self.header.version,
161            buffer_count: self.buffer_count(),
162            memory_bytes: self.memory_bytes(),
163            sha256: self.sha256(),
164            source: self.source.clone(),
165        }
166    }
167
168    pub fn write_decoded(&self, path: impl AsRef<Path>) -> Result<()> {
169        let path = path.as_ref();
170        fs::write(path, &self.decoded).map_err(|source| io_error(path, source))
171    }
172}
173
174fn read_u32(bytes: &[u8], offset: usize) -> Result<u32> {
175    let slice = bytes
176        .get(offset..offset + 4)
177        .ok_or_else(|| X86Error::InvalidState("truncated header".to_owned()))?;
178    Ok(u32::from_le_bytes(slice.try_into().unwrap()))
179}
180
181pub fn parse_header(bytes: &[u8]) -> Result<StateHeader> {
182    if bytes.len() < STATE_HEADER_LEN {
183        return Err(X86Error::InvalidState(
184            "state is shorter than header".to_owned(),
185        ));
186    }
187    let header = StateHeader {
188        magic: read_u32(bytes, 0)?,
189        version: read_u32(bytes, 4)?,
190        total_length: read_u32(bytes, 8)?,
191        metadata_length: read_u32(bytes, 12)?,
192    };
193    if header.magic != STATE_MAGIC {
194        return Err(X86Error::InvalidState(format!(
195            "invalid magic 0x{:08x}",
196            header.magic
197        )));
198    }
199    if header.version != STATE_VERSION {
200        return Err(X86Error::UnsupportedFormat(format!(
201            "saved state version {}; supported version is {}",
202            header.version, STATE_VERSION
203        )));
204    }
205    let metadata_end = STATE_HEADER_LEN
206        .checked_add(header.metadata_length as usize)
207        .ok_or_else(|| X86Error::InvalidState("metadata length overflow".to_owned()))?;
208    if metadata_end > bytes.len() {
209        return Err(X86Error::InvalidState(
210            "metadata exceeds state length".to_owned(),
211        ));
212    }
213    Ok(header)
214}
215
216pub fn decode(bytes: &[u8]) -> Result<(Vec<u8>, bool)> {
217    let compressed =
218        bytes.len() >= 4 && u32::from_le_bytes(bytes[0..4].try_into().unwrap()) == ZSTD_MAGIC;
219    if compressed {
220        #[cfg(feature = "zstd")]
221        {
222            let decoded = zstd::stream::decode_all(bytes)
223                .map_err(|error| X86Error::InvalidState(format!("zstd decode failed: {error}")))?;
224            return Ok((decoded, true));
225        }
226        #[cfg(not(feature = "zstd"))]
227        {
228            return Err(X86Error::UnsupportedFormat(
229                "zstd support is disabled; enable the `zstd` feature".to_owned(),
230            ));
231        }
232    }
233    Ok((bytes.to_vec(), false))
234}