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