rigless 0.3.0

Virtual hardware-in-the-loop runtime: run unmodified firmware on an emulated MCU with every peripheral, chip model and unit of virtual time in Rust, and a CPU core of its own that translates the firmware's instructions to host code
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
// Copyright (c) 2026 Rigless Labs.
// SPDX-License-Identifier: MIT OR Apache-2.0
//! A target's whole state at a stop, written to a file that a new process starts from.
//!
//! A run that's taken a snapshot and a run started from it are the same run: from the stop on, the
//! second does exactly what the first does. The file holds everything that decides that. The
//! emulator's own state, the core, its interrupt controller, its timers and the clock, comes from
//! [`Host::save_state`]. Every writable region of memory comes through the host's memory, and
//! flash isn't saved, since the firmware put it there and a restore refuses any other firmware. And
//! every model, chip and board, the session and whatever the process driving the run keeps, comes
//! through [`Store`].
//!
//! A restore builds everything fresh, the way a run starts, and loads each value's state back into
//! what it built. So [`Store::load`] reads into `self` rather than making a new value, and nothing
//! has to be built from the bytes alone: a chip on a bus is whatever the board fitted there, and it
//! reads its own state back. A value holds three kinds of field: values, saved whole through serde,
//! parts, which save themselves, and what construction built and a restore keeps, a register table
//! or a callback. [`store!`](crate::store) names every field once as one of the three, so a field
//! added later doesn't compile until someone says which. It's how every part, model, chip and board
//! this crate ships implements [`Store`], and yours can too.
//!
//! A file is `rigless snapshot`, a `u16` format, then parts. Each part is a name, its length, the
//! SHA-256 of its bytes and the bytes. The target writes the `target` part, and the engine appends
//! its own. A reader takes the part it knows by name and skips the rest.

use std::any::type_name;
use std::fmt::{self, Write as _};
use std::fs;
use std::marker::PhantomData;
use std::path::{Path, PathBuf};
use std::sync::{Mutex, PoisonError};

use serde::de::{Deserialize, Deserializer, SeqAccess, Visitor};
use serde::ser::{Serialize, SerializeTuple, Serializer};
use sha2::{Digest, Sha256};

use crate::emulator::{Error as EmulatorError, Host, Resume, Stop};
use crate::machine::Board;
use crate::session::Session;
use crate::soc::{Region, Width};
use crate::version::RUNTIME;

/// What every snapshot file starts with.
const MAGIC: &[u8; 16] = b"rigless snapshot";
/// How the parts after the magic are laid out.
const FORMAT: u16 = 1;
/// The part the target writes.
const TARGET: &str = "target";

/// Something a snapshot keeps: a model, a chip, a board, the session, a driver.
///
/// Both methods answer that the type can't be kept unless it says otherwise, so a board or a chip that
/// can't be kept needs only `impl Store for It {}`, and a snapshot that reaches it fails with its name.
pub trait Store {
    /// Writes what a restore needs to put `self` back as it stands.
    fn store(&self, to: &mut Writer) -> Result<(), Error> {
        let _ = to;
        Err(Error::Unsaved(type_name::<Self>()))
    }

    /// Reads what [`Store::store`] wrote back into a freshly built `self` of the same shape.
    fn load(&mut self, from: &mut Reader) -> Result<(), Error> {
        let _ = from;
        Err(Error::Unsaved(type_name::<Self>()))
    }
}

/// Where [`Store::store`] writes.
#[derive(Debug, Default)]
pub struct Writer(Vec<u8>);

impl Writer {
    /// Writes `value` whole.
    pub fn put<T: Serialize + ?Sized>(&mut self, value: &T) -> Result<(), Error> {
        let bytes = std::mem::take(&mut self.0);
        self.0 =
            postcard::to_extend(value, bytes).map_err(|error| Error::Bytes(error.to_string()))?;
        Ok(())
    }
}

/// Where [`Store::load`] reads from.
///
/// The bytes live as long as the process, so a model's `&'static str`, an instance's name or a told
/// reason, reads back pointing into them.
#[derive(Debug)]
pub struct Reader(&'static [u8]);

impl Reader {
    /// Reads a value [`Writer::put`] wrote.
    pub fn take<T: Deserialize<'static>>(&mut self) -> Result<T, Error> {
        let (value, rest) =
            postcard::take_from_bytes(self.0).map_err(|error| Error::Bytes(error.to_string()))?;
        self.0 = rest;
        Ok(value)
    }
}

/// Why a snapshot couldn't be taken or started from.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
    /// Something in the run can't be kept in a snapshot.
    #[error("{0} can't be kept in a snapshot")]
    Unsaved(&'static str),
    /// The session had already failed, so there's no state worth keeping.
    #[error("the run had already failed, so there's nothing to keep")]
    Failed,
    /// A reset was asked for inside this stop, by the core or by a model, and the emulator carries it
    /// out once the stop is over, so a snapshot would hold the models after it and the core before it.
    #[error(
        "a reset was asked for at this stop and it hasn't happened yet, so take the snapshot at another"
    )]
    Resetting,
    /// The run started from a snapshot and hasn't yet got back to the stop it was taken at, which
    /// it does before its first instruction.
    #[error("the run hasn't got back to the stop it was resumed at")]
    Resuming,
    /// The file isn't a snapshot this runtime reads.
    #[error("{} isn't a snapshot: {why}", path.display())]
    NotASnapshot {
        /// The file.
        path: PathBuf,
        /// Why not.
        why: &'static str,
    },
    /// The file names something other than what's running here.
    #[error("the snapshot is of {what} {snapshot}, and this is {here}")]
    Differs {
        /// What differs.
        what: &'static str,
        /// What the snapshot names.
        snapshot: String,
        /// What's here.
        here: String,
    },
    /// A value's saved shape doesn't match what was built: a different number of chips on a bus.
    #[error("the snapshot has {saved} of {what} where this build has {built}")]
    Shape {
        /// The collection.
        what: &'static str,
        /// How many the snapshot saved.
        saved: usize,
        /// How many were built.
        built: usize,
    },
    /// The bytes don't read as the values this build saves.
    #[error("the snapshot's bytes don't read back: {0}")]
    Bytes(String),
    /// A value read back left bytes behind, so it's not what wrote them.
    #[error("{0} bytes were left over reading the snapshot")]
    Trailing(usize),
    /// The emulator couldn't save or reach what it holds.
    #[error(transparent)]
    Emulator(#[from] EmulatorError),
    /// The file couldn't be read.
    #[error("reading {}: {source}", path.display())]
    Io {
        /// The file.
        path: PathBuf,
        /// Why.
        #[source]
        source: std::io::Error,
    },
}

/// Implements [`Store`](crate::snapshot::Store) for a type. `store!(Type)` saves the whole value
/// through serde. With fields, `values` are saved through serde, `parts` save themselves, and `built`
/// are kept as construction left them, and every field is named, so one added later doesn't compile
/// until it's placed. A value derives `serde::Serialize` and `serde::Deserialize`, and a part
/// implements `Store` itself, often with this.
///
/// ```
/// use rigless::snapshot::Store;
///
/// /// The bytes a model sent, saved whole.
/// #[derive(Debug, serde::Serialize, serde::Deserialize)]
/// struct Sent(Vec<u8>);
/// rigless::store!(Sent);
///
/// /// A model: the bytes it sent are its own state, and its table is what construction built.
/// struct Beacon { sent: Sent, frames: u32, table: &'static [u8] }
/// rigless::store!(Beacon { values: [frames], parts: [sent], built: [table] });
///
/// fn kept<T: Store>() {}
/// kept::<Beacon>();
/// ```
#[macro_export]
macro_rules! store {
    ($type:ident) => {
        impl $crate::snapshot::Store for $type {
            fn store(
                &self,
                to: &mut $crate::snapshot::Writer,
            ) -> ::core::result::Result<(), $crate::snapshot::Error> {
                to.put(self)
            }

            fn load(
                &mut self,
                from: &mut $crate::snapshot::Reader,
            ) -> ::core::result::Result<(), $crate::snapshot::Error> {
                *self = from.take()?;
                ::core::result::Result::Ok(())
            }
        }
    };
    ($type:ident {
        values: [$($value:ident),* $(,)?],
        parts: [$($part:ident),* $(,)?],
        built: [$($built:ident),* $(,)?] $(,)?
    }) => {
        impl $crate::snapshot::Store for $type {
            fn store(
                &self,
                to: &mut $crate::snapshot::Writer,
            ) -> ::core::result::Result<(), $crate::snapshot::Error> {
                let Self { $($value,)* $($part,)* $($built: _,)* } = self;
                $(to.put($value)?;)*
                $($crate::snapshot::Store::store($part, to)?;)*
                ::core::result::Result::Ok(())
            }

            fn load(
                &mut self,
                from: &mut $crate::snapshot::Reader,
            ) -> ::core::result::Result<(), $crate::snapshot::Error> {
                let Self { $($value,)* $($part,)* $($built: _,)* } = self;
                $(*$value = from.take()?;)*
                $($crate::snapshot::Store::load($part, from)?;)*
                ::core::result::Result::Ok(())
            }
        }
    };
}
pub use crate::store;

impl<T: Store, const N: usize> Store for [T; N] {
    fn store(&self, to: &mut Writer) -> Result<(), Error> {
        self.iter().try_for_each(|element| element.store(to))
    }

    fn load(&mut self, from: &mut Reader) -> Result<(), Error> {
        self.iter_mut().try_for_each(|element| element.load(from))
    }
}

impl<T: Store> Store for Vec<T> {
    fn store(&self, to: &mut Writer) -> Result<(), Error> {
        to.put(&self.len())?;
        self.iter().try_for_each(|element| element.store(to))
    }

    fn load(&mut self, from: &mut Reader) -> Result<(), Error> {
        let saved: usize = from.take()?;
        same_shape::<Self>(saved, self.len())?;
        self.iter_mut().try_for_each(|element| element.load(from))
    }
}

impl<T: Store> Store for Option<T> {
    fn store(&self, to: &mut Writer) -> Result<(), Error> {
        to.put(&self.is_some())?;
        self.as_ref().map_or(Ok(()), |value| value.store(to))
    }

    fn load(&mut self, from: &mut Reader) -> Result<(), Error> {
        let saved: bool = from.take()?;
        same_shape::<Self>(saved.into(), self.is_some().into())?;
        self.as_mut().map_or(Ok(()), |value| value.load(from))
    }
}

fn same_shape<T: ?Sized>(saved: usize, built: usize) -> Result<(), Error> {
    if saved == built {
        Ok(())
    } else {
        Err(Error::Shape {
            what: type_name::<T>(),
            saved,
            built,
        })
    }
}

/// Serde for an array of any length, which serde itself stops at 32 for:
/// `#[serde(with = "crate::snapshot::array")]`.
pub(crate) mod array {
    use super::{
        Deserialize, Deserializer, PhantomData, SeqAccess, Serialize, SerializeTuple, Serializer,
        Visitor, fmt,
    };

    pub(crate) fn serialize<S: Serializer, T: Serialize, const N: usize>(
        array: &[T; N],
        serializer: S,
    ) -> Result<S::Ok, S::Error> {
        let mut tuple = serializer.serialize_tuple(N)?;
        for element in array {
            tuple.serialize_element(element)?;
        }
        tuple.end()
    }

    pub(crate) fn deserialize<'de, D: Deserializer<'de>, T: Deserialize<'de>, const N: usize>(
        deserializer: D,
    ) -> Result<[T; N], D::Error> {
        deserializer.deserialize_tuple(N, Elements::<T, N>(PhantomData))
    }

    struct Elements<T, const N: usize>(PhantomData<T>);

    impl<'de, T: Deserialize<'de>, const N: usize> Visitor<'de> for Elements<T, N> {
        type Value = [T; N];

        fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
            write!(formatter, "{N} elements")
        }

        fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<[T; N], A::Error> {
            let mut elements = Vec::with_capacity(N);
            while let Some(element) = seq.next_element()? {
                elements.push(element);
            }
            let read = elements.len();
            elements
                .try_into()
                .map_err(|_short| serde::de::Error::invalid_length(read, &self))
        }
    }
}

/// A snapshot as a target writes it: the file's bytes, and the SHA-256 of the target's part, which
/// names it.
#[derive(Debug)]
pub struct Taken {
    /// The whole file.
    pub file: Vec<u8>,
    /// The SHA-256 of the target's part.
    pub hash: [u8; 32],
}

/// The target's part of `file`, read from `path`, and its hash, when it's a snapshot this runtime
/// wrote.
pub(crate) fn parse<'a>(file: &'a [u8], path: &Path) -> Result<(Body<'a>, [u8; 32]), Error> {
    let (body, hash) = target_part(file).map_err(|why| Error::NotASnapshot {
        path: path.to_path_buf(),
        why,
    })?;
    let (body, left) = postcard::take_from_bytes::<Body<'a>>(body)
        .map_err(|error| Error::Bytes(error.to_string()))?;
    if !left.is_empty() {
        return Err(Error::Trailing(left.len()));
    }
    // ceiling: the runtime's version names a commit, and every uncommitted tree on one commit says
    // the same `.dirty` version, so a snapshot from one such tree reads into another's models; upgrade
    // when snapshots cross between builds nobody tagged.
    same("the runtime", body.runtime, RUNTIME)?;
    Ok((body, hash))
}

/// The target's part.
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub(crate) struct Body<'a> {
    runtime: &'a str,
    machine: &'a str,
    target: &'a str,
    firmware: [u8; 32],
    stop: Stop,
    #[serde(serialize_with = "bytes")]
    emulator: &'a [u8],
    #[serde(borrow)]
    memory: Vec<Contents<'a>>,
    #[serde(serialize_with = "bytes")]
    session: &'a [u8],
    #[serde(serialize_with = "bytes")]
    driver: &'a [u8],
}

/// One saved region's bytes, at the address it starts at.
#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct Contents<'a> {
    base: u64,
    #[serde(serialize_with = "bytes")]
    bytes: &'a [u8],
}

/// Writes bytes as bytes, which is what a borrowed `&[u8]` reads back from, rather than as a sequence.
fn bytes<S: Serializer>(bytes: &&[u8], serializer: S) -> Result<S::Ok, S::Error> {
    serializer.serialize_bytes(bytes)
}

/// Takes a snapshot of `session`'s target where it stands, at `stop`, inside a driver's
/// [`crate::emulator::Driver::on_stop`]: the emulator's state and memory through `host`, every
/// model's and chip's through the session, and `driver`'s own. `firmware` is what the run's images
/// and the address it booted from digest to, [`crate::image::digest`].
pub fn take<D: Board + ?Sized>(
    session: &mut Session<D>,
    host: &mut dyn Host,
    stop: Stop,
    firmware: &[u8; 32],
    driver: &dyn Store,
) -> Result<Taken, Error> {
    let emulator = host.save_state()?;
    let memory = saved_regions(session.mcu().bus().memory())
        .map(|region| Ok((region.base, read_region(host, region)?)))
        .collect::<Result<Vec<_>, Error>>()?;
    let mut state = Writer::default();
    session.store(&mut state)?;
    let mut kept = Writer::default();
    driver.store(&mut kept)?;
    let target = session.board().target_names()[session.target()];
    let body = Body {
        runtime: RUNTIME,
        machine: session.board().name(),
        target,
        firmware: *firmware,
        stop,
        emulator: &emulator,
        memory: memory
            .iter()
            .map(|(base, bytes)| Contents { base: *base, bytes })
            .collect(),
        session: &state.0,
        driver: &kept.0,
    };
    let body = postcard::to_allocvec(&body).map_err(|error| Error::Bytes(error.to_string()))?;
    let mut file = MAGIC.to_vec();
    file.extend_from_slice(&FORMAT.to_le_bytes());
    let hash = put_part(&mut file, TARGET, &body);
    Ok(Taken { file, hash })
}

/// Appends a part to a file's bytes, its name, its length, its SHA-256 and its bytes, and answers the
/// SHA-256.
fn put_part(file: &mut Vec<u8>, name: &str, body: &[u8]) -> [u8; 32] {
    let hash = digest(body);
    file.push(u8::try_from(name.len()).expect("a short name"));
    file.extend_from_slice(name.as_bytes());
    file.extend_from_slice(
        &u64::try_from(body.len())
            .expect("a length fits")
            .to_le_bytes(),
    );
    file.extend_from_slice(&hash);
    file.extend_from_slice(body);
    hash
}

/// The target's part of a file and its SHA-256, checked against each other, or why the file isn't a
/// snapshot.
fn target_part(file: &[u8]) -> Result<(&[u8], [u8; 32]), &'static str> {
    const ENDS_EARLY: &str = "it ends partway through a part";
    fn split<'a>(bytes: &mut &'a [u8], length: usize) -> Result<&'a [u8], &'static str> {
        let (taken, rest) = bytes.split_at_checked(length).ok_or(ENDS_EARLY)?;
        *bytes = rest;
        Ok(taken)
    }
    let mut rest = file.strip_prefix(MAGIC).ok_or("it doesn't start as one")?;
    if split(&mut rest, 2)? != FORMAT.to_le_bytes() {
        return Err("it's laid out in a format this runtime doesn't read");
    }
    while !rest.is_empty() {
        let name = split(&mut rest, 1)?[0];
        let name = split(&mut rest, name.into())?;
        let length = u64::from_le_bytes(split(&mut rest, 8)?.try_into().expect("eight bytes"));
        let hash: [u8; 32] = split(&mut rest, 32)?.try_into().expect("thirty-two bytes");
        let body = split(
            &mut rest,
            usize::try_from(length).map_err(|_huge| ENDS_EARLY)?,
        )?;
        if name == TARGET.as_bytes() {
            return if digest(body) == hash {
                Ok((body, hash))
            } else {
                Err("its target part doesn't match its own hash")
            };
        }
    }
    Err("it has no target part")
}

/// The regions a snapshot saves: every one the firmware can write, which is every one the emulator
/// maps writable, and every one a model of the part programs.
fn saved_regions(memory: &[Region]) -> impl Iterator<Item = &Region> {
    memory
        .iter()
        .filter(|region| region.kind.is_writable() || region.programmer.is_some())
}

/// A region's bytes, eight at a time and then one at a time for what's left.
fn read_region(host: &mut dyn Host, region: &Region) -> Result<Vec<u8>, Error> {
    let size = usize::try_from(region.size).expect("a region fits in memory");
    let mut bytes = Vec::with_capacity(size);
    let mut at = region.base;
    while bytes.len() + 8 <= size {
        bytes.extend_from_slice(&crate::emulator::memory::to_target(
            host.read(at, Width::W64)?,
            8,
            host.endian(),
        ));
        at += 8;
    }
    while bytes.len() < size {
        bytes.push(host.read(at, Width::W8)?.to_le_bytes()[0]);
        at += 1;
    }
    Ok(bytes)
}

/// The SHA-256 of `bytes`: how a snapshot names itself and each part in it.
#[must_use]
pub fn digest(bytes: &[u8]) -> [u8; 32] {
    Sha256::digest(bytes).into()
}

/// A digest as the lowercase hexadecimal people read it in.
fn hex(digest: &[u8; 32]) -> String {
    digest
        .iter()
        .fold(String::with_capacity(64), |mut text, byte| {
            write!(text, "{byte:02x}").expect("a string takes any text");
            text
        })
}

/// A snapshot read back from its file, its target's part checked against its hash and against this
/// runtime, and ready to start a run from.
#[derive(Debug)]
pub struct Snapshot {
    hash: [u8; 32],
    body: Body<'static>,
}

impl Snapshot {
    /// Reads the snapshot at `path`.
    pub fn read(path: &Path) -> Result<Self, Error> {
        let file = fs::read(path).map_err(|source| Error::Io {
            path: path.to_path_buf(),
            source,
        })?;
        Self::from_bytes(keep(file), path)
    }

    fn from_bytes(file: &'static [u8], path: &Path) -> Result<Self, Error> {
        let (body, hash) = parse(file, path)?;
        Ok(Self { hash, body })
    }

    /// The SHA-256 of the target's part, as `Snapshotted` gave it when the snapshot was taken.
    #[must_use]
    pub fn hash(&self) -> [u8; 32] {
        self.hash
    }

    /// The stop the snapshot was taken at.
    #[must_use]
    pub fn stop(&self) -> Stop {
        self.body.stop
    }

    /// Loads the snapshot into `session`, built fresh for the same machine and target, and into
    /// `driver`, for a run whose images and boot address digest to `firmware`, and answers what the
    /// emulator starts from, [`crate::emulator::Start::resume`].
    pub fn restore<D: Board + ?Sized>(
        &self,
        session: &mut Session<D>,
        firmware: &[u8; 32],
        driver: &mut dyn Store,
    ) -> Result<Resume<'static>, Error> {
        let body = &self.body;
        let here = session.board().target_names()[session.target()];
        same("the machine", body.machine, session.board().name())?;
        same("the target", body.target, here)?;
        if body.firmware != *firmware {
            return Err(Error::Differs {
                what: "the firmware",
                snapshot: hex(&body.firmware),
                here: hex(firmware),
            });
        }
        let regions = |regions: &mut dyn Iterator<Item = (u64, usize)>| {
            regions
                .map(|(base, size)| format!("{size} bytes at {base:#x}"))
                .collect::<Vec<_>>()
                .join(", ")
        };
        let saved = regions(
            &mut body
                .memory
                .iter()
                .map(|contents| (contents.base, contents.bytes.len())),
        );
        let built = regions(
            &mut saved_regions(session.mcu().bus().memory()).map(|region| {
                let size = usize::try_from(region.size).expect("a region fits in memory");
                (region.base, size)
            }),
        );
        same("memory", &saved, &built)?;
        load(session, body.session)?;
        load(driver, body.driver)?;
        Ok(Resume {
            stop: self.body.stop,
            state: self.body.emulator,
            memory: self
                .body
                .memory
                .iter()
                .map(|contents| (contents.base, contents.bytes))
                .collect(),
        })
    }
}

/// What [`Store::store`] writes of `kept`, for a checkpoint a run keeps in memory.
pub(crate) fn stored(kept: &dyn Store) -> Result<Vec<u8>, Error> {
    let mut to = Writer::default();
    kept.store(&mut to)?;
    Ok(to.0)
}

/// Keeps `bytes` for the rest of the process and lends them for as long, which is what lets a name a
/// model reads back point into them.
// ceiling: every snapshot read stays in memory until the process ends; upgrade when one process
// reads more than one.
pub(crate) fn keep(bytes: Vec<u8>) -> &'static [u8] {
    // Held here so the bytes stay reachable, which is how a leak checker sees they're kept.
    static KEPT: Mutex<Vec<&'static [u8]>> = Mutex::new(Vec::new());
    let kept: &'static [u8] = Box::leak(bytes.into_boxed_slice());
    KEPT.lock()
        .unwrap_or_else(PoisonError::into_inner)
        .push(kept);
    kept
}

fn same(what: &'static str, snapshot: &str, here: &str) -> Result<(), Error> {
    if snapshot == here {
        Ok(())
    } else {
        Err(Error::Differs {
            what,
            snapshot: snapshot.to_owned(),
            here: here.to_owned(),
        })
    }
}

/// Reads `bytes` into `into`, all of them.
pub(crate) fn load(into: &mut dyn Store, bytes: &'static [u8]) -> Result<(), Error> {
    let mut from = Reader(bytes);
    into.load(&mut from)?;
    if from.0.is_empty() {
        Ok(())
    } else {
        Err(Error::Trailing(from.0.len()))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    /// A snapshot saves what the firmware writes and what a model programs, and leaves the rest as
    /// the images placed it.
    #[test]
    fn a_snapshot_saves_the_writable_regions_and_the_programmed_ones() {
        let memory = [
            Region::flash("flash", 0x0800_0000, 0x1000).programmed_by("PROG"),
            Region::flash("other", 0x0900_0000, 0x1000),
            Region::system("system", 0x0A00_0000, 0x100),
            Region::ram("sram", 0x2000_0000, 0x100),
        ];
        let saved: Vec<&str> = saved_regions(&memory).map(|region| region.name).collect();
        assert_eq!(saved, ["flash", "sram"]);
    }

    /// A driver that keeps one number.
    #[derive(Debug, Default, PartialEq)]
    struct Counting(u64);

    impl Store for Counting {
        fn store(&self, to: &mut Writer) -> Result<(), Error> {
            to.put(&self.0)
        }

        fn load(&mut self, from: &mut Reader) -> Result<(), Error> {
            self.0 = from.take()?;
            Ok(())
        }
    }

    /// A target's part holding `body` and, after it, a part called `rig` holding `rig`, byte for byte as
    /// the engine's own test lays the same file out.
    const LAID_OUT: &str = "7269676c65737320736e617073686f740100067461726765740400000000000000230d8358dc8e8890b4c58deeb62912ee2f20357ae92a5cc861b98e68fe31acb5626f6479037269670300000000000000c00ae0a63348054ecc8343f23278fe13bfa5c44255e208ff6102ffc2d7910a53726967";

    #[test]
    fn the_file_is_laid_out_as_the_engine_lays_it_out_and_the_target_finds_its_own_part() {
        let mut file = MAGIC.to_vec();
        file.extend_from_slice(&FORMAT.to_le_bytes());
        put_part(&mut file, TARGET, b"body");
        put_part(&mut file, "rig", b"rig");
        let laid_out = file.iter().fold(String::new(), |mut text, byte| {
            write!(text, "{byte:02x}").expect("a string takes any text");
            text
        });
        assert_eq!(laid_out, LAID_OUT);
        assert_eq!(target_part(&file).unwrap().0, b"body");
    }

    #[test]
    fn a_collection_of_another_length_or_presence_is_refused() {
        let mut to = Writer::default();
        vec![Counting(1), Counting(2)].store(&mut to).unwrap();
        Some(Counting(3)).store(&mut to).unwrap();
        let mut from = Reader(keep(to.0));
        assert!(matches!(
            vec![Counting(0)].load(&mut from),
            Err(Error::Shape {
                saved: 2,
                built: 1,
                ..
            })
        ));
        let mut to = Writer::default();
        Some(Counting(3)).store(&mut to).unwrap();
        let mut from = Reader(keep(to.0));
        assert!(matches!(
            None::<Counting>.load(&mut from),
            Err(Error::Shape { .. })
        ));
    }

    #[test]
    fn bytes_left_over_are_refused() {
        let mut to = Writer::default();
        Counting(1).store(&mut to).unwrap();
        to.put(&2_u64).unwrap();
        assert!(matches!(
            load(&mut Counting(0), keep(to.0)),
            Err(Error::Trailing(1))
        ));
    }

    #[test]
    fn an_array_of_any_length_reads_back() {
        #[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
        struct Wide {
            #[serde(with = "array")]
            bytes: [u8; 100],
        }
        let wide = Wide {
            bytes: std::array::from_fn(|index| u8::try_from(index).unwrap()),
        };
        let mut to = Writer::default();
        to.put(&wide).unwrap();
        assert_eq!(Reader(keep(to.0)).take::<Wide>().unwrap(), wide);
    }
}