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, 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 }
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 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 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 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 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 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 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 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 fn total_used(&self) -> usize {
320 STORE_HEADER_SIZE + self.values.iter().fold(0, |acc, v| acc + v.size())
321 }
322
323 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 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 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 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 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 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 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); 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); 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 nv.apply(&mut nvr)?;
693 assert_eq!(nvr.erase_count, 0);
694
695 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 nv_after.active_part_mut().insert_variable(
716 b"test-variable",
717 Cow::Borrowed(b"test-value2"),
718 VarType::Common,
719 );
720
721 nv_after.apply(&mut nvr)?;
723 assert_eq!(nvr.erase_count, 0);
724
725 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 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 nv.apply(&mut nvr)?;
779 assert_eq!(nvr.erase_count, 0);
780 assert_eq!(nv.active, 0);
781
782 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 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 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 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 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 nv.apply(&mut nvr)?;
874 assert_eq!(nvr.erase_count, 0);
875 assert_eq!(nv.active, 0);
876
877 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 nv_after.active_part_mut().insert_variable(
898 b"test-variable",
899 Cow::Borrowed(b"test-value2"),
900 VarType::Common,
901 );
902
903 nv_after.apply(&mut nvr)?;
905 assert_eq!(nvr.erase_count, 0);
906 assert_eq!(nv_after.active, 1);
907
908 Ok(())
909 }
910}