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 CapabilityMask::SYSTEM,
208 Tick(0),
209 SpawnSetAction::TYPE_CODE,
210 bytes,
211 )
212 .unwrap();
213 }
214
215 fn boot_with_state(actions: &[u64]) -> (Kernel, InstanceId) {
216 let mut k = Kernel::new();
217 k.register_action::<SpawnSetAction>();
218 let inst = k.create_instance(InstanceConfig::default());
219 for id in actions {
220 submit(&mut k, inst, *id);
221 let _ = k.step(Tick(0), CapabilityMask::SYSTEM);
222 }
223 (k, inst)
224 }
225
226 #[test]
227 fn snapshot_empty_kernel_serdes_roundtrip() {
228 let k = Kernel::new();
229 let snap = k.snapshot();
230 assert_eq!(snap.instance_count(), 0);
231 let bytes = snap.serialize().unwrap();
232 let back = KernelSnapshot::deserialize(&bytes).unwrap();
233 assert_eq!(back.instance_count(), 0);
234 }
235
236 #[test]
237 fn snapshot_captures_instance_state() {
238 let (k, inst) = boot_with_state(&[1, 2, 3]);
239 let snap = k.snapshot();
240 assert_eq!(snap.instance_count(), 1);
241 let ids: Vec<InstanceId> = snap.instance_ids().collect();
242 assert_eq!(ids, vec![inst]);
243 }
244
245 #[test]
246 fn snapshot_preserves_entities_and_components() {
247 let (k1, inst) = boot_with_state(&[1, 2]);
248 let snap = k1.snapshot();
249 let bytes = snap.serialize().unwrap();
250 let snap2 = KernelSnapshot::deserialize(&bytes).unwrap();
251 let mut k2 = Kernel::from_snapshot(snap2);
252 let v1 = k1.instance_view(inst).unwrap();
256 let v2 = k2.instance_view(inst).unwrap();
257 assert_eq!(v1.entity_count(), v2.entity_count());
258 assert_eq!(v1.component_count(), v2.component_count());
259 assert_eq!(
260 v1.component(EntityId::new(1).unwrap(), TypeCode(7)),
261 v2.component(EntityId::new(1).unwrap(), TypeCode(7)),
262 );
263 assert_eq!(
264 v1.component(EntityId::new(2).unwrap(), TypeCode(7)),
265 v2.component(EntityId::new(2).unwrap(), TypeCode(7)),
266 );
267 k2.register_action::<SpawnSetAction>();
270 submit(&mut k2, inst, 3);
271 let _ = k2.step(Tick(0), CapabilityMask::SYSTEM);
272 assert_eq!(k2.instance_view(inst).unwrap().entity_count(), 3);
273 }
274
275 #[test]
276 fn snapshot_preserves_id_counters() {
277 let mut k1 = Kernel::new();
280 let _ = k1.create_instance(InstanceConfig::default());
281 let _ = k1.create_instance(InstanceConfig::default());
282 let _ = k1.create_instance(InstanceConfig::default()); let snap = k1.snapshot();
284 let bytes = snap.serialize().unwrap();
285 let snap2 = KernelSnapshot::deserialize(&bytes).unwrap();
286 let mut k2 = Kernel::from_snapshot(snap2);
287 let next1 = k1.create_instance(InstanceConfig::default());
289 let next2 = k2.create_instance(InstanceConfig::default());
290 assert_eq!(next1, next2);
291 assert_eq!(next1.get(), 4);
292 }
293
294 #[test]
295 fn snapshot_preserves_local_tick_and_wall_remainder() {
296 let (k1, inst) = boot_with_state(&[1]);
300 let snap = k1.snapshot();
301 let bytes = snap.serialize().unwrap();
302 let k2 = Kernel::from_snapshot(KernelSnapshot::deserialize(&bytes).unwrap());
303 assert_eq!(
304 k1.instance_view(inst).unwrap().local_tick(),
305 k2.instance_view(inst).unwrap().local_tick(),
306 );
307 }
308
309 #[test]
310 fn snapshot_deserialize_fresh_kernel_no_observers_no_registry() {
311 let (k1, inst) = boot_with_state(&[1]);
315 let snap = k1.snapshot();
316 let mut k2 =
317 Kernel::from_snapshot(KernelSnapshot::deserialize(&snap.serialize().unwrap()).unwrap());
318 assert_eq!(k2.stats().observer_count, 0);
319 k2.submit(
322 inst,
323 Principal::System,
324 None,
325 CapabilityMask::SYSTEM,
326 Tick(0),
327 TypeCode(900),
328 vec![1u8],
329 )
330 .unwrap();
331 let report = k2.step(Tick(0), CapabilityMask::SYSTEM);
332 assert_eq!(report.actions_executed, 1);
333 assert_eq!(report.effects_applied, 0);
334 }
335
336 #[test]
337 fn snapshot_deserialize_verified_checks_digest() {
338 let (k, _) = boot_with_state(&[1, 2]);
339 let bytes = k.snapshot().serialize().unwrap();
340 let digest = KernelSnapshot::digest(&bytes);
341 assert!(KernelSnapshot::deserialize_verified(&bytes, &digest).is_ok());
343 let mut bad = digest;
345 bad[0] ^= 0xFF;
346 assert!(matches!(
347 KernelSnapshot::deserialize_verified(&bytes, &bad),
348 Err(SnapshotError::IntegrityMismatch),
349 ));
350 let mut bad_bytes = bytes.clone();
352 bad_bytes[0] ^= 0xFF;
353 assert!(matches!(
354 KernelSnapshot::deserialize_verified(&bad_bytes, &digest),
355 Err(SnapshotError::IntegrityMismatch),
356 ));
357 }
358
359 #[test]
360 fn snapshot_deterministic_same_state_same_bytes() {
361 let (k1, _) = boot_with_state(&[1, 2, 3]);
365 let (k2, _) = boot_with_state(&[1, 2, 3]);
366 let b1 = k1.snapshot().serialize().unwrap();
367 let b2 = k2.snapshot().serialize().unwrap();
368 assert_eq!(
369 b1, b2,
370 "identical state must produce identical snapshot bytes"
371 );
372 }
373}