1use std::{
2 borrow::Cow,
3 fmt::{Display, Formatter},
4 ops::ControlFlow,
5};
6
7use crate::{Error, VarType};
8
9const 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 }
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 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 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 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 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 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 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 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 fn total_used(&self) -> usize {
328 STORE_HEADER_SIZE + self.values.iter().fold(0, |acc, v| acc + v.size())
329 }
330
331 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 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 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 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 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 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 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); 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); 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 nv.apply(&mut nvr)?;
701 assert_eq!(nvr.erase_count, 0);
702
703 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 nv_after.active_part_mut().insert_variable(
724 b"test-variable",
725 Cow::Borrowed(b"test-value2"),
726 VarType::Common,
727 );
728
729 nv_after.apply(&mut nvr)?;
731 assert_eq!(nvr.erase_count, 0);
732
733 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 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 nv.apply(&mut nvr)?;
787 assert_eq!(nvr.erase_count, 0);
788 assert_eq!(nv.active, 0);
789
790 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 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 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 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 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 nv.apply(&mut nvr)?;
882 assert_eq!(nvr.erase_count, 0);
883 assert_eq!(nv.active, 0);
884
885 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 nv_after.active_part_mut().insert_variable(
906 b"test-variable",
907 Cow::Borrowed(b"test-value2"),
908 VarType::Common,
909 );
910
911 nv_after.apply(&mut nvr)?;
913 assert_eq!(nvr.erase_count, 0);
914 assert_eq!(nv_after.active, 1);
915
916 Ok(())
917 }
918}