| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use { |
| super::{Data, LazyNode, Metrics, Node, Payload, Property, ROOT_NAME}, |
| crate::metrics::{BlockMetrics, BlockStatus}, |
| anyhow::{bail, format_err, Error}, |
| fuchsia_inspect::{reader as ireader, reader::snapshot::ScannedBlock}, |
| fuchsia_zircon::Vmo, |
| inspect_format::{ |
| constants::MIN_ORDER_SIZE, ArrayFormat, BlockIndex, BlockType, LinkNodeDisposition, |
| PropertyFormat, |
| }, |
| std::{ |
| self, |
| cmp::min, |
| collections::{HashMap, HashSet}, |
| convert::TryFrom, |
| }, |
| }; |
| |
| // When reading from a VMO, the keys of the HashMaps are the indexes of the relevant |
| // blocks. Thus, they will never collide. |
| // |
| // Reading from a VMO is a complicated process. |
| // 1) Try to take a fuchsia_inspect::reader::snapshot::Snapshot of the VMO. |
| // 2) Iterate through it, pedantically examining all its blocks and loading |
| // the relevant blocks into a ScannedObjects structure (which contains |
| // ScannedNode, ScannedName, ScannedProperty, and ScannedExtent). |
| // 2.5) ScannedNodes may be added before they're scanned, since they need to |
| // track their child nodes and properties. In this case, their "validated" |
| // field will be false until they're actually scanned. |
| // 3) Starting from the "0" node, create Node and Property objects for all the |
| // dependent children and properties (verifying that all dependent objects |
| // exist (and are valid in the case of Nodes)). This is also when Extents are |
| // combined into byte vectors, and in the case of String, verified to be valid UTF-8. |
| // 4) Add the Node and Property objects (each with its ID) into the "nodes" and |
| // "properties" HashMaps of a new Data object. Note that these HashMaps do not |
| // hold the hierarchical information; instead, each Node contains a HashSet of |
| // the keys of its children and properties. |
| |
| #[derive(Debug)] |
| pub struct Scanner { |
| nodes: HashMap<BlockIndex, ScannedNode>, |
| names: HashMap<BlockIndex, ScannedName>, |
| properties: HashMap<BlockIndex, ScannedProperty>, |
| extents: HashMap<BlockIndex, ScannedExtent>, |
| final_dereferenced_strings: HashMap<BlockIndex, String>, |
| final_nodes: HashMap<BlockIndex, Node>, |
| final_properties: HashMap<BlockIndex, Property>, |
| metrics: Metrics, |
| child_trees: Option<HashMap<String, LazyNode>>, |
| } |
| |
| impl TryFrom<&[u8]> for Scanner { |
| type Error = Error; |
| |
| fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> { |
| let scanner = Scanner::new(None); |
| scanner.scan(ireader::snapshot::Snapshot::try_from(bytes.to_vec())?, bytes) |
| } |
| } |
| |
| impl TryFrom<&Vmo> for Scanner { |
| type Error = Error; |
| fn try_from(vmo: &Vmo) -> Result<Self, Self::Error> { |
| let scanner = Scanner::new(None); |
| scanner.scan(ireader::snapshot::Snapshot::try_from(vmo)?, &vmo_as_buffer(vmo)?) |
| } |
| } |
| |
| impl TryFrom<LazyNode> for Scanner { |
| type Error = Error; |
| |
| fn try_from(mut vmo_tree: LazyNode) -> Result<Self, Self::Error> { |
| let snapshot = ireader::snapshot::Snapshot::try_from(vmo_tree.vmo())?; |
| let buffer = vmo_as_buffer(vmo_tree.vmo())?; |
| let scanner = Scanner::new(vmo_tree.take_children()); |
| scanner.scan(snapshot, &buffer) |
| } |
| } |
| |
| fn vmo_as_buffer(vmo: &Vmo) -> Result<Vec<u8>, Error> { |
| // NOTE: In any context except a controlled test, it's not safe to read the VMO manually - |
| // the contents may differ or even be invalid (mid-update). |
| let size = vmo.get_size()?; |
| let mut buffer = vec![0u8; size as usize]; |
| vmo.read(&mut buffer[..], 0)?; |
| Ok(buffer) |
| } |
| |
| fn low_bits(number: u8, n_bits: usize) -> u8 { |
| let n_bits = min(n_bits, 8); |
| let mask = !(0xff_u16 << n_bits) as u8; |
| number & mask |
| } |
| |
| fn high_bits(number: u8, n_bits: usize) -> u8 { |
| let n_bits = min(n_bits, 8); |
| let mask = !(0xff_u16 >> n_bits) as u8; |
| number & mask |
| } |
| |
| const BITS_PER_BYTE: usize = 8; |
| |
| /// Get size in bytes of a given |order|. Copied from private mod fuchsia-inspect/src/utils.rs |
| fn order_to_size(order: u8) -> usize { |
| MIN_ORDER_SIZE << (order as usize) |
| } |
| |
| // Checks if these bits (start...end) are 0. Restricts the range checked to the given block. |
| fn check_zero_bits( |
| buffer: &[u8], |
| block: &ScannedBlock<'_>, |
| start: usize, |
| end: usize, |
| ) -> Result<(), Error> { |
| if end < start { |
| return Err(format_err!("End must be >= start")); |
| } |
| let bits_in_block = order_to_size(block.order()) * BITS_PER_BYTE; |
| if start > bits_in_block - 1 { |
| return Ok(()); |
| } |
| let end = min(end, bits_in_block - 1); |
| let block_offset = usize::from(block.index()) * MIN_ORDER_SIZE; |
| let low_byte = start / BITS_PER_BYTE; |
| let high_byte = end / BITS_PER_BYTE; |
| let bottom_bits = high_bits(buffer[low_byte + block_offset], 8 - (start % 8)); |
| let top_bits = low_bits(buffer[high_byte + block_offset], (end % 8) + 1); |
| if low_byte == high_byte { |
| match bottom_bits & top_bits { |
| 0 => return Ok(()), |
| nonzero => bail!( |
| "Bits {}...{} of block type {} at {} have nonzero value {}", |
| start, |
| end, |
| block.block_type_or()?, |
| block.index(), |
| nonzero |
| ), |
| } |
| } |
| if bottom_bits != 0 { |
| bail!( |
| "Non-zero value {} for bits {}.. of block type {} at {}", |
| bottom_bits, |
| start, |
| block.block_type_or()?, |
| block.index() |
| ); |
| } |
| if top_bits != 0 { |
| bail!( |
| "Non-zero value {} for bits ..{} of block type {} at {}", |
| top_bits, |
| end, |
| block.block_type_or()?, |
| block.index() |
| ); |
| } |
| for byte in low_byte + 1..high_byte { |
| if buffer[byte + block_offset] != 0 { |
| bail!( |
| "Non-zero value {} for byte {} of block type {} at {}", |
| buffer[byte], |
| byte, |
| block.block_type_or()?, |
| block.index() |
| ); |
| } |
| } |
| Ok(()) |
| } |
| |
| impl Scanner { |
| fn new(child_trees: Option<HashMap<String, LazyNode>>) -> Scanner { |
| let mut ret = Scanner { |
| nodes: HashMap::new(), |
| names: HashMap::new(), |
| properties: HashMap::new(), |
| extents: HashMap::new(), |
| final_dereferenced_strings: HashMap::new(), |
| metrics: Metrics::new(), |
| final_nodes: HashMap::new(), |
| final_properties: HashMap::new(), |
| child_trees, |
| }; |
| // The ScannedNode at 0 is the "root" node. It exists to receive pointers to objects |
| // whose parent is 0 while scanning the VMO. |
| ret.nodes.insert( |
| BlockIndex::ROOT, |
| ScannedNode { |
| validated: true, |
| parent: BlockIndex::ROOT, |
| name: BlockIndex::ROOT, |
| children: HashSet::new(), |
| properties: HashSet::new(), |
| metrics: None, |
| }, |
| ); |
| ret |
| } |
| |
| fn scan(mut self, snapshot: ireader::snapshot::Snapshot, buffer: &[u8]) -> Result<Self, Error> { |
| let mut link_blocks: Vec<ScannedBlock<'_>> = Vec::new(); |
| let mut string_references: Vec<ScannedStringReference> = Vec::new(); |
| for block in snapshot.scan() { |
| match block.block_type_or() { |
| Ok(BlockType::Free) => self.process_free(block)?, |
| Ok(BlockType::Reserved) => self.process_reserved(block)?, |
| Ok(BlockType::Header) => self.process_header(block)?, |
| Ok(BlockType::NodeValue) => self.process_node(block)?, |
| Ok(BlockType::IntValue) |
| | Ok(BlockType::UintValue) |
| | Ok(BlockType::DoubleValue) |
| | Ok(BlockType::ArrayValue) |
| | Ok(BlockType::BufferValue) |
| | Ok(BlockType::BoolValue) => self.process_property(block, buffer)?, |
| Ok(BlockType::LinkValue) => link_blocks.push(block), |
| Ok(BlockType::Extent) => self.process_extent(block, buffer)?, |
| Ok(BlockType::Name) => self.process_name(block, buffer)?, |
| Ok(BlockType::Tombstone) => self.process_tombstone(block)?, |
| Ok(BlockType::StringReference) => { |
| string_references.push(self.process_string_reference(block)?); |
| } |
| Err(error) => return Err(format_err!("Failed to read block type: {:?}", error)), |
| } |
| } |
| |
| for block in string_references.drain(..) { |
| let index = block.index; |
| let dereferenced = self.expand_string_reference(block)?; |
| self.final_dereferenced_strings.insert(index, dereferenced); |
| } |
| |
| // We defer processing LINK blocks after because the population of the ScannedPayload::Link depends on all NAME blocks having been read. |
| for block in link_blocks.into_iter() { |
| self.process_property(block, buffer)? |
| } |
| |
| let (mut new_nodes, mut new_properties) = self.make_valid_node_tree(BlockIndex::ROOT)?; |
| for (node, id) in new_nodes.drain(..) { |
| self.final_nodes.insert(id, node); |
| } |
| for (property, id) in new_properties.drain(..) { |
| self.final_properties.insert(id, property); |
| } |
| |
| self.record_unused_metrics(); |
| Ok(self) |
| } |
| |
| pub fn data(self) -> Data { |
| Data::build(self.final_nodes, self.final_properties) |
| } |
| |
| pub fn metrics(self) -> Metrics { |
| self.metrics |
| } |
| |
| // ***** Utility functions |
| fn record_unused_metrics(&mut self) { |
| for (_, node) in self.nodes.drain() { |
| if let Some(metrics) = node.metrics { |
| self.metrics.record(&metrics, BlockStatus::NotUsed); |
| } |
| } |
| for (_, name) in self.names.drain() { |
| self.metrics.record(&name.metrics, BlockStatus::NotUsed); |
| } |
| for (_, property) in self.properties.drain() { |
| self.metrics.record(&property.metrics, BlockStatus::NotUsed); |
| } |
| for (_, extent) in self.extents.drain() { |
| self.metrics.record(&extent.metrics, BlockStatus::NotUsed); |
| } |
| } |
| |
| fn use_node(&mut self, node_id: BlockIndex) -> Result<ScannedNode, Error> { |
| let mut node = |
| self.nodes.remove(&node_id).ok_or(format_err!("No node at index {}", node_id))?; |
| match node.metrics { |
| None => { |
| if node_id != BlockIndex::ROOT { |
| return Err(format_err!("Invalid node (no metrics) at index {}", node_id)); |
| } |
| } |
| Some(metrics) => { |
| // I actually want as_deref() but that's nightly-only. |
| self.metrics.record(&metrics, BlockStatus::Used); |
| node.metrics = Some(metrics); // Put it back after I borrow it. |
| } |
| } |
| Ok(node) |
| } |
| |
| fn use_property(&mut self, property_id: BlockIndex) -> Result<ScannedProperty, Error> { |
| let property = self |
| .properties |
| .remove(&property_id) |
| .ok_or(format_err!("No property at index {}", property_id))?; |
| self.metrics.record(&property.metrics, BlockStatus::Used); |
| Ok(property) |
| } |
| |
| // Used to find the value of a name index. This index, `name_id`, may refer to either a |
| // NAME block or a STRING_REFERENCE. If the block is a NAME, it will be removed. |
| fn use_owned_name(&mut self, name_id: BlockIndex) -> Result<String, Error> { |
| match self.names.remove(&name_id) { |
| Some(name) => { |
| self.metrics.record(&name.metrics, BlockStatus::Used); |
| Ok(name.name) |
| } |
| None => match self.final_dereferenced_strings.get(&name_id) { |
| Some(value) => { |
| // Once a string is de-referenced, it isn't part of the hierarchy, |
| // so we use metrics.process(block) when we process a STRING_REFERENCE. |
| Ok(value.clone()) |
| } |
| None => Err(format_err!("No string at index {}", name_id)), |
| }, |
| } |
| } |
| |
| // Used to find the value of an index that points to a NAME or STRING_REFERENCE. In either |
| // case, the value is not consumed. |
| fn lookup_name_or_string_reference(&mut self, name_id: BlockIndex) -> Result<String, Error> { |
| match self.names.get(&name_id) { |
| Some(name) => { |
| self.metrics.record(&name.metrics, BlockStatus::Used); |
| Ok(name.name.clone()) |
| } |
| None => match self.final_dereferenced_strings.get(&name_id) { |
| Some(value) => { |
| // Once a string is de-referenced, it isn't part of the hierarchy, |
| // so we use metrics.process(block) when we process a STRING_REFERENCE. |
| Ok(value.clone()) |
| } |
| None => Err(format_err!("No string at index {}", name_id)), |
| }, |
| } |
| } |
| |
| // ***** Functions which read fuchsia_inspect::format::block::Block (actual |
| // ***** VMO blocks), validate them, turn them into Scanned* objects, and |
| // ***** add the ones we care about to Self. |
| |
| // Some blocks' metrics can only be calculated in the context of a tree. Metrics aren't run |
| // on those in the process_ functions, but rather while the tree is being built. |
| |
| // Note: process_ functions are only called from the scan() iterator on the |
| // VMO's blocks, so indexes of the blocks themselves will never be duplicated; that's one |
| // thing we don't have to verify. |
| fn process_free(&mut self, block: ScannedBlock<'_>) -> Result<(), Error> { |
| // TODO(fxbug.dev/39975): Uncomment or delete this line depending on the resolution of fxbug.dev/40012. |
| // check_zero_bits(buffer, &block, 64, MAX_BLOCK_BITS)?; |
| self.metrics.process(block)?; |
| Ok(()) |
| } |
| |
| fn process_header(&mut self, block: ScannedBlock<'_>) -> Result<(), Error> { |
| self.metrics.process(block)?; |
| Ok(()) |
| } |
| |
| fn process_tombstone(&mut self, block: ScannedBlock<'_>) -> Result<(), Error> { |
| self.metrics.process(block)?; |
| Ok(()) |
| } |
| |
| fn process_reserved(&mut self, block: ScannedBlock<'_>) -> Result<(), Error> { |
| self.metrics.process(block)?; |
| Ok(()) |
| } |
| |
| fn process_extent(&mut self, block: ScannedBlock<'_>, buffer: &[u8]) -> Result<(), Error> { |
| check_zero_bits(buffer, &block, 40, 63)?; |
| self.extents.insert( |
| block.index(), |
| ScannedExtent { |
| next: block.next_extent()?, |
| data: block.extent_contents()?.to_vec(), |
| metrics: Metrics::analyze(block)?, |
| }, |
| ); |
| Ok(()) |
| } |
| |
| fn process_name(&mut self, block: ScannedBlock<'_>, buffer: &[u8]) -> Result<(), Error> { |
| check_zero_bits(buffer, &block, 28, 63)?; |
| self.names.insert( |
| block.index(), |
| ScannedName { |
| name: block.name_contents()?.to_string(), |
| metrics: Metrics::analyze(block)?, |
| }, |
| ); |
| Ok(()) |
| } |
| |
| fn process_node(&mut self, block: ScannedBlock<'_>) -> Result<(), Error> { |
| let parent = block.parent_index()?; |
| let id = block.index(); |
| let name = block.name_index()?; |
| let mut node; |
| let metrics = Some(Metrics::analyze(block)?); |
| if let Some(placeholder) = self.nodes.remove(&id) { |
| // We need to preserve the children and properties. |
| node = placeholder; |
| node.validated = true; |
| node.parent = parent; |
| node.name = name; |
| node.metrics = metrics; |
| } else { |
| node = ScannedNode { |
| validated: true, |
| name, |
| parent, |
| children: HashSet::new(), |
| properties: HashSet::new(), |
| metrics, |
| } |
| } |
| self.nodes.insert(id, node); |
| self.add_to_parent(parent, id, |node| &mut node.children); |
| Ok(()) |
| } |
| |
| fn add_to_parent<F: FnOnce(&mut ScannedNode) -> &mut HashSet<BlockIndex>>( |
| &mut self, |
| parent: BlockIndex, |
| id: BlockIndex, |
| get_the_hashset: F, // Gets children or properties |
| ) { |
| if !self.nodes.contains_key(&parent) { |
| self.nodes.insert( |
| parent, |
| ScannedNode { |
| validated: false, |
| name: BlockIndex::EMPTY, |
| parent: BlockIndex::ROOT, |
| children: HashSet::new(), |
| properties: HashSet::new(), |
| metrics: None, |
| }, |
| ); |
| } |
| if let Some(parent_node) = self.nodes.get_mut(&parent) { |
| get_the_hashset(parent_node).insert(id); |
| } |
| } |
| |
| fn build_scanned_payload( |
| &mut self, |
| block: &ScannedBlock<'_>, |
| block_type: BlockType, |
| ) -> Result<ScannedPayload, Error> { |
| Ok(match block_type { |
| BlockType::IntValue => ScannedPayload::Int(block.int_value()?), |
| BlockType::UintValue => ScannedPayload::Uint(block.uint_value()?), |
| BlockType::DoubleValue => ScannedPayload::Double(block.double_value()?), |
| BlockType::BoolValue => ScannedPayload::Bool(block.bool_value()?), |
| BlockType::BufferValue => { |
| let format = block.property_format()?; |
| let length = block.total_length()?; |
| let link = block.property_extent_index()?; |
| match format { |
| PropertyFormat::String => ScannedPayload::String { length, link }, |
| PropertyFormat::Bytes => ScannedPayload::Bytes { length, link }, |
| } |
| } |
| BlockType::ArrayValue => { |
| let entry_type = block.array_entry_type()?; |
| let array_format = block.array_format()?; |
| let slots = block.array_slots()? as usize; |
| match entry_type { |
| BlockType::IntValue => { |
| let numbers: Result<Vec<i64>, _> = |
| (0..slots).map(|i| block.array_get_int_slot(i)).collect(); |
| ScannedPayload::IntArray(numbers?, array_format) |
| } |
| BlockType::UintValue => { |
| let numbers: Result<Vec<u64>, _> = |
| (0..slots).map(|i| block.array_get_uint_slot(i)).collect(); |
| ScannedPayload::UintArray(numbers?, array_format) |
| } |
| BlockType::DoubleValue => { |
| let numbers: Result<Vec<f64>, _> = |
| (0..slots).map(|i| block.array_get_double_slot(i)).collect(); |
| ScannedPayload::DoubleArray(numbers?, array_format) |
| } |
| BlockType::StringReference => { |
| let indexes: Result<Vec<BlockIndex>, _> = |
| (0..slots).map(|i| block.array_get_string_index_slot(i)).collect(); |
| ScannedPayload::StringArray(indexes?, array_format) |
| } |
| illegal_type => { |
| return Err(format_err!( |
| "No way I should see {:?} for ArrayEntryType", |
| illegal_type |
| )) |
| } |
| } |
| } |
| BlockType::LinkValue => { |
| let child_name = |
| self.lookup_name_or_string_reference(block.link_content_index()?).ok().ok_or( |
| format_err!("Child name not found for LinkValue block {}.", block.index()), |
| )?; |
| let child_trees = self |
| .child_trees |
| .as_mut() |
| .ok_or(format_err!("LinkValue encountered without child tree."))?; |
| let child_tree = child_trees.remove(&child_name).ok_or(format_err!( |
| "Lazy node not found for LinkValue block {} with name {}.", |
| block.index(), |
| child_name |
| ))?; |
| ScannedPayload::Link { |
| disposition: block.link_node_disposition()?, |
| scanned_tree: Scanner::try_from(child_tree)?, |
| } |
| } |
| illegal_type => { |
| return Err(format_err!("No way I should see {:?} for BlockType", illegal_type)) |
| } |
| }) |
| } |
| |
| fn process_string_reference( |
| &mut self, |
| block: ScannedBlock<'_>, |
| ) -> Result<ScannedStringReference, Error> { |
| let scanned = ScannedStringReference { |
| index: block.index(), |
| value: block.inline_string_reference()?.to_vec(), |
| length: block.total_length()?, |
| next_extent: block.next_extent()?, |
| }; |
| |
| self.metrics.process(block)?; |
| Ok(scanned) |
| } |
| |
| fn process_property(&mut self, block: ScannedBlock<'_>, buffer: &[u8]) -> Result<(), Error> { |
| if block.block_type_or()? == BlockType::ArrayValue { |
| check_zero_bits(buffer, &block, 80, 127)?; |
| } |
| let id = block.index(); |
| let parent = block.parent_index()?; |
| let block_type = block.block_type_or()?; |
| let payload = self.build_scanned_payload(&block, block_type)?; |
| let property = ScannedProperty { |
| name: block.name_index()?, |
| parent, |
| payload, |
| metrics: Metrics::analyze(block)?, |
| }; |
| self.properties.insert(id, property); |
| self.add_to_parent(parent, id, |node| &mut node.properties); |
| Ok(()) |
| } |
| |
| // ***** Functions which convert Scanned* objects into Node and Property objects. |
| |
| fn make_valid_node_tree( |
| &mut self, |
| id: BlockIndex, |
| ) -> Result<(Vec<(Node, BlockIndex)>, Vec<(Property, BlockIndex)>), Error> { |
| let scanned_node = self.use_node(id)?; |
| if !scanned_node.validated { |
| return Err(format_err!("No node at {}", id)); |
| } |
| let mut nodes_in_tree = vec![]; |
| let mut properties_under = vec![]; |
| for node_id in scanned_node.children.iter() { |
| let (mut nodes_of, mut properties_of) = self.make_valid_node_tree(*node_id)?; |
| nodes_in_tree.append(&mut nodes_of); |
| properties_under.append(&mut properties_of); |
| } |
| for property_id in scanned_node.properties.iter() { |
| properties_under.push((self.make_valid_property(*property_id)?, *property_id)); |
| } |
| let name = if id == BlockIndex::ROOT { |
| ROOT_NAME.to_owned() |
| } else { |
| self.use_owned_name(scanned_node.name)? |
| }; |
| let this_node = Node { |
| name, |
| parent: scanned_node.parent, |
| children: scanned_node.children.clone(), |
| properties: scanned_node.properties.clone(), |
| }; |
| nodes_in_tree.push((this_node, id)); |
| Ok((nodes_in_tree, properties_under)) |
| } |
| |
| fn make_valid_property(&mut self, id: BlockIndex) -> Result<Property, Error> { |
| let scanned_property = self.use_property(id)?; |
| let name = self.use_owned_name(scanned_property.name)?; |
| let payload = self.make_valid_payload(scanned_property.payload)?; |
| Ok(Property { id, name, parent: scanned_property.parent, payload }) |
| } |
| |
| fn make_valid_payload(&mut self, payload: ScannedPayload) -> Result<Payload, Error> { |
| Ok(match payload { |
| ScannedPayload::Int(data) => Payload::Int(data), |
| ScannedPayload::Uint(data) => Payload::Uint(data), |
| ScannedPayload::Double(data) => Payload::Double(data), |
| ScannedPayload::Bool(data) => Payload::Bool(data), |
| ScannedPayload::IntArray(data, format) => Payload::IntArray(data, format), |
| ScannedPayload::UintArray(data, format) => Payload::UintArray(data, format), |
| ScannedPayload::DoubleArray(data, format) => Payload::DoubleArray(data, format), |
| ScannedPayload::StringArray(indexes, _) => Payload::StringArray( |
| indexes |
| .iter() |
| .map(|i| { |
| if *i == BlockIndex::EMPTY { |
| return "".into(); |
| } |
| self.final_dereferenced_strings.get(i).unwrap().clone() |
| }) |
| .collect(), |
| ), |
| ScannedPayload::Bytes { length, link } => { |
| Payload::Bytes(self.make_valid_vector(length, link)?) |
| } |
| ScannedPayload::String { length, link } => Payload::String( |
| std::str::from_utf8(&self.make_valid_vector(length, link)?)?.to_owned(), |
| ), |
| ScannedPayload::Link { disposition, scanned_tree } => { |
| Payload::Link { disposition, parsed_data: scanned_tree.data() } |
| } |
| }) |
| } |
| |
| fn expand_string_reference( |
| &mut self, |
| mut block: ScannedStringReference, |
| ) -> Result<String, Error> { |
| let length_of_inlined = block.value.len(); |
| if block.next_extent != BlockIndex::EMPTY { |
| block.value.append( |
| &mut self.make_valid_vector(block.length - length_of_inlined, block.next_extent)?, |
| ); |
| } |
| |
| Ok(String::from_utf8(block.value)?) |
| } |
| |
| fn make_valid_vector(&mut self, length: usize, link: BlockIndex) -> Result<Vec<u8>, Error> { |
| let mut dest = vec![]; |
| let mut length_remaining = length; |
| let mut next_link = link; |
| while length_remaining > 0 { |
| // This is effectively use_extent() |
| let mut extent = |
| self.extents.remove(&next_link).ok_or(format_err!("No extent at {}", next_link))?; |
| let copy_len = min(extent.data.len(), length_remaining); |
| extent.metrics.set_data_bytes(copy_len); |
| self.metrics.record(&extent.metrics, BlockStatus::Used); |
| dest.extend_from_slice(&extent.data[..copy_len]); |
| length_remaining -= copy_len; |
| next_link = extent.next; |
| } |
| Ok(dest) |
| } |
| } |
| |
| #[derive(Debug)] |
| struct ScannedNode { |
| // These may be created two ways: Either from being named as a parent, or |
| // from being processed in the VMO. Those named but not yet processed will |
| // have validated = false. Of course after a complete VMO scan, |
| // everything descended from a root node must be validated. |
| // validated refers to the binary contents of this block; it doesn't |
| // guarantee that properties, descendents, name, etc. are valid. |
| validated: bool, |
| name: BlockIndex, |
| parent: BlockIndex, |
| children: HashSet<BlockIndex>, |
| properties: HashSet<BlockIndex>, |
| metrics: Option<BlockMetrics>, |
| } |
| |
| #[derive(Debug)] |
| struct ScannedProperty { |
| name: BlockIndex, |
| parent: BlockIndex, |
| payload: ScannedPayload, |
| metrics: BlockMetrics, |
| } |
| |
| #[derive(Debug)] |
| struct ScannedStringReference { |
| index: BlockIndex, |
| value: Vec<u8>, |
| length: usize, |
| next_extent: BlockIndex, |
| } |
| |
| #[derive(Debug)] |
| struct ScannedName { |
| name: String, |
| metrics: BlockMetrics, |
| } |
| |
| #[derive(Debug)] |
| struct ScannedExtent { |
| next: BlockIndex, |
| data: Vec<u8>, |
| metrics: BlockMetrics, |
| } |
| |
| #[derive(Debug)] |
| enum ScannedPayload { |
| String { length: usize, link: BlockIndex }, |
| Bytes { length: usize, link: BlockIndex }, |
| Int(i64), |
| Uint(u64), |
| Double(f64), |
| Bool(bool), |
| IntArray(Vec<i64>, ArrayFormat), |
| UintArray(Vec<u64>, ArrayFormat), |
| DoubleArray(Vec<f64>, ArrayFormat), |
| StringArray(Vec<BlockIndex>, ArrayFormat), |
| Link { disposition: LinkNodeDisposition, scanned_tree: Scanner }, |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use num_traits::ToPrimitive; |
| use { |
| super::*, |
| crate::*, |
| fidl_diagnostics_validate::*, |
| fuchsia_inspect::reader::snapshot::BackingBuffer, |
| inspect_format::{ |
| constants, Block, BlockIndex, BlockType, HeaderFields, PayloadFields, ReadBytes, |
| }, |
| }; |
| |
| // TODO(fxbug.dev/39975): Depending on the resolution of fxbug.dev/40012, move this const out of mod test. |
| const MAX_BLOCK_BITS: usize = constants::MAX_ORDER_SIZE * BITS_PER_BYTE; |
| |
| fn copy_into(source: &[u8], dest: &mut [u8], offset: usize) { |
| dest[offset..offset + source.len()].copy_from_slice(source); |
| } |
| |
| // Run "fx test inspect-validator-test -- --nocapture" to see all the output |
| // and verify you're getting appropriate error messages for each tweaked byte. |
| // (The alternative is hard-coding expected error strings, which is possible but ugh.) |
| fn try_byte( |
| buffer: &mut [u8], |
| (index, offset): (usize, usize), |
| value: u8, |
| predicted: Option<&str>, |
| ) { |
| let location = index * 16 + offset; |
| let previous = buffer[location]; |
| buffer[location] = value; |
| let actual = data::Scanner::try_from(buffer as &[u8]).map(|d| d.data().to_string()); |
| if predicted.is_none() { |
| if actual.is_err() { |
| println!( |
| "With ({},{}) -> {}, got expected error {:?}", |
| index, offset, value, actual |
| ); |
| } else { |
| println!( |
| "BAD: With ({},{}) -> {}, expected error but got string {:?}", |
| index, |
| offset, |
| value, |
| actual.as_ref().unwrap() |
| ); |
| } |
| } else { |
| if actual.is_err() { |
| println!( |
| "BAD: With ({},{}) -> {}, got unexpected error {:?}", |
| index, offset, value, actual |
| ); |
| } else if actual.as_ref().ok().map(|s| &s[..]) == predicted { |
| println!( |
| "With ({},{}) -> {}, got expected string {:?}", |
| index, |
| offset, |
| value, |
| predicted.unwrap() |
| ); |
| } else { |
| println!( |
| "BAD: With ({},{}) -> {}, expected string {:?} but got {:?}", |
| index, |
| offset, |
| value, |
| predicted.unwrap(), |
| actual.as_ref().unwrap() |
| ); |
| println!("Raw data: {:?}", data::Scanner::try_from(buffer as &[u8])) |
| } |
| } |
| assert_eq!(predicted, actual.as_ref().ok().map(|s| &s[..])); |
| buffer[location] = previous; |
| } |
| |
| fn put_header<T: ReadBytes>(header: &Block<&mut T>, buffer: &mut [u8], index: usize) { |
| copy_into(&HeaderFields::value(&header).to_le_bytes(), buffer, index * 16); |
| } |
| |
| fn put_payload<T: ReadBytes>(payload: &Block<&mut T>, buffer: &mut [u8], index: usize) { |
| copy_into(&PayloadFields::value(&payload).to_le_bytes(), buffer, index * 16 + 8); |
| } |
| |
| #[fuchsia::test] |
| fn test_scanning_string_reference() { |
| let mut buffer = [0u8; 4096]; |
| const NODE: BlockIndex = BlockIndex::new(3); |
| const NUMBER_NAME: BlockIndex = BlockIndex::new(4); |
| const NUMBER_EXTENT: BlockIndex = BlockIndex::new(5); |
| |
| // VMO Header block (index 0) |
| let mut container = [0u8; 16]; |
| let mut header = Block::new(&mut container, BlockIndex::EMPTY); |
| HeaderFields::set_order(&mut header, 0); |
| HeaderFields::set_block_type(&mut header, BlockType::Header.to_u8().unwrap()); |
| HeaderFields::set_header_magic(&mut header, constants::HEADER_MAGIC_NUMBER); |
| HeaderFields::set_header_version(&mut header, constants::HEADER_VERSION_NUMBER); |
| put_header(&header, &mut buffer, (*BlockIndex::HEADER).try_into().unwrap()); |
| |
| // create a Node named number |
| HeaderFields::set_order(&mut header, 0); |
| HeaderFields::set_block_type(&mut header, BlockType::NodeValue.to_u8().unwrap()); |
| HeaderFields::set_value_name_index(&mut header, *NUMBER_NAME); |
| HeaderFields::set_value_parent_index(&mut header, *BlockIndex::HEADER); |
| put_header(&header, &mut buffer, (*NODE).try_into().unwrap()); |
| |
| // create a STRING_REFERENCE with value "number" that is the above Node's name. |
| HeaderFields::set_order(&mut header, 0); |
| HeaderFields::set_block_type(&mut header, BlockType::StringReference.to_u8().unwrap()); |
| HeaderFields::set_extent_next_index(&mut header, *NUMBER_EXTENT); |
| put_header(&header, &mut buffer, (*NUMBER_NAME).try_into().unwrap()); |
| copy_into(&[6, 0, 0, 0], &mut buffer, (*NUMBER_NAME * 16 + 8).try_into().unwrap()); |
| copy_into(b"numb", &mut buffer, (*NUMBER_NAME * 16 + 12).try_into().unwrap()); |
| let mut container = [0u8; 16]; |
| let mut number_extent = Block::new(&mut container, BlockIndex::EMPTY); |
| HeaderFields::set_order(&mut number_extent, 0); |
| HeaderFields::set_block_type(&mut number_extent, BlockType::Extent.to_u8().unwrap()); |
| HeaderFields::set_extent_next_index(&mut number_extent, 0); |
| put_header(&number_extent, &mut buffer, (*NUMBER_EXTENT).try_into().unwrap()); |
| copy_into(b"er", &mut buffer, (*NUMBER_EXTENT * 16 + 8).try_into().unwrap()); |
| |
| try_byte(&mut buffer, (16, 0), 0, Some("root ->\n> number ->")); |
| } |
| |
| #[fuchsia::test] |
| fn test_scanning_logic() { |
| let mut buffer = [0u8; 4096]; |
| // VMO Header block (index 0) |
| const HEADER: usize = 0; |
| { |
| let mut container = [0u8; 16]; |
| let mut header = Block::new(&mut container, BlockIndex::EMPTY); |
| HeaderFields::set_order(&mut header, 0); |
| HeaderFields::set_block_type(&mut header, BlockType::Header.to_u8().unwrap()); |
| HeaderFields::set_header_magic(&mut header, constants::HEADER_MAGIC_NUMBER); |
| HeaderFields::set_header_version(&mut header, constants::HEADER_VERSION_NUMBER); |
| put_header(&header, &mut buffer, HEADER); |
| } |
| const ROOT: usize = 1; |
| { |
| let mut container = [0u8; 16]; |
| let mut header = Block::new(&mut container, BlockIndex::EMPTY); |
| HeaderFields::set_order(&mut header, 0); |
| HeaderFields::set_block_type(&mut header, BlockType::NodeValue.to_u8().unwrap()); |
| HeaderFields::set_value_name_index(&mut header, 2); |
| HeaderFields::set_value_parent_index(&mut header, 0); |
| put_header(&header, &mut buffer, ROOT); |
| } |
| |
| // Root's Name block |
| const ROOT_NAME: usize = 2; |
| { |
| let mut container = [0u8; 16]; |
| let mut header = Block::new(&mut container, BlockIndex::EMPTY); |
| HeaderFields::set_order(&mut header, 0); |
| HeaderFields::set_block_type(&mut header, BlockType::Name.to_u8().unwrap()); |
| HeaderFields::set_name_length(&mut header, 4); |
| put_header(&header, &mut buffer, ROOT_NAME); |
| } |
| copy_into(b"node", &mut buffer, ROOT_NAME * 16 + 8); |
| try_byte(&mut buffer, (16, 0), 0, Some("root ->\n> node ->")); |
| // Mess up HEADER_MAGIC_NUMBER - it should fail to load. |
| try_byte(&mut buffer, (HEADER, 7), 0, None); |
| // Mess up node's parent; should disappear. |
| try_byte(&mut buffer, (ROOT, 1), 1, Some("root ->")); |
| // Mess up root's name; should fail. |
| try_byte(&mut buffer, (ROOT, 5), 1, None); |
| // Mess up generation count; should fail (and not hang). |
| try_byte(&mut buffer, (HEADER, 8), 1, None); |
| // But an even generation count should work. |
| try_byte(&mut buffer, (HEADER, 8), 2, Some("root ->\n> node ->")); |
| |
| // Let's give it a property. |
| const NUMBER: usize = 4; |
| let mut container = [0u8; 16]; |
| let mut number_header = Block::new(&mut container, BlockIndex::EMPTY); |
| HeaderFields::set_order(&mut number_header, 0); |
| HeaderFields::set_block_type(&mut number_header, BlockType::IntValue.to_u8().unwrap()); |
| HeaderFields::set_value_name_index(&mut number_header, 3); |
| HeaderFields::set_value_parent_index(&mut number_header, 1); |
| put_header(&number_header, &mut buffer, NUMBER); |
| const NUMBER_NAME: usize = 3; |
| { |
| let mut container = [0u8; 16]; |
| let mut header = Block::new(&mut container, BlockIndex::EMPTY); |
| HeaderFields::set_order(&mut header, 0); |
| HeaderFields::set_block_type(&mut header, BlockType::Name.to_u8().unwrap()); |
| HeaderFields::set_name_length(&mut header, 6); |
| put_header(&header, &mut buffer, NUMBER_NAME); |
| copy_into(b"number", &mut buffer, NUMBER_NAME * 16 + 8); |
| } |
| |
| try_byte(&mut buffer, (HEADER, 8), 2, Some("root ->\n> node ->\n> > number: Int(0)")); |
| try_byte(&mut buffer, (NUMBER, 1), 5, Some("root ->\n> node ->\n> > number: Uint(0)")); |
| try_byte(&mut buffer, (NUMBER, 1), 6, Some("root ->\n> node ->\n> > number: Double(0.0)")); |
| try_byte(&mut buffer, (NUMBER, 1), 7, Some("root ->\n> node ->\n> > number: String(\"\")")); |
| // Array block will have illegal Array Entry Type of 0. |
| try_byte(&mut buffer, (NUMBER, 1), 0xb0, None); |
| // 15 is an illegal block type. |
| try_byte(&mut buffer, (NUMBER, 1), 0xf, None); |
| HeaderFields::set_order(&mut number_header, 2); |
| HeaderFields::set_block_type(&mut number_header, BlockType::ArrayValue.to_u8().unwrap()); |
| put_header(&number_header, &mut buffer, NUMBER); |
| // Array block again has illegal Array Entry Type of 0. |
| try_byte(&mut buffer, (128, 0), 0, None); |
| // 4, 5, and 6 are legal array types. |
| try_byte( |
| &mut buffer, |
| (NUMBER, 8), |
| 0x04, |
| Some("root ->\n> node ->\n> > number: IntArray([], Default)"), |
| ); |
| try_byte( |
| &mut buffer, |
| (NUMBER, 8), |
| 0x05, |
| Some("root ->\n> node ->\n> > number: UintArray([], Default)"), |
| ); |
| try_byte( |
| &mut buffer, |
| (NUMBER, 8), |
| 0x06, |
| Some("root ->\n> node ->\n> > number: DoubleArray([], Default)"), |
| ); |
| // 0, 1, and 2 are legal formats. |
| try_byte( |
| &mut buffer, |
| (NUMBER, 8), |
| 0x14, |
| Some("root ->\n> node ->\n> > number: IntArray([], LinearHistogram)"), |
| ); |
| try_byte( |
| &mut buffer, |
| (NUMBER, 8), |
| 0x24, |
| Some("root ->\n> node ->\n> > number: IntArray([], ExponentialHistogram)"), |
| ); |
| try_byte(&mut buffer, (NUMBER, 8), 0x34, None); |
| // Let's make sure other Value block-type numbers are rejected. |
| try_byte(&mut buffer, (NUMBER, 8), BlockType::ArrayValue.to_u8().unwrap(), None); |
| buffer[NUMBER * 16 + 8] = 4; // Int, Default |
| buffer[NUMBER * 16 + 9] = 2; // 2 entries |
| try_byte( |
| &mut buffer, |
| (NUMBER, 16), |
| 42, |
| Some("root ->\n> node ->\n> > number: IntArray([42, 0], Default)"), |
| ); |
| try_byte( |
| &mut buffer, |
| (NUMBER, 24), |
| 42, |
| Some("root ->\n> node ->\n> > number: IntArray([0, 42], Default)"), |
| ); |
| } |
| |
| #[fuchsia::test] |
| async fn test_to_string_order() -> Result<(), Error> { |
| // Make sure property payloads are distinguished by name, value, and type |
| // but ignore id and parent, and that prefix is used. |
| let int0 = Property { |
| name: "int0".into(), |
| id: 2.into(), |
| parent: 1.into(), |
| payload: Payload::Int(0), |
| } |
| .to_string(""); |
| let int1_struct = Property { |
| name: "int1".into(), |
| id: 2.into(), |
| parent: 1.into(), |
| payload: Payload::Int(1), |
| }; |
| let int1 = int1_struct.to_string(""); |
| assert_ne!(int0, int1); |
| let uint0 = Property { |
| name: "uint0".into(), |
| id: 2.into(), |
| parent: 1.into(), |
| payload: Payload::Uint(0), |
| } |
| .to_string(""); |
| assert_ne!(int0, uint0); |
| let int0_different_name = Property { |
| name: "int0_different_name".into(), |
| id: 2.into(), |
| parent: 1.into(), |
| payload: Payload::Int(0), |
| } |
| .to_string(""); |
| assert_ne!(int0, int0_different_name); |
| let uint0_different_ids = Property { |
| name: "uint0".into(), |
| id: 3.into(), |
| parent: 4.into(), |
| payload: Payload::Uint(0), |
| } |
| .to_string(""); |
| assert_eq!(uint0, uint0_different_ids); |
| let int1_different_prefix = int1_struct.to_string("foo"); |
| assert_ne!(int1, int1_different_prefix); |
| // Test that order doesn't matter. Use a real VMO rather than Data's |
| // HashMaps which may not reflect order of addition. |
| let mut puppet1 = puppet::tests::local_incomplete_puppet().await?; |
| let mut child1_action = create_node!(parent:0, id:1, name:"child1"); |
| let mut child2_action = create_node!(parent:0, id:2, name:"child2"); |
| let mut property1_action = |
| create_numeric_property!(parent:0, id:1, name:"prop1", value: Value::IntT(1)); |
| let mut property2_action = |
| create_numeric_property!(parent:0, id:2, name:"prop2", value: Value::IntT(2)); |
| puppet1.apply(&mut child1_action).await?; |
| puppet1.apply(&mut child2_action).await?; |
| let mut puppet2 = puppet::tests::local_incomplete_puppet().await?; |
| puppet2.apply(&mut child2_action).await?; |
| puppet2.apply(&mut child1_action).await?; |
| assert_eq!(puppet1.read_data().await?.to_string(), puppet2.read_data().await?.to_string()); |
| puppet1.apply(&mut property1_action).await?; |
| puppet1.apply(&mut property2_action).await?; |
| puppet2.apply(&mut property2_action).await?; |
| puppet2.apply(&mut property1_action).await?; |
| assert_eq!(puppet1.read_data().await?.to_string(), puppet2.read_data().await?.to_string()); |
| // Make sure the tree distinguishes based on node position |
| puppet1 = puppet::tests::local_incomplete_puppet().await?; |
| puppet2 = puppet::tests::local_incomplete_puppet().await?; |
| let mut subchild2_action = create_node!(parent:1, id:2, name:"child2"); |
| puppet1.apply(&mut child1_action).await?; |
| puppet2.apply(&mut child1_action).await?; |
| puppet1.apply(&mut child2_action).await?; |
| puppet2.apply(&mut subchild2_action).await?; |
| assert_ne!(puppet1.read_data().await?.to_string(), puppet2.read_data().await?.to_string()); |
| // ... and property position |
| let mut subproperty2_action = |
| create_numeric_property!(parent:1, id:2, name:"prop2", value: Value::IntT(1)); |
| puppet1.apply(&mut child1_action).await?; |
| puppet2.apply(&mut child1_action).await?; |
| puppet1.apply(&mut property2_action).await?; |
| puppet2.apply(&mut subproperty2_action).await?; |
| Ok(()) |
| } |
| |
| #[fuchsia::test] |
| fn test_bit_ops() -> Result<(), Error> { |
| assert_eq!(low_bits(0xff, 3), 7); |
| assert_eq!(low_bits(0x04, 3), 4); |
| assert_eq!(low_bits(0xf8, 3), 0); |
| assert_eq!(low_bits(0xab, 99), 0xab); |
| assert_eq!(low_bits(0xff, 0), 0); |
| assert_eq!(high_bits(0xff, 3), 0xe0); |
| assert_eq!(high_bits(0x20, 3), 0x20); |
| assert_eq!(high_bits(0x1f, 3), 0); |
| assert_eq!(high_bits(0xab, 99), 0xab); |
| assert_eq!(high_bits(0xff, 0), 0); |
| Ok(()) |
| } |
| |
| #[fuchsia::test] |
| fn test_zero_bits() -> Result<(), Error> { |
| let mut buffer = [0u8; 48]; |
| for byte in 0..16 { |
| buffer[byte] = 0xff; |
| } |
| for byte in 32..48 { |
| buffer[byte] = 0xff; |
| } |
| { |
| let backing_buffer = BackingBuffer::from(buffer.to_vec()); |
| let block = Block::new(&backing_buffer, 1.into()); |
| assert!(check_zero_bits(&buffer, &block, 1, 0).is_err()); |
| assert!(check_zero_bits(&buffer, &block, 0, 0).is_ok()); |
| assert!(check_zero_bits(&buffer, &block, 0, MAX_BLOCK_BITS).is_ok()); |
| } |
| // Don't mess with buffer[0]; that defines block size and type. |
| // The block I'm testing (index 1) is in between two all-ones blocks. |
| // Its bytes are thus 16..23 in the buffer. |
| buffer[1 + 16] = 1; |
| // Now bit 8 of the block is 1. Checking any range that includes bit 8 should give an |
| // error (even single-bit 8...8). Other ranges should succeed. |
| { |
| let backing_buffer = BackingBuffer::from(buffer.to_vec()); |
| let block = Block::new(&backing_buffer, 1.into()); |
| assert!(check_zero_bits(&buffer, &block, 8, 8).is_err()); |
| assert!(check_zero_bits(&buffer, &block, 8, MAX_BLOCK_BITS).is_err()); |
| assert!(check_zero_bits(&buffer, &block, 9, MAX_BLOCK_BITS).is_ok()); |
| } |
| buffer[2 + 16] = 0x80; |
| // Now bits 8 and 23 are 1. The range 9...MAX_BLOCK_BITS that succeeded before should fail. |
| // 9...22 and 24...MAX_BLOCK_BITS should succeed. So should 24...63. |
| { |
| let backing_buffer = BackingBuffer::from(buffer.to_vec()); |
| let block = Block::new(&backing_buffer, 1.into()); |
| assert!(check_zero_bits(&buffer, &block, 9, MAX_BLOCK_BITS).is_err()); |
| assert!(check_zero_bits(&buffer, &block, 9, 22).is_ok()); |
| assert!(check_zero_bits(&buffer, &block, 24, MAX_BLOCK_BITS).is_ok()); |
| assert!(check_zero_bits(&buffer, &block, 24, 63).is_ok()); |
| } |
| buffer[2 + 16] = 0x20; |
| // Now bits 8 and 21 are 1. This tests bit-checks in the middle of the byte. |
| { |
| let backing_buffer = BackingBuffer::from(buffer.to_vec()); |
| let block = Block::new(&backing_buffer, 1.into()); |
| assert!(check_zero_bits(&buffer, &block, 16, 20).is_ok()); |
| assert!(check_zero_bits(&buffer, &block, 21, 21).is_err()); |
| assert!(check_zero_bits(&buffer, &block, 22, 63).is_ok()); |
| } |
| buffer[7 + 16] = 0x80; |
| // Now bits 8, 21, and 63 are 1. Checking 22...63 should fail; 22...62 should succeed. |
| { |
| let backing_buffer = BackingBuffer::from(buffer.to_vec()); |
| let block = Block::new(&backing_buffer, 1.into()); |
| assert!(check_zero_bits(&buffer, &block, 22, 63).is_err()); |
| assert!(check_zero_bits(&buffer, &block, 22, 62).is_ok()); |
| } |
| buffer[3 + 16] = 0x10; |
| // Here I'm testing whether 1 bits in the bytes between the ends of the range are also |
| // detected (cause the check to fail) (to make sure my loop doesn't have an off by 1 error). |
| { |
| let backing_buffer = BackingBuffer::from(buffer.to_vec()); |
| let block = Block::new(&backing_buffer, 1.into()); |
| assert!(check_zero_bits(&buffer, &block, 22, 62).is_err()); |
| } |
| buffer[3 + 16] = 0; |
| buffer[4 + 16] = 0x10; |
| { |
| let backing_buffer = BackingBuffer::from(buffer.to_vec()); |
| let block = Block::new(&backing_buffer, 1.into()); |
| assert!(check_zero_bits(&buffer, &block, 22, 62).is_err()); |
| } |
| buffer[4 + 16] = 0; |
| buffer[5 + 16] = 0x10; |
| { |
| let backing_buffer = BackingBuffer::from(buffer.to_vec()); |
| let block = Block::new(&backing_buffer, 1.into()); |
| assert!(check_zero_bits(&buffer, &block, 22, 62).is_err()); |
| } |
| buffer[5 + 16] = 0; |
| buffer[6 + 16] = 0x10; |
| { |
| let backing_buffer = BackingBuffer::from(buffer.to_vec()); |
| let block = Block::new(&backing_buffer, 1.into()); |
| assert!(check_zero_bits(&buffer, &block, 22, 62).is_err()); |
| } |
| buffer[1 + 16] = 0x81; |
| // Testing whether I can correctly ignore 1 bits within a single byte that are outside |
| // the specified range, and detect 1 bits that are inside the range. |
| { |
| let backing_buffer = BackingBuffer::from(buffer.to_vec()); |
| let block = Block::new(&backing_buffer, 1.into()); |
| assert!(check_zero_bits(&buffer, &block, 9, 14).is_ok()); |
| assert!(check_zero_bits(&buffer, &block, 8, 14).is_err()); |
| assert!(check_zero_bits(&buffer, &block, 9, 15).is_err()); |
| } |
| Ok(()) |
| } |
| |
| #[fuchsia::test] |
| fn test_reserved_fields() { |
| let mut buffer = [0u8; 4096]; |
| // VMO Header block (index 0) |
| const HEADER: usize = 0; |
| { |
| let mut container = [0u8; 16]; |
| let mut header = Block::new(&mut container, BlockIndex::EMPTY); |
| HeaderFields::set_order(&mut header, 0); |
| HeaderFields::set_block_type(&mut header, BlockType::Header.to_u8().unwrap()); |
| HeaderFields::set_header_magic(&mut header, constants::HEADER_MAGIC_NUMBER); |
| HeaderFields::set_header_version(&mut header, constants::HEADER_VERSION_NUMBER); |
| put_header(&header, &mut buffer, HEADER); |
| } |
| const VALUE: usize = 1; |
| let mut container = [0u8; 16]; |
| let mut value_header = Block::new(&mut container, BlockIndex::EMPTY); |
| HeaderFields::set_order(&mut value_header, 0); |
| HeaderFields::set_block_type(&mut value_header, BlockType::NodeValue.to_u8().unwrap()); |
| HeaderFields::set_value_name_index(&mut value_header, 2); |
| HeaderFields::set_value_parent_index(&mut value_header, 0); |
| put_header(&value_header, &mut buffer, VALUE); |
| // Root's Name block |
| const VALUE_NAME: usize = 2; |
| { |
| let mut buf = [0; 16]; |
| let mut header = Block::new(&mut buf, 0.into()); |
| HeaderFields::set_order(&mut header, 0); |
| HeaderFields::set_block_type(&mut header, BlockType::Name.to_u8().unwrap()); |
| HeaderFields::set_name_length(&mut header, 5); |
| put_header(&header, &mut buffer, VALUE_NAME); |
| } |
| copy_into(b"value", &mut buffer, VALUE_NAME * 16 + 8); |
| // Extent block (not linked into tree) |
| const EXTENT: usize = 3; |
| { |
| let mut container = [0u8; 16]; |
| let mut header = Block::new(&mut container, BlockIndex::EMPTY); |
| HeaderFields::set_order(&mut header, 0); |
| HeaderFields::set_block_type(&mut header, BlockType::Extent.to_u8().unwrap()); |
| HeaderFields::set_extent_next_index(&mut header, 0); |
| put_header(&header, &mut buffer, EXTENT); |
| } |
| // Let's make sure it scans. |
| try_byte(&mut buffer, (16, 0), 0, Some("root ->\n> value ->")); |
| // Put garbage in a random FREE block body - should fail. |
| // TODO(fxbug.dev/39975): Depending on the resolution of fxbug.dev/40012, uncomment or delete this test. |
| //try_byte(&mut buffer, (6, 9), 42, None); |
| // Put garbage in a random FREE block header - should be fine. |
| try_byte(&mut buffer, (6, 7), 42, Some("root ->\n> value ->")); |
| // Put garbage in NAME header - should fail. |
| try_byte(&mut buffer, (VALUE_NAME, 7), 42, None); |
| // Put garbage in EXTENT header - should fail. |
| try_byte(&mut buffer, (EXTENT, 6), 42, None); |
| HeaderFields::set_block_type(&mut value_header, BlockType::ArrayValue.to_u8().unwrap()); |
| put_header(&value_header, &mut buffer, VALUE); |
| { |
| let mut container = [0u8; 16]; |
| let mut array_subheader = Block::new(&mut container, BlockIndex::EMPTY); |
| PayloadFields::set_array_entry_type( |
| &mut array_subheader, |
| BlockType::IntValue.to_u8().unwrap(), |
| ); |
| PayloadFields::set_array_flags( |
| &mut array_subheader, |
| ArrayFormat::Default.to_u8().unwrap(), |
| ); |
| put_payload(&array_subheader, &mut buffer, VALUE); |
| } |
| try_byte(&mut buffer, (16, 0), 0, Some("root ->\n> value: IntArray([], Default)")); |
| // Put garbage in reserved part of Array spec, should fail. |
| try_byte(&mut buffer, (VALUE, 12), 42, None); |
| HeaderFields::set_block_type(&mut value_header, BlockType::IntValue.to_u8().unwrap()); |
| put_header(&value_header, &mut buffer, VALUE); |
| // Now the array spec is just a (large) value; it should succeed. |
| try_byte(&mut buffer, (VALUE, 12), 42, Some("root ->\n> value: Int(180388626436)")); |
| } |
| } |