1use std::collections::BTreeMap;
26
27use serde::{Deserialize, Serialize};
28
29use crate::abi::InstanceId;
30use crate::state::instance::InstanceSnapshot;
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct KernelSnapshot {
41 pub(crate) instances: BTreeMap<InstanceId, InstanceSnapshot>,
42 pub(crate) next_instance_id: u64,
43}
44
45impl KernelSnapshot {
46 pub fn serialize(&self) -> Result<Vec<u8>, SnapshotError> {
48 postcard::to_allocvec(self).map_err(|e| SnapshotError::SerializeFailed(format!("{}", e)))
49 }
50
51 pub fn deserialize(bytes: &[u8]) -> Result<Self, SnapshotError> {
60 let snap: Self = postcard::from_bytes(bytes)
61 .map_err(|e| SnapshotError::DeserializeFailed(format!("{}", e)))?;
62 snap.validate_structure()?;
63 Ok(snap)
64 }
65
66 pub fn deserialize_verified(
75 bytes: &[u8],
76 expected_digest: &[u8; 32],
77 ) -> Result<Self, SnapshotError> {
78 if blake3::hash(bytes).as_bytes() != expected_digest {
79 return Err(SnapshotError::IntegrityMismatch);
80 }
81 Self::deserialize(bytes)
82 }
83
84 pub fn digest(bytes: &[u8]) -> [u8; 32] {
88 *blake3::hash(bytes).as_bytes()
89 }
90
91 fn validate_structure(&self) -> Result<(), SnapshotError> {
95 for (id, inst) in &self.instances {
96 inst.scheduler
97 .validate()
98 .map_err(|reason| SnapshotError::Inconsistent(format!("instance {:?}: {}", id, reason)))?;
99 }
100 Ok(())
101 }
102
103 pub fn instance_count(&self) -> usize {
105 self.instances.len()
106 }
107
108 pub fn instance_ids(&self) -> impl Iterator<Item = InstanceId> + '_ {
110 self.instances.keys().copied()
111 }
112
113 #[doc(hidden)]
115 pub fn __construct(
116 instances: BTreeMap<InstanceId, InstanceSnapshot>,
117 next_instance_id: u64,
118 ) -> Self {
119 Self {
120 instances,
121 next_instance_id,
122 }
123 }
124
125 #[doc(hidden)]
127 pub fn __into_parts(self) -> (BTreeMap<InstanceId, InstanceSnapshot>, u64) {
128 (self.instances, self.next_instance_id)
129 }
130}
131
132#[non_exhaustive]
134#[derive(Debug, Clone, PartialEq, Eq)]
135pub enum SnapshotError {
136 SerializeFailed(String),
138 DeserializeFailed(String),
140 Inconsistent(String),
143 IntegrityMismatch,
146}
147
148impl core::fmt::Display for SnapshotError {
149 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
150 match self {
151 Self::SerializeFailed(m) => write!(f, "snapshot serialize failed: {}", m),
152 Self::DeserializeFailed(m) => write!(f, "snapshot deserialize failed: {}", m),
153 Self::Inconsistent(m) => write!(f, "snapshot structural validation failed: {}", m),
154 Self::IntegrityMismatch => write!(f, "snapshot integrity digest mismatch"),
155 }
156 }
157}
158
159impl std::error::Error for SnapshotError {}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164 use crate::abi::{CapabilityMask, EntityId, Principal, Tick, TypeCode};
165 use crate::state::traits::_sealed::Sealed;
166 use crate::state::{ActionCompute, ActionContext, ActionDeriv, InstanceConfig, Op};
167 use crate::Kernel;
168 use bytes::Bytes;
169 use serde::{Deserialize as De, Serialize as Ser};
170
171 #[derive(Ser, De)]
174 struct SpawnSetAction {
175 id: u64,
176 }
177 impl Sealed for SpawnSetAction {}
178 impl ActionDeriv for SpawnSetAction {
179 const TYPE_CODE: TypeCode = TypeCode(900);
180 const SCHEMA_VERSION: u32 = 1;
181 }
182 impl ActionCompute for SpawnSetAction {
183 fn compute(&self, _ctx: &ActionContext) -> Vec<Op> {
184 let entity = EntityId::new(self.id).unwrap();
185 vec![
186 Op::SpawnEntity {
187 id: entity,
188 owner: Principal::System,
189 },
190 Op::SetComponent {
191 entity,
192 type_code: TypeCode(7),
193 bytes: Bytes::from(vec![0xCDu8; 4]),
194 size: 4,
195 },
196 ]
197 }
198 }
199
200 fn submit(k: &mut Kernel, inst: InstanceId, id: u64) {
201 use crate::state::Action;
202 let bytes = Action::canonical_bytes(&SpawnSetAction { id });
203 k.submit(
204 inst,
205 Principal::System,
206 None,
207 Tick(0),
208 SpawnSetAction::TYPE_CODE,
209 bytes,
210 )
211 .unwrap();
212 }
213
214 fn boot_with_state(actions: &[u64]) -> (Kernel, InstanceId) {
215 let mut k = Kernel::new();
216 k.register_action::<SpawnSetAction>();
217 let inst = k.create_instance(InstanceConfig::default());
218 for id in actions {
219 submit(&mut k, inst, *id);
220 let _ = k.step(Tick(0), CapabilityMask::SYSTEM);
221 }
222 (k, inst)
223 }
224
225 #[test]
226 fn snapshot_empty_kernel_serdes_roundtrip() {
227 let k = Kernel::new();
228 let snap = k.snapshot();
229 assert_eq!(snap.instance_count(), 0);
230 let bytes = snap.serialize().unwrap();
231 let back = KernelSnapshot::deserialize(&bytes).unwrap();
232 assert_eq!(back.instance_count(), 0);
233 }
234
235 #[test]
236 fn snapshot_captures_instance_state() {
237 let (k, inst) = boot_with_state(&[1, 2, 3]);
238 let snap = k.snapshot();
239 assert_eq!(snap.instance_count(), 1);
240 let ids: Vec<InstanceId> = snap.instance_ids().collect();
241 assert_eq!(ids, vec![inst]);
242 }
243
244 #[test]
245 fn snapshot_preserves_entities_and_components() {
246 let (k1, inst) = boot_with_state(&[1, 2]);
247 let snap = k1.snapshot();
248 let bytes = snap.serialize().unwrap();
249 let snap2 = KernelSnapshot::deserialize(&bytes).unwrap();
250 let mut k2 = Kernel::from_snapshot(snap2);
251 let v1 = k1.instance_view(inst).unwrap();
255 let v2 = k2.instance_view(inst).unwrap();
256 assert_eq!(v1.entity_count(), v2.entity_count());
257 assert_eq!(v1.component_count(), v2.component_count());
258 assert_eq!(
259 v1.component(EntityId::new(1).unwrap(), TypeCode(7)),
260 v2.component(EntityId::new(1).unwrap(), TypeCode(7)),
261 );
262 assert_eq!(
263 v1.component(EntityId::new(2).unwrap(), TypeCode(7)),
264 v2.component(EntityId::new(2).unwrap(), TypeCode(7)),
265 );
266 k2.register_action::<SpawnSetAction>();
269 submit(&mut k2, inst, 3);
270 let _ = k2.step(Tick(0), CapabilityMask::SYSTEM);
271 assert_eq!(k2.instance_view(inst).unwrap().entity_count(), 3);
272 }
273
274 #[test]
275 fn snapshot_preserves_id_counters() {
276 let mut k1 = Kernel::new();
279 let _ = k1.create_instance(InstanceConfig::default());
280 let _ = k1.create_instance(InstanceConfig::default());
281 let _ = k1.create_instance(InstanceConfig::default()); let snap = k1.snapshot();
283 let bytes = snap.serialize().unwrap();
284 let snap2 = KernelSnapshot::deserialize(&bytes).unwrap();
285 let mut k2 = Kernel::from_snapshot(snap2);
286 let next1 = k1.create_instance(InstanceConfig::default());
288 let next2 = k2.create_instance(InstanceConfig::default());
289 assert_eq!(next1, next2);
290 assert_eq!(next1.get(), 4);
291 }
292
293 #[test]
294 fn snapshot_preserves_local_tick_and_wall_remainder() {
295 let (k1, inst) = boot_with_state(&[1]);
299 let snap = k1.snapshot();
300 let bytes = snap.serialize().unwrap();
301 let k2 = Kernel::from_snapshot(KernelSnapshot::deserialize(&bytes).unwrap());
302 assert_eq!(
303 k1.instance_view(inst).unwrap().local_tick(),
304 k2.instance_view(inst).unwrap().local_tick(),
305 );
306 }
307
308 #[test]
309 fn snapshot_deserialize_fresh_kernel_no_observers_no_registry() {
310 let (k1, inst) = boot_with_state(&[1]);
314 let snap = k1.snapshot();
315 let mut k2 =
316 Kernel::from_snapshot(KernelSnapshot::deserialize(&snap.serialize().unwrap()).unwrap());
317 assert_eq!(k2.stats().observer_count, 0);
318 k2.submit(
321 inst,
322 Principal::System,
323 None,
324 Tick(0),
325 TypeCode(900),
326 vec![1u8],
327 )
328 .unwrap();
329 let report = k2.step(Tick(0), CapabilityMask::SYSTEM);
330 assert_eq!(report.actions_executed, 1);
331 assert_eq!(report.effects_applied, 0);
332 }
333
334 #[test]
335 fn snapshot_deserialize_verified_checks_digest() {
336 let (k, _) = boot_with_state(&[1, 2]);
337 let bytes = k.snapshot().serialize().unwrap();
338 let digest = KernelSnapshot::digest(&bytes);
339 assert!(KernelSnapshot::deserialize_verified(&bytes, &digest).is_ok());
341 let mut bad = digest;
343 bad[0] ^= 0xFF;
344 assert!(matches!(
345 KernelSnapshot::deserialize_verified(&bytes, &bad),
346 Err(SnapshotError::IntegrityMismatch),
347 ));
348 let mut bad_bytes = bytes.clone();
350 bad_bytes[0] ^= 0xFF;
351 assert!(matches!(
352 KernelSnapshot::deserialize_verified(&bad_bytes, &digest),
353 Err(SnapshotError::IntegrityMismatch),
354 ));
355 }
356
357 #[test]
358 fn snapshot_deterministic_same_state_same_bytes() {
359 let (k1, _) = boot_with_state(&[1, 2, 3]);
363 let (k2, _) = boot_with_state(&[1, 2, 3]);
364 let b1 = k1.snapshot().serialize().unwrap();
365 let b2 = k2.snapshot().serialize().unwrap();
366 assert_eq!(
367 b1, b2,
368 "identical state must produce identical snapshot bytes"
369 );
370 }
371}