use crate::reader::snapshot::{ScannedBlock, Snapshot};
use diagnostics_hierarchy::*;
use inspect_format::{BlockIndex, BlockType};
use maplit::btreemap;
use std::borrow::Cow;
use std::collections::BTreeMap;
pub use crate::reader::error::ReaderError;
pub use crate::reader::readable_tree::ReadableTree;
pub use crate::reader::tree_reader::{read, read_with_timeout};
pub use diagnostics_hierarchy::{ArrayContent, ArrayFormat, DiagnosticsHierarchy, Property};
pub use inspect_format::LinkNodeDisposition;
mod error;
mod readable_tree;
pub mod snapshot;
mod tree_reader;
#[derive(Clone, Debug, PartialEq)]
pub struct PartialNodeHierarchy {
pub(crate) name: String,
pub(crate) properties: Vec<Property>,
pub(crate) children: Vec<PartialNodeHierarchy>,
pub(crate) links: Vec<LinkValue>,
}
#[derive(Debug, PartialEq, Clone)]
pub(crate) struct LinkValue {
pub name: String,
pub content: String,
pub disposition: LinkNodeDisposition,
}
impl PartialNodeHierarchy {
pub fn new(
name: impl Into<String>,
properties: Vec<Property>,
children: Vec<PartialNodeHierarchy>,
) -> Self {
Self { name: name.into(), properties, children, links: vec![] }
}
pub fn empty() -> Self {
PartialNodeHierarchy::new("", vec![], vec![])
}
pub fn is_complete(&self) -> bool {
self.links.is_empty()
}
}
impl From<PartialNodeHierarchy> for DiagnosticsHierarchy {
fn from(partial: PartialNodeHierarchy) -> DiagnosticsHierarchy {
DiagnosticsHierarchy {
name: partial.name,
children: partial.children.into_iter().map(|child| child.into()).collect(),
properties: partial.properties,
missing: partial
.links
.into_iter()
.map(|link_value| MissingValue {
reason: MissingValueReason::LinkNeverExpanded,
name: link_value.name,
})
.collect(),
}
}
}
impl DiagnosticsHierarchyGetter<String> for PartialNodeHierarchy {
fn get_diagnostics_hierarchy(&self) -> Cow<'_, DiagnosticsHierarchy> {
let hierarchy: DiagnosticsHierarchy = self.clone().into();
if !hierarchy.missing.is_empty() {
panic!(
"Missing links: {:?}",
hierarchy
.missing
.iter()
.map(|missing| {
format!("(name:{:?}, reason:{:?})", missing.name, missing.reason)
})
.collect::<Vec<_>>()
.join(", ")
);
}
Cow::Owned(hierarchy)
}
}
impl TryFrom<Snapshot> for PartialNodeHierarchy {
type Error = ReaderError;
fn try_from(snapshot: Snapshot) -> Result<Self, Self::Error> {
read_snapshot(&snapshot)
}
}
#[cfg(target_os = "fuchsia")]
impl TryFrom<&zx::Vmo> for PartialNodeHierarchy {
type Error = ReaderError;
fn try_from(vmo: &zx::Vmo) -> Result<Self, Self::Error> {
let snapshot = Snapshot::try_from(vmo)?;
read_snapshot(&snapshot)
}
}
impl TryFrom<Vec<u8>> for PartialNodeHierarchy {
type Error = ReaderError;
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
let snapshot = Snapshot::try_from(bytes)?;
read_snapshot(&snapshot)
}
}
fn read_snapshot(snapshot: &Snapshot) -> Result<PartialNodeHierarchy, ReaderError> {
let result = scan_blocks(snapshot)?;
result.reduce()
}
fn scan_blocks(snapshot: &Snapshot) -> Result<ScanResult<'_>, ReaderError> {
let mut result = ScanResult::new(snapshot);
for block in snapshot.scan() {
if block.index() == BlockIndex::ROOT
&& block.block_type_or().map_err(ReaderError::VmoFormat)? != BlockType::Header
{
return Err(ReaderError::MissingHeader);
}
match block.block_type_or().map_err(ReaderError::VmoFormat)? {
BlockType::NodeValue => {
result.parse_node(&block)?;
}
BlockType::IntValue | BlockType::UintValue | BlockType::DoubleValue => {
result.parse_numeric_property(&block)?;
}
BlockType::BoolValue => {
result.parse_bool_property(&block)?;
}
BlockType::ArrayValue => {
result.parse_array_property(&block)?;
}
BlockType::BufferValue => {
result.parse_property(&block)?;
}
BlockType::LinkValue => {
result.parse_link(&block)?;
}
_ => {}
}
}
Ok(result)
}
struct ScanResult<'a> {
parsed_nodes: BTreeMap<BlockIndex, ScannedNode>,
snapshot: &'a Snapshot,
}
#[derive(Debug)]
struct ScannedNode {
partial_hierarchy: PartialNodeHierarchy,
child_nodes_count: usize,
parent_index: BlockIndex,
initialized: bool,
}
impl ScannedNode {
fn new() -> Self {
ScannedNode {
partial_hierarchy: PartialNodeHierarchy::empty(),
child_nodes_count: 0,
parent_index: BlockIndex::EMPTY,
initialized: false,
}
}
fn initialize(&mut self, name: String, parent_index: BlockIndex) {
self.partial_hierarchy.name = name;
self.parent_index = parent_index;
self.initialized = true;
}
fn is_complete(&self) -> bool {
self.partial_hierarchy.children.len() == self.child_nodes_count
}
fn is_initialized(&self) -> bool {
self.initialized
}
}
macro_rules! get_or_create_scanned_node {
($map:expr, $key:expr) => {
$map.entry($key).or_insert(ScannedNode::new())
};
}
impl<'a> ScanResult<'a> {
fn new(snapshot: &'a Snapshot) -> Self {
let mut root_node = ScannedNode::new();
root_node.initialize("root".to_string(), BlockIndex::ROOT);
let parsed_nodes = btreemap!(
BlockIndex::ROOT => root_node,
);
ScanResult { snapshot, parsed_nodes }
}
fn reduce(self) -> Result<PartialNodeHierarchy, ReaderError> {
let mut complete_nodes = Vec::<ScannedNode>::new();
let mut pending_nodes = BTreeMap::<BlockIndex, ScannedNode>::new();
let mut uninitialized_nodes = std::collections::BTreeSet::new();
for (index, scanned_node) in self.parsed_nodes.into_iter() {
if !scanned_node.is_initialized() {
uninitialized_nodes.insert(index);
continue;
}
if scanned_node.is_complete() {
if index == BlockIndex::ROOT {
return Ok(scanned_node.partial_hierarchy);
}
complete_nodes.push(scanned_node);
} else {
pending_nodes.insert(index, scanned_node);
}
}
while let Some(scanned_node) = complete_nodes.pop() {
if uninitialized_nodes.contains(&scanned_node.parent_index) {
continue;
}
{
let parent_node = pending_nodes
.get_mut(&scanned_node.parent_index)
.ok_or(ReaderError::ParentIndexNotFound(scanned_node.parent_index))?;
parent_node.partial_hierarchy.children.push(scanned_node.partial_hierarchy);
}
if pending_nodes
.get(&scanned_node.parent_index)
.ok_or(ReaderError::ParentIndexNotFound(scanned_node.parent_index))?
.is_complete()
{
let parent_node = pending_nodes.remove(&scanned_node.parent_index).unwrap();
if scanned_node.parent_index == BlockIndex::ROOT {
return Ok(parent_node.partial_hierarchy);
}
complete_nodes.push(parent_node);
}
}
Err(ReaderError::MalformedTree)
}
pub fn get_name(&self, index: BlockIndex) -> Option<String> {
self.snapshot.get_name(index)
}
fn parse_node(&mut self, block: &ScannedBlock<'_>) -> Result<(), ReaderError> {
let name_index = block.name_index()?;
let name = self.get_name(name_index).ok_or(ReaderError::ParseName(name_index))?;
let parent_index = block.parent_index()?;
get_or_create_scanned_node!(self.parsed_nodes, block.index())
.initialize(name, parent_index);
if parent_index != block.index() {
get_or_create_scanned_node!(self.parsed_nodes, parent_index).child_nodes_count += 1;
}
Ok(())
}
fn push_property(
&mut self,
block: &ScannedBlock<'_>,
property: Property,
) -> Result<(), ReaderError> {
let parent_index = block.parent_index()?;
let parent = get_or_create_scanned_node!(self.parsed_nodes, parent_index);
parent.partial_hierarchy.properties.push(property);
Ok(())
}
fn parse_numeric_property(&mut self, block: &ScannedBlock<'_>) -> Result<(), ReaderError> {
self.push_property(block, self.snapshot.parse_numeric_property(block)?)?;
Ok(())
}
fn parse_bool_property(&mut self, block: &ScannedBlock<'_>) -> Result<(), ReaderError> {
self.push_property(block, self.snapshot.parse_bool_property(block)?)?;
Ok(())
}
fn parse_array_property(&mut self, block: &ScannedBlock<'_>) -> Result<(), ReaderError> {
self.push_property(block, self.snapshot.parse_array_property(block)?)?;
Ok(())
}
fn parse_property(&mut self, block: &ScannedBlock<'_>) -> Result<(), ReaderError> {
self.push_property(block, self.snapshot.parse_property(block)?)?;
Ok(())
}
fn parse_link(&mut self, block: &ScannedBlock<'_>) -> Result<(), ReaderError> {
let parent_index = block.parent_index()?;
let parent = get_or_create_scanned_node!(self.parsed_nodes, parent_index);
parent.partial_hierarchy.links.push(self.snapshot.parse_link(block)?);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::private::InspectTypeInternal;
use crate::writer::testing_utils::GetBlockExt;
use crate::{ArrayProperty, HistogramProperty, Inspector, StringReference};
use anyhow::Error;
use diagnostics_assertions::{assert_data_tree, assert_json_diff};
use futures::prelude::*;
use inspect_format::{constants, BlockContainer, CopyBytes, PayloadFields};
#[fuchsia::test]
async fn test_load_string_reference() {
let inspector = Inspector::default();
let root = inspector.root();
let name_value = StringReference::from("abc");
let longer_name_value = StringReference::from("abcdefg");
let child = root.create_child(&name_value);
child.record_int(&name_value, 5);
root.record_bool(&longer_name_value, false);
let result = read(&inspector).await.unwrap();
assert_json_diff!(result, root: {
abc: {
abc: 5i64,
},
abcdefg: false,
});
}
#[fuchsia::test]
async fn read_string_array() {
let inspector = Inspector::default();
let root = inspector.root();
let zero = (0..3000).map(|_| '0').collect::<String>();
let one: String = "1".into();
let two: String = "two".into();
let three: String = "three three three".into();
let four: String = "fourth".into();
let array = root.create_string_array("array", 5);
array.set(0, &zero);
array.set(1, &one);
array.set(2, &two);
array.set(3, &three);
array.set(4, &four);
let result = read(&inspector).await.unwrap();
assert_json_diff!(result, root: {
"array": vec![zero, one, two, three, four],
});
}
#[fuchsia::test]
async fn read_unset_string_array() {
let inspector = Inspector::default();
let root = inspector.root();
let zero = (0..3000).map(|_| '0').collect::<String>();
let one: String = "1".into();
let four: String = "fourth".into();
let array = root.create_string_array("array", 5);
array.set(0, &zero);
array.set(1, &one);
array.set(4, &four);
let result = read(&inspector).await.unwrap();
assert_json_diff!(result, root: {
"array": vec![zero, one, "".into(), "".into(), four],
});
}
#[fuchsia::test]
async fn read_vmo() {
let inspector = Inspector::default();
let root = inspector.root();
let _root_int = root.create_int("int-root", 3);
let root_double_array = root.create_double_array("property-double-array", 5);
let double_array_data = vec![-1.2, 2.3, 3.4, 4.5, -5.6];
for (i, x) in double_array_data.iter().enumerate() {
root_double_array.set(i, *x);
}
let child1 = root.create_child("child-1");
let _child1_uint = child1.create_uint("property-uint", 10);
let _child1_double = child1.create_double("property-double", -3.4);
let _child1_bool = child1.create_bool("property-bool", true);
let chars = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
let string_data = chars.iter().cycle().take(6000).collect::<String>();
let _string_prop = child1.create_string("property-string", &string_data);
let child1_int_array = child1.create_int_linear_histogram(
"property-int-array",
LinearHistogramParams { floor: 1, step_size: 2, buckets: 3 },
);
for x in [-1, 2, 3, 5, 8].iter() {
child1_int_array.insert(*x);
}
let child2 = root.create_child("child-2");
let _child2_double = child2.create_double("property-double", 5.8);
let _child2_bool = child2.create_bool("property-bool", false);
let child3 = child1.create_child("child-1-1");
let _child3_int = child3.create_int("property-int", -9);
let bytes_data = (0u8..=9u8).cycle().take(5000).collect::<Vec<u8>>();
let _bytes_prop = child3.create_bytes("property-bytes", &bytes_data);
let child3_uint_array = child3.create_uint_exponential_histogram(
"property-uint-array",
ExponentialHistogramParams {
floor: 1,
initial_step: 1,
step_multiplier: 2,
buckets: 5,
},
);
for x in [1, 2, 3, 4].iter() {
child3_uint_array.insert(*x);
}
let result = read(&inspector).await.unwrap();
assert_data_tree!(result, root: {
"int-root": 3i64,
"property-double-array": double_array_data,
"child-1": {
"property-uint": 10u64,
"property-double": -3.4,
"property-bool": true,
"property-string": string_data,
"property-int-array": LinearHistogram {
floor: 1i64,
step: 2,
counts: vec![1, 1, 1, 1, 1],
indexes: None,
size: 5
},
"child-1-1": {
"property-int": -9i64,
"property-bytes": bytes_data,
"property-uint-array": ExponentialHistogram {
floor: 1u64,
initial_step: 1,
step_multiplier: 2,
counts: vec![1, 1, 2],
indexes: Some(vec![1, 2, 3]),
size: 7
},
}
},
"child-2": {
"property-double": 5.8,
"property-bool": false,
}
})
}
#[fuchsia::test]
async fn siblings_with_same_name() {
let inspector = Inspector::default();
let foo: StringReference = "foo".into();
inspector.root().record_int("foo", 0);
inspector.root().record_int("foo", 1);
inspector.root().record_int(&foo, 2);
inspector.root().record_int(&foo, 3);
let dh = read(&inspector).await.unwrap();
assert_eq!(dh.properties.len(), 4);
for i in 0..dh.properties.len() {
match &dh.properties[i] {
Property::Int(n, v) => {
assert_eq!(n, "foo");
assert_eq!(*v, i as i64);
}
_ => panic!("We only record int properties"),
}
}
}
#[fuchsia::test]
fn tombstone_reads() {
let inspector = Inspector::default();
let node1 = inspector.root().create_child("child1");
let node2 = node1.create_child("child2");
let node3 = node2.create_child("child3");
let prop1 = node1.create_string("val", "test");
let prop2 = node2.create_string("val", "test");
let prop3 = node3.create_string("val", "test");
assert_json_diff!(inspector,
root: {
child1: {
val: "test",
child2: {
val: "test",
child3: {
val: "test",
}
}
}
}
);
std::mem::drop(node3);
assert_json_diff!(inspector,
root: {
child1: {
val: "test",
child2: {
val: "test",
}
}
}
);
std::mem::drop(node2);
assert_json_diff!(inspector,
root: {
child1: {
val: "test",
}
}
);
let node2 = node1.create_child("child2");
let _node3 = node2.create_child("child3");
assert_json_diff!(inspector,
root: {
child1: {
val: "test",
child2: {
child3: {}
}
}
}
);
std::mem::drop(node2);
assert_json_diff!(inspector,
root: {
child1: {
val: "test",
}
}
);
std::mem::drop(node1);
assert_json_diff!(inspector,
root: {
}
);
std::mem::drop(prop3);
assert_json_diff!(inspector,
root: {
}
);
std::mem::drop(prop2);
assert_json_diff!(inspector,
root: {
}
);
std::mem::drop(prop1);
assert_json_diff!(inspector,
root: {
}
);
}
#[fuchsia::test]
async fn from_invalid_utf8_string() {
let inspector = Inspector::default();
let root = inspector.root();
let prop = root.create_string("property", "hello world");
let vmo = inspector.vmo().await.unwrap();
let snapshot = Snapshot::try_from(&vmo).expect("getting snapshot");
let block = snapshot.get_block(prop.block_index().unwrap()).expect("getting block");
let byte_offset = constants::MIN_ORDER_SIZE
* (*block.property_extent_index().unwrap() as usize)
+ constants::HEADER_SIZE_BYTES;
let vmo_size = BlockContainer::len(&vmo);
let mut buf = vec![0u8; vmo_size];
vmo.copy_bytes(&mut buf[..]);
buf[byte_offset] = 0xFE;
let hierarchy: DiagnosticsHierarchy = PartialNodeHierarchy::try_from(Snapshot::build(&buf))
.expect("creating node hierarchy")
.into();
assert_json_diff!(hierarchy, root: {
property: "\u{FFFD}ello world",
});
}
#[fuchsia::test]
async fn test_invalid_array_slots() -> Result<(), Error> {
let inspector = Inspector::default();
let root = inspector.root();
let array = root.create_int_array("int-array", 3);
array.get_block_mut(|array_block| {
PayloadFields::set_array_slots_count(array_block, 255);
});
let vmo = inspector.vmo().await.unwrap();
let vmo_size = BlockContainer::len(&vmo);
let mut buf = vec![0u8; vmo_size];
vmo.copy_bytes(&mut buf[..]);
assert!(PartialNodeHierarchy::try_from(Snapshot::build(&buf)).is_err());
Ok(())
}
#[fuchsia::test]
async fn lazy_nodes() -> Result<(), Error> {
let inspector = Inspector::default();
inspector.root().record_int("int", 3);
let child = inspector.root().create_child("child");
child.record_double("double", 1.5);
inspector.root().record_lazy_child("lazy", || {
async move {
let inspector = Inspector::default();
inspector.root().record_uint("uint", 5);
inspector.root().record_lazy_values("nested-lazy-values", || {
async move {
let inspector = Inspector::default();
inspector.root().record_string("string", "test");
let child = inspector.root().create_child("nested-lazy-child");
let array = child.create_int_array("array", 3);
array.set(0, 1);
child.record(array);
inspector.root().record(child);
Ok(inspector)
}
.boxed()
});
Ok(inspector)
}
.boxed()
});
inspector.root().record_lazy_values("lazy-values", || {
async move {
let inspector = Inspector::default();
let child = inspector.root().create_child("lazy-child-1");
child.record_string("test", "testing");
inspector.root().record(child);
inspector.root().record_uint("some-uint", 3);
inspector.root().record_lazy_values("nested-lazy-values", || {
async move {
let inspector = Inspector::default();
inspector.root().record_int("lazy-int", -3);
let child = inspector.root().create_child("one-more-child");
child.record_double("lazy-double", 4.3);
inspector.root().record(child);
Ok(inspector)
}
.boxed()
});
inspector.root().record_lazy_child("nested-lazy-child", || {
async move {
let inspector = Inspector::default();
let _double = inspector.root().create_double("double", -1.2);
Ok(inspector)
}
.boxed()
});
Ok(inspector)
}
.boxed()
});
let hierarchy = read(&inspector).await?;
assert_json_diff!(hierarchy, root: {
int: 3i64,
child: {
double: 1.5,
},
lazy: {
uint: 5u64,
string: "test",
"nested-lazy-child": {
array: vec![1i64, 0, 0],
}
},
"some-uint": 3u64,
"lazy-child-1": {
test: "testing",
},
"lazy-int": -3i64,
"one-more-child": {
"lazy-double": 4.3,
},
"nested-lazy-child": {
}
});
Ok(())
}
#[fuchsia::test]
fn test_matching_with_inspector() {
let inspector = Inspector::default();
assert_json_diff!(inspector, root: {});
}
#[fuchsia::test]
fn test_matching_with_partial() {
let propreties = vec![Property::String("sub".to_string(), "sub_value".to_string())];
let partial = PartialNodeHierarchy::new("root", propreties, vec![]);
assert_json_diff!(partial, root: {
sub: "sub_value",
});
}
#[fuchsia::test]
#[should_panic]
fn test_missing_values_with_partial() {
let mut partial = PartialNodeHierarchy::new("root", vec![], vec![]);
partial.links = vec![LinkValue {
name: "missing-link".to_string(),
content: "missing-link-404".to_string(),
disposition: LinkNodeDisposition::Child,
}];
assert_json_diff!(partial, root: {});
}
#[fuchsia::test]
fn test_matching_with_expression_as_key() {
let properties = vec![Property::String("sub".to_string(), "sub_value".to_string())];
let partial = PartialNodeHierarchy::new("root", properties, vec![]);
let value = || "sub_value";
let key = || "sub".to_string();
assert_json_diff!(partial, root: {
key() => value(),
});
}
}