blob: 066ab7ebb78c85d0d176bc2c00253a34e184e51b [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::{validate::ROOT_ID, Data, LazyNode, Metrics, Node, Payload, Property, ROOT_NAME},
crate::metrics::{BlockMetrics, BlockStatus},
anyhow::{bail, format_err, Error},
diagnostics_hierarchy::LinkNodeDisposition,
fuchsia_inspect::{
self,
format::{
block::{ArrayFormat, Block, PropertyFormat},
block_type::BlockType,
constants::MIN_ORDER_SIZE,
},
reader as ireader,
},
fuchsia_zircon::Vmo,
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<u32, ScannedNode>,
names: HashMap<u32, ScannedName>,
properties: HashMap<u32, ScannedProperty>,
extents: HashMap<u32, ScannedExtent>,
final_nodes: HashMap<u32, Node>,
final_properties: HashMap<u32, 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)?, 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: usize) -> usize {
MIN_ORDER_SIZE << order
}
// Checks if these bits (start...end) are 0. Restricts the range checked to the given block.
fn check_zero_bits(
buffer: &[u8],
block: &Block<&[u8]>,
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 = block.index() as usize * 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(),
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(
0,
ScannedNode {
validated: true,
parent: 0,
name: 0,
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<Block<&[u8]>> = 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)?,
Err(error) => return Err(format_err!("Failed to read block type: {:?}", error)),
}
}
// 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(ROOT_ID)?;
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: u32) -> 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 != 0 {
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: u32) -> 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)
}
fn use_owned_name(&mut self, name_id: u32) -> Result<String, Error> {
let name =
self.names.remove(&name_id).ok_or(format_err!("No string at index {}", name_id))?;
self.metrics.record(&name.metrics, BlockStatus::Used);
Ok(name.name.clone())
}
// ***** 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: Block<&[u8]>) -> 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: Block<&[u8]>) -> Result<(), Error> {
self.metrics.process(block)?;
Ok(())
}
fn process_tombstone(&mut self, block: Block<&[u8]>) -> Result<(), Error> {
self.metrics.process(block)?;
Ok(())
}
fn process_reserved(&mut self, block: Block<&[u8]>) -> Result<(), Error> {
self.metrics.process(block)?;
Ok(())
}
fn process_extent(&mut self, block: Block<&[u8]>, 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()?,
metrics: Metrics::analyze(block)?,
},
);
Ok(())
}
fn process_name(&mut self, block: Block<&[u8]>, buffer: &[u8]) -> Result<(), Error> {
check_zero_bits(buffer, &block, 28, 63)?;
self.names.insert(
block.index(),
ScannedName { name: block.name_contents()?, metrics: Metrics::analyze(block)? },
);
Ok(())
}
fn process_node(&mut self, block: Block<&[u8]>) -> 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<u32>>(
&mut self,
parent: u32,
id: u32,
get_the_hashset: F, // Gets children or properties
) {
if !self.nodes.contains_key(&parent) {
self.nodes.insert(
parent,
ScannedNode {
validated: false,
name: 0,
parent: 0,
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: &Block<&[u8]>,
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.property_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)
}
illegal_type => {
return Err(format_err!(
"No way I should see {:?} for ArrayEntryType",
illegal_type
))
}
}
}
BlockType::LinkValue => {
let child_trees = self
.child_trees
.as_mut()
.ok_or(format_err!("LinkValue encountered without child tree."))?;
let child_name = &self
.names
.get(&block.link_content_index()?)
.ok_or(format_err!(
"Child name not found for LinkValue block {}.",
block.index()
))?
.name;
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_property(&mut self, block: Block<&[u8]>, 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: u32,
) -> Result<(Vec<(Node, u32)>, Vec<(Property, u32)>), 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 == 0 { 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: u32) -> 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::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 make_valid_vector(&mut self, length: usize, link: u32) -> 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: u32,
parent: u32,
children: HashSet<u32>,
properties: HashSet<u32>,
metrics: Option<BlockMetrics>,
}
#[derive(Debug)]
struct ScannedProperty {
name: u32,
parent: u32,
payload: ScannedPayload,
metrics: BlockMetrics,
}
#[derive(Debug)]
struct ScannedName {
name: String,
metrics: BlockMetrics,
}
#[derive(Debug)]
struct ScannedExtent {
next: u32,
data: Vec<u8>,
metrics: BlockMetrics,
}
#[derive(Debug)]
enum ScannedPayload {
String { length: usize, link: u32 },
Bytes { length: usize, link: u32 },
Int(i64),
Uint(u64),
Double(f64),
Bool(bool),
IntArray(Vec<i64>, ArrayFormat),
UintArray(Vec<u64>, ArrayFormat),
DoubleArray(Vec<f64>, ArrayFormat),
Link { disposition: LinkNodeDisposition, scanned_tree: Scanner },
}
#[cfg(test)]
mod tests {
use num_traits::ToPrimitive;
use {
super::*,
crate::*,
fidl_test_inspect_validate::Number,
fuchsia_async as fasync,
fuchsia_inspect::format::{
bitfields::{BlockHeader, Payload as BlockPayload},
block_type::BlockType,
constants,
},
};
// 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 run-test inspect_validator_tests -- --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(header: &BlockHeader, buffer: &mut [u8], index: usize) {
copy_into(&header.value().to_le_bytes(), buffer, index * 16);
}
fn put_payload(payload: &BlockPayload, buffer: &mut [u8], index: usize) {
copy_into(&payload.value().to_le_bytes(), buffer, index * 16 + 8);
}
#[test]
fn test_scanning_logic() {
let mut buffer = [0u8; 4096];
// VMO Header block (index 0)
const HEADER: usize = 0;
let mut header = BlockHeader(0);
header.set_order(0);
header.set_block_type(BlockType::Header.to_u8().unwrap());
header.set_header_magic(constants::HEADER_MAGIC_NUMBER);
header.set_header_version(constants::HEADER_VERSION_NUMBER);
put_header(&header, &mut buffer, HEADER);
const ROOT: usize = 1;
let mut header = BlockHeader(0);
header.set_order(0);
header.set_block_type(BlockType::NodeValue.to_u8().unwrap());
header.set_value_name_index(2);
header.set_value_parent_index(0);
put_header(&header, &mut buffer, ROOT);
// Root's Name block
const ROOT_NAME: usize = 2;
let mut header = BlockHeader(0);
header.set_order(0);
header.set_block_type(BlockType::Name.to_u8().unwrap());
header.set_name_length(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 number_header = BlockHeader(0);
number_header.set_order(0);
number_header.set_block_type(BlockType::IntValue.to_u8().unwrap());
number_header.set_value_name_index(3);
number_header.set_value_parent_index(1);
put_header(&number_header, &mut buffer, NUMBER);
const NUMBER_NAME: usize = 3;
let mut header = BlockHeader(0);
header.set_order(0);
header.set_block_type(BlockType::Name.to_u8().unwrap());
header.set_name_length(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);
number_header.set_order(2);
number_header.set_block_type(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)"),
);
}
#[fasync::run_singlethreaded(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, parent: 1, payload: Payload::Int(0) }
.to_string("");
let int1_struct =
Property { name: "int1".into(), id: 2, parent: 1, payload: Payload::Int(1) };
let int1 = int1_struct.to_string("");
assert_ne!(int0, int1);
let uint0 = Property { name: "uint0".into(), id: 2, parent: 1, payload: Payload::Uint(0) }
.to_string("");
assert_ne!(int0, uint0);
let int0_different_name = Property {
name: "int0_different_name".into(),
id: 2,
parent: 1,
payload: Payload::Int(0),
}
.to_string("");
assert_ne!(int0, int0_different_name);
let uint0_different_ids =
Property { name: "uint0".into(), id: 3, parent: 4, 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: Number::IntT(1));
let mut property2_action =
create_numeric_property!(parent:0, id:2, name:"prop2", value: Number::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: Number::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(())
}
#[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(())
}
#[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 block = Block::new(&buffer[..], 1);
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 block = Block::new(&buffer[..], 1);
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 block = Block::new(&buffer[..], 1);
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 block = Block::new(&buffer[..], 1);
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 block = Block::new(&buffer[..], 1);
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 block = Block::new(&buffer[..], 1);
assert!(check_zero_bits(&buffer, &block, 22, 62).is_err());
}
buffer[3 + 16] = 0;
buffer[4 + 16] = 0x10;
{
let block = Block::new(&buffer[..], 1);
assert!(check_zero_bits(&buffer, &block, 22, 62).is_err());
}
buffer[4 + 16] = 0;
buffer[5 + 16] = 0x10;
{
let block = Block::new(&buffer[..], 1);
assert!(check_zero_bits(&buffer, &block, 22, 62).is_err());
}
buffer[5 + 16] = 0;
buffer[6 + 16] = 0x10;
{
let block = Block::new(&buffer[..], 1);
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 block = Block::new(&buffer[..], 1);
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(())
}
#[test]
fn test_reserved_fields() {
let mut buffer = [0u8; 4096];
// VMO Header block (index 0)
const HEADER: usize = 0;
let mut header = BlockHeader(0);
header.set_order(0);
header.set_block_type(BlockType::Header.to_u8().unwrap());
header.set_header_magic(constants::HEADER_MAGIC_NUMBER);
header.set_header_version(constants::HEADER_VERSION_NUMBER);
put_header(&header, &mut buffer, HEADER);
const VALUE: usize = 1;
let mut value_header = BlockHeader(0);
value_header.set_order(0);
value_header.set_block_type(BlockType::NodeValue.to_u8().unwrap());
value_header.set_value_name_index(2);
value_header.set_value_parent_index(0);
put_header(&value_header, &mut buffer, VALUE);
// Root's Name block
const VALUE_NAME: usize = 2;
let mut header = BlockHeader(0);
header.set_order(0);
header.set_block_type(BlockType::Name.to_u8().unwrap());
header.set_name_length(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 header = BlockHeader(0);
header.set_order(0);
header.set_block_type(BlockType::Extent.to_u8().unwrap());
header.set_extent_next_index(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);
value_header.set_block_type(BlockType::ArrayValue.to_u8().unwrap());
put_header(&value_header, &mut buffer, VALUE);
let mut array_subheader = BlockPayload(0);
array_subheader.set_array_entry_type(BlockType::IntValue.to_u8().unwrap());
array_subheader.set_array_flags(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);
value_header.set_block_type(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)"));
}
}