blob: 4369c42443080aacc47ff44ded0f2661b2ba5c2c [file] [log] [blame]
// 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::{
cmp::min,
collections::{HashMap, HashSet},
},
};
// 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(https://fxbug.dev/42115894): Uncomment or delete this line depending on the resolution of https://fxbug.dev/42115938.
// 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,
}
#[allow(dead_code, clippy::large_enum_variant)] // TODO(https://fxbug.dev/318827209)
#[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, HeaderFields, PayloadFields, ReadBytes},
};
// TODO(https://fxbug.dev/42115894): Depending on the resolution of https://fxbug.dev/42115938, 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(https://fxbug.dev/42115894): Depending on the resolution of https://fxbug.dev/42115938, 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)"));
}
}