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, 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    // =========================================================================
50    // Generic snapshot helpers (shared by regular "INSP" VMOs and "BLOB" VMOs)
51    //
52    // `try_once_*` methods perform a single snapshot attempt and return
53    // `Err(ReaderError::InconsistentSnapshot)` immediately if the VMO header is
54    // locked or modified concurrently during the read.
55    //
56    // `try_from_*` methods retry `try_once_*` up to `SNAPSHOT_TRIES` (1024)
57    // times to obtain a consistent snapshot under concurrent writer activity.
58    // =========================================================================
59
60    /// Performs a single attempt to take a consistent snapshot of `source` with
61    /// the given `expected_magic` header word (`"INSP"` or `"BLOB"`), without retrying.
62    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        // Read the generation count one time
72        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        // Read the buffer
95        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        // Read the generation count one more time to ensure the previous buffer read is
110        // consistent. It's safe to unwrap this time, we already checked we can read 32 bytes from
111        // the slice.
112        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    /// Tries to take a consistent snapshot of `source` with the given `expected_magic`
123    /// header word, retrying up to `SNAPSHOT_TRIES` times if concurrent writes are detected.
124    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    // =========================================================================
153    // Regular Inspect VMO ("INSP" header) snapshot methods
154    //
155    // Regular Inspect VMOs begin with an "INSP" (`HEADER_MAGIC_NUMBER`) header
156    // block followed by a buddy-allocated hierarchy of 16-byte blocks. Because
157    // block indices are 24-bit values, regular Inspect VMOs are capped at
158    // `constants::MAX_VMO_SIZE`. See also the `TryFrom` implementations below.
159    // =========================================================================
160
161    /// Try to take a consistent snapshot of the given regular Inspect VMO once,
162    /// without retrying if a concurrent write is in progress.
163    ///
164    /// Returns a Snapshot on success or an Error if a consistent snapshot could not be taken.
165    pub fn try_once_from_vmo(source: &SnapshotSource) -> Result<Snapshot, ReaderError> {
166        Snapshot::try_once_with_callback(source, &mut || {})
167    }
168
169    /// Single-attempt helper for regular Inspect VMOs ("INSP" header), used by tests
170    /// to inject a callback between reading the buffer and re-verifying the header.
171    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    /// Multi-attempt helper for regular Inspect VMOs ("INSP" header) that retries
187    /// up to `SNAPSHOT_TRIES` times; backs `Snapshot::try_from`.
188    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    // =========================================================================
204    // Blob VMO ("BLOB" header) snapshot methods
205    //
206    // Blob VMOs (`BlobProperty`) share the same 32-byte header block format and
207    // seqlock/CoW synchronization protocol as regular Inspect VMOs, but use the
208    // "BLOB" (`BLOB_HEADER_MAGIC_NUMBER`) magic word and store raw, contiguous
209    // payload bytes immediately after the 32-byte header rather than 16-byte
210    // Inspect blocks. Because they do not use 24-bit block indices, their size
211    // is not capped at `constants::MAX_VMO_SIZE` (`max_vmo_size = None`).
212    // =========================================================================
213
214    /// Try to take a consistent snapshot of the given Blob VMO once, without
215    /// retrying if a concurrent write is in progress.
216    ///
217    /// Returns a Snapshot on success or an Error if a consistent snapshot could not be taken.
218    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    /// Try to take a consistent snapshot of the given Blob VMO, retrying up to
228    /// `SNAPSHOT_TRIES` times if concurrent writes are detected.
229    ///
230    /// Returns a Snapshot on success or an Error if a consistent snapshot could not be taken.
231    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        // Safety: So long as the array is valid, array_capacity will return a valid value.
293        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                    // Safety: if the block is an array, it must have an array format.
308                    // We have already verified it is an array.
309                    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                    // Safety: if the block is an array, it must have an array format.
320                    // We have already verified it is an array.
321                    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                    // Safety: if the block is an array, it must have an array format.
332                    // We have already verified it is an array.
333                    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                        // default initialize unset values -- 0 index is never a string, it is always
344                        // the header block
345                        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    // Incrementally add the contents of each extent in the extent linked list
405    // until we reach the last extent or the maximum expected length.
406    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    // Used for snapshot tests.
435    #[cfg(test)]
436    pub fn build(bytes: &[u8]) -> Self {
437        Snapshot { buffer: BackingBuffer::from(bytes.to_vec()) }
438    }
439}
440
441/// Parses the HEADER block with `expected_magic` and returns its generation count
442/// if the header is valid and unlocked (even generation count).
443fn 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
447/// Reads the given bytes as a regular Inspect VMO ("INSP") HEADER block and returns the
448/// generation count if the header is valid and nobody is writing to it.
449fn header_generation_count<T: ReadBytes>(bytes: &T) -> Option<u64> {
450    header_generation_count_with_magic(bytes, constants::HEADER_MAGIC_NUMBER)
451}
452
453// =============================================================================
454// `TryFrom` implementations for regular Inspect VMOs ("INSP" header)
455// =============================================================================
456
457/// Construct a snapshot of a regular Inspect VMO from a byte vector.
458impl 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
498/// Iterates over a byte array containing Inspect API blocks and returns the
499/// blocks in order.
500pub struct BlockIterator<'a> {
501    /// Current offset at which the iterator is reading.
502    offset: usize,
503
504    /// The bytes being read.
505    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        // Scan blocks
696        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        // Verify get_block
722        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        // create a header block with an invalid version number
740        container.copy_from_slice(&[
741            0x00, /* order/reserved */
742            0x02, /* type */
743            0xff, /* invalid version number */
744            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        // When in the host, we don't have underlying shared memory, so this can't fail as we
827        // had already cloned the underlying vector.
828        #[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            // Silence unused variable warning, which happens to be set to a unit value.
869            #[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    // Check that snapshot fails if the VMO is frozen but not immutable.
892    // This test is only valid on Fuchsia, where VMOs can be used.
893    #[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}