| // Copyright 2021 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 { |
| crate::{ |
| capability_routing::{ |
| error::CapabilityRouteError, |
| source::CapabilitySourceType, |
| verifier::{CapabilityRouteState, CapabilityRouteVerifier}, |
| }, |
| component_tree::ComponentNode, |
| }, |
| cm_rust::{ |
| CapabilityDecl, CapabilityName, ExposeDecl, ExposeProtocolDecl, ExposeTarget, OfferDecl, |
| OfferProtocolDecl, OfferTarget, ProtocolDecl, UseDecl, UseProtocolDecl, |
| }, |
| moniker::PartialMoniker, |
| }; |
| |
| /// A verifier for protocol capability routes. |
| pub struct ProtocolCapabilityRouteVerifier {} |
| |
| impl ProtocolCapabilityRouteVerifier { |
| pub fn new() -> Self { |
| ProtocolCapabilityRouteVerifier {} |
| } |
| } |
| |
| pub struct ProtocolFields {} |
| |
| impl<'a> CapabilityRouteVerifier<'a> for ProtocolCapabilityRouteVerifier { |
| type UseDeclType = UseProtocolDecl; |
| type OfferDeclType = OfferProtocolDecl; |
| type ExposeDeclType = ExposeProtocolDecl; |
| type CapabilityDeclType = ProtocolDecl; |
| type FieldsType = ProtocolFields; |
| |
| fn verify_route_segment( |
| &self, |
| _target_state: &CapabilityRouteState<'a, ProtocolFields>, |
| _source_state: &CapabilityRouteState<'a, ProtocolFields>, |
| ) -> Result<(), CapabilityRouteError> { |
| Ok(()) |
| } |
| |
| // Implementations specific to `UseProtocolDecl` |
| |
| fn get_use_info(&self, use_decl: &UseProtocolDecl) -> (CapabilityName, CapabilitySourceType) { |
| (use_decl.source_name.clone(), CapabilitySourceType::from(&use_decl.source)) |
| } |
| |
| fn get_use_decls(&self, node: &'a ComponentNode) -> Vec<&'a UseProtocolDecl> { |
| let mut use_dir_decls = Vec::<&UseProtocolDecl>::new(); |
| for decl in node.decl.uses.iter() { |
| if let UseDecl::Protocol(use_dir_decl) = decl { |
| use_dir_decls.push(use_dir_decl); |
| } |
| } |
| use_dir_decls |
| } |
| |
| // Implementations specific to `OfferProtocolDecl` |
| |
| fn get_offer_info( |
| &self, |
| offer_decl: &OfferProtocolDecl, |
| ) -> (CapabilityName, CapabilityName, CapabilitySourceType) { |
| ( |
| offer_decl.target_name.clone(), |
| offer_decl.source_name.clone(), |
| CapabilitySourceType::from(&offer_decl.source), |
| ) |
| } |
| |
| fn get_offer_decls(&self, node: &'a ComponentNode) -> Vec<&'a OfferProtocolDecl> { |
| let mut offer_dir_decls = Vec::<&OfferProtocolDecl>::new(); |
| for decl in node.decl.offers.iter() { |
| if let OfferDecl::Protocol(offer_dir_decl) = decl { |
| offer_dir_decls.push(offer_dir_decl); |
| } |
| } |
| offer_dir_decls |
| } |
| |
| fn is_matching_offer( |
| &self, |
| target_state: &CapabilityRouteState<'a, ProtocolFields>, |
| target_moniker: &'a PartialMoniker, |
| offer_decl: &'a OfferProtocolDecl, |
| ) -> bool { |
| if let OfferTarget::Child(child_name) = &offer_decl.target { |
| return (&child_name.as_str(), &offer_decl.target_name) |
| == (&target_moniker.as_str(), &target_state.name); |
| } |
| false |
| } |
| |
| // Implementations specific to `ExposeProtocolDecl` |
| |
| fn get_expose_info( |
| &self, |
| expose_decl: &ExposeProtocolDecl, |
| ) -> (CapabilityName, CapabilityName, CapabilitySourceType) { |
| ( |
| expose_decl.target_name.clone(), |
| expose_decl.source_name.clone(), |
| CapabilitySourceType::from(&expose_decl.source), |
| ) |
| } |
| |
| fn get_expose_decls(&self, node: &'a ComponentNode) -> Vec<&'a ExposeProtocolDecl> { |
| let mut expose_dir_decls = Vec::<&ExposeProtocolDecl>::new(); |
| for decl in node.decl.exposes.iter() { |
| if let ExposeDecl::Protocol(expose_dir_decl) = decl { |
| expose_dir_decls.push(expose_dir_decl); |
| } |
| } |
| expose_dir_decls |
| } |
| |
| fn is_matching_expose( |
| &self, |
| target_state: &CapabilityRouteState<'a, ProtocolFields>, |
| expose_decl: &'a ExposeProtocolDecl, |
| ) -> bool { |
| if ExposeTarget::Parent == expose_decl.target { |
| return expose_decl.target_name == target_state.name; |
| } |
| false |
| } |
| |
| // Implementations specific to `ProtocolDecl` |
| |
| fn get_declare_info(&self, capability_decl: &ProtocolDecl) -> CapabilityName { |
| capability_decl.name.clone() |
| } |
| |
| fn get_capability_decls(&self, node: &'a ComponentNode) -> Vec<&'a ProtocolDecl> { |
| let mut dir_decls = Vec::<&ProtocolDecl>::new(); |
| for decl in node.decl.capabilities.iter() { |
| if let CapabilityDecl::Protocol(dir_decl) = decl { |
| dir_decls.push(dir_decl); |
| } |
| } |
| dir_decls |
| } |
| |
| fn is_matching_declare( |
| &self, |
| route_state: &CapabilityRouteState<'a, ProtocolFields>, |
| decl: &'a ProtocolDecl, |
| ) -> bool { |
| decl.name == route_state.name |
| } |
| |
| // Implementations specific to `ProtocolFields` |
| |
| fn fields_from_use( |
| &self, |
| _use_decl: &UseProtocolDecl, |
| ) -> Result<ProtocolFields, CapabilityRouteError> { |
| Ok(ProtocolFields {}) |
| } |
| |
| fn fields_from_offer( |
| &self, |
| _target_state: &CapabilityRouteState<'a, ProtocolFields>, |
| _offer_decl: &'a OfferProtocolDecl, |
| ) -> Result<ProtocolFields, CapabilityRouteError> { |
| Ok(ProtocolFields {}) |
| } |
| |
| fn fields_from_expose( |
| &self, |
| _target_state: &CapabilityRouteState<'a, ProtocolFields>, |
| _expose_decl: &'a ExposeProtocolDecl, |
| ) -> Result<ProtocolFields, CapabilityRouteError> { |
| Ok(ProtocolFields {}) |
| } |
| |
| fn fields_from_declare( |
| &self, |
| _capability_decl: &'a ProtocolDecl, |
| ) -> Result<ProtocolFields, CapabilityRouteError> { |
| Ok(ProtocolFields {}) |
| } |
| |
| fn fields_from_framework( |
| &self, |
| _target_state: &CapabilityRouteState<'a, ProtocolFields>, |
| ) -> Result<ProtocolFields, CapabilityRouteError> { |
| Ok(ProtocolFields {}) |
| } |
| |
| fn fields_from_root_parent( |
| &self, |
| _target_state: &CapabilityRouteState<'a, ProtocolFields>, |
| ) -> Result<ProtocolFields, CapabilityRouteError> { |
| Ok(ProtocolFields {}) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use { |
| super::*, |
| crate::{ |
| capability_routing::testing::*, |
| component_tree::{BuildTreeResult, ComponentTreeBuilder, NodePath}, |
| }, |
| cm_rust::{CapabilityPath, DependencyType, ExposeSource, OfferSource, UseSource}, |
| moniker::PartialMoniker, |
| std::collections::HashMap, |
| }; |
| |
| fn new_use_protocol_decl(source: UseSource, source_name: CapabilityName) -> UseProtocolDecl { |
| UseProtocolDecl { |
| source, |
| source_name, |
| target_path: CapabilityPath { dirname: "".to_string(), basename: "".to_string() }, |
| } |
| } |
| |
| fn new_offer_protocol_decl( |
| source: OfferSource, |
| source_name: CapabilityName, |
| target: OfferTarget, |
| target_name: CapabilityName, |
| ) -> OfferProtocolDecl { |
| OfferProtocolDecl { |
| source, |
| source_name, |
| target, |
| target_name, |
| dependency_type: DependencyType::Strong, |
| } |
| } |
| |
| fn new_expose_protocol_decl( |
| source: ExposeSource, |
| source_name: CapabilityName, |
| target: ExposeTarget, |
| target_name: CapabilityName, |
| ) -> ExposeProtocolDecl { |
| ExposeProtocolDecl { source, source_name, target, target_name } |
| } |
| |
| fn new_protocol_decl(name: CapabilityName) -> ProtocolDecl { |
| ProtocolDecl { |
| name, |
| source_path: CapabilityPath { dirname: "".to_string(), basename: "".to_string() }, |
| } |
| } |
| |
| // Builds a `ComponentTree` with 4 nodes and the following structure: |
| // |
| // root |
| // / \ |
| // foo bar |
| // / |
| // baz |
| // |
| // In addition, adds routing for the protocol capability specified in `use_decl`. |
| // The capability is provided by `bar` and used by `baz` via `root` and `foo`. Each node |
| // defines its own alias for the capability. |
| fn build_tree_with_protocol_route() -> BuildTreeResult { |
| let root_url = "root_url".to_string(); |
| let foo_url = "foo_url".to_string(); |
| let bar_url = "bar_url".to_string(); |
| let baz_url = "baz_url".to_string(); |
| |
| let foo_name = "foo".to_string(); |
| let bar_name = "bar".to_string(); |
| let baz_name = "baz".to_string(); |
| |
| let root_protocol_name = CapabilityName("root_protocol_name".to_string()); |
| let foo_protocol_name = CapabilityName("foo_protocol_name".to_string()); |
| let bar_protocol_name = CapabilityName("bar_protocol_name".to_string()); |
| let baz_protocol_name = CapabilityName("baz_protocol_name".to_string()); |
| |
| let bar_decl = new_component_decl( |
| vec![], |
| vec![ExposeDecl::Protocol(new_expose_protocol_decl( |
| ExposeSource::Self_, |
| bar_protocol_name.clone(), |
| ExposeTarget::Parent, |
| root_protocol_name.clone(), |
| ))], |
| vec![], |
| vec![CapabilityDecl::Protocol(new_protocol_decl(bar_protocol_name))], |
| vec![], |
| ); |
| let root_decl = new_component_decl( |
| vec![], |
| vec![], |
| vec![OfferDecl::Protocol(new_offer_protocol_decl( |
| OfferSource::Child(bar_name.clone()), |
| root_protocol_name, |
| OfferTarget::Child(foo_name.clone()), |
| foo_protocol_name.clone(), |
| ))], |
| vec![], |
| vec![new_child_decl(&foo_name, &foo_url), new_child_decl(&bar_name, &bar_url)], |
| ); |
| let foo_decl = new_component_decl( |
| vec![], |
| vec![], |
| vec![OfferDecl::Protocol(new_offer_protocol_decl( |
| OfferSource::Parent, |
| foo_protocol_name, |
| OfferTarget::Child(baz_name.clone()), |
| baz_protocol_name.clone(), |
| ))], |
| vec![], |
| vec![new_child_decl(&baz_name, &baz_url)], |
| ); |
| let baz_decl = new_component_decl( |
| vec![UseDecl::Protocol(new_use_protocol_decl(UseSource::Parent, baz_protocol_name))], |
| vec![], |
| vec![], |
| vec![], |
| vec![], |
| ); |
| |
| let mut decls = HashMap::new(); |
| decls.insert(root_url.to_string(), root_decl.clone()); |
| decls.insert(foo_url.to_string(), foo_decl.clone()); |
| decls.insert(bar_url.to_string(), bar_decl.clone()); |
| decls.insert(baz_url.to_string(), baz_decl.clone()); |
| |
| ComponentTreeBuilder::new(decls).build(root_url) |
| } |
| |
| // Checks that `ProtocolCapabilityRouteVerifier::verify_route()` accepts a valid 2-node route. |
| #[test] |
| fn protocol_verify_offer_from_parent() -> Result<(), CapabilityRouteError> { |
| let verifier = ProtocolCapabilityRouteVerifier::new(); |
| let child_name = "child".to_string(); |
| let protocol_name = CapabilityName("protocol_name".to_string()); |
| |
| let root_offer_protocol = new_offer_protocol_decl( |
| OfferSource::Self_, |
| protocol_name.clone(), |
| OfferTarget::Child(child_name.clone()), |
| protocol_name.clone(), |
| ); |
| let root_protocol_decl = new_protocol_decl(protocol_name.clone()); |
| |
| let child_use_protocol = new_use_protocol_decl(UseSource::Parent, protocol_name); |
| |
| let build_tree_result = build_two_node_tree( |
| vec![], |
| vec![OfferDecl::Protocol(root_offer_protocol)], |
| vec![CapabilityDecl::Protocol(root_protocol_decl)], |
| vec![UseDecl::Protocol(child_use_protocol.clone())], |
| vec![], |
| vec![], |
| ); |
| assert!(build_tree_result.tree.is_some()); |
| let tree = build_tree_result.tree.unwrap(); |
| |
| let child_node = |
| tree.get_node(&NodePath::new(vec![PartialMoniker::new(child_name, None)]))?; |
| let route = verifier.verify_route(&tree, &child_use_protocol, &child_node)?; |
| assert_eq!(route.len(), 3); |
| assert_eq!(route[0].to_string(), "use by `/child` as `protocol_name` from parent"); |
| assert_eq!(route[1].to_string(), "offer by `/` as `protocol_name` from self"); |
| assert_eq!(route[2].to_string(), "declare by `/` as `protocol_name`"); |
| |
| Ok(()) |
| } |
| |
| // Checks that `ProtocolCapabilityRouteVerifier::verify_route()` rejects a route when |
| // a child node has a `UseProtocolDecl` with a source of Parent, but its parent has no |
| // matching `OfferProtocolDecl`. |
| #[test] |
| fn protocol_verify_missing_offer() -> Result<(), CapabilityRouteError> { |
| let verifier = ProtocolCapabilityRouteVerifier::new(); |
| let child_name = "child".to_string(); |
| let protocol_name = CapabilityName("protocol_name".to_string()); |
| let child_use_protocol = new_use_protocol_decl(UseSource::Parent, protocol_name.clone()); |
| |
| let build_tree_result = build_two_node_tree( |
| vec![], |
| vec![], |
| vec![], |
| vec![UseDecl::Protocol(child_use_protocol.clone())], |
| vec![], |
| vec![], |
| ); |
| assert!(build_tree_result.tree.is_some()); |
| let tree = build_tree_result.tree.unwrap(); |
| |
| let child_node = |
| tree.get_node(&NodePath::new(vec![PartialMoniker::new(child_name, None)]))?; |
| let verify_result = verifier.verify_route(&tree, &child_use_protocol, &child_node); |
| assert!(verify_result.is_err()); |
| assert_eq!( |
| verify_result.err().unwrap(), |
| CapabilityRouteError::OfferDeclNotFound("/".to_string(), protocol_name.to_string()) |
| ); |
| |
| Ok(()) |
| } |
| |
| // Checks that `ProtocolCapabilityRouteVerifier::verify_route()` rejects a route when |
| // a child node has a `UseProtocolDecl` with a source of Parent, and the parent node |
| // has a matching `OfferProtocolDecl` with a source of Self but no matching |
| // `ProtocolDecl.` |
| #[test] |
| fn protocol_verify_missing_capability() -> Result<(), CapabilityRouteError> { |
| let verifier = ProtocolCapabilityRouteVerifier::new(); |
| let child_name = "child".to_string(); |
| let protocol_name = CapabilityName("protocol_name".to_string()); |
| |
| let root_offer_protocol = new_offer_protocol_decl( |
| OfferSource::Self_, |
| protocol_name.clone(), |
| OfferTarget::Child(child_name.clone()), |
| protocol_name.clone(), |
| ); |
| |
| let child_use_protocol = new_use_protocol_decl(UseSource::Parent, protocol_name.clone()); |
| |
| let build_tree_result = build_two_node_tree( |
| vec![], |
| vec![OfferDecl::Protocol(root_offer_protocol)], |
| vec![], |
| vec![UseDecl::Protocol(child_use_protocol.clone())], |
| vec![], |
| vec![], |
| ); |
| assert!(build_tree_result.tree.is_some()); |
| let tree = build_tree_result.tree.unwrap(); |
| |
| let child_node = |
| tree.get_node(&NodePath::new(vec![PartialMoniker::new(child_name, None)]))?; |
| let verify_result = verifier.verify_route(&tree, &child_use_protocol, &child_node); |
| assert!(verify_result.is_err()); |
| assert_eq!( |
| verify_result.err().unwrap(), |
| CapabilityRouteError::CapabilityDeclNotFound( |
| "/".to_string(), |
| protocol_name.to_string() |
| ) |
| ); |
| |
| Ok(()) |
| } |
| |
| // Checks that `ProtocolCapabilityRouteVerifier::verify_route()` accepts a valid 4-node route |
| // consisting of a use, two offers, and one expose with matching definition. |
| #[test] |
| fn protocol_verify_4_node_route() -> Result<(), CapabilityRouteError> { |
| let verifier = ProtocolCapabilityRouteVerifier::new(); |
| |
| let build_tree_result = build_tree_with_protocol_route(); |
| assert!(build_tree_result.tree.is_some()); |
| let tree = build_tree_result.tree.unwrap(); |
| |
| let using_node = tree.get_node(&NodePath::new(vec![ |
| PartialMoniker::new("foo".to_string(), None), |
| PartialMoniker::new("baz".to_string(), None), |
| ]))?; |
| assert_eq!(using_node.decl.uses.len(), 1); |
| let use_decl = &using_node.decl.uses[0]; |
| |
| if let UseDecl::Protocol(use_protocol_decl) = use_decl { |
| let route = verifier.verify_route(&tree, &use_protocol_decl, &using_node)?; |
| assert_eq!(route.len(), 5); |
| assert_eq!( |
| route[0].to_string(), |
| "use by `/foo/baz` as `baz_protocol_name` from parent" |
| ); |
| assert_eq!(route[1].to_string(), "offer by `/foo` as `baz_protocol_name` from parent"); |
| assert_eq!( |
| route[2].to_string(), |
| "offer by `/` as `foo_protocol_name` from child `bar`" |
| ); |
| assert_eq!(route[3].to_string(), "expose by `/bar` as `root_protocol_name` from self"); |
| assert_eq!(route[4].to_string(), "declare by `/bar` as `bar_protocol_name`"); |
| Ok(()) |
| } else { |
| panic!["Failed precondition: expected UseDecl of type Protocol"] |
| } |
| } |
| |
| // Checks that `ProtocolCapabilityRouteVerifier::verify_all_routes()` accepts a valid route and |
| // rejects an invalid route. |
| #[test] |
| fn protocol_verify_all_routes() -> Result<(), CapabilityRouteError> { |
| let verifier = ProtocolCapabilityRouteVerifier::new(); |
| |
| let child_name = "child".to_string(); |
| |
| let good_protocol_name = CapabilityName("good_protocol".to_string()); |
| let bad_protocol_name = CapabilityName("bad_protocol".to_string()); |
| |
| let good_offer_protocol = new_offer_protocol_decl( |
| OfferSource::Self_, |
| good_protocol_name.clone(), |
| OfferTarget::Child(child_name.clone()), |
| good_protocol_name.clone(), |
| ); |
| |
| let good_protocol_decl = new_protocol_decl(good_protocol_name.clone()); |
| let bad_protocol_decl = new_protocol_decl(bad_protocol_name.clone()); |
| |
| let good_use_protocol = |
| new_use_protocol_decl(UseSource::Parent, good_protocol_name.clone()); |
| let bad_use_protocol = new_use_protocol_decl(UseSource::Parent, bad_protocol_name.clone()); |
| |
| let build_tree_result = build_two_node_tree( |
| vec![], |
| vec![OfferDecl::Protocol(good_offer_protocol)], |
| vec![ |
| CapabilityDecl::Protocol(good_protocol_decl), |
| CapabilityDecl::Protocol(bad_protocol_decl), |
| ], |
| vec![UseDecl::Protocol(good_use_protocol), UseDecl::Protocol(bad_use_protocol)], |
| vec![], |
| vec![], |
| ); |
| assert!(build_tree_result.tree.is_some()); |
| let tree = build_tree_result.tree.unwrap(); |
| |
| let child_node = |
| tree.get_node(&NodePath::new(vec![PartialMoniker::new(child_name, None)]))?; |
| let mut results = verifier.verify_all_routes(&tree, &child_node); |
| |
| assert_eq!(results.len(), 2); |
| let good_result = results.remove(0); |
| let bad_result = results.remove(0); |
| |
| assert_eq!(good_result.using_node.to_string(), "/child"); |
| assert_eq!(good_result.capability, good_protocol_name); |
| assert!(good_result.result.is_ok()); |
| let good_route = good_result.result.as_ref().unwrap(); |
| assert_eq!(good_route.len(), 3); |
| assert_eq!(good_route[0].to_string(), "use by `/child` as `good_protocol` from parent"); |
| assert_eq!(good_route[1].to_string(), "offer by `/` as `good_protocol` from self"); |
| assert_eq!(good_route[2].to_string(), "declare by `/` as `good_protocol`"); |
| |
| assert_eq!(bad_result.using_node.to_string(), "/child"); |
| assert_eq!(bad_result.capability, bad_protocol_name); |
| assert!(bad_result.result.is_err()); |
| assert_eq!( |
| *bad_result.result.err().as_ref().unwrap(), |
| CapabilityRouteError::OfferDeclNotFound("/".to_string(), "bad_protocol".to_string()) |
| ); |
| |
| Ok(()) |
| } |
| } |