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