Skip to main content

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            #[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        // Read the buffer
84        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        // Read the generation count one more time to ensure the previous buffer read is
96        // consistent. It's safe to unwrap this time, we already checked we can read 32 bytes from
97        // the slice.
98        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 total_length <= data.len() {
151            return Ok(String::from_utf8_lossy(&data[..total_length]).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        // Safety: So long as the array is valid, array_capacity will return a valid value.
182        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).ok_or(ReaderError::InvalidVmo))
193                    .collect::<Result<Vec<i64>, _>>()?;
194                Property::IntArray(
195                    name,
196                    // Safety: if the block is an array, it must have an array format.
197                    // We have already verified it is an array.
198                    ArrayContent::new(values, format)?,
199                )
200            }
201            Some(BlockType::UintValue) => {
202                let block = block.cast_array_unchecked::<Uint>();
203                let values = value_indexes
204                    .map(|i| block.get(i).ok_or(ReaderError::InvalidVmo))
205                    .collect::<Result<Vec<u64>, _>>()?;
206                Property::UintArray(
207                    name,
208                    // Safety: if the block is an array, it must have an array format.
209                    // We have already verified it is an array.
210                    ArrayContent::new(values, format)?,
211                )
212            }
213            Some(BlockType::DoubleValue) => {
214                let block = block.cast_array_unchecked::<Double>();
215                let values = value_indexes
216                    .map(|i| block.get(i).ok_or(ReaderError::InvalidVmo))
217                    .collect::<Result<Vec<f64>, _>>()?;
218                Property::DoubleArray(
219                    name,
220                    // Safety: if the block is an array, it must have an array format.
221                    // We have already verified it is an array.
222                    ArrayContent::new(values, format)?,
223                )
224            }
225            Some(BlockType::StringReference) => {
226                let block = block.cast_array_unchecked::<StringRef>();
227                let values = value_indexes
228                    .map(|value_index| {
229                        let string_idx = block
230                            .get_string_index_at(value_index)
231                            .ok_or(ReaderError::InvalidVmo)?;
232                        // default initialize unset values -- 0 index is never a string, it is always
233                        // the header block
234                        if string_idx == BlockIndex::EMPTY {
235                            return Ok(String::new());
236                        }
237
238                        let ref_block = self
239                            .get_block(string_idx)?
240                            .cast::<StringRef>()
241                            .ok_or(ReaderError::InvalidVmo)?;
242                        self.load_string_reference(ref_block)
243                    })
244                    .collect::<Result<Vec<String>, _>>()?;
245                Property::StringList(name, values)
246            }
247            _ => return Err(ReaderError::UnexpectedArrayEntryFormat(block.entry_type_raw())),
248        };
249        Ok(parsed_property)
250    }
251
252    pub(crate) fn parse_property(
253        &self,
254        block: ScannedBlock<'_, Buffer>,
255    ) -> Result<Property, ReaderError> {
256        let name_index = block.name_index();
257        let name = self.get_name(name_index).ok_or(ReaderError::ParseName(name_index))?;
258        let data_index = block.extent_index();
259        match block.format().ok_or(ReaderError::InvalidVmo)? {
260            PropertyFormat::String => {
261                let total_length = block.total_length();
262                let buffer = self.read_extents(total_length, data_index)?;
263                Ok(Property::String(name, String::from_utf8_lossy(&buffer).to_string()))
264            }
265            PropertyFormat::Bytes => {
266                let total_length = block.total_length();
267                let buffer = self.read_extents(total_length, data_index)?;
268                Ok(Property::Bytes(name, buffer))
269            }
270            PropertyFormat::StringReference => {
271                let data_head = self
272                    .get_block(data_index)?
273                    .cast::<StringRef>()
274                    .ok_or(ReaderError::InvalidVmo)?;
275                Ok(Property::String(name, self.load_string_reference(data_head)?))
276            }
277        }
278    }
279
280    pub(crate) fn parse_link(
281        &self,
282        block: ScannedBlock<'_, Link>,
283    ) -> Result<LinkValue, ReaderError> {
284        let name_index = block.name_index();
285        let name = self.get_name(name_index).ok_or(ReaderError::ParseName(name_index))?;
286        let link_content_index = block.content_index();
287        let content =
288            self.get_name(link_content_index).ok_or(ReaderError::ParseName(link_content_index))?;
289        let disposition = block.link_node_disposition().ok_or(ReaderError::InvalidVmo)?;
290        Ok(LinkValue { name, content, disposition })
291    }
292
293    // Incrementally add the contents of each extent in the extent linked list
294    // until we reach the last extent or the maximum expected length.
295    pub(crate) fn read_extents(
296        &self,
297        total_length: usize,
298        first_extent: BlockIndex,
299    ) -> Result<Vec<u8>, ReaderError> {
300        if total_length > self.buffer.len() {
301            return Err(ReaderError::InvalidVmo);
302        }
303        let mut buffer = vec![0u8; total_length];
304        let mut offset = 0;
305        let mut extent_index = first_extent;
306        while extent_index != BlockIndex::EMPTY && offset < total_length {
307            let extent = self
308                .get_block(extent_index)
309                .and_then(|b| b.cast::<Extent>().ok_or(ReaderError::InvalidVmo))?;
310            let content = extent.contents()?;
311            if content.is_empty() {
312                break;
313            }
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    // Used for snapshot tests.
324    #[cfg(test)]
325    pub fn build(bytes: &[u8]) -> Self {
326        Snapshot { buffer: BackingBuffer::from(bytes.to_vec()) }
327    }
328}
329
330/// Reads the given 16 bytes as an Inspect Block Header and returns the
331/// generation count if the header is valid: correct magic number, version number
332/// and nobody is writing to it.
333fn 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
347/// Construct a snapshot from a byte vector.
348impl 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
388/// Iterates over a byte array containing Inspect API blocks and returns the
389/// blocks in order.
390pub struct BlockIterator<'a> {
391    /// Current offset at which the iterator is reading.
392    offset: usize,
393
394    /// The bytes being read.
395    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 test_string_reference_short_total_length() -> 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
536        let mut str_block = Block::free(&mut container, 2.into(), 0, BlockIndex::EMPTY)?
537            .become_reserved()
538            .become_string_reference();
539        str_block.write_inline(b"hello");
540        str_block.set_total_length(0);
541
542        let snapshot = get_snapshot!(container, storage, || {})?;
543        let name = snapshot.get_name(2.into());
544        assert_eq!(name, Some("".to_string()));
545
546        Ok(())
547    }
548
549    #[fuchsia::test]
550    fn scan() -> Result<(), Error> {
551        let size = 4096;
552        let (mut container, storage) = Container::read_and_write(size).unwrap();
553        let _ = Block::free(
554            &mut container,
555            BlockIndex::HEADER,
556            constants::HEADER_ORDER,
557            BlockIndex::EMPTY,
558        )?
559        .become_reserved()
560        .become_header(size)?;
561        let _ = Block::free(&mut container, 2.into(), 2, BlockIndex::EMPTY)?
562            .become_reserved()
563            .become_extent(6.into());
564        let _ = Block::free(&mut container, 6.into(), 0, BlockIndex::EMPTY)?
565            .become_reserved()
566            .become_int_value(1, 3.into(), 4.into());
567
568        let snapshot = get_snapshot!(container, storage, || {})?;
569
570        // Scan blocks
571        let mut blocks = snapshot.scan();
572
573        let block = blocks.next().unwrap().cast::<Header>().unwrap();
574        assert_eq!(block.block_type(), Some(BlockType::Header));
575        assert_eq!(*block.index(), 0);
576        assert_eq!(block.order(), constants::HEADER_ORDER);
577        assert_eq!(block.magic_number(), constants::HEADER_MAGIC_NUMBER);
578        assert_eq!(block.version(), constants::HEADER_VERSION_NUMBER);
579
580        let block = blocks.next().unwrap().cast::<Extent>().unwrap();
581        assert_eq!(block.block_type(), Some(BlockType::Extent));
582        assert_eq!(*block.index(), 2);
583        assert_eq!(block.order(), 2);
584        assert_eq!(*block.next_extent(), 6);
585
586        let block = blocks.next().unwrap().cast::<Int>().unwrap();
587        assert_eq!(block.block_type(), Some(BlockType::IntValue));
588        assert_eq!(*block.index(), 6);
589        assert_eq!(block.order(), 0);
590        assert_eq!(*block.name_index(), 3);
591        assert_eq!(*block.parent_index(), 4);
592        assert_eq!(block.value(), 1);
593
594        assert!(blocks.all(|b| b.block_type() == Some(BlockType::Free)));
595
596        // Verify get_block
597        assert_eq!(snapshot.get_block(0.into()).unwrap().block_type(), Some(BlockType::Header));
598        assert_eq!(snapshot.get_block(2.into()).unwrap().block_type(), Some(BlockType::Extent));
599        assert_eq!(snapshot.get_block(6.into()).unwrap().block_type(), Some(BlockType::IntValue));
600        assert_eq!(snapshot.get_block(7.into()).unwrap().block_type(), Some(BlockType::Free));
601        let bad_index = BlockIndex::from(4096);
602        assert_matches!(
603            snapshot.get_block(bad_index),
604            Err(ReaderError::GetBlock(index)) if index == bad_index
605        );
606
607        Ok(())
608    }
609
610    #[fuchsia::test]
611    fn scan_bad_header() -> Result<(), Error> {
612        let (mut container, storage) = Container::read_and_write(4096).unwrap();
613
614        // create a header block with an invalid version number
615        container.copy_from_slice(&[
616            0x00, /* order/reserved */
617            0x02, /* type */
618            0xff, /* invalid version number */
619            b'I', b'N', b'S', b'P',
620        ]);
621        assert!(get_snapshot!(container, storage, || {}).is_err());
622        Ok(())
623    }
624
625    #[fuchsia::test]
626    fn invalid_type() -> Result<(), Error> {
627        let (mut container, storage) = Container::read_and_write(4096).unwrap();
628        container.copy_from_slice(&[0x00, 0xff, 0x01]);
629        assert!(get_snapshot!(container, storage, || {}).is_err());
630        Ok(())
631    }
632
633    #[fuchsia::test]
634    fn invalid_order() -> Result<(), Error> {
635        let (mut container, storage) = Container::read_and_write(4096).unwrap();
636        container.copy_from_slice(&[0xff, 0xff]);
637        assert!(get_snapshot!(container, storage, || {}).is_err());
638        Ok(())
639    }
640
641    #[fuchsia::test]
642    fn invalid_pending_write() -> Result<(), Error> {
643        let size = 4096;
644        let (mut container, storage) = Container::read_and_write(size).unwrap();
645        let mut header = Block::free(
646            &mut container,
647            BlockIndex::HEADER,
648            constants::HEADER_ORDER,
649            BlockIndex::EMPTY,
650        )?
651        .become_reserved()
652        .become_header(size)?;
653        header.lock();
654        assert!(get_snapshot!(container, storage, || {}).is_err());
655        Ok(())
656    }
657
658    #[fuchsia::test]
659    fn invalid_magic_number() -> Result<(), Error> {
660        let size = 4096;
661        let (mut container, storage) = Container::read_and_write(size).unwrap();
662        let mut header = Block::free(
663            &mut container,
664            BlockIndex::HEADER,
665            constants::HEADER_ORDER,
666            BlockIndex::EMPTY,
667        )?
668        .become_reserved()
669        .become_header(size)?;
670        header.set_magic(3);
671        assert!(get_snapshot!(container, storage, || {}).is_err());
672        Ok(())
673    }
674
675    #[fuchsia::test]
676    fn invalid_generation_count() -> 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        let result = get_snapshot!(container, storage, || {
688            let mut header = container.block_at_unchecked_mut::<Header>(BlockIndex::HEADER);
689            header.lock();
690            header.unlock();
691        });
692        #[cfg(target_os = "fuchsia")]
693        assert!(result.is_err());
694        // When in the host, we don't have underlying shared memory, so this can't fail as we
695        // had already cloned the underlying vector.
696        #[cfg(not(target_os = "fuchsia"))]
697        assert!(result.is_ok());
698        Ok(())
699    }
700
701    #[fuchsia::test]
702    fn snapshot_from_few_bytes() {
703        let values = (0u8..16).collect::<Vec<u8>>();
704        assert!(Snapshot::try_from(values.clone()).is_err());
705        assert!(Snapshot::try_from(values).is_err());
706        assert!(Snapshot::try_from(vec![]).is_err());
707        assert!(Snapshot::try_from(vec![0u8, 1, 2, 3, 4]).is_err());
708    }
709
710    #[fuchsia::test]
711    fn snapshot_frozen_vmo() -> Result<(), Error> {
712        let size = 4096;
713        let (mut container, parent_storage) = Container::read_and_write(size).unwrap();
714        let _ = Block::free(
715            &mut container,
716            BlockIndex::HEADER,
717            constants::HEADER_ORDER,
718            BlockIndex::EMPTY,
719        )?
720        .become_reserved()
721        .become_header(size)?;
722        container.copy_from_slice_at(8, &[0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]);
723
724        let snapshot;
725        #[cfg(target_os = "fuchsia")]
726        {
727            let storage = parent_storage.create_child(
728                zx::VmoChildOptions::SNAPSHOT | zx::VmoChildOptions::NO_WRITE,
729                0,
730                size as u64,
731            )?;
732            snapshot = get_snapshot!(container, storage, || {})?;
733        };
734        #[cfg(not(target_os = "fuchsia"))]
735        {
736            // Silence unused variable warning, which happens to be set to a unit value.
737            #[allow(clippy::let_unit_value)]
738            let _ = parent_storage;
739            snapshot = get_snapshot!(container, (), || {})?
740        };
741        assert!(matches!(snapshot.buffer, BackingBuffer::Container(_)));
742
743        let (mut container2, storage2) = Container::read_and_write(size).unwrap();
744        let _ = Block::free(
745            &mut container2,
746            BlockIndex::HEADER,
747            constants::HEADER_ORDER,
748            BlockIndex::EMPTY,
749        )?
750        .become_reserved()
751        .become_header(size)?;
752        container2.copy_from_slice_at(8, &[2u8; 8]);
753        let snapshot = get_snapshot!(container2, storage2, || {})?;
754        assert!(matches!(snapshot.buffer, BackingBuffer::Bytes(_)));
755
756        Ok(())
757    }
758
759    // Check that snapshot fails if the VMO is frozen but not immutable.
760    // This test is only valid on Fuchsia, where VMOs can be used.
761    #[cfg(target_os = "fuchsia")]
762    #[fuchsia::test]
763    fn snapshot_frozen_mutable_vmo_fails() -> Result<(), Error> {
764        let size = 4096;
765        let (mut container, storage) = Container::read_and_write(size).unwrap();
766        let _ = Block::free(
767            &mut container,
768            BlockIndex::HEADER,
769            constants::HEADER_ORDER,
770            BlockIndex::EMPTY,
771        )?
772        .become_reserved()
773        .become_header(size)?;
774        container.copy_from_slice_at(8, &[0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]);
775
776        assert_matches!(
777            get_snapshot!(container, storage, || {}),
778            Err(ReaderError::Vmo(zx::Status::BAD_STATE))
779        );
780
781        Ok(())
782    }
783
784    #[fuchsia::test]
785    fn snapshot_vmo_with_unused_space() -> Result<(), Error> {
786        let size = 4 * constants::PAGE_SIZE_BYTES;
787        let (mut container, storage) = Container::read_and_write(size).unwrap();
788        let _ = Block::free(
789            &mut container,
790            BlockIndex::HEADER,
791            constants::HEADER_ORDER,
792            BlockIndex::EMPTY,
793        )?
794        .become_reserved()
795        .become_header(constants::PAGE_SIZE_BYTES)?;
796
797        let snapshot = get_snapshot!(container, storage, || {})?;
798        assert_eq!(snapshot.buffer.len(), constants::PAGE_SIZE_BYTES);
799
800        Ok(())
801    }
802
803    #[fuchsia::test]
804    fn snapshot_vmo_with_very_large_vmo() -> Result<(), Error> {
805        let size = 2 * constants::MAX_VMO_SIZE;
806        let (mut container, storage) = Container::read_and_write(size).unwrap();
807        let _ = Block::free(
808            &mut container,
809            BlockIndex::HEADER,
810            constants::HEADER_ORDER,
811            BlockIndex::EMPTY,
812        )?
813        .become_reserved()
814        .become_header(size)?;
815
816        let snapshot = get_snapshot!(container, storage, || {})?;
817        assert_eq!(snapshot.buffer.len(), constants::MAX_VMO_SIZE);
818
819        Ok(())
820    }
821
822    #[fuchsia::test]
823    fn snapshot_vmo_with_header_without_size_info() -> Result<(), Error> {
824        let size = 2 * constants::PAGE_SIZE_BYTES;
825        let (mut container, storage) = Container::read_and_write(size).unwrap();
826        let mut header = Block::free(&mut container, BlockIndex::HEADER, 0, BlockIndex::EMPTY)?
827            .become_reserved()
828            .become_header(constants::PAGE_SIZE_BYTES)?;
829        header.set_order(0)?;
830
831        let snapshot = get_snapshot!(container, storage, || {})?;
832        assert_eq!(snapshot.buffer.len(), size);
833
834        Ok(())
835    }
836}