Skip to main content

apple_nvram/
v3.rs

1use std::{
2    borrow::Cow,
3    fmt::{Display, Formatter},
4    ops::ControlFlow,
5};
6
7use crate::{Error, VarType};
8
9// https://github.com/apple-oss-distributions/xnu/blob/main/iokit/Kernel/IONVRAMV3Handler.cpp#L630
10
11const VARIABLE_STORE_SIGNATURE: &[u8; 4] = b"3VVN";
12const VARIABLE_STORE_VERSION: u8 = 0x1;
13const VARIABLE_DATA: u16 = 0x55AA;
14
15const PARTITION_SIZE: usize = 0x10000;
16const STORE_HEADER_SIZE: usize = 24;
17const VAR_HEADER_SIZE: usize = 36;
18const VAR_ADDED: u8 = 0x7F;
19const VAR_IN_DELETED_TRANSITION: u8 = 0xFE;
20const VAR_DELETED: u8 = 0xFD;
21
22const APPLE_COMMON_VARIABLE_GUID: &[u8; 16] = &[
23    0x7C, 0x43, 0x61, 0x10, 0xAB, 0x2A, 0x4B, 0xBB, 0xA8, 0x80, 0xFE, 0x41, 0x99, 0x5C, 0x9F, 0x82,
24];
25const APPLE_SYSTEM_VARIABLE_GUID: &[u8; 16] = &[
26    0x40, 0xA0, 0xDD, 0xD2, 0x77, 0xF8, 0x43, 0x92, 0xB4, 0xA3, 0x1E, 0x73, 0x04, 0x20, 0x65, 0x16,
27];
28
29#[derive(Debug, Default, Clone)]
30enum Slot<T> {
31    Valid(T),
32    Invalid,
33    #[default]
34    Empty,
35}
36
37impl<T> Slot<T> {
38    pub const fn as_ref(&self) -> Slot<&T> {
39        match self {
40            Slot::Valid(v) => Slot::Valid(v),
41            Slot::Invalid => Slot::Invalid,
42            Slot::Empty => Slot::Empty,
43        }
44    }
45
46    pub fn as_mut(&mut self) -> Slot<&mut T> {
47        match self {
48            Slot::Valid(v) => Slot::Valid(v),
49            Slot::Invalid => Slot::Invalid,
50            Slot::Empty => Slot::Empty,
51        }
52    }
53
54    pub fn unwrap(self) -> T {
55        match self {
56            Slot::Valid(v) => v,
57            Slot::Invalid => panic!("called `Slot::unwrap()` on an `Invalid` value"),
58            Slot::Empty => panic!("called `Slot::unwrap()` on an `Empty` value"),
59        }
60    }
61
62    pub fn empty(&self) -> bool {
63        match self {
64            Slot::Empty => true,
65            _ => false,
66        }
67    }
68}
69
70#[derive(Debug)]
71pub struct Nvram<'a> {
72    partitions: Vec<Slot<Partition<'a>>>,
73    active: usize,
74}
75
76impl<'a> Nvram<'a> {
77    pub fn parse(nvr: &'a [u8]) -> crate::Result<Nvram<'a>> {
78        let partition_count = nvr.len() / PARTITION_SIZE;
79        let mut partitions = vec![Default::default(); partition_count];
80        let mut active = 0;
81        let mut max_gen = 0;
82        let mut valid_partitions = 0;
83
84        for i in 0..partition_count {
85            let offset = i * PARTITION_SIZE;
86            if offset >= nvr.len() {
87                break;
88            }
89            match Partition::parse(&nvr[offset..offset + PARTITION_SIZE]) {
90                Ok(p) => {
91                    let p_gen = p.generation();
92                    if p_gen > max_gen {
93                        active = i;
94                        max_gen = p_gen;
95                    }
96                    partitions[i] = Slot::Valid(p);
97                    valid_partitions += 1;
98                }
99                Err(V3Error::Empty) => {
100                    partitions[i] = Slot::Empty;
101                }
102                Err(_) => {
103                    partitions[i] = Slot::Invalid;
104                }
105            }
106        }
107
108        if valid_partitions == 0 {
109            return Err(Error::ParseError);
110        }
111
112        Ok(Nvram { partitions, active })
113    }
114
115    fn partitions(&self) -> impl Iterator<Item = &Partition<'a>> {
116        self.partitions.iter().filter_map(|x| match x {
117            Slot::Valid(p) => Some(p),
118            Slot::Invalid => None,
119            Slot::Empty => None,
120        })
121    }
122
123    fn active_part(&self) -> &Partition<'a> {
124        self.partitions[self.active].as_ref().unwrap()
125    }
126
127    #[cfg(test)]
128    fn active_part_mut(&mut self) -> &mut Partition<'a> {
129        self.partitions[self.active].as_mut().unwrap()
130    }
131}
132
133impl<'a> crate::Nvram<'a> for Nvram<'a> {
134    fn serialize(&self) -> crate::Result<Vec<u8>> {
135        let mut v = Vec::with_capacity(self.partitions.len() * PARTITION_SIZE);
136        for p in self.partitions() {
137            p.serialize(&mut v);
138        }
139        Ok(v)
140    }
141
142    fn prepare_for_write(&mut self) {
143        // nop
144    }
145
146    fn partitions(&self) -> Box<dyn Iterator<Item = &dyn crate::Partition<'a>> + '_> {
147        Box::new(self.partitions().map(|p| p as &dyn crate::Partition<'a>))
148    }
149
150    fn active_part_mut(&mut self) -> &mut dyn crate::Partition<'a> {
151        self.partitions[self.active].as_mut().unwrap()
152    }
153
154    fn apply(&mut self, w: &mut dyn crate::NvramWriter) -> crate::Result<()> {
155        let ap = self.active_part();
156        let offset;
157        // there aren't really any sections in v3 but the store header still
158        // specifies limits for maximum combined size of each kind of variable
159        if ap.system_used() > ap.system_size() {
160            return Err(Error::SectionTooBig);
161        }
162        if ap.common_used() > ap.common_size() {
163            return Err(Error::SectionTooBig);
164        }
165
166        // if total size is too big, copy added variables to the next bank
167        if ap.total_used() <= ap.usable_size() {
168            offset = (self.active * PARTITION_SIZE) as u32;
169        } else {
170            let new_active = (self.active + 1) % self.partitions.len();
171            offset = (new_active * PARTITION_SIZE) as u32;
172            if !self.partitions[new_active].empty() {
173                w.erase_if_needed(offset, PARTITION_SIZE);
174            }
175            // must only clone 0x7F variables to the next partition
176            self.partitions[new_active] = Slot::Valid(
177                self.partitions[self.active]
178                    .as_ref()
179                    .unwrap()
180                    .clone_active(),
181            );
182            self.active = new_active;
183            // we could still have too many active variables
184            if self.active_part().total_used() > PARTITION_SIZE {
185                return Err(Error::SectionTooBig);
186            }
187        }
188
189        let mut data = Vec::with_capacity(PARTITION_SIZE);
190        self.active_part().serialize(&mut data);
191        w.write_all(offset, &data)
192            .map_err(|e| Error::ApplyError(e))?;
193        Ok(())
194    }
195}
196
197#[derive(Debug, Clone)]
198pub struct Partition<'a> {
199    pub header: StoreHeader<'a>,
200    pub values: Vec<Variable<'a>>,
201    empty_region_end: usize,
202}
203
204#[derive(Debug)]
205enum V3Error {
206    ParseError,
207    Empty,
208}
209
210type Result<T> = std::result::Result<T, V3Error>;
211
212impl<'a> Partition<'a> {
213    fn parse(nvr: &'a [u8]) -> Result<Partition<'a>> {
214        if let Ok(header) = StoreHeader::parse(&nvr[..STORE_HEADER_SIZE]) {
215            let mut offset = STORE_HEADER_SIZE;
216            let mut values = Vec::new();
217            // one byte past the last 0xFF or the end of partition
218            let mut empty_region_end = header.size();
219
220            while offset + VAR_HEADER_SIZE < header.size() {
221                let mut empty = true;
222                for i in 0..VAR_HEADER_SIZE {
223                    if nvr[offset + i] != 0 && nvr[offset + i] != 0xFF {
224                        empty = false;
225                        break;
226                    }
227                }
228                if empty {
229                    // check where exactly the "empty" space ends
230                    // (it might not be at the end of partition)
231                    offset += VAR_HEADER_SIZE;
232                    while offset < header.size() {
233                        if nvr[offset] != 0xFF {
234                            empty_region_end = offset;
235                            break;
236                        }
237                        offset += 1
238                    }
239                    break;
240                }
241
242                let Ok(v_header) = VarHeader::parse(&nvr[offset..]) else {
243                    // if there's no valid header, just end here and return values parsed so far
244                    // we also know there is no space for adding any new or updated variables
245                    empty_region_end = offset;
246                    break;
247                };
248
249                let k_begin = offset + VAR_HEADER_SIZE;
250                let k_end = k_begin + v_header.name_size as usize;
251                let key = &nvr[k_begin..k_end - 1];
252
253                let v_begin = k_end;
254                let v_end = v_begin + v_header.data_size as usize;
255                let value = &nvr[v_begin..v_end];
256
257                let crc = crc32fast::hash(value);
258                if crc != v_header.crc {
259                    return Err(V3Error::ParseError);
260                }
261                let v = Variable {
262                    header: v_header,
263                    key: Cow::Borrowed(key),
264                    value: Cow::Borrowed(value),
265                };
266
267                offset += v.size();
268                values.push(v);
269            }
270
271            Ok(Partition {
272                header,
273                values,
274                empty_region_end,
275            })
276        } else {
277            match nvr.iter().copied().try_for_each(|v| match v {
278                0xFF => ControlFlow::Continue(()),
279                _ => ControlFlow::Break(()),
280            }) {
281                ControlFlow::Continue(_) => Err(V3Error::Empty),
282                ControlFlow::Break(_) => Err(V3Error::ParseError),
283            }
284        }
285    }
286
287    fn generation(&self) -> u32 {
288        self.header.generation
289    }
290
291    fn entries<'b, 'c>(
292        &'b mut self,
293        key: &'c [u8],
294        typ: VarType,
295    ) -> impl Iterator<Item = &'b mut Variable<'a>>
296    where
297        'a: 'b,
298        'c: 'b,
299    {
300        self.values
301            .iter_mut()
302            .filter(move |e| e.key == key && e.typ() == typ)
303    }
304
305    fn entries_added<'b, 'c>(
306        &'b mut self,
307        key: &'c [u8],
308        typ: VarType,
309    ) -> impl Iterator<Item = &'b mut Variable<'a>>
310    where
311        'a: 'b,
312        'c: 'b,
313    {
314        self.entries(key, typ)
315            .filter(|v| v.header.state == VAR_ADDED)
316    }
317
318    // total size of store header + all variables including the inactive duplicates
319    fn total_used(&self) -> usize {
320        STORE_HEADER_SIZE + self.values.iter().fold(0, |acc, v| acc + v.size())
321    }
322
323    // size of active system variables
324    fn system_used(&self) -> usize {
325        self.values
326            .iter()
327            .filter(|&v| v.header.state == VAR_ADDED && v.header.guid == APPLE_SYSTEM_VARIABLE_GUID)
328            .fold(0, |acc, v| acc + v.size())
329    }
330
331    // size of active common variables
332    fn common_used(&self) -> usize {
333        self.values
334            .iter()
335            .filter(|&v| v.header.state == VAR_ADDED && v.header.guid == APPLE_COMMON_VARIABLE_GUID)
336            .fold(0, |acc, v| acc + v.size())
337    }
338
339    fn system_size(&self) -> usize {
340        self.header.system_size as usize
341    }
342
343    fn common_size(&self) -> usize {
344        self.header.common_size as usize
345    }
346
347    // total usable size, usually equal to partition size
348    // unless there are any non-0xFF bytes after last valid variable
349    fn usable_size(&self) -> usize {
350        self.empty_region_end
351    }
352
353    fn serialize(&self, v: &mut Vec<u8>) {
354        let start_size = v.len();
355        self.header.serialize(v);
356        // Here we actually want to iterate over all versions of variables so we use the struct field directly.
357        for var in &self.values {
358            var.serialize(v);
359        }
360        let my_size = v.len() - start_size;
361        debug_assert!(v.len() == self.total_used());
362
363        // padding
364        for _ in 0..(self.header.size() - my_size) {
365            v.push(0xFF);
366        }
367    }
368
369    fn variables(&self) -> impl Iterator<Item = &Variable<'a>> {
370        self.values.iter().filter(|v| v.header.state == VAR_ADDED)
371    }
372
373    fn clone_active(&self) -> Partition<'a> {
374        let mut header = self.header.clone();
375        header.generation += 1;
376        Partition {
377            header,
378            values: self
379                .values
380                .iter()
381                .filter_map(|v| {
382                    if v.header.state == VAR_ADDED {
383                        Some(v.clone())
384                    } else {
385                        None
386                    }
387                })
388                .collect(),
389            empty_region_end: self.header.size(),
390        }
391    }
392}
393
394impl<'a> crate::Partition<'a> for Partition<'a> {
395    fn get_variable(&self, key: &[u8], typ: VarType) -> Option<&dyn crate::Variable<'a>> {
396        self.values.iter().find_map(|e| {
397            if e.key == key && e.typ() == typ && e.header.state == VAR_ADDED {
398                Some(e as &dyn crate::Variable<'a>)
399            } else {
400                None
401            }
402        })
403    }
404
405    fn insert_variable(&mut self, key: &[u8], value: Cow<'a, [u8]>, typ: VarType) {
406        // invalidate any previous variable instances
407        for var in self.entries_added(key, typ) {
408            var.header.state = var.header.state & VAR_DELETED & VAR_IN_DELETED_TRANSITION;
409        }
410
411        let guid = match typ {
412            VarType::Common => APPLE_COMMON_VARIABLE_GUID,
413            VarType::System => APPLE_SYSTEM_VARIABLE_GUID,
414        };
415        let var = Variable {
416            header: VarHeader {
417                state: VAR_ADDED,
418                attrs: 0,
419                name_size: (key.len() + 1) as u32,
420                data_size: value.len() as u32,
421                guid,
422                crc: crc32fast::hash(&value),
423            },
424            key: Cow::Owned(key.into()),
425            value,
426        };
427        self.values.push(var);
428    }
429
430    fn remove_variable(&mut self, key: &[u8], typ: VarType) {
431        // invalidate all previous variable instances
432        for var in self.entries_added(key, typ) {
433            var.header.state = var.header.state & VAR_DELETED & VAR_IN_DELETED_TRANSITION;
434        }
435    }
436
437    fn variables(&self) -> Box<dyn Iterator<Item = &dyn crate::Variable<'a>> + '_> {
438        Box::new(self.variables().map(|e| e as &dyn crate::Variable<'a>))
439    }
440}
441
442impl Display for Partition<'_> {
443    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
444        write!(
445            f,
446            "size: {}, total_used: {}, system_used: {}, common_used: {}, generation: 0x{:02x}, state: 0x{:02x}, flags: 0x{:02x}, count: {}",
447            self.header.size,
448            self.total_used(),
449            self.system_used(),
450            self.common_used(),
451            self.generation(),
452            self.header.state,
453            self.header.flags,
454            self.values.len()
455        )
456    }
457}
458
459#[derive(Debug, Clone)]
460pub struct StoreHeader<'a> {
461    pub name: &'a [u8],
462    pub size: u32,
463    pub generation: u32,
464    pub state: u8,
465    pub flags: u8,
466    pub version: u8,
467    pub system_size: u32,
468    pub common_size: u32,
469}
470
471impl<'a> StoreHeader<'a> {
472    fn parse(nvr: &[u8]) -> Result<StoreHeader<'_>> {
473        let name = &nvr[..4];
474        let size = u32::from_le_bytes(nvr[4..8].try_into().unwrap());
475        let generation = u32::from_le_bytes(nvr[8..12].try_into().unwrap());
476        let state = nvr[12];
477        let flags = nvr[13];
478        let version = nvr[14];
479        let system_size = u32::from_le_bytes(nvr[16..20].try_into().unwrap());
480        let common_size = u32::from_le_bytes(nvr[20..24].try_into().unwrap());
481
482        if name != VARIABLE_STORE_SIGNATURE {
483            return Err(V3Error::ParseError);
484        }
485        if version != VARIABLE_STORE_VERSION {
486            return Err(V3Error::ParseError);
487        }
488
489        Ok(StoreHeader {
490            name,
491            size,
492            generation,
493            state,
494            flags,
495            version,
496            system_size,
497            common_size,
498        })
499    }
500
501    fn serialize(&self, v: &mut Vec<u8>) {
502        v.extend_from_slice(VARIABLE_STORE_SIGNATURE);
503        v.extend_from_slice(&self.size.to_le_bytes());
504        v.extend_from_slice(&self.generation.to_le_bytes());
505        v.push(self.state);
506        v.push(self.flags);
507        v.push(self.version);
508        v.push(0); // reserved
509        v.extend_from_slice(&self.system_size.to_le_bytes());
510        v.extend_from_slice(&self.common_size.to_le_bytes());
511    }
512
513    fn size(&self) -> usize {
514        self.size as usize
515    }
516}
517
518#[derive(Debug, Default, Clone)]
519pub struct Variable<'a> {
520    pub header: VarHeader<'a>,
521    pub key: Cow<'a, [u8]>,
522    pub value: Cow<'a, [u8]>,
523}
524
525impl<'a> Variable<'a> {
526    fn size(&self) -> usize {
527        VAR_HEADER_SIZE + (self.header.name_size + self.header.data_size) as usize
528    }
529
530    fn typ(&self) -> VarType {
531        if self.header.guid == APPLE_SYSTEM_VARIABLE_GUID {
532            return VarType::System;
533        }
534        VarType::Common
535    }
536
537    fn serialize(&self, v: &mut Vec<u8>) {
538        self.header.serialize(v);
539        v.extend_from_slice(&self.key);
540        v.push(0);
541        v.extend_from_slice(&self.value);
542    }
543}
544
545impl<'a> crate::Variable<'a> for Variable<'a> {
546    fn value(&self) -> Cow<'a, [u8]> {
547        self.value.clone()
548    }
549}
550
551impl Display for Variable<'_> {
552    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
553        let key = String::from_utf8_lossy(&self.key);
554        let mut value = String::new();
555        for c in self.value.iter().copied() {
556            if (c as char).is_ascii() && !(c as char).is_ascii_control() {
557                value.push(c as char);
558            } else {
559                value.push_str(&format!("%{c:02x}"));
560            }
561        }
562
563        write!(f, "{}:{}={}", self.typ(), key, value)
564    }
565}
566
567#[derive(Debug, Default, Clone)]
568pub struct VarHeader<'a> {
569    pub state: u8,
570    pub attrs: u32,
571    pub name_size: u32,
572    pub data_size: u32,
573    pub guid: &'a [u8],
574    pub crc: u32,
575}
576
577impl<'a> VarHeader<'a> {
578    fn parse(nvr: &[u8]) -> Result<VarHeader<'_>> {
579        let start_id = u16::from_le_bytes(nvr[..2].try_into().unwrap());
580        if start_id != VARIABLE_DATA {
581            return Err(V3Error::ParseError);
582        }
583        let state = nvr[2];
584        let attrs = u32::from_le_bytes(nvr[4..8].try_into().unwrap());
585        let name_size = u32::from_le_bytes(nvr[8..12].try_into().unwrap());
586        let data_size = u32::from_le_bytes(nvr[12..16].try_into().unwrap());
587        let guid = &nvr[16..32];
588        let crc = u32::from_le_bytes(nvr[32..36].try_into().unwrap());
589
590        if VAR_HEADER_SIZE + (name_size + data_size) as usize > nvr.len() {
591            return Err(V3Error::ParseError);
592        }
593
594        Ok(VarHeader {
595            state,
596            attrs,
597            name_size,
598            data_size,
599            guid,
600            crc,
601        })
602    }
603
604    fn serialize(&self, v: &mut Vec<u8>) {
605        v.extend_from_slice(&VARIABLE_DATA.to_le_bytes());
606        v.push(self.state);
607        v.push(0); // reserved
608        v.extend_from_slice(&self.attrs.to_le_bytes());
609        v.extend_from_slice(&self.name_size.to_le_bytes());
610        v.extend_from_slice(&self.data_size.to_le_bytes());
611        v.extend_from_slice(self.guid);
612        v.extend_from_slice(&self.crc.to_le_bytes());
613    }
614}
615
616#[cfg(test)]
617mod tests {
618    use super::*;
619    use crate::{Nvram as NvramT, NvramWriter, Partition};
620
621    struct TestNvram {
622        data: Vec<u8>,
623        erase_count: usize,
624    }
625
626    impl TestNvram {
627        fn new(data: Vec<u8>) -> TestNvram {
628            Self {
629                data,
630                erase_count: 0,
631            }
632        }
633
634        fn get_data(&self) -> &[u8] {
635            &self.data
636        }
637    }
638
639    impl NvramWriter for TestNvram {
640        fn erase_if_needed(&mut self, offset: u32, size: usize) {
641            for b in self.data.iter_mut().skip(offset as usize).take(size) {
642                *b = 0xFF;
643            }
644            self.erase_count += 1;
645        }
646
647        fn write_all(&mut self, offset: u32, buf: &[u8]) -> std::io::Result<()> {
648            for (d, s) in self
649                .data
650                .iter_mut()
651                .skip(offset as usize)
652                .take(buf.len())
653                .zip(buf.iter().copied())
654            {
655                *d &= s;
656            }
657            Ok(())
658        }
659    }
660
661    #[rustfmt::skip]
662    fn store_header() -> &'static [u8] {
663        &[
664            0x33, 0x56, 0x56, 0x4e, 0x00, 0x00, 0x01, 0x00,
665            0x01, 0x00, 0x00, 0x00, 0xfe, 0x5a, 0x01, 0x00,
666            0x00, 0x40, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00,
667        ]
668    }
669
670    fn empty_nvram(bank_count: usize) -> Vec<u8> {
671        let mut data = vec![0xFF; PARTITION_SIZE * bank_count];
672        data[0..STORE_HEADER_SIZE].copy_from_slice(store_header());
673        data
674    }
675
676    #[test]
677    fn test_insert_variable() -> crate::Result<()> {
678        let mut nvr = TestNvram::new(empty_nvram(2));
679        let data = nvr.get_data().to_owned();
680        let mut nv = Nvram::parse(&data)?;
681
682        assert!(matches!(nv.partitions[0], Slot::Valid(_)));
683        assert!(matches!(nv.partitions[1], Slot::Empty));
684
685        nv.active_part_mut().insert_variable(
686            b"test-variable",
687            Cow::Borrowed(b"test-value"),
688            VarType::Common,
689        );
690
691        // write changes
692        nv.apply(&mut nvr)?;
693        assert_eq!(nvr.erase_count, 0);
694
695        // try to parse again
696        let data_after = nvr.get_data().to_owned();
697        let mut nv_after = Nvram::parse(&data_after)?;
698
699        let test_var = nv_after
700            .active_part()
701            .get_variable(b"test-variable", VarType::Common)
702            .unwrap();
703
704        assert_eq!(test_var.value(), Cow::Borrowed(b"test-value"));
705
706        let test_var_entries: Vec<_> = nv_after
707            .active_part_mut()
708            .entries(b"test-variable", VarType::Common)
709            .collect();
710
711        assert_eq!(test_var_entries.len(), 1);
712        assert_eq!(test_var_entries[0].header.state, VAR_ADDED);
713
714        // update variable
715        nv_after.active_part_mut().insert_variable(
716            b"test-variable",
717            Cow::Borrowed(b"test-value2"),
718            VarType::Common,
719        );
720
721        // write changes
722        nv_after.apply(&mut nvr)?;
723        assert_eq!(nvr.erase_count, 0);
724
725        // try to parse again
726        let data_after2 = nvr.get_data().to_owned();
727        let mut nv_after2 = Nvram::parse(&data_after2)?;
728
729        let test_var2 = nv_after2
730            .active_part()
731            .get_variable(b"test-variable", VarType::Common)
732            .unwrap();
733
734        assert_eq!(test_var2.value(), Cow::Borrowed(b"test-value2"));
735
736        let test_var2_entries: Vec<_> = nv_after2
737            .active_part_mut()
738            .entries(b"test-variable", VarType::Common)
739            .collect();
740
741        assert_eq!(test_var2_entries.len(), 2);
742        assert_eq!(
743            test_var2_entries[0].header.state,
744            VAR_ADDED & VAR_DELETED & VAR_IN_DELETED_TRANSITION
745        );
746        assert_eq!(test_var2_entries[1].header.state, VAR_ADDED);
747
748        Ok(())
749    }
750
751    #[test]
752    fn test_write_to_next_bank() -> crate::Result<()> {
753        let mut nvr = TestNvram::new(empty_nvram(2));
754        // write something to the second bank to force nvram erase later
755        nvr.data[0x10000..0x10007].copy_from_slice(b"garbage");
756
757        let data = nvr.get_data().to_owned();
758        let mut nv = Nvram::parse(&data)?;
759
760        assert!(matches!(nv.partitions[0], Slot::Valid(_)));
761        assert!(matches!(nv.partitions[1], Slot::Invalid));
762
763        let orig_sys_val = vec![b'.'; 8192];
764        nv.active_part_mut().insert_variable(
765            b"test-large-variable",
766            Cow::Borrowed(&orig_sys_val),
767            VarType::System,
768        );
769
770        let orig_common_val = vec![b'.'; 24576];
771        nv.active_part_mut().insert_variable(
772            b"test-large-variable",
773            Cow::Borrowed(&orig_common_val),
774            VarType::Common,
775        );
776
777        // write changes
778        nv.apply(&mut nvr)?;
779        assert_eq!(nvr.erase_count, 0);
780        assert_eq!(nv.active, 0);
781
782        // try to parse again
783        let data_after = nvr.get_data().to_owned();
784        let mut nv_after = Nvram::parse(&data_after)?;
785        assert_eq!(nv_after.active_part().header.generation, 1);
786
787        assert!(matches!(nv_after.partitions[0], Slot::Valid(_)));
788        assert!(matches!(nv_after.partitions[1], Slot::Invalid));
789
790        // update variable
791        let updated_sys_val = vec![b'.'; 9000];
792        nv_after.active_part_mut().insert_variable(
793            b"test-large-variable",
794            Cow::Borrowed(&updated_sys_val),
795            VarType::System,
796        );
797
798        let updated_common_val = vec![b'.'; 25000];
799        nv_after.active_part_mut().insert_variable(
800            b"test-large-variable",
801            Cow::Borrowed(&updated_common_val),
802            VarType::Common,
803        );
804
805        assert_eq!(nv_after.active_part().values.len(), 4);
806
807        // write changes
808        nv_after.apply(&mut nvr)?;
809        assert_eq!(nvr.erase_count, 1);
810        assert_eq!(nv_after.active, 1);
811        assert_eq!(nv_after.active_part().values.len(), 2);
812
813        // try to parse again
814        let data_after2 = nvr.get_data().to_owned();
815        let nv_after2 = Nvram::parse(&data_after2)?;
816        assert_eq!(nv_after2.active, 1);
817        assert_eq!(nv_after2.active_part().values.len(), 2);
818        assert_eq!(nv_after2.active_part().header.generation, 2);
819
820        assert!(matches!(nv_after2.partitions[0], Slot::Valid(_)));
821        assert!(matches!(nv_after2.partitions[1], Slot::Valid(_)));
822
823        let test_sys_var2 = nv_after2
824            .active_part()
825            .get_variable(b"test-large-variable", VarType::System)
826            .unwrap();
827
828        let test_common_var2 = nv_after2
829            .active_part()
830            .get_variable(b"test-large-variable", VarType::Common)
831            .unwrap();
832
833        assert_eq!(test_sys_var2.value(), Cow::Borrowed(&updated_sys_val));
834        assert_eq!(test_common_var2.value(), Cow::Borrowed(&updated_common_val));
835
836        let test_old_sys_var = nv_after2.partitions[0]
837            .as_ref()
838            .unwrap()
839            .get_variable(b"test-large-variable", VarType::System)
840            .unwrap();
841
842        let test_old_common_var = nv_after2.partitions[0]
843            .as_ref()
844            .unwrap()
845            .get_variable(b"test-large-variable", VarType::Common)
846            .unwrap();
847
848        assert_eq!(test_old_sys_var.value(), Cow::Borrowed(&orig_sys_val));
849        assert_eq!(test_old_common_var.value(), Cow::Borrowed(&orig_common_val));
850
851        Ok(())
852    }
853
854    #[test]
855    fn test_insert_with_low_space() -> crate::Result<()> {
856        let mut nvr = TestNvram::new(empty_nvram(2));
857        // this will shrink usable size to 100 bytes
858        nvr.data[STORE_HEADER_SIZE + 100] = 0x42;
859
860        let data = nvr.get_data().to_owned();
861        let mut nv = Nvram::parse(&data)?;
862
863        assert!(matches!(nv.partitions[0], Slot::Valid(_)));
864        assert!(matches!(nv.partitions[1], Slot::Empty));
865
866        nv.active_part_mut().insert_variable(
867            b"test-variable",
868            Cow::Borrowed(b"test-value"),
869            VarType::Common,
870        );
871
872        // write changes
873        nv.apply(&mut nvr)?;
874        assert_eq!(nvr.erase_count, 0);
875        assert_eq!(nv.active, 0);
876
877        // try to parse again
878        let data_after = nvr.get_data().to_owned();
879        let mut nv_after = Nvram::parse(&data_after)?;
880
881        let test_var = nv_after
882            .active_part()
883            .get_variable(b"test-variable", VarType::Common)
884            .unwrap();
885
886        assert_eq!(test_var.value(), Cow::Borrowed(b"test-value"));
887
888        let test_var_entries: Vec<_> = nv_after
889            .active_part_mut()
890            .entries(b"test-variable", VarType::Common)
891            .collect();
892
893        assert_eq!(test_var_entries.len(), 1);
894        assert_eq!(test_var_entries[0].header.state, VAR_ADDED);
895
896        // update variable
897        nv_after.active_part_mut().insert_variable(
898            b"test-variable",
899            Cow::Borrowed(b"test-value2"),
900            VarType::Common,
901        );
902
903        // write changes
904        nv_after.apply(&mut nvr)?;
905        assert_eq!(nvr.erase_count, 0);
906        assert_eq!(nv_after.active, 1);
907
908        Ok(())
909    }
910}