Skip to main content

sim_lib_auto_core/
flash.rs

1//! Modeled ECU flash backup and restore helpers.
2
3use sim_kernel::{Error, Expr, Result, Symbol};
4
5/// Content-addressed stock-map backup for a modeled ECU.
6#[derive(Clone, Debug, PartialEq, Eq)]
7pub struct StockMapBackup {
8    /// ECU label the stock bytes came from.
9    pub ecu: String,
10    /// Stable content key for the stock bytes.
11    pub content_key: String,
12    /// Synthetic stock bytes held by the modeled backup.
13    pub bytes: Vec<u8>,
14}
15
16impl StockMapBackup {
17    /// Builds a stock-map backup and derives its content key.
18    pub fn new(ecu: impl Into<String>, bytes: Vec<u8>) -> Self {
19        let ecu = ecu.into();
20        let content_key = stock_content_key(&ecu, &bytes);
21        Self {
22            ecu,
23            content_key,
24            bytes,
25        }
26    }
27
28    /// Returns the reversal artifact required by irreversible flash writes.
29    pub fn reversal_artifact(&self) -> Expr {
30        Expr::Map(vec![
31            entry(
32                "kind",
33                Expr::Symbol(Symbol::qualified("auto", "StockMapBackup")),
34            ),
35            string_entry("ecu", &self.ecu),
36            string_entry("content-key", &self.content_key),
37            entry("bytes", Expr::Bytes(self.bytes.clone())),
38        ])
39    }
40}
41
42/// Modeled flash session that never touches a live VCI or ECU.
43#[derive(Clone, Debug, PartialEq, Eq)]
44pub struct ModeledFlashSession {
45    ecu: String,
46    stock_bytes: Vec<u8>,
47    current_bytes: Vec<u8>,
48}
49
50impl ModeledFlashSession {
51    /// Builds a modeled flash session from synthetic stock bytes.
52    pub fn new(ecu: impl Into<String>, stock_bytes: Vec<u8>) -> Self {
53        Self {
54            ecu: ecu.into(),
55            current_bytes: stock_bytes.clone(),
56            stock_bytes,
57        }
58    }
59
60    /// Returns the current modeled ECU bytes.
61    pub fn read_ecu(&self) -> &[u8] {
62        &self.current_bytes
63    }
64
65    /// Stores a stock-map backup for the original modeled ECU bytes.
66    pub fn backup_stock(&self) -> StockMapBackup {
67        StockMapBackup::new(self.ecu.clone(), self.stock_bytes.clone())
68    }
69
70    /// Applies a modeled flash payload after checking the stock backup.
71    pub fn flash(&mut self, tuned_bytes: Vec<u8>, backup: &StockMapBackup) -> Result<()> {
72        self.validate_backup(backup)?;
73        self.current_bytes = tuned_bytes;
74        Ok(())
75    }
76
77    /// Restores the modeled ECU to the backed-up stock bytes.
78    pub fn restore(&mut self, backup: &StockMapBackup) -> Result<Vec<u8>> {
79        self.validate_backup(backup)?;
80        self.current_bytes = backup.bytes.clone();
81        Ok(self.current_bytes.clone())
82    }
83
84    fn validate_backup(&self, backup: &StockMapBackup) -> Result<()> {
85        if backup.ecu != self.ecu {
86            return Err(Error::Eval(format!(
87                "stock-map backup for ECU {} cannot restore ECU {}",
88                backup.ecu, self.ecu
89            )));
90        }
91        let expected = stock_content_key(&backup.ecu, &backup.bytes);
92        if backup.content_key != expected {
93            return Err(Error::Eval(
94                "stock-map backup content key does not match bytes".to_owned(),
95            ));
96        }
97        if backup.bytes != self.stock_bytes {
98            return Err(Error::Eval(
99                "stock-map backup bytes do not match modeled stock".to_owned(),
100            ));
101        }
102        Ok(())
103    }
104}
105
106/// Computes a stable content key for modeled stock-map bytes.
107pub fn stock_content_key(ecu: &str, bytes: &[u8]) -> String {
108    let mut hash = 0xcbf29ce484222325u64;
109    for byte in ecu
110        .as_bytes()
111        .iter()
112        .copied()
113        .chain(std::iter::once(0xff))
114        .chain(bytes.iter().copied())
115    {
116        hash ^= u64::from(byte);
117        hash = hash.wrapping_mul(0x100000001b3);
118    }
119    format!("auto-stock-fnv1a64-{hash:016x}")
120}
121
122fn string_entry(name: &str, value: &str) -> (Expr, Expr) {
123    entry(name, Expr::String(value.to_owned()))
124}
125
126fn entry(name: &str, value: Expr) -> (Expr, Expr) {
127    (Expr::Symbol(Symbol::new(name.to_owned())), value)
128}