fuchsia_inspect/reader/
snapshot.rs

1// Copyright 2019 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! A snapshot represents all the loaded blocks of the VMO in a way that we can reconstruct the
6//! implicit tree.
7
8use 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/// Enables to scan all the blocks in a given buffer.
23#[derive(Debug)]
24pub struct Snapshot {
25    /// The buffer read from an Inspect VMO.
26    buffer: BackingBuffer,
27}
28
29/// A scanned block.
30pub type ScannedBlock<'a, K> = Block<&'a BackingBuffer, K>;
31
32const SNAPSHOT_TRIES: u64 = 1024;
33
34impl Snapshot {
35    /// Returns an iterator that returns all the Blocks in the buffer.
36    pub fn scan(&self) -> BlockIterator<'_> {
37        BlockIterator::from(&self.buffer)
38    }
39
40    /// Gets the block at the given |index|.
41    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    /// Try to take a consistent snapshot of the given VMO once.
50    ///
51    /// Returns a Snapshot on success or an Error if a consistent snapshot could not be taken.
52    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        // Read the generation count one time
64        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        // Read the buffer
77        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        // Read the generation count one more time to ensure the previous buffer read is
89        // consistent. It's safe to unwrap this time, we already checked we can read 32 bytes from
90        // the slice.
91        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        // Safety: So long as the array is valid, array_capacity will return a valid value.
175        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                    // Safety: in release mode, this can only error for index-out-of-bounds.
186                    // We check above that indexes are in-bounds.
187                    .map(|i| block.get(i).unwrap())
188                    .collect::<Vec<i64>>();
189                Property::IntArray(
190                    name,
191                    // Safety: if the block is an array, it must have an array format.
192                    // We have already verified it is an array.
193                    ArrayContent::new(values, format)?,
194                )
195            }
196            Some(BlockType::UintValue) => {
197                let block = block.cast_array_unchecked::<Uint>();
198                let values = value_indexes
199                    // Safety: in release mode, this can only error for index-out-of-bounds.
200                    // We check above that indexes are in-bounds.
201                    .map(|i| block.get(i).unwrap())
202                    .collect::<Vec<u64>>();
203                Property::UintArray(
204                    name,
205                    // Safety: if the block is an array, it must have an array format.
206                    // We have already verified it is an array.
207                    ArrayContent::new(values, format)?,
208                )
209            }
210            Some(BlockType::DoubleValue) => {
211                let block = block.cast_array_unchecked::<Double>();
212                let values = value_indexes
213                    // Safety: in release mode, this can only error for index-out-of-bounds.
214                    // We check above that indexes are in-bounds.
215                    .map(|i| block.get(i).unwrap())
216                    .collect::<Vec<f64>>();
217                Property::DoubleArray(
218                    name,
219                    // Safety: if the block is an array, it must have an array format.
220                    // We have already verified it is an array.
221                    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                        // default initialize unset values -- 0 index is never a string, it is always
232                        // the header block
233                        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    // Incrementally add the contents of each extent in the extent linked list
293    // until we reach the last extent or the maximum expected length.
294    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    // Used for snapshot tests.
317    #[cfg(test)]
318    pub fn build(bytes: &[u8]) -> Self {
319        Snapshot { buffer: BackingBuffer::from(bytes.to_vec()) }
320    }
321}
322
323/// Reads the given 16 bytes as an Inspect Block Header and returns the
324/// generation count if the header is valid: correct magic number, version number
325/// and nobody is writing to it.
326fn 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
340/// Construct a snapshot from a byte vector.
341impl 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
381/// Iterates over a byte array containing Inspect API blocks and returns the
382/// blocks in order.
383pub struct BlockIterator<'a> {
384    /// Current offset at which the iterator is reading.
385    offset: usize,
386
387    /// The bytes being read.
388    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        // Scan blocks
538        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        // Verify get_block
564        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        // create a header block with an invalid version number
582        container.copy_from_slice(&[
583            0x00, /* order/reserved */
584            0x02, /* type */
585            0xff, /* invalid version number */
586            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        // When in the host, we don't have underlying shared memory, so this can't fail as we
662        // had already cloned the underlying vector.
663        #[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}