blob: 9f055ec1a2181a76729d04f0fbcfb6e189130641 [file] [log] [blame]
// Copyright 2022 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 {
bind::compiler::Symbol,
bind::interpreter::match_bind::{DeviceProperties, PropertyKey},
fidl_fuchsia_driver_framework as fdf, fidl_fuchsia_driver_index as fdi,
fuchsia_zircon::{zx_status_t, Status},
std::collections::HashMap,
};
#[derive(Debug, PartialEq)]
struct DeviceGroupNodePropertyValue {
is_equal: bool,
values: Vec<Symbol>,
}
#[derive(Debug, PartialEq)]
pub struct Node {
name: String,
properties: HashMap<PropertyKey, DeviceGroupNodePropertyValue>,
}
#[derive(Debug, PartialEq)]
pub struct DeviceGroup {
topological_path: String,
nodes: Vec<Node>,
}
impl DeviceGroup {
pub fn create(
topological_path: String,
device_group_nodes: Vec<fdf::DeviceGroupNode>,
) -> Result<Self, zx_status_t> {
Ok(DeviceGroup {
topological_path: topological_path,
nodes: device_group_nodes
.into_iter()
.map(|node| {
Ok(Node {
name: node.name,
properties: group_node_to_device_property(&node.properties)?,
})
})
.collect::<Result<Vec<Node>, zx_status_t>>()?,
})
}
// TODO(fxb/103206): Return a list of device groups in a MatchedDeviceGroupNode.
pub fn matches(&self, properties: &DeviceProperties) -> Option<fdi::MatchedDriver> {
for (index, node) in self.nodes.iter().enumerate() {
if match_node(&node, properties) {
let node_info = fdi::MatchedDeviceGroupInfo {
topological_path: Some(self.topological_path.clone()),
node_index: Some(index as u32),
..fdi::MatchedDeviceGroupInfo::EMPTY
};
return Some(fdi::MatchedDriver::DeviceGroupNode(
fdi::MatchedDeviceGroupNodeInfo {
device_groups: Some(vec![node_info]),
..fdi::MatchedDeviceGroupNodeInfo::EMPTY
},
));
}
}
None
}
}
fn match_node(node: &Node, device_properties: &DeviceProperties) -> bool {
for (key, prop_values) in node.properties.iter() {
let match_property = match device_properties.get(key) {
Some(val) => prop_values.values.contains(val),
None => {
// If the node properties contain a false boolean
// value, then evaluate this to true.
prop_values.values.contains(&Symbol::BoolValue(false))
}
};
if prop_values.is_equal && !match_property {
return false;
}
if !prop_values.is_equal && match_property {
return false;
}
}
true
}
fn node_property_to_symbol(value: &fdf::NodePropertyValue) -> Symbol {
match value {
fdf::NodePropertyValue::IntValue(i) => {
bind::compiler::Symbol::NumberValue(i.clone().into())
}
fdf::NodePropertyValue::StringValue(s) => bind::compiler::Symbol::StringValue(s.clone()),
fdf::NodePropertyValue::EnumValue(s) => bind::compiler::Symbol::EnumValue(s.clone()),
fdf::NodePropertyValue::BoolValue(b) => bind::compiler::Symbol::BoolValue(b.clone()),
}
}
fn group_node_to_device_property(
node_properties: &Vec<fdf::DeviceGroupProperty>,
) -> Result<HashMap<PropertyKey, DeviceGroupNodePropertyValue>, zx_status_t> {
let mut device_properties = HashMap::new();
for property in node_properties {
let key = match &property.key {
fdf::NodePropertyKey::IntValue(i) => PropertyKey::NumberKey(i.clone().into()),
fdf::NodePropertyKey::StringValue(s) => PropertyKey::StringKey(s.clone()),
};
// Contains duplicate keys.
if device_properties.contains_key(&key) {
return Err(Status::INVALID_ARGS.into_raw());
}
let first_val = property.values.first().ok_or(Status::INVALID_ARGS.into_raw())?;
let values = property
.values
.iter()
.map(|val| {
// The properties should all be the same type.
if std::mem::discriminant(first_val) != std::mem::discriminant(val) {
return Err(Status::INVALID_ARGS.into_raw());
}
Ok(node_property_to_symbol(val))
})
.collect::<Result<Vec<Symbol>, zx_status_t>>()?;
let is_equal = property.condition == fdf::Condition::Accept;
device_properties
.insert(key, DeviceGroupNodePropertyValue { is_equal: is_equal, values: values });
}
Ok(device_properties)
}
#[cfg(test)]
mod tests {
use super::*;
use bind::compiler::Symbol;
use bind::interpreter::match_bind::PropertyKey;
use fuchsia_async as fasync;
#[fasync::run_singlethreaded(test)]
async fn test_property_match() {
let node_properties_1 = vec![
fdf::DeviceGroupProperty {
key: fdf::NodePropertyKey::IntValue(1),
condition: fdf::Condition::Accept,
values: vec![fdf::NodePropertyValue::IntValue(200)],
},
fdf::DeviceGroupProperty {
key: fdf::NodePropertyKey::IntValue(3),
condition: fdf::Condition::Accept,
values: vec![fdf::NodePropertyValue::BoolValue(true)],
},
fdf::DeviceGroupProperty {
key: fdf::NodePropertyKey::StringValue("killdeer".to_string()),
condition: fdf::Condition::Accept,
values: vec![fdf::NodePropertyValue::StringValue("plover".to_string())],
},
];
let node_properties_2 = vec![
fdf::DeviceGroupProperty {
key: fdf::NodePropertyKey::StringValue("killdeer".to_string()),
condition: fdf::Condition::Reject,
values: vec![fdf::NodePropertyValue::StringValue("plover".to_string())],
},
fdf::DeviceGroupProperty {
key: fdf::NodePropertyKey::StringValue("Moon".to_string()),
condition: fdf::Condition::Accept,
values: vec![fdf::NodePropertyValue::EnumValue("Moon.Half".to_string())],
},
fdf::DeviceGroupProperty {
key: fdf::NodePropertyKey::StringValue("yellowlegs".to_string()),
condition: fdf::Condition::Reject,
values: vec![fdf::NodePropertyValue::BoolValue(true)],
},
];
let device_group = DeviceGroup::create(
"test/path".to_string(),
vec![
fdf::DeviceGroupNode {
name: "whimbrel".to_string(),
properties: node_properties_1.clone(),
},
fdf::DeviceGroupNode {
name: "godwit".to_string(),
properties: node_properties_2.clone(),
},
],
)
.unwrap();
// Match node 1.
let mut device_properties_1: DeviceProperties = HashMap::new();
device_properties_1.insert(PropertyKey::NumberKey(1), Symbol::NumberValue(200));
device_properties_1.insert(
PropertyKey::StringKey("kingfisher".to_string()),
Symbol::StringValue("kookaburra".to_string()),
);
device_properties_1.insert(PropertyKey::NumberKey(3), Symbol::BoolValue(true));
device_properties_1.insert(
PropertyKey::StringKey("killdeer".to_string()),
Symbol::StringValue("plover".to_string()),
);
let expected_device_group = fdi::MatchedDeviceGroupInfo {
topological_path: Some("test/path".to_string()),
node_index: Some(0),
..fdi::MatchedDeviceGroupInfo::EMPTY
};
assert_eq!(
Some(fdi::MatchedDriver::DeviceGroupNode(fdi::MatchedDeviceGroupNodeInfo {
device_groups: Some(vec![expected_device_group]),
..fdi::MatchedDeviceGroupNodeInfo::EMPTY
})),
device_group.matches(&device_properties_1)
);
// Match node 2.
let mut device_properties_2: DeviceProperties = HashMap::new();
device_properties_2
.insert(PropertyKey::StringKey("yellowlegs".to_string()), Symbol::BoolValue(false));
device_properties_2.insert(
PropertyKey::StringKey("killdeer".to_string()),
Symbol::StringValue("lapwing".to_string()),
);
device_properties_2.insert(
PropertyKey::StringKey("Moon".to_string()),
Symbol::EnumValue("Moon.Half".to_string()),
);
let expected_device_group_2 = fdi::MatchedDeviceGroupInfo {
topological_path: Some("test/path".to_string()),
node_index: Some(1),
..fdi::MatchedDeviceGroupInfo::EMPTY
};
assert_eq!(
Some(fdi::MatchedDriver::DeviceGroupNode(fdi::MatchedDeviceGroupNodeInfo {
device_groups: Some(vec![expected_device_group_2]),
..fdi::MatchedDeviceGroupNodeInfo::EMPTY
})),
device_group.matches(&device_properties_2)
);
}
#[fasync::run_singlethreaded(test)]
async fn test_property_mismatch() {
let node_properties_1 = vec![
fdf::DeviceGroupProperty {
key: fdf::NodePropertyKey::IntValue(1),
condition: fdf::Condition::Accept,
values: vec![fdf::NodePropertyValue::IntValue(200)],
},
fdf::DeviceGroupProperty {
key: fdf::NodePropertyKey::IntValue(3),
condition: fdf::Condition::Accept,
values: vec![fdf::NodePropertyValue::BoolValue(true)],
},
fdf::DeviceGroupProperty {
key: fdf::NodePropertyKey::StringValue("killdeer".to_string()),
condition: fdf::Condition::Accept,
values: vec![fdf::NodePropertyValue::StringValue("plover".to_string())],
},
];
let node_properties_2 = vec![
fdf::DeviceGroupProperty {
key: fdf::NodePropertyKey::StringValue("killdeer".to_string()),
condition: fdf::Condition::Accept,
values: vec![fdf::NodePropertyValue::StringValue("plover".to_string())],
},
fdf::DeviceGroupProperty {
key: fdf::NodePropertyKey::StringValue("yellowlegs".to_string()),
condition: fdf::Condition::Reject,
values: vec![fdf::NodePropertyValue::BoolValue(false)],
},
];
let device_group = DeviceGroup::create(
"test/path".to_string(),
vec![
fdf::DeviceGroupNode {
name: "whimbrel".to_string(),
properties: node_properties_1.clone(),
},
fdf::DeviceGroupNode {
name: "godwit".to_string(),
properties: node_properties_2.clone(),
},
],
)
.unwrap();
let mut device_properties: DeviceProperties = HashMap::new();
device_properties.insert(PropertyKey::NumberKey(1), Symbol::NumberValue(200));
device_properties.insert(
PropertyKey::StringKey("kingfisher".to_string()),
Symbol::StringValue("bee-eater".to_string()),
);
device_properties
.insert(PropertyKey::StringKey("yellowlegs".to_string()), Symbol::BoolValue(false));
device_properties.insert(
PropertyKey::StringKey("killdeer".to_string()),
Symbol::StringValue("plover".to_string()),
);
assert_eq!(None, device_group.matches(&device_properties));
}
#[fasync::run_singlethreaded(test)]
async fn test_property_match_list() {
let node_properties_1 = vec![
fdf::DeviceGroupProperty {
key: fdf::NodePropertyKey::IntValue(10),
condition: fdf::Condition::Reject,
values: vec![
fdf::NodePropertyValue::IntValue(200),
fdf::NodePropertyValue::IntValue(150),
],
},
fdf::DeviceGroupProperty {
key: fdf::NodePropertyKey::StringValue("plover".to_string()),
condition: fdf::Condition::Accept,
values: vec![
fdf::NodePropertyValue::StringValue("killdeer".to_string()),
fdf::NodePropertyValue::StringValue("lapwing".to_string()),
],
},
];
let node_properties_2 = vec![fdf::DeviceGroupProperty {
key: fdf::NodePropertyKey::IntValue(11),
condition: fdf::Condition::Reject,
values: vec![
fdf::NodePropertyValue::IntValue(20),
fdf::NodePropertyValue::IntValue(10),
],
}];
let device_group = DeviceGroup::create(
"test/path".to_string(),
vec![
fdf::DeviceGroupNode {
name: "whimbrel".to_string(),
properties: node_properties_1.clone(),
},
fdf::DeviceGroupNode {
name: "godwit".to_string(),
properties: node_properties_2.clone(),
},
],
)
.unwrap();
// Match node 1.
let mut device_properties_1: DeviceProperties = HashMap::new();
device_properties_1.insert(PropertyKey::NumberKey(10), Symbol::NumberValue(20));
device_properties_1.insert(
PropertyKey::StringKey("plover".to_string()),
Symbol::StringValue("lapwing".to_string()),
);
let expected_device_group = fdi::MatchedDeviceGroupInfo {
topological_path: Some("test/path".to_string()),
node_index: Some(0),
..fdi::MatchedDeviceGroupInfo::EMPTY
};
assert_eq!(
Some(fdi::MatchedDriver::DeviceGroupNode(fdi::MatchedDeviceGroupNodeInfo {
device_groups: Some(vec![expected_device_group]),
..fdi::MatchedDeviceGroupNodeInfo::EMPTY
})),
device_group.matches(&device_properties_1)
);
// Match node 2.
let mut device_properties_2: DeviceProperties = HashMap::new();
device_properties_2.insert(PropertyKey::NumberKey(5), Symbol::NumberValue(20));
let expected_device_group_2 = fdi::MatchedDeviceGroupInfo {
topological_path: Some("test/path".to_string()),
node_index: Some(1),
..fdi::MatchedDeviceGroupInfo::EMPTY
};
assert_eq!(
Some(fdi::MatchedDriver::DeviceGroupNode(fdi::MatchedDeviceGroupNodeInfo {
device_groups: Some(vec![expected_device_group_2]),
..fdi::MatchedDeviceGroupNodeInfo::EMPTY
})),
device_group.matches(&device_properties_2)
);
}
#[fasync::run_singlethreaded(test)]
async fn test_property_mismatch_list() {
let node_properties_1 = vec![
fdf::DeviceGroupProperty {
key: fdf::NodePropertyKey::IntValue(10),
condition: fdf::Condition::Reject,
values: vec![
fdf::NodePropertyValue::IntValue(200),
fdf::NodePropertyValue::IntValue(150),
],
},
fdf::DeviceGroupProperty {
key: fdf::NodePropertyKey::StringValue("plover".to_string()),
condition: fdf::Condition::Accept,
values: vec![
fdf::NodePropertyValue::StringValue("killdeer".to_string()),
fdf::NodePropertyValue::StringValue("lapwing".to_string()),
],
},
];
let node_properties_2 = vec![
fdf::DeviceGroupProperty {
key: fdf::NodePropertyKey::IntValue(11),
condition: fdf::Condition::Reject,
values: vec![
fdf::NodePropertyValue::IntValue(20),
fdf::NodePropertyValue::IntValue(10),
],
},
fdf::DeviceGroupProperty {
key: fdf::NodePropertyKey::IntValue(2),
condition: fdf::Condition::Accept,
values: vec![fdf::NodePropertyValue::BoolValue(true)],
},
];
let device_group = DeviceGroup::create(
"test/path".to_string(),
vec![
fdf::DeviceGroupNode {
name: "whimbrel".to_string(),
properties: node_properties_1.clone(),
},
fdf::DeviceGroupNode {
name: "godwit".to_string(),
properties: node_properties_2.clone(),
},
],
)
.unwrap();
// Match node 1.
let mut device_properties_1: DeviceProperties = HashMap::new();
device_properties_1.insert(PropertyKey::NumberKey(10), Symbol::NumberValue(200));
device_properties_1.insert(
PropertyKey::StringKey("plover".to_string()),
Symbol::StringValue("lapwing".to_string()),
);
assert_eq!(None, device_group.matches(&device_properties_1));
// Match node 2.
let mut device_properties_2: DeviceProperties = HashMap::new();
device_properties_2.insert(PropertyKey::NumberKey(11), Symbol::NumberValue(10));
device_properties_2.insert(PropertyKey::NumberKey(2), Symbol::BoolValue(true));
assert_eq!(None, device_group.matches(&device_properties_2));
}
#[fasync::run_singlethreaded(test)]
async fn test_property_multiple_value_types() {
let node_properties_1 = vec![fdf::DeviceGroupProperty {
key: fdf::NodePropertyKey::IntValue(10),
condition: fdf::Condition::Reject,
values: vec![
fdf::NodePropertyValue::IntValue(200),
fdf::NodePropertyValue::BoolValue(false),
],
}];
assert_eq!(
Err(Status::INVALID_ARGS.into_raw()),
DeviceGroup::create(
"test/path".to_string(),
vec![fdf::DeviceGroupNode {
name: "whimbrel".to_string(),
properties: node_properties_1.clone(),
}],
)
);
}
#[fasync::run_singlethreaded(test)]
async fn test_property_duplicate_key() {
let node_properties_1 = vec![
fdf::DeviceGroupProperty {
key: fdf::NodePropertyKey::IntValue(10),
condition: fdf::Condition::Reject,
values: vec![fdf::NodePropertyValue::IntValue(200)],
},
fdf::DeviceGroupProperty {
key: fdf::NodePropertyKey::IntValue(10),
condition: fdf::Condition::Accept,
values: vec![fdf::NodePropertyValue::IntValue(10)],
},
];
assert_eq!(
Err(Status::INVALID_ARGS.into_raw()),
DeviceGroup::create(
"test/path".to_string(),
vec![fdf::DeviceGroupNode {
name: "whimbrel".to_string(),
properties: node_properties_1.clone(),
},],
)
);
}
}