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, 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(crate) fn try_once_with_magic_and_callback<F>(
63 source: &SnapshotSource,
64 expected_magic: u32,
65 max_vmo_size: Option<usize>,
66 read_callback: &mut F,
67 ) -> Result<Snapshot, ReaderError>
68 where
69 F: FnMut(),
70 {
71 let mut header_bytes: [u8; 32] = [0; 32];
73 source.copy_bytes(&mut header_bytes);
74 let Some(header_block) = Block::parse_header_with_magic(&header_bytes, expected_magic)
75 else {
76 return Err(ReaderError::InvalidVmo);
77 };
78 let Some(generation) = header_block.unlocked_generation_count() else {
79 return Err(ReaderError::InconsistentSnapshot);
80 };
81 if header_block.is_frozen() {
82 #[cfg(target_os = "fuchsia")]
83 {
84 let info = source.info().map_err(ReaderError::Vmo)?;
85 if !info.flags.contains(zx::VmoInfoFlags::IMMUTABLE) {
86 return Err(ReaderError::Vmo(zx::Status::BAD_STATE));
87 }
88 }
89 if let Ok(buffer) = BackingBuffer::try_from(source) {
90 return Ok(Snapshot { buffer });
91 }
92 }
93
94 let mut vmo_size = if let Some(vmo_size) = header_block.vmo_size()? {
96 vmo_size as usize
97 } else {
98 source.len()
99 };
100 if let Some(max_size) = max_vmo_size {
101 vmo_size = cmp::min(vmo_size, max_size);
102 }
103 let mut buffer = vec![0u8; vmo_size];
104 source.copy_bytes(&mut buffer);
105 if cfg!(test) {
106 read_callback();
107 }
108
109 source.copy_bytes(&mut header_bytes);
113 match header_generation_count_with_magic(&header_bytes, expected_magic) {
114 None => Err(ReaderError::InconsistentSnapshot),
115 Some(new_generation) if new_generation != generation => {
116 Err(ReaderError::InconsistentSnapshot)
117 }
118 Some(_) => Ok(Snapshot { buffer: BackingBuffer::from(buffer) }),
119 }
120 }
121
122 pub(crate) fn try_from_with_magic_and_callback<F>(
125 source: &SnapshotSource,
126 expected_magic: u32,
127 max_vmo_size: Option<usize>,
128 mut read_callback: F,
129 ) -> Result<Snapshot, ReaderError>
130 where
131 F: FnMut(),
132 {
133 let mut i = 0;
134 loop {
135 match Snapshot::try_once_with_magic_and_callback(
136 source,
137 expected_magic,
138 max_vmo_size,
139 &mut read_callback,
140 ) {
141 Ok(snapshot) => return Ok(snapshot),
142 Err(e) => {
143 if i >= SNAPSHOT_TRIES {
144 return Err(e);
145 }
146 }
147 };
148 i += 1;
149 }
150 }
151
152 pub fn try_once_from_vmo(source: &SnapshotSource) -> Result<Snapshot, ReaderError> {
166 Snapshot::try_once_with_callback(source, &mut || {})
167 }
168
169 fn try_once_with_callback<F>(
172 source: &SnapshotSource,
173 read_callback: &mut F,
174 ) -> Result<Snapshot, ReaderError>
175 where
176 F: FnMut(),
177 {
178 Snapshot::try_once_with_magic_and_callback(
179 source,
180 constants::HEADER_MAGIC_NUMBER,
181 Some(constants::MAX_VMO_SIZE),
182 read_callback,
183 )
184 }
185
186 fn try_from_with_callback<F>(
189 source: &SnapshotSource,
190 read_callback: F,
191 ) -> Result<Snapshot, ReaderError>
192 where
193 F: FnMut(),
194 {
195 Snapshot::try_from_with_magic_and_callback(
196 source,
197 constants::HEADER_MAGIC_NUMBER,
198 Some(constants::MAX_VMO_SIZE),
199 read_callback,
200 )
201 }
202
203 pub fn try_once_from_blob_vmo(source: &SnapshotSource) -> Result<Snapshot, ReaderError> {
219 Snapshot::try_once_with_magic_and_callback(
220 source,
221 constants::BLOB_HEADER_MAGIC_NUMBER,
222 None,
223 &mut || {},
224 )
225 }
226
227 pub fn try_from_blob_vmo(source: &SnapshotSource) -> Result<Snapshot, ReaderError> {
232 Snapshot::try_from_with_magic_and_callback(
233 source,
234 constants::BLOB_HEADER_MAGIC_NUMBER,
235 None,
236 || {},
237 )
238 }
239
240 pub(crate) fn get_name(&self, index: BlockIndex) -> Option<String> {
241 let block = self.get_block(index).ok()?;
242 match block.block_type()? {
243 BlockType::Name => self.load_name(block.cast::<Name>().unwrap()),
244 BlockType::StringReference => {
245 self.load_string_reference(block.cast::<StringRef>().unwrap()).ok()
246 }
247 _ => None,
248 }
249 }
250
251 pub(crate) fn load_name(&self, block: ScannedBlock<'_, Name>) -> Option<String> {
252 block.contents().ok().map(|s| s.to_string())
253 }
254
255 pub(crate) fn load_string_reference(
256 &self,
257 block: ScannedBlock<'_, StringRef>,
258 ) -> Result<String, ReaderError> {
259 let mut data = block.inline_data()?.to_vec();
260 let total_length = block.total_length();
261 if total_length <= data.len() {
262 return Ok(String::from_utf8_lossy(&data[..total_length]).to_string());
263 }
264
265 let extent_index = block.next_extent();
266 let still_to_read_length = total_length - data.len();
267 data.append(&mut self.read_extents(still_to_read_length, extent_index)?);
268
269 Ok(String::from_utf8_lossy(&data).to_string())
270 }
271
272 pub(crate) fn parse_primitive_property<'a, K>(
273 &self,
274 block: ScannedBlock<'a, K>,
275 ) -> Result<Property, ReaderError>
276 where
277 ScannedBlock<'a, K>: MakePrimitiveProperty,
278 K: ValueBlockKind,
279 {
280 let name_index = block.name_index();
281 let name = self.get_name(name_index).ok_or(ReaderError::ParseName(name_index))?;
282 Ok(block.make_property(name))
283 }
284
285 pub(crate) fn parse_array_property(
286 &self,
287 block: ScannedBlock<'_, Array<Unknown>>,
288 ) -> Result<Property, ReaderError> {
289 let name_index = block.name_index();
290 let name = self.get_name(name_index).ok_or(ReaderError::ParseName(name_index))?;
291 let array_slots = block.slots();
292 let capacity = block.capacity().ok_or(ReaderError::InvalidVmo)?;
294 if capacity < array_slots {
295 return Err(ReaderError::AttemptedToReadTooManyArraySlots(block.index()));
296 }
297 let value_indexes = 0..array_slots;
298 let format = block.format().ok_or(ReaderError::InvalidVmo)?;
299 let parsed_property = match block.entry_type() {
300 Some(BlockType::IntValue) => {
301 let block = block.cast_array_unchecked::<Int>();
302 let values = value_indexes
303 .map(|i| block.get(i).ok_or(ReaderError::InvalidVmo))
304 .collect::<Result<Vec<i64>, _>>()?;
305 Property::IntArray(
306 name,
307 ArrayContent::new(values, format)?,
310 )
311 }
312 Some(BlockType::UintValue) => {
313 let block = block.cast_array_unchecked::<Uint>();
314 let values = value_indexes
315 .map(|i| block.get(i).ok_or(ReaderError::InvalidVmo))
316 .collect::<Result<Vec<u64>, _>>()?;
317 Property::UintArray(
318 name,
319 ArrayContent::new(values, format)?,
322 )
323 }
324 Some(BlockType::DoubleValue) => {
325 let block = block.cast_array_unchecked::<Double>();
326 let values = value_indexes
327 .map(|i| block.get(i).ok_or(ReaderError::InvalidVmo))
328 .collect::<Result<Vec<f64>, _>>()?;
329 Property::DoubleArray(
330 name,
331 ArrayContent::new(values, format)?,
334 )
335 }
336 Some(BlockType::StringReference) => {
337 let block = block.cast_array_unchecked::<StringRef>();
338 let values = value_indexes
339 .map(|value_index| {
340 let string_idx = block
341 .get_string_index_at(value_index)
342 .ok_or(ReaderError::InvalidVmo)?;
343 if string_idx == BlockIndex::EMPTY {
346 return Ok(String::new());
347 }
348
349 let ref_block = self
350 .get_block(string_idx)?
351 .cast::<StringRef>()
352 .ok_or(ReaderError::InvalidVmo)?;
353 self.load_string_reference(ref_block)
354 })
355 .collect::<Result<Vec<String>, _>>()?;
356 Property::StringList(name, values)
357 }
358 _ => return Err(ReaderError::UnexpectedArrayEntryFormat(block.entry_type_raw())),
359 };
360 Ok(parsed_property)
361 }
362
363 pub(crate) fn parse_property(
364 &self,
365 block: ScannedBlock<'_, Buffer>,
366 ) -> Result<Property, ReaderError> {
367 let name_index = block.name_index();
368 let name = self.get_name(name_index).ok_or(ReaderError::ParseName(name_index))?;
369 let data_index = block.extent_index();
370 match block.format().ok_or(ReaderError::InvalidVmo)? {
371 PropertyFormat::String => {
372 let total_length = block.total_length();
373 let buffer = self.read_extents(total_length, data_index)?;
374 Ok(Property::String(name, String::from_utf8_lossy(&buffer).to_string()))
375 }
376 PropertyFormat::Bytes => {
377 let total_length = block.total_length();
378 let buffer = self.read_extents(total_length, data_index)?;
379 Ok(Property::Bytes(name, buffer))
380 }
381 PropertyFormat::StringReference => {
382 let data_head = self
383 .get_block(data_index)?
384 .cast::<StringRef>()
385 .ok_or(ReaderError::InvalidVmo)?;
386 Ok(Property::String(name, self.load_string_reference(data_head)?))
387 }
388 }
389 }
390
391 pub(crate) fn parse_link(
392 &self,
393 block: ScannedBlock<'_, Link>,
394 ) -> Result<LinkValue, ReaderError> {
395 let name_index = block.name_index();
396 let name = self.get_name(name_index).ok_or(ReaderError::ParseName(name_index))?;
397 let link_content_index = block.content_index();
398 let content =
399 self.get_name(link_content_index).ok_or(ReaderError::ParseName(link_content_index))?;
400 let disposition = block.link_node_disposition().ok_or(ReaderError::InvalidVmo)?;
401 Ok(LinkValue { name, content, disposition })
402 }
403
404 pub(crate) fn read_extents(
407 &self,
408 total_length: usize,
409 first_extent: BlockIndex,
410 ) -> Result<Vec<u8>, ReaderError> {
411 if total_length > self.buffer.len() {
412 return Err(ReaderError::InvalidVmo);
413 }
414 let mut buffer = vec![0u8; total_length];
415 let mut offset = 0;
416 let mut extent_index = first_extent;
417 while extent_index != BlockIndex::EMPTY && offset < total_length {
418 let extent = self
419 .get_block(extent_index)
420 .and_then(|b| b.cast::<Extent>().ok_or(ReaderError::InvalidVmo))?;
421 let content = extent.contents()?;
422 if content.is_empty() {
423 break;
424 }
425 let extent_length = cmp::min(total_length - offset, content.len());
426 buffer[offset..offset + extent_length].copy_from_slice(&content[..extent_length]);
427 offset += extent_length;
428 extent_index = extent.next_extent();
429 }
430
431 Ok(buffer)
432 }
433
434 #[cfg(test)]
436 pub fn build(bytes: &[u8]) -> Self {
437 Snapshot { buffer: BackingBuffer::from(bytes.to_vec()) }
438 }
439}
440
441fn header_generation_count_with_magic<T: ReadBytes>(bytes: &T, expected_magic: u32) -> Option<u64> {
444 Block::parse_header_with_magic(bytes, expected_magic)?.unlocked_generation_count()
445}
446
447fn header_generation_count<T: ReadBytes>(bytes: &T) -> Option<u64> {
450 header_generation_count_with_magic(bytes, constants::HEADER_MAGIC_NUMBER)
451}
452
453impl TryFrom<Vec<u8>> for Snapshot {
459 type Error = ReaderError;
460
461 fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
462 if header_generation_count(&bytes).is_some() {
463 Ok(Snapshot { buffer: BackingBuffer::from(bytes) })
464 } else {
465 Err(ReaderError::MissingHeaderOrLocked)
466 }
467 }
468}
469
470impl TryFrom<&Inspector> for Snapshot {
471 type Error = ReaderError;
472
473 fn try_from(inspector: &Inspector) -> Result<Self, Self::Error> {
474 let handle = inspector.get_storage_handle();
475 let storage = handle.as_ref().ok_or(ReaderError::NoOpInspector)?;
476 Snapshot::try_from_with_callback(storage, || {})
477 }
478}
479
480#[cfg(target_os = "fuchsia")]
481impl TryFrom<&zx::Vmo> for Snapshot {
482 type Error = ReaderError;
483
484 fn try_from(vmo: &zx::Vmo) -> Result<Self, Self::Error> {
485 Snapshot::try_from_with_callback(vmo, || {})
486 }
487}
488
489#[cfg(not(target_os = "fuchsia"))]
490impl TryFrom<&Vec<u8>> for Snapshot {
491 type Error = ReaderError;
492
493 fn try_from(buffer: &Vec<u8>) -> Result<Self, Self::Error> {
494 Snapshot::try_from_with_callback(buffer, || {})
495 }
496}
497
498pub struct BlockIterator<'a> {
501 offset: usize,
503
504 container: &'a BackingBuffer,
506}
507
508impl<'a> From<&'a BackingBuffer> for BlockIterator<'a> {
509 fn from(container: &'a BackingBuffer) -> Self {
510 BlockIterator { offset: 0, container }
511 }
512}
513
514impl<'a> Iterator for BlockIterator<'a> {
515 type Item = ScannedBlock<'a, Unknown>;
516
517 fn next(&mut self) -> Option<Self::Item> {
518 if self.offset >= self.container.len() {
519 return None;
520 }
521 let index = BlockIndex::from_offset(self.offset);
522 let block = self.container.block_at(index);
523 if self.container.len() - self.offset < utils::order_to_size(block.order()) {
524 return None;
525 }
526 self.offset += utils::order_to_size(block.order());
527 Some(block)
528 }
529}
530
531#[derive(Debug)]
532pub enum BackingBuffer {
533 Bytes(Vec<u8>),
534 Container(Container),
535}
536
537#[cfg(target_os = "fuchsia")]
538impl TryFrom<&zx::Vmo> for BackingBuffer {
539 type Error = ReaderError;
540 fn try_from(source: &zx::Vmo) -> Result<Self, Self::Error> {
541 let container = Container::read_only(source)?;
542 Ok(BackingBuffer::Container(container))
543 }
544}
545
546#[cfg(not(target_os = "fuchsia"))]
547impl TryFrom<&Vec<u8>> for BackingBuffer {
548 type Error = ReaderError;
549 fn try_from(source: &Vec<u8>) -> Result<Self, Self::Error> {
550 let container = Container::read_only(source);
551 Ok(BackingBuffer::Container(container))
552 }
553}
554
555impl From<Vec<u8>> for BackingBuffer {
556 fn from(v: Vec<u8>) -> Self {
557 BackingBuffer::Bytes(v)
558 }
559}
560
561impl ReadBytes for BackingBuffer {
562 fn get_slice_at(&self, offset: usize, size: usize) -> Option<&[u8]> {
563 match &self {
564 BackingBuffer::Container(m) => m.get_slice_at(offset, size),
565 BackingBuffer::Bytes(b) => b.get_slice_at(offset, size),
566 }
567 }
568}
569
570impl BlockContainer for BackingBuffer {
571 type Data = Self;
572 type ShareableData = ();
573
574 fn len(&self) -> usize {
575 match &self {
576 BackingBuffer::Container(m) => m.len(),
577 BackingBuffer::Bytes(v) => v.len(),
578 }
579 }
580}
581
582pub(crate) trait MakePrimitiveProperty {
583 fn make_property(&self, name: String) -> Property;
584}
585
586impl MakePrimitiveProperty for ScannedBlock<'_, Int> {
587 fn make_property(&self, name: String) -> Property {
588 Property::Int(name, self.value())
589 }
590}
591
592impl MakePrimitiveProperty for ScannedBlock<'_, Uint> {
593 fn make_property(&self, name: String) -> Property {
594 Property::Uint(name, self.value())
595 }
596}
597
598impl MakePrimitiveProperty for ScannedBlock<'_, Double> {
599 fn make_property(&self, name: String) -> Property {
600 Property::Double(name, self.value())
601 }
602}
603
604impl MakePrimitiveProperty for ScannedBlock<'_, Bool> {
605 fn make_property(&self, name: String) -> Property {
606 Property::Bool(name, self.value())
607 }
608}
609
610#[cfg(test)]
611mod tests {
612 use super::*;
613 use anyhow::Error;
614 use assert_matches::assert_matches;
615 use inspect_format::{BlockAccessorMutExt, Header, WriteBytes};
616
617 #[cfg(target_os = "fuchsia")]
618 macro_rules! get_snapshot {
619 ($container:ident, $storage:expr, $callback:expr) => {
620 Snapshot::try_from_with_callback(&$storage, $callback)
621 };
622 }
623
624 #[cfg(not(target_os = "fuchsia"))]
625 macro_rules! get_snapshot {
626 ($container:ident, $storage:expr, $callback:expr) => {{
627 let _storage = $storage;
628 let slice = $container.get_slice($container.len()).unwrap().to_vec();
629 Snapshot::try_from_with_callback(&slice, $callback)
630 }};
631 }
632
633 #[cfg(target_os = "fuchsia")]
634 macro_rules! get_source {
635 ($container:ident, $storage:expr) => {
636 $storage
637 };
638 }
639
640 #[cfg(not(target_os = "fuchsia"))]
641 macro_rules! get_source {
642 ($container:ident, $storage:expr) => {{
643 let _storage = $storage;
644 $container.get_slice($container.len()).unwrap().to_vec()
645 }};
646 }
647
648 #[fuchsia::test]
649 fn test_string_reference_short_total_length() -> Result<(), Error> {
650 let size = 4096;
651 let (mut container, storage) = Container::read_and_write(size).unwrap();
652 let _ = Block::free(
653 &mut container,
654 BlockIndex::HEADER,
655 constants::HEADER_ORDER,
656 BlockIndex::EMPTY,
657 )?
658 .become_reserved()
659 .become_inspect_header(size)?;
660
661 let mut str_block = Block::free(&mut container, 2.into(), 0, BlockIndex::EMPTY)?
662 .become_reserved()
663 .become_string_reference();
664 str_block.write_inline(b"hello");
665 str_block.set_total_length(0);
666
667 let snapshot = get_snapshot!(container, storage, || {})?;
668 let name = snapshot.get_name(2.into());
669 assert_eq!(name, Some("".to_string()));
670
671 Ok(())
672 }
673
674 #[fuchsia::test]
675 fn scan() -> Result<(), Error> {
676 let size = 4096;
677 let (mut container, storage) = Container::read_and_write(size).unwrap();
678 let _ = Block::free(
679 &mut container,
680 BlockIndex::HEADER,
681 constants::HEADER_ORDER,
682 BlockIndex::EMPTY,
683 )?
684 .become_reserved()
685 .become_inspect_header(size)?;
686 let _ = Block::free(&mut container, 2.into(), 2, BlockIndex::EMPTY)?
687 .become_reserved()
688 .become_extent(6.into());
689 let _ = Block::free(&mut container, 6.into(), 0, BlockIndex::EMPTY)?
690 .become_reserved()
691 .become_int_value(1, 3.into(), 4.into());
692
693 let snapshot = get_snapshot!(container, storage, || {})?;
694
695 let mut blocks = snapshot.scan();
697
698 let block = blocks.next().unwrap().cast::<Header>().unwrap();
699 assert_eq!(block.block_type(), Some(BlockType::Header));
700 assert_eq!(*block.index(), 0);
701 assert_eq!(block.order(), constants::HEADER_ORDER);
702 assert_eq!(block.magic_number(), constants::HEADER_MAGIC_NUMBER);
703 assert_eq!(block.version(), constants::HEADER_VERSION_NUMBER);
704
705 let block = blocks.next().unwrap().cast::<Extent>().unwrap();
706 assert_eq!(block.block_type(), Some(BlockType::Extent));
707 assert_eq!(*block.index(), 2);
708 assert_eq!(block.order(), 2);
709 assert_eq!(*block.next_extent(), 6);
710
711 let block = blocks.next().unwrap().cast::<Int>().unwrap();
712 assert_eq!(block.block_type(), Some(BlockType::IntValue));
713 assert_eq!(*block.index(), 6);
714 assert_eq!(block.order(), 0);
715 assert_eq!(*block.name_index(), 3);
716 assert_eq!(*block.parent_index(), 4);
717 assert_eq!(block.value(), 1);
718
719 assert!(blocks.all(|b| b.block_type() == Some(BlockType::Free)));
720
721 assert_eq!(snapshot.get_block(0.into()).unwrap().block_type(), Some(BlockType::Header));
723 assert_eq!(snapshot.get_block(2.into()).unwrap().block_type(), Some(BlockType::Extent));
724 assert_eq!(snapshot.get_block(6.into()).unwrap().block_type(), Some(BlockType::IntValue));
725 assert_eq!(snapshot.get_block(7.into()).unwrap().block_type(), Some(BlockType::Free));
726 let bad_index = BlockIndex::from(4096);
727 assert_matches!(
728 snapshot.get_block(bad_index),
729 Err(ReaderError::GetBlock(index)) if index == bad_index
730 );
731
732 Ok(())
733 }
734
735 #[fuchsia::test]
736 fn scan_bad_header() -> Result<(), Error> {
737 let (mut container, storage) = Container::read_and_write(4096).unwrap();
738
739 container.copy_from_slice(&[
741 0x00, 0x02, 0xff, b'I', b'N', b'S', b'P',
745 ]);
746 assert!(get_snapshot!(container, storage, || {}).is_err());
747 Ok(())
748 }
749
750 #[fuchsia::test]
751 fn invalid_type() -> Result<(), Error> {
752 let (mut container, storage) = Container::read_and_write(4096).unwrap();
753 container.copy_from_slice(&[0x00, 0xff, 0x01]);
754 assert!(get_snapshot!(container, storage, || {}).is_err());
755 Ok(())
756 }
757
758 #[fuchsia::test]
759 fn invalid_order() -> Result<(), Error> {
760 let (mut container, storage) = Container::read_and_write(4096).unwrap();
761 container.copy_from_slice(&[0xff, 0xff]);
762 assert!(get_snapshot!(container, storage, || {}).is_err());
763 Ok(())
764 }
765
766 #[fuchsia::test]
767 fn invalid_pending_write() -> Result<(), Error> {
768 let size = 4096;
769 let (mut container, storage) = Container::read_and_write(size).unwrap();
770 let mut header = Block::free(
771 &mut container,
772 BlockIndex::HEADER,
773 constants::HEADER_ORDER,
774 BlockIndex::EMPTY,
775 )?
776 .become_reserved()
777 .become_inspect_header(size)?;
778 header.lock();
779 let mut full_copy_performed = false;
780 assert!(
781 get_snapshot!(container, storage, || {
782 full_copy_performed = true;
783 })
784 .is_err()
785 );
786 assert!(!full_copy_performed);
787 Ok(())
788 }
789
790 #[fuchsia::test]
791 fn invalid_magic_number() -> Result<(), Error> {
792 let size = 4096;
793 let (mut container, storage) = Container::read_and_write(size).unwrap();
794 let mut header = Block::free(
795 &mut container,
796 BlockIndex::HEADER,
797 constants::HEADER_ORDER,
798 BlockIndex::EMPTY,
799 )?
800 .become_reserved()
801 .become_inspect_header(size)?;
802 header.set_magic(3);
803 assert!(get_snapshot!(container, storage, || {}).is_err());
804 Ok(())
805 }
806
807 #[fuchsia::test]
808 fn invalid_generation_count() -> Result<(), Error> {
809 let size = 4096;
810 let (mut container, storage) = Container::read_and_write(size).unwrap();
811 let _ = Block::free(
812 &mut container,
813 BlockIndex::HEADER,
814 constants::HEADER_ORDER,
815 BlockIndex::EMPTY,
816 )?
817 .become_reserved()
818 .become_inspect_header(size)?;
819 let result = get_snapshot!(container, storage, || {
820 let mut header = container.block_at_unchecked_mut::<Header>(BlockIndex::HEADER);
821 header.lock();
822 header.unlock();
823 });
824 #[cfg(target_os = "fuchsia")]
825 assert!(result.is_err());
826 #[cfg(not(target_os = "fuchsia"))]
829 assert!(result.is_ok());
830 Ok(())
831 }
832
833 #[fuchsia::test]
834 fn snapshot_from_few_bytes() {
835 let values = (0u8..16).collect::<Vec<u8>>();
836 assert!(Snapshot::try_from(values.clone()).is_err());
837 assert!(Snapshot::try_from(values).is_err());
838 assert!(Snapshot::try_from(vec![]).is_err());
839 assert!(Snapshot::try_from(vec![0u8, 1, 2, 3, 4]).is_err());
840 }
841
842 #[fuchsia::test]
843 fn snapshot_frozen_vmo() -> Result<(), Error> {
844 let size = 4096;
845 let (mut container, parent_storage) = Container::read_and_write(size).unwrap();
846 let _ = Block::free(
847 &mut container,
848 BlockIndex::HEADER,
849 constants::HEADER_ORDER,
850 BlockIndex::EMPTY,
851 )?
852 .become_reserved()
853 .become_inspect_header(size)?;
854 container.copy_from_slice_at(8, &[0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]);
855
856 let snapshot;
857 #[cfg(target_os = "fuchsia")]
858 {
859 let storage = parent_storage.create_child(
860 zx::VmoChildOptions::SNAPSHOT | zx::VmoChildOptions::NO_WRITE,
861 0,
862 size as u64,
863 )?;
864 snapshot = get_snapshot!(container, storage, || {})?;
865 };
866 #[cfg(not(target_os = "fuchsia"))]
867 {
868 #[allow(clippy::let_unit_value)]
870 let _ = parent_storage;
871 snapshot = get_snapshot!(container, (), || {})?
872 };
873 assert!(matches!(snapshot.buffer, BackingBuffer::Container(_)));
874
875 let (mut container2, storage2) = Container::read_and_write(size).unwrap();
876 let _ = Block::free(
877 &mut container2,
878 BlockIndex::HEADER,
879 constants::HEADER_ORDER,
880 BlockIndex::EMPTY,
881 )?
882 .become_reserved()
883 .become_inspect_header(size)?;
884 container2.copy_from_slice_at(8, &[2u8; 8]);
885 let snapshot = get_snapshot!(container2, storage2, || {})?;
886 assert!(matches!(snapshot.buffer, BackingBuffer::Bytes(_)));
887
888 Ok(())
889 }
890
891 #[cfg(target_os = "fuchsia")]
894 #[fuchsia::test]
895 fn snapshot_frozen_mutable_vmo_fails() -> Result<(), Error> {
896 let size = 4096;
897 let (mut container, storage) = Container::read_and_write(size).unwrap();
898 let _ = Block::free(
899 &mut container,
900 BlockIndex::HEADER,
901 constants::HEADER_ORDER,
902 BlockIndex::EMPTY,
903 )?
904 .become_reserved()
905 .become_inspect_header(size)?;
906 container.copy_from_slice_at(8, &[0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]);
907
908 assert_matches!(
909 get_snapshot!(container, storage, || {}),
910 Err(ReaderError::Vmo(zx::Status::BAD_STATE))
911 );
912
913 Ok(())
914 }
915
916 #[fuchsia::test]
917 fn snapshot_vmo_with_unused_space() -> Result<(), Error> {
918 let size = 4 * constants::PAGE_SIZE_BYTES;
919 let (mut container, storage) = Container::read_and_write(size).unwrap();
920 let _ = Block::free(
921 &mut container,
922 BlockIndex::HEADER,
923 constants::HEADER_ORDER,
924 BlockIndex::EMPTY,
925 )?
926 .become_reserved()
927 .become_inspect_header(constants::PAGE_SIZE_BYTES)?;
928
929 let snapshot = get_snapshot!(container, storage, || {})?;
930 assert_eq!(snapshot.buffer.len(), constants::PAGE_SIZE_BYTES);
931
932 Ok(())
933 }
934
935 #[fuchsia::test]
936 fn snapshot_vmo_with_very_large_vmo() -> Result<(), Error> {
937 let size = 2 * constants::MAX_VMO_SIZE;
938 let (mut container, storage) = Container::read_and_write(size).unwrap();
939 let _ = Block::free(
940 &mut container,
941 BlockIndex::HEADER,
942 constants::HEADER_ORDER,
943 BlockIndex::EMPTY,
944 )?
945 .become_reserved()
946 .become_inspect_header(size)?;
947
948 let snapshot = get_snapshot!(container, storage, || {})?;
949 assert_eq!(snapshot.buffer.len(), constants::MAX_VMO_SIZE);
950
951 Ok(())
952 }
953
954 #[fuchsia::test]
955 fn snapshot_vmo_with_header_without_size_info() -> Result<(), Error> {
956 let size = 2 * constants::PAGE_SIZE_BYTES;
957 let (mut container, storage) = Container::read_and_write(size).unwrap();
958 let mut header = Block::free(&mut container, BlockIndex::HEADER, 0, BlockIndex::EMPTY)?
959 .become_reserved()
960 .become_inspect_header(constants::PAGE_SIZE_BYTES)?;
961 header.set_order(0)?;
962
963 let snapshot = get_snapshot!(container, storage, || {})?;
964 assert_eq!(snapshot.buffer.len(), size);
965
966 Ok(())
967 }
968
969 #[fuchsia::test]
970 fn valid_main_vmo_does_not_snapshot_with_blob_vmo_method() -> Result<(), Error> {
971 let size = 4096;
972 let (mut container, storage) = Container::read_and_write(size).unwrap();
973 let _ = Block::free(
974 &mut container,
975 BlockIndex::HEADER,
976 constants::HEADER_ORDER,
977 BlockIndex::EMPTY,
978 )?
979 .become_reserved()
980 .become_inspect_header(size)?;
981
982 let source = get_source!(container, storage);
983 assert!(Snapshot::try_once_from_vmo(&source).is_ok());
984 assert!(Snapshot::try_from(&source).is_ok());
985
986 assert_matches!(Snapshot::try_once_from_blob_vmo(&source), Err(ReaderError::InvalidVmo));
987 assert_matches!(Snapshot::try_from_blob_vmo(&source), Err(ReaderError::InvalidVmo));
988
989 Ok(())
990 }
991
992 #[fuchsia::test]
993 fn valid_blob_vmo_does_not_snapshot_with_main_vmo_method() -> Result<(), Error> {
994 let size = 4096;
995 let (mut container, storage) = Container::read_and_write(size).unwrap();
996 let _ = Block::free(
997 &mut container,
998 BlockIndex::HEADER,
999 constants::HEADER_ORDER,
1000 BlockIndex::EMPTY,
1001 )?
1002 .become_reserved()
1003 .become_blob_header(size)?;
1004
1005 let source = get_source!(container, storage);
1006 assert!(Snapshot::try_once_from_blob_vmo(&source).is_ok());
1007 assert!(Snapshot::try_from_blob_vmo(&source).is_ok());
1008
1009 assert_matches!(Snapshot::try_once_from_vmo(&source), Err(ReaderError::InvalidVmo));
1010 assert_matches!(Snapshot::try_from(&source), Err(ReaderError::InvalidVmo));
1011
1012 Ok(())
1013 }
1014}