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