1use crate::Inspector;
9use crate::reader::LinkValue;
10use crate::reader::error::ReaderError;
11use crate::reader::readable_tree::SnapshotSource;
12use diagnostics_hierarchy::{ArrayContent, Property};
13use inspect_format::{
14 Array, Block, BlockAccessorExt, BlockContainer, BlockIndex, BlockType, Bool, Buffer, Container,
15 CopyBytes, Double, Extent, Header, Int, Link, Name, PropertyFormat, ReadBytes, StringRef, Uint,
16 Unknown, ValueBlockKind, constants, utils,
17};
18use std::cmp;
19
20pub use crate::reader::tree_reader::SnapshotTree;
21
22#[derive(Debug)]
24pub struct Snapshot {
25 buffer: BackingBuffer,
27}
28
29pub type ScannedBlock<'a, K> = Block<&'a BackingBuffer, K>;
31
32const SNAPSHOT_TRIES: u64 = 1024;
33
34impl Snapshot {
35 pub fn scan(&self) -> BlockIterator<'_> {
37 BlockIterator::from(&self.buffer)
38 }
39
40 pub fn get_block(&self, index: BlockIndex) -> Result<ScannedBlock<'_, Unknown>, ReaderError> {
42 if index.offset() < self.buffer.len() {
43 Ok(self.buffer.block_at(index))
44 } else {
45 Err(ReaderError::GetBlock(index))
46 }
47 }
48
49 pub fn try_once_from_vmo(source: &SnapshotSource) -> Result<Snapshot, ReaderError> {
53 Snapshot::try_once_with_callback(source, &mut || {})
54 }
55
56 fn try_once_with_callback<F>(
57 source: &SnapshotSource,
58 read_callback: &mut F,
59 ) -> Result<Snapshot, ReaderError>
60 where
61 F: FnMut(),
62 {
63 let mut header_bytes: [u8; 32] = [0; 32];
65 source.copy_bytes(&mut header_bytes);
66 let Some(header_block) = header_bytes.maybe_block_at::<Header>(BlockIndex::HEADER) else {
67 return Err(ReaderError::InvalidVmo);
68 };
69 let generation = header_block.generation_count();
70 if generation == constants::VMO_FROZEN
71 && let Ok(buffer) = BackingBuffer::try_from(source)
72 {
73 return Ok(Snapshot { buffer });
74 }
75
76 let vmo_size = if let Some(vmo_size) = header_block.vmo_size()? {
78 cmp::min(vmo_size as usize, constants::MAX_VMO_SIZE)
79 } else {
80 cmp::min(source.len(), constants::MAX_VMO_SIZE)
81 };
82 let mut buffer = vec![0u8; vmo_size];
83 source.copy_bytes(&mut buffer);
84 if cfg!(test) {
85 read_callback();
86 }
87
88 source.copy_bytes(&mut header_bytes);
92 match header_generation_count(&header_bytes) {
93 None => Err(ReaderError::InconsistentSnapshot),
94 Some(new_generation) if new_generation != generation => {
95 Err(ReaderError::InconsistentSnapshot)
96 }
97 Some(_) => Ok(Snapshot { buffer: BackingBuffer::from(buffer) }),
98 }
99 }
100
101 fn try_from_with_callback<F>(
102 source: &SnapshotSource,
103 mut read_callback: F,
104 ) -> Result<Snapshot, ReaderError>
105 where
106 F: FnMut(),
107 {
108 let mut i = 0;
109 loop {
110 match Snapshot::try_once_with_callback(source, &mut read_callback) {
111 Ok(snapshot) => return Ok(snapshot),
112 Err(e) => {
113 if i >= SNAPSHOT_TRIES {
114 return Err(e);
115 }
116 }
117 };
118 i += 1;
119 }
120 }
121
122 pub(crate) fn get_name(&self, index: BlockIndex) -> Option<String> {
123 let block = self.get_block(index).ok()?;
124 match block.block_type()? {
125 BlockType::Name => self.load_name(block.cast::<Name>().unwrap()),
126 BlockType::StringReference => {
127 self.load_string_reference(block.cast::<StringRef>().unwrap()).ok()
128 }
129 _ => None,
130 }
131 }
132
133 pub(crate) fn load_name(&self, block: ScannedBlock<'_, Name>) -> Option<String> {
134 block.contents().ok().map(|s| s.to_string())
135 }
136
137 pub(crate) fn load_string_reference(
138 &self,
139 block: ScannedBlock<'_, StringRef>,
140 ) -> Result<String, ReaderError> {
141 let mut data = block.inline_data()?.to_vec();
142 let total_length = block.total_length();
143 if data.len() == total_length {
144 return Ok(String::from_utf8_lossy(&data).to_string());
145 }
146
147 let extent_index = block.next_extent();
148 let still_to_read_length = total_length - data.len();
149 data.append(&mut self.read_extents(still_to_read_length, extent_index)?);
150
151 Ok(String::from_utf8_lossy(&data).to_string())
152 }
153
154 pub(crate) fn parse_primitive_property<'a, K>(
155 &self,
156 block: ScannedBlock<'a, K>,
157 ) -> Result<Property, ReaderError>
158 where
159 ScannedBlock<'a, K>: MakePrimitiveProperty,
160 K: ValueBlockKind,
161 {
162 let name_index = block.name_index();
163 let name = self.get_name(name_index).ok_or(ReaderError::ParseName(name_index))?;
164 Ok(block.make_property(name))
165 }
166
167 pub(crate) fn parse_array_property(
168 &self,
169 block: ScannedBlock<'_, Array<Unknown>>,
170 ) -> Result<Property, ReaderError> {
171 let name_index = block.name_index();
172 let name = self.get_name(name_index).ok_or(ReaderError::ParseName(name_index))?;
173 let array_slots = block.slots();
174 let capacity = block.capacity().ok_or(ReaderError::InvalidVmo)?;
176 if capacity < array_slots {
177 return Err(ReaderError::AttemptedToReadTooManyArraySlots(block.index()));
178 }
179 let value_indexes = 0..array_slots;
180 let format = block.format().ok_or(ReaderError::InvalidVmo)?;
181 let parsed_property = match block.entry_type() {
182 Some(BlockType::IntValue) => {
183 let block = block.cast_array_unchecked::<Int>();
184 let values = value_indexes
185 .map(|i| block.get(i).unwrap())
188 .collect::<Vec<i64>>();
189 Property::IntArray(
190 name,
191 ArrayContent::new(values, format)?,
194 )
195 }
196 Some(BlockType::UintValue) => {
197 let block = block.cast_array_unchecked::<Uint>();
198 let values = value_indexes
199 .map(|i| block.get(i).unwrap())
202 .collect::<Vec<u64>>();
203 Property::UintArray(
204 name,
205 ArrayContent::new(values, format)?,
208 )
209 }
210 Some(BlockType::DoubleValue) => {
211 let block = block.cast_array_unchecked::<Double>();
212 let values = value_indexes
213 .map(|i| block.get(i).unwrap())
216 .collect::<Vec<f64>>();
217 Property::DoubleArray(
218 name,
219 ArrayContent::new(values, format)?,
222 )
223 }
224 Some(BlockType::StringReference) => {
225 let block = block.cast_array_unchecked::<StringRef>();
226 let values = value_indexes
227 .map(|value_index| {
228 let string_idx = block
229 .get_string_index_at(value_index)
230 .ok_or(ReaderError::InvalidVmo)?;
231 if string_idx == BlockIndex::EMPTY {
234 return Ok(String::new());
235 }
236
237 let ref_block = self
238 .get_block(string_idx)?
239 .cast::<StringRef>()
240 .ok_or(ReaderError::InvalidVmo)?;
241 self.load_string_reference(ref_block)
242 })
243 .collect::<Result<Vec<String>, _>>()?;
244 Property::StringList(name, values)
245 }
246 _ => return Err(ReaderError::UnexpectedArrayEntryFormat(block.entry_type_raw())),
247 };
248 Ok(parsed_property)
249 }
250
251 pub(crate) fn parse_property(
252 &self,
253 block: ScannedBlock<'_, Buffer>,
254 ) -> Result<Property, ReaderError> {
255 let name_index = block.name_index();
256 let name = self.get_name(name_index).ok_or(ReaderError::ParseName(name_index))?;
257 let data_index = block.extent_index();
258 match block.format().ok_or(ReaderError::InvalidVmo)? {
259 PropertyFormat::String => {
260 let total_length = block.total_length();
261 let buffer = self.read_extents(total_length, data_index)?;
262 Ok(Property::String(name, String::from_utf8_lossy(&buffer).to_string()))
263 }
264 PropertyFormat::Bytes => {
265 let total_length = block.total_length();
266 let buffer = self.read_extents(total_length, data_index)?;
267 Ok(Property::Bytes(name, buffer))
268 }
269 PropertyFormat::StringReference => {
270 let data_head = self
271 .get_block(data_index)?
272 .cast::<StringRef>()
273 .ok_or(ReaderError::InvalidVmo)?;
274 Ok(Property::String(name, self.load_string_reference(data_head)?))
275 }
276 }
277 }
278
279 pub(crate) fn parse_link(
280 &self,
281 block: ScannedBlock<'_, Link>,
282 ) -> Result<LinkValue, ReaderError> {
283 let name_index = block.name_index();
284 let name = self.get_name(name_index).ok_or(ReaderError::ParseName(name_index))?;
285 let link_content_index = block.content_index();
286 let content =
287 self.get_name(link_content_index).ok_or(ReaderError::ParseName(link_content_index))?;
288 let disposition = block.link_node_disposition().ok_or(ReaderError::InvalidVmo)?;
289 Ok(LinkValue { name, content, disposition })
290 }
291
292 pub(crate) fn read_extents(
295 &self,
296 total_length: usize,
297 first_extent: BlockIndex,
298 ) -> Result<Vec<u8>, ReaderError> {
299 let mut buffer = vec![0u8; total_length];
300 let mut offset = 0;
301 let mut extent_index = first_extent;
302 while extent_index != BlockIndex::EMPTY && offset < total_length {
303 let extent = self
304 .get_block(extent_index)
305 .and_then(|b| b.cast::<Extent>().ok_or(ReaderError::InvalidVmo))?;
306 let content = extent.contents()?;
307 let extent_length = cmp::min(total_length - offset, content.len());
308 buffer[offset..offset + extent_length].copy_from_slice(&content[..extent_length]);
309 offset += extent_length;
310 extent_index = extent.next_extent();
311 }
312
313 Ok(buffer)
314 }
315
316 #[cfg(test)]
318 pub fn build(bytes: &[u8]) -> Self {
319 Snapshot { buffer: BackingBuffer::from(bytes.to_vec()) }
320 }
321}
322
323fn header_generation_count<T: ReadBytes>(bytes: &T) -> Option<u64> {
327 if bytes.len() < 16 {
328 return None;
329 }
330 let block = bytes.maybe_block_at::<Header>(BlockIndex::HEADER)?;
331 if block.magic_number() == constants::HEADER_MAGIC_NUMBER
332 && block.version() <= constants::HEADER_VERSION_NUMBER
333 && !block.is_locked()
334 {
335 return Some(block.generation_count());
336 }
337 None
338}
339
340impl TryFrom<Vec<u8>> for Snapshot {
342 type Error = ReaderError;
343
344 fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
345 if header_generation_count(&bytes).is_some() {
346 Ok(Snapshot { buffer: BackingBuffer::from(bytes) })
347 } else {
348 Err(ReaderError::MissingHeaderOrLocked)
349 }
350 }
351}
352
353impl TryFrom<&Inspector> for Snapshot {
354 type Error = ReaderError;
355
356 fn try_from(inspector: &Inspector) -> Result<Self, Self::Error> {
357 let handle = inspector.get_storage_handle();
358 let storage = handle.as_ref().ok_or(ReaderError::NoOpInspector)?;
359 Snapshot::try_from_with_callback(storage, || {})
360 }
361}
362
363#[cfg(target_os = "fuchsia")]
364impl TryFrom<&zx::Vmo> for Snapshot {
365 type Error = ReaderError;
366
367 fn try_from(vmo: &zx::Vmo) -> Result<Self, Self::Error> {
368 Snapshot::try_from_with_callback(vmo, || {})
369 }
370}
371
372#[cfg(not(target_os = "fuchsia"))]
373impl TryFrom<&Vec<u8>> for Snapshot {
374 type Error = ReaderError;
375
376 fn try_from(buffer: &Vec<u8>) -> Result<Self, Self::Error> {
377 Snapshot::try_from_with_callback(buffer, || {})
378 }
379}
380
381pub struct BlockIterator<'a> {
384 offset: usize,
386
387 container: &'a BackingBuffer,
389}
390
391impl<'a> From<&'a BackingBuffer> for BlockIterator<'a> {
392 fn from(container: &'a BackingBuffer) -> Self {
393 BlockIterator { offset: 0, container }
394 }
395}
396
397impl<'a> Iterator for BlockIterator<'a> {
398 type Item = ScannedBlock<'a, Unknown>;
399
400 fn next(&mut self) -> Option<Self::Item> {
401 if self.offset >= self.container.len() {
402 return None;
403 }
404 let index = BlockIndex::from_offset(self.offset);
405 let block = self.container.block_at(index);
406 if self.container.len() - self.offset < utils::order_to_size(block.order()) {
407 return None;
408 }
409 self.offset += utils::order_to_size(block.order());
410 Some(block)
411 }
412}
413
414#[derive(Debug)]
415pub enum BackingBuffer {
416 Bytes(Vec<u8>),
417 Container(Container),
418}
419
420#[cfg(target_os = "fuchsia")]
421impl TryFrom<&zx::Vmo> for BackingBuffer {
422 type Error = ReaderError;
423 fn try_from(source: &zx::Vmo) -> Result<Self, Self::Error> {
424 let container = Container::read_only(source)?;
425 Ok(BackingBuffer::Container(container))
426 }
427}
428
429#[cfg(not(target_os = "fuchsia"))]
430impl TryFrom<&Vec<u8>> for BackingBuffer {
431 type Error = ReaderError;
432 fn try_from(source: &Vec<u8>) -> Result<Self, Self::Error> {
433 let container = Container::read_only(source);
434 Ok(BackingBuffer::Container(container))
435 }
436}
437
438impl From<Vec<u8>> for BackingBuffer {
439 fn from(v: Vec<u8>) -> Self {
440 BackingBuffer::Bytes(v)
441 }
442}
443
444impl ReadBytes for BackingBuffer {
445 fn get_slice_at(&self, offset: usize, size: usize) -> Option<&[u8]> {
446 match &self {
447 BackingBuffer::Container(m) => m.get_slice_at(offset, size),
448 BackingBuffer::Bytes(b) => b.get_slice_at(offset, size),
449 }
450 }
451}
452
453impl BlockContainer for BackingBuffer {
454 type Data = Self;
455 type ShareableData = ();
456
457 fn len(&self) -> usize {
458 match &self {
459 BackingBuffer::Container(m) => m.len(),
460 BackingBuffer::Bytes(v) => v.len(),
461 }
462 }
463}
464
465pub(crate) trait MakePrimitiveProperty {
466 fn make_property(&self, name: String) -> Property;
467}
468
469impl MakePrimitiveProperty for ScannedBlock<'_, Int> {
470 fn make_property(&self, name: String) -> Property {
471 Property::Int(name, self.value())
472 }
473}
474
475impl MakePrimitiveProperty for ScannedBlock<'_, Uint> {
476 fn make_property(&self, name: String) -> Property {
477 Property::Uint(name, self.value())
478 }
479}
480
481impl MakePrimitiveProperty for ScannedBlock<'_, Double> {
482 fn make_property(&self, name: String) -> Property {
483 Property::Double(name, self.value())
484 }
485}
486
487impl MakePrimitiveProperty for ScannedBlock<'_, Bool> {
488 fn make_property(&self, name: String) -> Property {
489 Property::Bool(name, self.value())
490 }
491}
492
493#[cfg(test)]
494mod tests {
495 use super::*;
496 use anyhow::Error;
497 use assert_matches::assert_matches;
498 use inspect_format::{BlockAccessorMutExt, WriteBytes};
499
500 #[cfg(target_os = "fuchsia")]
501 macro_rules! get_snapshot {
502 ($container:ident, $storage:ident, $callback:expr) => {
503 Snapshot::try_from_with_callback(&$storage, $callback)
504 };
505 }
506
507 #[cfg(not(target_os = "fuchsia"))]
508 macro_rules! get_snapshot {
509 ($container:ident, $storage:ident, $callback:expr) => {{
510 let _storage = $storage;
511 let slice = $container.get_slice($container.len()).unwrap().to_vec();
512 Snapshot::try_from_with_callback(&slice, $callback)
513 }};
514 }
515
516 #[fuchsia::test]
517 fn scan() -> Result<(), Error> {
518 let size = 4096;
519 let (mut container, storage) = Container::read_and_write(size).unwrap();
520 let _ = Block::free(
521 &mut container,
522 BlockIndex::HEADER,
523 constants::HEADER_ORDER,
524 BlockIndex::EMPTY,
525 )?
526 .become_reserved()
527 .become_header(size)?;
528 let _ = Block::free(&mut container, 2.into(), 2, BlockIndex::EMPTY)?
529 .become_reserved()
530 .become_extent(6.into());
531 let _ = Block::free(&mut container, 6.into(), 0, BlockIndex::EMPTY)?
532 .become_reserved()
533 .become_int_value(1, 3.into(), 4.into());
534
535 let snapshot = get_snapshot!(container, storage, || {})?;
536
537 let mut blocks = snapshot.scan();
539
540 let block = blocks.next().unwrap().cast::<Header>().unwrap();
541 assert_eq!(block.block_type(), Some(BlockType::Header));
542 assert_eq!(*block.index(), 0);
543 assert_eq!(block.order(), constants::HEADER_ORDER);
544 assert_eq!(block.magic_number(), constants::HEADER_MAGIC_NUMBER);
545 assert_eq!(block.version(), constants::HEADER_VERSION_NUMBER);
546
547 let block = blocks.next().unwrap().cast::<Extent>().unwrap();
548 assert_eq!(block.block_type(), Some(BlockType::Extent));
549 assert_eq!(*block.index(), 2);
550 assert_eq!(block.order(), 2);
551 assert_eq!(*block.next_extent(), 6);
552
553 let block = blocks.next().unwrap().cast::<Int>().unwrap();
554 assert_eq!(block.block_type(), Some(BlockType::IntValue));
555 assert_eq!(*block.index(), 6);
556 assert_eq!(block.order(), 0);
557 assert_eq!(*block.name_index(), 3);
558 assert_eq!(*block.parent_index(), 4);
559 assert_eq!(block.value(), 1);
560
561 assert!(blocks.all(|b| b.block_type() == Some(BlockType::Free)));
562
563 assert_eq!(snapshot.get_block(0.into()).unwrap().block_type(), Some(BlockType::Header));
565 assert_eq!(snapshot.get_block(2.into()).unwrap().block_type(), Some(BlockType::Extent));
566 assert_eq!(snapshot.get_block(6.into()).unwrap().block_type(), Some(BlockType::IntValue));
567 assert_eq!(snapshot.get_block(7.into()).unwrap().block_type(), Some(BlockType::Free));
568 let bad_index = BlockIndex::from(4096);
569 assert_matches!(
570 snapshot.get_block(bad_index),
571 Err(ReaderError::GetBlock(index)) if index == bad_index
572 );
573
574 Ok(())
575 }
576
577 #[fuchsia::test]
578 fn scan_bad_header() -> Result<(), Error> {
579 let (mut container, storage) = Container::read_and_write(4096).unwrap();
580
581 container.copy_from_slice(&[
583 0x00, 0x02, 0xff, b'I', b'N', b'S', b'P',
587 ]);
588 assert!(get_snapshot!(container, storage, || {}).is_err());
589 Ok(())
590 }
591
592 #[fuchsia::test]
593 fn invalid_type() -> Result<(), Error> {
594 let (mut container, storage) = Container::read_and_write(4096).unwrap();
595 container.copy_from_slice(&[0x00, 0xff, 0x01]);
596 assert!(get_snapshot!(container, storage, || {}).is_err());
597 Ok(())
598 }
599
600 #[fuchsia::test]
601 fn invalid_order() -> Result<(), Error> {
602 let (mut container, storage) = Container::read_and_write(4096).unwrap();
603 container.copy_from_slice(&[0xff, 0xff]);
604 assert!(get_snapshot!(container, storage, || {}).is_err());
605 Ok(())
606 }
607
608 #[fuchsia::test]
609 fn invalid_pending_write() -> Result<(), Error> {
610 let size = 4096;
611 let (mut container, storage) = Container::read_and_write(size).unwrap();
612 let mut header = Block::free(
613 &mut container,
614 BlockIndex::HEADER,
615 constants::HEADER_ORDER,
616 BlockIndex::EMPTY,
617 )?
618 .become_reserved()
619 .become_header(size)?;
620 header.lock();
621 assert!(get_snapshot!(container, storage, || {}).is_err());
622 Ok(())
623 }
624
625 #[fuchsia::test]
626 fn invalid_magic_number() -> Result<(), Error> {
627 let size = 4096;
628 let (mut container, storage) = Container::read_and_write(size).unwrap();
629 let mut header = Block::free(
630 &mut container,
631 BlockIndex::HEADER,
632 constants::HEADER_ORDER,
633 BlockIndex::EMPTY,
634 )?
635 .become_reserved()
636 .become_header(size)?;
637 header.set_magic(3);
638 assert!(get_snapshot!(container, storage, || {}).is_err());
639 Ok(())
640 }
641
642 #[fuchsia::test]
643 fn invalid_generation_count() -> Result<(), Error> {
644 let size = 4096;
645 let (mut container, storage) = Container::read_and_write(size).unwrap();
646 let _ = Block::free(
647 &mut container,
648 BlockIndex::HEADER,
649 constants::HEADER_ORDER,
650 BlockIndex::EMPTY,
651 )?
652 .become_reserved()
653 .become_header(size)?;
654 let result = get_snapshot!(container, storage, || {
655 let mut header = container.block_at_unchecked_mut::<Header>(BlockIndex::HEADER);
656 header.lock();
657 header.unlock();
658 });
659 #[cfg(target_os = "fuchsia")]
660 assert!(result.is_err());
661 #[cfg(not(target_os = "fuchsia"))]
664 assert!(result.is_ok());
665 Ok(())
666 }
667
668 #[fuchsia::test]
669 fn snapshot_from_few_bytes() {
670 let values = (0u8..16).collect::<Vec<u8>>();
671 assert!(Snapshot::try_from(values.clone()).is_err());
672 assert!(Snapshot::try_from(values).is_err());
673 assert!(Snapshot::try_from(vec![]).is_err());
674 assert!(Snapshot::try_from(vec![0u8, 1, 2, 3, 4]).is_err());
675 }
676
677 #[fuchsia::test]
678 fn snapshot_frozen_vmo() -> Result<(), Error> {
679 let size = 4096;
680 let (mut container, storage) = Container::read_and_write(size).unwrap();
681 let _ = Block::free(
682 &mut container,
683 BlockIndex::HEADER,
684 constants::HEADER_ORDER,
685 BlockIndex::EMPTY,
686 )?
687 .become_reserved()
688 .become_header(size)?;
689 container.copy_from_slice_at(8, &[0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]);
690
691 let snapshot = get_snapshot!(container, storage, || {})?;
692
693 assert!(matches!(snapshot.buffer, BackingBuffer::Container(_)));
694
695 container.copy_from_slice_at(8, &[2u8; 8]);
696 let snapshot = get_snapshot!(container, storage, || {})?;
697 assert!(matches!(snapshot.buffer, BackingBuffer::Bytes(_)));
698
699 Ok(())
700 }
701
702 #[fuchsia::test]
703 fn snapshot_vmo_with_unused_space() -> Result<(), Error> {
704 let size = 4 * constants::PAGE_SIZE_BYTES;
705 let (mut container, storage) = Container::read_and_write(size).unwrap();
706 let _ = Block::free(
707 &mut container,
708 BlockIndex::HEADER,
709 constants::HEADER_ORDER,
710 BlockIndex::EMPTY,
711 )?
712 .become_reserved()
713 .become_header(constants::PAGE_SIZE_BYTES)?;
714
715 let snapshot = get_snapshot!(container, storage, || {})?;
716 assert_eq!(snapshot.buffer.len(), constants::PAGE_SIZE_BYTES);
717
718 Ok(())
719 }
720
721 #[fuchsia::test]
722 fn snapshot_vmo_with_very_large_vmo() -> Result<(), Error> {
723 let size = 2 * constants::MAX_VMO_SIZE;
724 let (mut container, storage) = Container::read_and_write(size).unwrap();
725 let _ = Block::free(
726 &mut container,
727 BlockIndex::HEADER,
728 constants::HEADER_ORDER,
729 BlockIndex::EMPTY,
730 )?
731 .become_reserved()
732 .become_header(size)?;
733
734 let snapshot = get_snapshot!(container, storage, || {})?;
735 assert_eq!(snapshot.buffer.len(), constants::MAX_VMO_SIZE);
736
737 Ok(())
738 }
739
740 #[fuchsia::test]
741 fn snapshot_vmo_with_header_without_size_info() -> Result<(), Error> {
742 let size = 2 * constants::PAGE_SIZE_BYTES;
743 let (mut container, storage) = Container::read_and_write(size).unwrap();
744 let mut header = Block::free(&mut container, BlockIndex::HEADER, 0, BlockIndex::EMPTY)?
745 .become_reserved()
746 .become_header(constants::PAGE_SIZE_BYTES)?;
747 header.set_order(0)?;
748
749 let snapshot = get_snapshot!(container, storage, || {})?;
750 assert_eq!(snapshot.buffer.len(), size);
751
752 Ok(())
753 }
754}