| // 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::{ |
| component_instance::{ComponentInstanceForAnalyzer, TopInstanceForAnalyzer}, |
| match_absolute_component_urls, |
| node_path::NodePath, |
| route::{RouteMap, RouteSegment, VerifyRouteResult}, |
| PkgUrlMatch, |
| }, |
| anyhow::{anyhow, Context, Result}, |
| cm_moniker::InstancedRelativeMoniker, |
| cm_rust::{ |
| CapabilityDecl, CapabilityPath, CapabilityTypeName, ComponentDecl, ExposeDecl, |
| ExposeDeclCommon, ProgramDecl, ResolverRegistration, UseDecl, UseEventStreamDecl, |
| UseStorageDecl, |
| }, |
| fidl::prelude::*, |
| fidl_fuchsia_sys2 as fsys, |
| fuchsia_url::AbsoluteComponentUrl, |
| fuchsia_zircon_status as zx_status, |
| futures::FutureExt, |
| moniker::{AbsoluteMoniker, AbsoluteMonikerBase, ChildMoniker}, |
| routing::{ |
| capability_source::{ |
| CapabilitySourceInterface, ComponentCapability, StorageCapabilitySource, |
| }, |
| component_id_index::ComponentIdIndex, |
| component_instance::{ |
| ComponentInstanceInterface, ExtendedInstanceInterface, TopInstanceInterface, |
| }, |
| config::RuntimeConfig, |
| environment::{ |
| component_has_relative_url, find_first_absolute_ancestor_url, RunnerRegistry, |
| }, |
| error::{AvailabilityRoutingError, ComponentInstanceError, RoutingError}, |
| policy::GlobalPolicyChecker, |
| route_capability, route_event_stream_capability, route_storage_and_backing_directory, |
| DebugRouteMapper, RouteRequest, RouteSource, |
| }, |
| serde::{Deserialize, Serialize}, |
| std::{ |
| collections::{HashMap, HashSet}, |
| sync::Arc, |
| }, |
| thiserror::Error, |
| url::Url, |
| }; |
| |
| /// Errors that may occur when building a `ComponentModelForAnalyzer` from |
| /// a set of component manifests. |
| #[derive(Clone, Debug, Deserialize, Error, PartialEq, Serialize)] |
| #[serde(rename_all = "snake_case")] |
| pub enum BuildAnalyzerModelError { |
| #[error("no component declaration found for url `{0}` requested by node `{1}`")] |
| ComponentDeclNotFound(String, String), |
| |
| #[error("invalid child declaration containing url `{0}` at node `{1}`")] |
| InvalidChildDecl(String, String), |
| |
| #[error("no node found with path `{0}`")] |
| ComponentNodeNotFound(String), |
| |
| #[error("environment `{0}` requested by child `{1}` not found at node `{2}`")] |
| EnvironmentNotFound(String, String, String), |
| |
| #[error("multiple resolvers found for scheme `{0}`")] |
| DuplicateResolverScheme(String), |
| |
| #[error("malformed url {0} for component instance {1}")] |
| MalformedUrl(String, String), |
| |
| #[error("dynamic component with url {0} an invalid moniker")] |
| DynamicComponentInvalidMoniker(String), |
| |
| #[error("dynamic component at {0} with url {1} is not part of a collection")] |
| DynamicComponentWithoutCollection(String, String), |
| } |
| |
| /// Errors that a `ComponentModelForAnalyzer` may detect in the component graph. |
| #[derive(Clone, Debug, Error, Deserialize, Serialize, PartialEq)] |
| #[serde(rename_all = "snake_case")] |
| pub enum AnalyzerModelError { |
| #[error("the source instance `{0}` is not executable")] |
| SourceInstanceNotExecutable(String), |
| |
| #[error("the capability `{0}` is not a valid source for the capability `{1}`")] |
| InvalidSourceCapability(String, String), |
| |
| #[error("uses Event capability `{0}` without using the EventSource protocol")] |
| MissingEventSourceProtocol(String), |
| |
| #[error("no resolver found in environment for scheme `{0}`")] |
| MissingResolverForScheme(String), |
| |
| #[error(transparent)] |
| ComponentInstanceError(#[from] ComponentInstanceError), |
| |
| #[error(transparent)] |
| RoutingError(#[from] RoutingError), |
| } |
| |
| impl AnalyzerModelError { |
| pub fn as_zx_status(&self) -> zx_status::Status { |
| match self { |
| Self::SourceInstanceNotExecutable(_) => zx_status::Status::UNAVAILABLE, |
| Self::InvalidSourceCapability(_, _) => zx_status::Status::UNAVAILABLE, |
| Self::MissingEventSourceProtocol(_) => zx_status::Status::UNAVAILABLE, |
| Self::MissingResolverForScheme(_) => zx_status::Status::NOT_FOUND, |
| Self::ComponentInstanceError(err) => err.as_zx_status(), |
| Self::RoutingError(err) => err.as_zx_status(), |
| } |
| } |
| } |
| |
| /// Builds a `ComponentModelForAnalyzer` from a set of component manifests. |
| pub struct ModelBuilderForAnalyzer { |
| default_root_url: Url, |
| } |
| |
| /// The type returned by `ModelBuilderForAnalyzer::build()`. May contain some |
| /// errors even if `model` is `Some`. |
| pub struct BuildModelResult { |
| pub model: Option<Arc<ComponentModelForAnalyzer>>, |
| pub errors: Vec<anyhow::Error>, |
| } |
| |
| impl BuildModelResult { |
| fn new() -> Self { |
| Self { model: None, errors: Vec::new() } |
| } |
| } |
| |
| impl ModelBuilderForAnalyzer { |
| pub fn new(default_root_url: Url) -> Self { |
| Self { default_root_url } |
| } |
| |
| fn load_dynamic_components( |
| input: HashMap<NodePath, (AbsoluteComponentUrl, Option<String>)>, |
| ) -> (HashMap<AbsoluteMoniker, Vec<Child>>, Vec<anyhow::Error>) { |
| let mut errors: Vec<anyhow::Error> = vec![]; |
| let mut dynamic_components: HashMap<AbsoluteMoniker, Vec<Child>> = HashMap::new(); |
| for (node_path, (url, environment)) in input.into_iter() { |
| let mut moniker_vec = node_path.as_vec(); |
| let child_moniker_str = moniker_vec.pop(); |
| if child_moniker_str.is_none() { |
| errors.push( |
| BuildAnalyzerModelError::DynamicComponentInvalidMoniker(url.to_string()).into(), |
| ); |
| continue; |
| } |
| let child_moniker_str = child_moniker_str.unwrap(); |
| |
| let abs_moniker: AbsoluteMoniker = moniker_vec.into(); |
| let child_moniker: ChildMoniker = child_moniker_str.into(); |
| if child_moniker.collection.is_none() { |
| errors.push( |
| BuildAnalyzerModelError::DynamicComponentWithoutCollection( |
| node_path.to_string(), |
| url.to_string(), |
| ) |
| .into(), |
| ); |
| continue; |
| } |
| |
| let children = dynamic_components.entry(abs_moniker.clone()).or_insert_with(|| vec![]); |
| match Url::parse(&url.to_string()) { |
| Ok(url) => { |
| children.push(Child { child_moniker, url, environment }); |
| } |
| Err(_) => { |
| let node_path: NodePath = abs_moniker.into(); |
| errors.push( |
| BuildAnalyzerModelError::MalformedUrl( |
| url.to_string(), |
| node_path.to_string(), |
| ) |
| .into(), |
| ); |
| } |
| } |
| } |
| (dynamic_components, errors) |
| } |
| |
| pub fn build( |
| self, |
| decls_by_url: HashMap<Url, ComponentDecl>, |
| runtime_config: Arc<RuntimeConfig>, |
| component_id_index: Arc<ComponentIdIndex>, |
| runner_registry: RunnerRegistry, |
| ) -> BuildModelResult { |
| self.build_with_dynamic_components( |
| HashMap::new(), |
| decls_by_url, |
| runtime_config, |
| component_id_index, |
| runner_registry, |
| ) |
| } |
| |
| pub fn build_with_dynamic_components( |
| self, |
| dynamic_components: HashMap<NodePath, (AbsoluteComponentUrl, Option<String>)>, |
| decls_by_url: HashMap<Url, ComponentDecl>, |
| runtime_config: Arc<RuntimeConfig>, |
| component_id_index: Arc<ComponentIdIndex>, |
| runner_registry: RunnerRegistry, |
| ) -> BuildModelResult { |
| let mut result = BuildModelResult::new(); |
| |
| let (dynamic_components, mut dynamic_component_errors) = |
| Self::load_dynamic_components(dynamic_components); |
| result.errors.append(&mut dynamic_component_errors); |
| |
| // Initialize the model with an empty `instances` map. |
| let mut model = ComponentModelForAnalyzer { |
| top_instance: TopInstanceForAnalyzer::new( |
| runtime_config.namespace_capabilities.clone(), |
| runtime_config.builtin_capabilities.clone(), |
| ), |
| instances: HashMap::new(), |
| policy_checker: GlobalPolicyChecker::new(Arc::clone(&runtime_config)), |
| component_id_index, |
| }; |
| |
| let root_url = match &runtime_config.root_component_url { |
| None => Some(self.default_root_url.clone()), |
| Some(url) => match Url::parse(url.as_str()) { |
| Ok(url) => Some(url), |
| Err(err) => { |
| result.errors.push(anyhow!( |
| r#"Failed to parse root cm URL "{}" as generic URL: {:?}"#, |
| url.as_str(), |
| err |
| )); |
| None |
| } |
| }, |
| }; |
| |
| // If `root_url` matches a `ComponentDecl` in `decls_by_url`, construct the root |
| // instance and then recursively add child instances to the model. |
| if let Some(root_url) = root_url { |
| match Self::get_decl_by_url(&decls_by_url, &root_url) { |
| Err(err) => { |
| result |
| .errors |
| .push(err.context("Failed to parse root URL as fuchsia package URL")); |
| } |
| Ok(None) => { |
| result |
| .errors |
| .push(anyhow!("Failed to locate root component with URL: {}", &root_url)); |
| } |
| Ok(Some(root_decl)) => { |
| let root_instance = ComponentInstanceForAnalyzer::new_root( |
| root_decl.clone(), |
| root_url.to_string(), |
| Arc::clone(&model.top_instance), |
| Arc::clone(&runtime_config), |
| model.policy_checker.clone(), |
| Arc::clone(&model.component_id_index), |
| runner_registry, |
| false, |
| ); |
| |
| Self::add_descendants( |
| &root_instance, |
| &decls_by_url, |
| &dynamic_components, |
| &mut model, |
| &mut result, |
| ); |
| |
| model |
| .instances |
| .insert(NodePath::from(root_instance.abs_moniker().clone()), root_instance); |
| |
| result.model = Some(Arc::new(model)); |
| } |
| } |
| } |
| |
| result |
| } |
| |
| // Adds all descendants of `instance` to `model`, also inserting each new instance |
| // in the `children` map of its parent, including children denoted in |
| // `dynamic_components`. |
| fn add_descendants( |
| instance: &Arc<ComponentInstanceForAnalyzer>, |
| decls_by_url: &HashMap<Url, ComponentDecl>, |
| dynamic_components: &HashMap<AbsoluteMoniker, Vec<Child>>, |
| model: &mut ComponentModelForAnalyzer, |
| result: &mut BuildModelResult, |
| ) { |
| let mut children = vec![]; |
| for child_decl in instance.decl.children.iter() { |
| match Self::get_absolute_child_url(&child_decl.url, instance) { |
| Ok(url) => { |
| children.push(Child { |
| child_moniker: ChildMoniker::new(child_decl.name.clone(), None), |
| url, |
| environment: child_decl.environment.clone(), |
| }); |
| } |
| Err(err) => { |
| result.errors.push(anyhow!(err)); |
| } |
| } |
| } |
| if let Some(dynamic_children) = dynamic_components.get(instance.abs_moniker()) { |
| children.append( |
| &mut dynamic_children |
| .into_iter() |
| .map(|dynamic_child| dynamic_child.clone()) |
| .collect(), |
| ); |
| } |
| |
| for child in children.iter() { |
| match Self::get_absolute_child_url(&child.url.to_string(), instance) { |
| Ok(url) => { |
| let absolute_url = url; |
| if child.child_moniker.name.is_empty() { |
| result.errors.push(anyhow!(BuildAnalyzerModelError::InvalidChildDecl( |
| absolute_url.to_string(), |
| NodePath::from(instance.abs_moniker().clone()).to_string(), |
| ))); |
| continue; |
| } |
| |
| match Self::get_decl_by_url(decls_by_url, &absolute_url) |
| .context("Failed to parse absolute child URL") |
| { |
| Err(err) => { |
| result.errors.push(err); |
| } |
| Ok(Some(child_component_decl)) => { |
| match ComponentInstanceForAnalyzer::new_for_child( |
| child, |
| absolute_url.to_string(), |
| child_component_decl.clone(), |
| Arc::clone(instance), |
| model.policy_checker.clone(), |
| Arc::clone(&model.component_id_index), |
| ) { |
| Ok(child_instance) => { |
| Self::add_descendants( |
| &child_instance, |
| decls_by_url, |
| dynamic_components, |
| model, |
| result, |
| ); |
| |
| instance.add_child( |
| child.child_moniker.clone(), |
| Arc::clone(&child_instance), |
| ); |
| |
| model.instances.insert( |
| NodePath::from(child_instance.abs_moniker().clone()), |
| child_instance, |
| ); |
| } |
| Err(err) => { |
| result.errors.push(anyhow!(err)); |
| } |
| } |
| } |
| Ok(None) => { |
| result.errors.push(anyhow!( |
| BuildAnalyzerModelError::ComponentDeclNotFound( |
| absolute_url.to_string(), |
| NodePath::from(instance.abs_moniker().clone()).to_string(), |
| ) |
| )); |
| } |
| } |
| } |
| Err(err) => { |
| result.errors.push(anyhow!(err)); |
| } |
| } |
| } |
| } |
| |
| // Given a component instance and the url `child_url` of a child of that instance, |
| // returns an absolute url for the child. |
| fn get_absolute_child_url( |
| child_url: &str, |
| instance: &Arc<ComponentInstanceForAnalyzer>, |
| ) -> Result<Url, BuildAnalyzerModelError> { |
| let err = BuildAnalyzerModelError::MalformedUrl( |
| instance.url().to_string(), |
| instance.node_path().to_string(), |
| ); |
| |
| match Url::parse(child_url) { |
| Ok(url) => Ok(url), |
| Err(url::ParseError::RelativeUrlWithoutBase) => { |
| let absolute_prefix = match component_has_relative_url(instance) { |
| true => find_first_absolute_ancestor_url(instance).map_err(|_| err), |
| false => Url::parse(instance.url()).map_err(|_| err), |
| }?; |
| Ok(absolute_prefix |
| .join(child_url) |
| .expect("failed to join child URL to absolute prefix")) |
| } |
| _ => Err(err), |
| } |
| } |
| |
| fn get_decl_by_url<'a>( |
| decls_by_url: &'a HashMap<Url, ComponentDecl>, |
| url: &Url, |
| ) -> Result<Option<&'a ComponentDecl>> { |
| // Non-`fuchsia-pkg` URLs are not matched with nuance: they must precisely match an entry in |
| // `decls_by_url`. |
| if url.scheme() != "fuchsia-pkg" { |
| return Ok(decls_by_url.get(url)); |
| } |
| |
| let fuchsia_component_url = AbsoluteComponentUrl::parse(url.as_str()) |
| .context("Failed to parse component fuchsia-pkg URL as absolute package URL")?; |
| |
| // Gather both strong and weak URL matches against `fuchsia_component_url`. |
| let decl_url_matches = decls_by_url |
| .keys() |
| .filter_map(|decl_url| { |
| if decl_url.scheme() != "fuchsia-pkg" { |
| None |
| } else if let Ok(decl_fuchsia_pkg_url) = |
| AbsoluteComponentUrl::parse(decl_url.as_str()) |
| { |
| match match_absolute_component_urls( |
| &decl_fuchsia_pkg_url, |
| &fuchsia_component_url, |
| ) { |
| PkgUrlMatch::NoMatch => None, |
| pkg_url_match => Some((decl_url, pkg_url_match)), |
| } |
| } else { |
| None |
| } |
| }) |
| .collect::<Vec<(&Url, PkgUrlMatch)>>(); |
| |
| // Return best match. Emit warning or error when multiple matches are found. |
| if decl_url_matches.len() == 0 { |
| return Ok(None); |
| } else if decl_url_matches.len() == 1 { |
| if decl_url_matches[0].1 == PkgUrlMatch::WeakMatch { |
| log::warn!("Weak component URL match: {} matches {}", url, decl_url_matches[0].0); |
| } |
| return Ok(decls_by_url.get(decl_url_matches[0].0)); |
| } else { |
| let strong_decl_url_matches = decl_url_matches |
| .iter() |
| .filter_map(|(url, url_match)| match url_match { |
| PkgUrlMatch::StrongMatch => Some(*url), |
| _ => None, |
| }) |
| .collect::<Vec<&Url>>(); |
| |
| if strong_decl_url_matches.len() == 0 { |
| log::warn!( |
| "Multiple weak component URL matches for {}; matching to first: {}", |
| url, |
| decl_url_matches[0].0 |
| ); |
| return Ok(decls_by_url.get(decl_url_matches[0].0)); |
| } else { |
| if strong_decl_url_matches.len() > 1 { |
| log::error!( |
| "Multiple strong package URL matches for {}; matching to first: {}", |
| url, |
| strong_decl_url_matches[0] |
| ); |
| } |
| return Ok(decls_by_url.get(strong_decl_url_matches[0])); |
| } |
| } |
| } |
| } |
| |
| /// `ComponentModelForAnalyzer` owns a representation of the v2 component graph and |
| /// supports lookup of component instances by `NodePath`. |
| #[derive(Default)] |
| pub struct ComponentModelForAnalyzer { |
| top_instance: Arc<TopInstanceForAnalyzer>, |
| instances: HashMap<NodePath, Arc<ComponentInstanceForAnalyzer>>, |
| policy_checker: GlobalPolicyChecker, |
| component_id_index: Arc<ComponentIdIndex>, |
| } |
| |
| impl ComponentModelForAnalyzer { |
| /// Returns the number of component instances in the model, not counting the top instance. |
| pub fn len(&self) -> usize { |
| self.instances.len() |
| } |
| |
| pub fn get_root_instance( |
| self: &Arc<Self>, |
| ) -> Result<Arc<ComponentInstanceForAnalyzer>, ComponentInstanceError> { |
| self.get_instance(&NodePath::absolute_from_vec(vec![])) |
| } |
| |
| /// Returns the component instance corresponding to `id` if it is present in the model, or an |
| /// `InstanceNotFound` error if not. |
| pub fn get_instance( |
| self: &Arc<Self>, |
| id: &NodePath, |
| ) -> Result<Arc<ComponentInstanceForAnalyzer>, ComponentInstanceError> { |
| match self.instances.get(id) { |
| Some(instance) => Ok(Arc::clone(instance)), |
| None => Err(ComponentInstanceError::instance_not_found( |
| AbsoluteMoniker::parse_str(&id.to_string()).unwrap(), |
| )), |
| } |
| } |
| |
| /// Checks the routing for all capabilities of the specified types that are `used` by `target`. |
| pub fn check_routes_for_instance( |
| self: &Arc<Self>, |
| target: &Arc<ComponentInstanceForAnalyzer>, |
| capability_types: &HashSet<CapabilityTypeName>, |
| ) -> HashMap<CapabilityTypeName, Vec<VerifyRouteResult>> { |
| let mut results = HashMap::new(); |
| for capability_type in capability_types.iter() { |
| results.insert(capability_type.clone(), vec![]); |
| } |
| |
| for use_decl in target.decl.uses.iter().filter(|&u| capability_types.contains(&u.into())) { |
| let type_results = results |
| .get_mut(&CapabilityTypeName::from(use_decl)) |
| .expect("expected results for capability type"); |
| for result in self.check_use_capability(use_decl, &target) { |
| type_results.push(result); |
| } |
| } |
| |
| for expose_decl in |
| target.decl.exposes.iter().filter(|&e| capability_types.contains(&e.into())) |
| { |
| let type_results = results |
| .get_mut(&CapabilityTypeName::from(expose_decl)) |
| .expect("expected results for capability type"); |
| if let Some(result) = self.check_use_exposed_capability(expose_decl, &target) { |
| type_results.push(result); |
| } |
| } |
| |
| if capability_types.contains(&CapabilityTypeName::Runner) { |
| if let Some(ref program) = target.decl.program { |
| let type_results = results |
| .get_mut(&CapabilityTypeName::Runner) |
| .expect("expected results for capability type"); |
| if let Some(result) = self.check_program_runner(program, &target) { |
| type_results.push(result); |
| } |
| } |
| } |
| |
| if capability_types.contains(&CapabilityTypeName::Resolver) { |
| let type_results = results |
| .get_mut(&CapabilityTypeName::Resolver) |
| .expect("expected results for capability type"); |
| type_results.push(self.check_resolver(&target)); |
| } |
| |
| results |
| } |
| |
| /// Given a `UseDecl` for a capability at an instance `target`, first routes the capability |
| /// to its source and then validates the source. |
| pub fn check_use_capability( |
| self: &Arc<Self>, |
| use_decl: &UseDecl, |
| target: &Arc<ComponentInstanceForAnalyzer>, |
| ) -> Vec<VerifyRouteResult> { |
| let mut results = Vec::new(); |
| let route_result = match use_decl.clone() { |
| UseDecl::Directory(use_directory_decl) => { |
| let capability = use_directory_decl.source_name.clone(); |
| match Self::route_capability_sync( |
| RouteRequest::UseDirectory(use_directory_decl), |
| target, |
| ) { |
| Ok((source, route)) => (Ok((source, vec![route])), capability), |
| // Ignore any route that failed due to a void offer to a target with an |
| // optional dependency on the capability. |
| Err(RoutingError::AvailabilityRoutingError( |
| AvailabilityRoutingError::OfferFromVoidToOptionalTarget, |
| )) => return vec![], |
| Err(err) => (Err(err.into()), capability), |
| } |
| } |
| UseDecl::Event(use_event_decl) => { |
| let capability = use_event_decl.target_name.clone(); |
| match self.uses_event_source_protocol(&target.decl) { |
| true => { |
| match Self::route_capability_sync( |
| RouteRequest::UseEvent(use_event_decl), |
| target, |
| ) { |
| Ok((source, route)) => (Ok((source, vec![route])), capability), |
| // Ignore any route that failed due to a void offer to a target with an |
| // optional dependency on the capability. |
| Err(RoutingError::AvailabilityRoutingError( |
| AvailabilityRoutingError::OfferFromVoidToOptionalTarget, |
| )) => return vec![], |
| Err(err) => (Err(err.into()), capability), |
| } |
| } |
| false => ( |
| Err(AnalyzerModelError::MissingEventSourceProtocol(capability.to_string())), |
| capability, |
| ), |
| } |
| } |
| UseDecl::Protocol(use_protocol_decl) => { |
| let capability = use_protocol_decl.source_name.clone(); |
| match Self::route_capability_sync( |
| RouteRequest::UseProtocol(use_protocol_decl), |
| target, |
| ) { |
| Ok((source, route)) => (Ok((source, vec![route])), capability), |
| // Ignore any route that failed due to a void offer to a target with an |
| // optional dependency on the capability. |
| Err(RoutingError::AvailabilityRoutingError( |
| AvailabilityRoutingError::OfferFromVoidToOptionalTarget, |
| )) => return vec![], |
| Err(err) => (Err(err.into()), capability), |
| } |
| } |
| UseDecl::Service(use_service_decl) => { |
| let capability = use_service_decl.source_name.clone(); |
| match Self::route_capability_sync( |
| RouteRequest::UseService(use_service_decl.clone()), |
| target, |
| ) { |
| Ok((source, route)) => (Ok((source, vec![route])), capability), |
| // Ignore any route that failed due to a void offer to a target with an |
| // optional dependency on the capability. |
| Err(RoutingError::AvailabilityRoutingError( |
| AvailabilityRoutingError::OfferFromVoidToOptionalTarget, |
| )) => return vec![], |
| Err(err) => (Err(err.into()), capability), |
| } |
| } |
| UseDecl::Storage(use_storage_decl) => { |
| let capability = use_storage_decl.source_name.clone(); |
| match Self::route_storage_and_backing_directory_sync(use_storage_decl, target) { |
| Ok((storage_source, _relative_moniker, storage_route, dir_route)) => ( |
| Ok(( |
| RouteSource::StorageBackingDirectory(storage_source), |
| vec![storage_route, dir_route], |
| )), |
| capability, |
| ), |
| // Ignore any route that failed due to a void offer to a target with an |
| // optional dependency on the capability. |
| Err(RoutingError::AvailabilityRoutingError( |
| AvailabilityRoutingError::OfferFromVoidToOptionalTarget, |
| )) => return vec![], |
| Err(err) => (Err(err.into()), capability), |
| } |
| } |
| UseDecl::EventStream(use_event_stream_decl) => { |
| let capability = use_event_stream_decl.source_name.clone(); |
| match Self::route_capability_sync( |
| RouteRequest::UseEventStream(use_event_stream_decl), |
| target, |
| ) { |
| Ok((source, route)) => (Ok((source, vec![route])), capability), |
| Err(err) => (Err(err.into()), capability), |
| } |
| } |
| _ => unimplemented![], |
| }; |
| match route_result { |
| (Ok((source, routes)), capability) => match self.check_use_source(&source) { |
| Ok(()) => { |
| for route in routes.into_iter() { |
| results.push(VerifyRouteResult { |
| using_node: target.node_path(), |
| capability: capability.clone(), |
| result: Ok(route.into()), |
| }); |
| } |
| } |
| Err(err) => { |
| results.push(VerifyRouteResult { |
| using_node: target.node_path(), |
| capability: capability.clone(), |
| result: Err(err.into()), |
| }); |
| } |
| }, |
| (Err(err), capability) => results.push(VerifyRouteResult { |
| using_node: target.node_path(), |
| capability: capability.clone(), |
| result: Err(err.into()), |
| }), |
| } |
| results |
| } |
| |
| /// Given a `ExposeDecl` for a capability at an instance `target`, checks whether the capability |
| /// can be used from an expose declaration. If so, routes the capability to its source and then |
| /// validates the source. |
| pub fn check_use_exposed_capability( |
| self: &Arc<Self>, |
| expose_decl: &ExposeDecl, |
| target: &Arc<ComponentInstanceForAnalyzer>, |
| ) -> Option<VerifyRouteResult> { |
| match self.request_from_expose(expose_decl) { |
| Some(request) => { |
| let result = match Self::route_capability_sync(request, target) { |
| Ok((source, route)) => match self.check_use_source(&source) { |
| Ok(()) => Ok(route.into()), |
| Err(err) => Err(err.into()), |
| }, |
| Err(err) => Err(AnalyzerModelError::from(err).into()), |
| }; |
| Some(VerifyRouteResult { |
| using_node: target.node_path(), |
| capability: expose_decl.target_name().clone(), |
| result, |
| }) |
| } |
| None => None, |
| } |
| } |
| |
| /// Given a `ProgramDecl` for a component instance, checks whether the specified runner has |
| /// a valid capability route. |
| pub fn check_program_runner( |
| self: &Arc<Self>, |
| program_decl: &ProgramDecl, |
| target: &Arc<ComponentInstanceForAnalyzer>, |
| ) -> Option<VerifyRouteResult> { |
| match program_decl.runner { |
| Some(ref runner) => { |
| let mut route = RouteMap::from_segments(vec![RouteSegment::RequireRunner { |
| node_path: target.node_path(), |
| runner: runner.clone(), |
| }]); |
| match Self::route_capability_sync(RouteRequest::Runner(runner.clone()), target) { |
| Ok((_source, mut segments)) => { |
| route.append(&mut segments); |
| Some(VerifyRouteResult { |
| using_node: target.node_path(), |
| capability: runner.clone(), |
| result: Ok(route.into()), |
| }) |
| } |
| Err(err) => Some(VerifyRouteResult { |
| using_node: target.node_path(), |
| capability: runner.clone(), |
| result: Err(AnalyzerModelError::from(err).into()), |
| }), |
| } |
| } |
| None => None, |
| } |
| } |
| |
| /// Given a component instance, extracts the URL scheme for that instance and looks for a |
| /// resolver for that scheme in the instance's environment, recording an error if none |
| /// is found. If a resolver is found, checks that it has a valid capability route. |
| pub fn check_resolver( |
| self: &Arc<Self>, |
| target: &Arc<ComponentInstanceForAnalyzer>, |
| ) -> VerifyRouteResult { |
| let url = Url::parse(target.url()).expect("failed to parse target URL"); |
| let scheme = url.scheme(); |
| let mut route = vec![RouteSegment::RequireResolver { |
| node_path: target.node_path(), |
| scheme: scheme.to_string(), |
| }]; |
| |
| let check_route = match target.environment.get_registered_resolver(scheme) { |
| Ok(Some((ExtendedInstanceInterface::Component(instance), resolver))) => { |
| match Self::route_capability_sync( |
| RouteRequest::Resolver(resolver.clone()), |
| &instance, |
| ) { |
| Ok((_source, route)) => VerifyRouteResult { |
| using_node: target.node_path(), |
| capability: resolver.resolver, |
| result: Ok(route.into()), |
| }, |
| Err(err) => VerifyRouteResult { |
| using_node: target.node_path(), |
| capability: resolver.resolver, |
| result: Err(AnalyzerModelError::from(err).into()), |
| }, |
| } |
| } |
| Ok(Some((ExtendedInstanceInterface::AboveRoot(_), resolver))) => { |
| match self.get_builtin_resolver_decl(&resolver) { |
| Ok(decl) => { |
| let mut route = RouteMap::new(); |
| route.push(RouteSegment::ProvideAsBuiltin { capability: decl }); |
| VerifyRouteResult { |
| using_node: target.node_path(), |
| capability: resolver.resolver, |
| result: Ok(route.into()), |
| } |
| } |
| Err(err) => VerifyRouteResult { |
| using_node: target.node_path(), |
| capability: resolver.resolver, |
| result: Err(err.into()), |
| }, |
| } |
| } |
| Ok(None) => VerifyRouteResult { |
| using_node: target.node_path(), |
| capability: "".into(), |
| result: Err(AnalyzerModelError::MissingResolverForScheme(scheme.to_string()).into()), |
| }, |
| Err(err) => VerifyRouteResult { |
| using_node: target.node_path(), |
| capability: "".into(), |
| result: Err(AnalyzerModelError::from(err).into()), |
| }, |
| }; |
| |
| let check_result = match check_route.result { |
| Ok(mut segments) => { |
| route.append(&mut segments); |
| Ok(route.into()) |
| } |
| Err(err) => Err(err.into()), |
| }; |
| VerifyRouteResult { |
| using_node: target.node_path(), |
| capability: check_route.capability, |
| result: check_result, |
| } |
| } |
| |
| // Retrieves the `CapabilityDecl` for a built-in resolver from its registration, or an |
| // error if the resolver is not provided as a built-in capability. |
| fn get_builtin_resolver_decl( |
| &self, |
| resolver: &ResolverRegistration, |
| ) -> Result<CapabilityDecl, AnalyzerModelError> { |
| match self.top_instance.builtin_capabilities().iter().find(|&decl| { |
| if let CapabilityDecl::Resolver(resolver_decl) = decl { |
| resolver_decl.name == resolver.resolver |
| } else { |
| false |
| } |
| }) { |
| Some(decl) => Ok(decl.clone()), |
| None => Err(AnalyzerModelError::RoutingError( |
| RoutingError::use_from_component_manager_not_found(resolver.resolver.to_string()), |
| )), |
| } |
| } |
| |
| // Checks properties of a capability source that are necessary to use the capability |
| // and that are possible to verify statically. |
| fn check_use_source( |
| &self, |
| route_source: &RouteSource<ComponentInstanceForAnalyzer>, |
| ) -> Result<(), AnalyzerModelError> { |
| match route_source { |
| RouteSource::Directory(source, _) => self.check_directory_source(source), |
| RouteSource::Event(_) => Ok(()), |
| RouteSource::EventStream(source) => self.check_protocol_source(source), |
| RouteSource::Protocol(source) => self.check_protocol_source(source), |
| RouteSource::Service(source) => self.check_service_source(source), |
| RouteSource::StorageBackingDirectory(source) => self.check_storage_source(source), |
| _ => unimplemented![], |
| } |
| } |
| |
| // If the source of a directory capability is a component instance, checks that that |
| // instance is executable. |
| fn check_directory_source( |
| &self, |
| source: &CapabilitySourceInterface<ComponentInstanceForAnalyzer>, |
| ) -> Result<(), AnalyzerModelError> { |
| match source { |
| CapabilitySourceInterface::Component { component: weak, .. } => { |
| self.check_executable(&weak.upgrade()?) |
| } |
| CapabilitySourceInterface::Namespace { .. } => Ok(()), |
| CapabilitySourceInterface::Builtin { .. } => Ok(()), |
| CapabilitySourceInterface::Framework { .. } => Ok(()), |
| _ => unimplemented![], |
| } |
| } |
| |
| // If the source of a protocol capability is a component instance, checks that that |
| // instance is executable. |
| // |
| // If the source is a capability, checks that the protocol is the `StorageAdmin` |
| // protocol and that the source is a valid storage capability. |
| fn check_protocol_source( |
| &self, |
| source: &CapabilitySourceInterface<ComponentInstanceForAnalyzer>, |
| ) -> Result<(), AnalyzerModelError> { |
| match source { |
| CapabilitySourceInterface::Component { component: weak, .. } => { |
| self.check_executable(&weak.upgrade()?) |
| } |
| CapabilitySourceInterface::Namespace { .. } => Ok(()), |
| CapabilitySourceInterface::Capability { source_capability, component: weak } => { |
| self.check_protocol_capability_source(&weak.upgrade()?, &source_capability) |
| } |
| CapabilitySourceInterface::Builtin { .. } => Ok(()), |
| CapabilitySourceInterface::Framework { .. } => Ok(()), |
| _ => unimplemented![], |
| } |
| } |
| |
| // A helper function validating a source of type `Capability` for a protocol capability. |
| // If the protocol is the `StorageAdmin` protocol, then it should have a valid storage |
| // source. |
| fn check_protocol_capability_source( |
| &self, |
| source_component: &Arc<ComponentInstanceForAnalyzer>, |
| source_capability: &ComponentCapability, |
| ) -> Result<(), AnalyzerModelError> { |
| let source_capability_name = source_capability |
| .source_capability_name() |
| .expect("failed to get source capability name"); |
| |
| match source_capability.source_name().map(|name| name.to_string()).as_deref() { |
| Some(fsys::StorageAdminMarker::PROTOCOL_NAME) => { |
| match source_component.decl.find_storage_source(source_capability_name) { |
| Some(_) => Ok(()), |
| None => Err(AnalyzerModelError::InvalidSourceCapability( |
| source_capability_name.to_string(), |
| fsys::StorageAdminMarker::PROTOCOL_NAME.to_string(), |
| )), |
| } |
| } |
| _ => Err(AnalyzerModelError::InvalidSourceCapability( |
| source_capability_name.to_string(), |
| source_capability |
| .source_name() |
| .map_or_else(|| "".to_string(), |name| name.to_string()), |
| )), |
| } |
| } |
| |
| // If the source of a service capability is a component instance, checks that that |
| // instance is executable. |
| fn check_service_source( |
| &self, |
| source: &CapabilitySourceInterface<ComponentInstanceForAnalyzer>, |
| ) -> Result<(), AnalyzerModelError> { |
| match source { |
| CapabilitySourceInterface::Component { component: weak, .. } => { |
| self.check_executable(&weak.upgrade()?) |
| } |
| CapabilitySourceInterface::Namespace { .. } => Ok(()), |
| _ => unimplemented![], |
| } |
| } |
| |
| // If the source of a storage backing directory is a component instance, checks that that |
| // instance is executable. |
| fn check_storage_source( |
| &self, |
| source: &StorageCapabilitySource<ComponentInstanceForAnalyzer>, |
| ) -> Result<(), AnalyzerModelError> { |
| if let Some(provider) = &source.storage_provider { |
| self.check_executable(provider)? |
| } |
| Ok(()) |
| } |
| |
| // A helper function which prepares a route request for capabilities which can be used |
| // from an expose declaration, and returns None if the capability type cannot be used |
| // from an expose. |
| fn request_from_expose(self: &Arc<Self>, expose_decl: &ExposeDecl) -> Option<RouteRequest> { |
| match expose_decl { |
| ExposeDecl::Directory(expose_directory_decl) => { |
| Some(RouteRequest::ExposeDirectory(expose_directory_decl.clone())) |
| } |
| ExposeDecl::Protocol(expose_protocol_decl) => { |
| Some(RouteRequest::ExposeProtocol(expose_protocol_decl.clone())) |
| } |
| ExposeDecl::Service(expose_service_decl) => { |
| Some(RouteRequest::ExposeService(expose_service_decl.clone())) |
| } |
| _ => None, |
| } |
| } |
| |
| // A helper function checking whether a component instance is executable. |
| fn check_executable( |
| &self, |
| component: &Arc<ComponentInstanceForAnalyzer>, |
| ) -> Result<(), AnalyzerModelError> { |
| match component.decl.program { |
| Some(_) => Ok(()), |
| None => Err(AnalyzerModelError::SourceInstanceNotExecutable( |
| component.abs_moniker().to_string(), |
| )), |
| } |
| } |
| |
| fn uses_event_source_protocol(&self, decl: &ComponentDecl) -> bool { |
| decl.uses.iter().any(|u| match u { |
| UseDecl::Protocol(p) => { |
| p.target_path |
| == CapabilityPath { |
| dirname: "/svc".to_string(), |
| basename: "fuchsia.sys2.EventSource".to_string(), |
| } |
| } |
| _ => false, |
| }) |
| } |
| |
| // Routes a capability from a `ComponentInstanceForAnalyzer` and panics if the future returned by |
| // `route_capability` is not ready immediately. |
| // |
| // TODO(fxbug.dev/87204): Remove this function and use `route_capability` directly when Scrutiny's |
| // `DataController`s allow async function calls. |
| pub fn route_capability_sync( |
| request: RouteRequest, |
| target: &Arc<ComponentInstanceForAnalyzer>, |
| ) -> Result< |
| ( |
| RouteSource<ComponentInstanceForAnalyzer>, |
| <<ComponentInstanceForAnalyzer as ComponentInstanceInterface>::DebugRouteMapper as DebugRouteMapper>::RouteMap, |
| ), |
| RoutingError> |
| { |
| route_capability(request, target).now_or_never().expect("future was not ready immediately") |
| } |
| |
| pub fn route_event_stream_sync( |
| request: UseEventStreamDecl, |
| target: &Arc<ComponentInstanceForAnalyzer>, |
| map: &mut Vec<Arc<ComponentInstanceForAnalyzer>>, |
| ) -> Result< |
| ( |
| RouteSource<ComponentInstanceForAnalyzer>, |
| <<ComponentInstanceForAnalyzer as ComponentInstanceInterface>::DebugRouteMapper as DebugRouteMapper>::RouteMap, |
| ), |
| RoutingError> |
| { |
| route_event_stream_capability(request, target, map) |
| .now_or_never() |
| .expect("future was not ready immediately") |
| } |
| |
| // Routes a storage capability and its backing directory from a `ComponentInstanceForAnalyzer` and |
| // panics if the future returned by `route_storage_and_backing_directory` is not ready immediately. |
| // |
| // TODO(fxbug.dev/87204): Remove this function and use `route_capability` directly when Scrutiny's |
| // `DataController`s allow async function calls. |
| fn route_storage_and_backing_directory_sync( |
| use_decl: UseStorageDecl, |
| target: &Arc<ComponentInstanceForAnalyzer>, |
| ) -> Result< |
| ( |
| StorageCapabilitySource<ComponentInstanceForAnalyzer>, |
| InstancedRelativeMoniker, |
| <<ComponentInstanceForAnalyzer as ComponentInstanceInterface>::DebugRouteMapper as DebugRouteMapper>::RouteMap, |
| <<ComponentInstanceForAnalyzer as ComponentInstanceInterface>::DebugRouteMapper as DebugRouteMapper>::RouteMap, |
| ), |
| RoutingError> |
| { |
| route_storage_and_backing_directory(use_decl, target) |
| .now_or_never() |
| .expect("future was not ready immediately") |
| } |
| } |
| |
| #[derive(Clone)] |
| pub struct Child { |
| pub child_moniker: ChildMoniker, |
| pub url: Url, |
| pub environment: Option<String>, |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use { |
| super::ModelBuilderForAnalyzer, |
| crate::{environment::BOOT_SCHEME, node_path::NodePath, ComponentModelForAnalyzer}, |
| anyhow::Result, |
| cm_moniker::InstancedAbsoluteMoniker, |
| cm_rust::{ |
| Availability, CapabilityName, CapabilityPath, ComponentDecl, DependencyType, |
| RegistrationSource, ResolverRegistration, RunnerRegistration, UseProtocolDecl, |
| UseSource, UseStorageDecl, |
| }, |
| cm_rust_testing::{ChildDeclBuilder, ComponentDeclBuilder, EnvironmentDeclBuilder}, |
| fidl_fuchsia_component_decl as fdecl, |
| fidl_fuchsia_component_internal as component_internal, |
| maplit::hashmap, |
| moniker::{AbsoluteMoniker, AbsoluteMonikerBase, ChildMoniker}, |
| routing::{ |
| component_id_index::ComponentIdIndex, |
| component_instance::{ |
| ComponentInstanceInterface, ExtendedInstanceInterface, |
| WeakExtendedInstanceInterface, |
| }, |
| config::RuntimeConfig, |
| environment::{EnvironmentInterface, RunnerRegistry}, |
| error::ComponentInstanceError, |
| RouteRequest, |
| }, |
| std::{ |
| collections::HashMap, |
| convert::{TryFrom, TryInto}, |
| iter::FromIterator, |
| sync::Arc, |
| }, |
| url::Url, |
| }; |
| |
| const TEST_URL_PREFIX: &str = "test:///"; |
| |
| fn make_test_url(component_name: &str) -> Url { |
| Url::parse(&format!("{}{}", TEST_URL_PREFIX, component_name)).unwrap() |
| } |
| |
| fn make_decl_map( |
| components: Vec<(&'static str, ComponentDecl)>, |
| ) -> HashMap<Url, ComponentDecl> { |
| HashMap::from_iter(components.into_iter().map(|(name, decl)| (make_test_url(name), decl))) |
| } |
| |
| // Builds a model with structure `root -- child`, retrieves each of the 2 resulting component |
| // instances, and tests their public methods. |
| #[fuchsia::test] |
| fn build_model() -> Result<()> { |
| let components = vec![ |
| ("root", ComponentDeclBuilder::new().add_lazy_child("child").build()), |
| ("child", ComponentDeclBuilder::new().build()), |
| ]; |
| |
| let config = Arc::new(RuntimeConfig::default()); |
| let url = make_test_url("root"); |
| let build_model_result = ModelBuilderForAnalyzer::new(url).build( |
| make_decl_map(components), |
| config, |
| Arc::new(ComponentIdIndex::default()), |
| RunnerRegistry::default(), |
| ); |
| assert_eq!(build_model_result.errors.len(), 0); |
| assert!(build_model_result.model.is_some()); |
| let model = build_model_result.model.unwrap(); |
| assert_eq!(model.len(), 2); |
| |
| let root_instance = |
| model.get_instance(&NodePath::absolute_from_vec(vec![])).expect("root instance"); |
| let child_instance = model |
| .get_instance(&NodePath::absolute_from_vec(vec!["child"])) |
| .expect("child instance"); |
| |
| let other_id = NodePath::absolute_from_vec(vec!["other"]); |
| let get_other_result = model.get_instance(&other_id); |
| assert_eq!( |
| get_other_result.err().unwrap().to_string(), |
| ComponentInstanceError::instance_not_found( |
| AbsoluteMoniker::parse_str(&other_id.to_string()).unwrap() |
| ) |
| .to_string() |
| ); |
| |
| // Include tests for `.instanced_moniker()` alongside `.abs_moniker()` |
| // until`.instanced_moniker()` is removed from the public API. |
| assert_eq!(root_instance.abs_moniker(), &AbsoluteMoniker::root()); |
| assert_eq!(root_instance.instanced_moniker(), &InstancedAbsoluteMoniker::root()); |
| |
| assert_eq!(child_instance.abs_moniker(), &AbsoluteMoniker::parse_str("/child").unwrap()); |
| assert_eq!( |
| child_instance.instanced_moniker(), |
| &InstancedAbsoluteMoniker::parse_str("/child:0").unwrap() |
| ); |
| |
| match root_instance.try_get_parent()? { |
| ExtendedInstanceInterface::AboveRoot(_) => {} |
| _ => panic!("root instance's parent should be `AboveRoot`"), |
| } |
| match child_instance.try_get_parent()? { |
| ExtendedInstanceInterface::Component(component) => { |
| assert_eq!(component.abs_moniker(), root_instance.abs_moniker()); |
| assert_eq!(component.instanced_moniker(), root_instance.instanced_moniker()) |
| } |
| _ => panic!("child instance's parent should be root component"), |
| } |
| |
| let get_child = root_instance |
| .resolve() |
| .map(|locked| locked.get_child(&ChildMoniker::new("child".to_string(), None)))?; |
| assert!(get_child.is_some()); |
| assert_eq!(get_child.as_ref().unwrap().abs_moniker(), child_instance.abs_moniker()); |
| assert_eq!(get_child.unwrap().instanced_moniker(), child_instance.instanced_moniker()); |
| |
| let root_environment = root_instance.environment(); |
| let child_environment = child_instance.environment(); |
| |
| assert_eq!(root_environment.name(), None); |
| match root_environment.parent() { |
| WeakExtendedInstanceInterface::AboveRoot(_) => {} |
| _ => panic!("root environment's parent should be `AboveRoot`"), |
| } |
| |
| assert_eq!(child_environment.name(), None); |
| match child_environment.parent() { |
| WeakExtendedInstanceInterface::Component(component) => { |
| assert_eq!(component.upgrade()?.abs_moniker(), root_instance.abs_moniker()); |
| assert_eq!( |
| component.upgrade()?.instanced_moniker(), |
| root_instance.instanced_moniker() |
| ) |
| } |
| _ => panic!("child environment's parent should be root component"), |
| } |
| |
| root_instance.try_get_policy_checker()?; |
| root_instance.try_get_component_id_index()?; |
| |
| child_instance.try_get_policy_checker()?; |
| child_instance.try_get_component_id_index()?; |
| |
| assert!(root_instance.resolve().is_ok()); |
| assert!(child_instance.resolve().is_ok()); |
| |
| Ok(()) |
| } |
| |
| // Builds a model with structure `root -- child` where the child's URL is expressed in |
| // the root manifest as a relative URL. |
| #[fuchsia::test] |
| fn build_model_with_relative_url() { |
| let root_decl = ComponentDeclBuilder::new() |
| .add_child(ChildDeclBuilder::new().name("child").url("#child").build()) |
| .build(); |
| let child_decl = ComponentDeclBuilder::new().build(); |
| let root_url = make_test_url("root"); |
| let absolute_child_url = Url::parse(&format!("{}#child", root_url)).unwrap(); |
| |
| let mut decls_by_url = HashMap::new(); |
| decls_by_url.insert(root_url.clone(), root_decl); |
| decls_by_url.insert(absolute_child_url.clone(), child_decl); |
| |
| let config = Arc::new(RuntimeConfig::default()); |
| let build_model_result = ModelBuilderForAnalyzer::new(root_url).build( |
| decls_by_url, |
| config, |
| Arc::new(ComponentIdIndex::default()), |
| RunnerRegistry::default(), |
| ); |
| assert_eq!(build_model_result.errors.len(), 0); |
| assert!(build_model_result.model.is_some()); |
| let model = build_model_result.model.unwrap(); |
| assert_eq!(model.len(), 2); |
| |
| let child_instance = model |
| .get_instance(&NodePath::absolute_from_vec(vec!["child"])) |
| .expect("child instance"); |
| |
| assert_eq!(child_instance.url(), absolute_child_url.as_str()); |
| } |
| |
| // Spot-checks that `route_capability` returns immediately when routing a capability from a |
| // `ComponentInstanceForAnalyzer`. In addition, updates to that method should |
| // be reviewed to make sure that this property holds; otherwise, `ComponentModelForAnalyzer`'s |
| // sync methods may panic. |
| #[fuchsia::test] |
| fn route_capability_is_sync() { |
| let components = vec![("root", ComponentDeclBuilder::new().build())]; |
| |
| let config = Arc::new(RuntimeConfig::default()); |
| let url = make_test_url("root"); |
| let build_model_result = ModelBuilderForAnalyzer::new(url).build( |
| make_decl_map(components), |
| config, |
| Arc::new(ComponentIdIndex::default()), |
| RunnerRegistry::default(), |
| ); |
| assert_eq!(build_model_result.errors.len(), 0); |
| assert!(build_model_result.model.is_some()); |
| let model = build_model_result.model.unwrap(); |
| assert_eq!(model.len(), 1); |
| |
| let root_instance = |
| model.get_instance(&NodePath::absolute_from_vec(vec![])).expect("root instance"); |
| |
| // Panics if the future returned by `route_capability` was not ready immediately. |
| // If no panic, discard the result. |
| let _ = ComponentModelForAnalyzer::route_capability_sync( |
| RouteRequest::UseProtocol(UseProtocolDecl { |
| source: UseSource::Parent, |
| source_name: "bar_svc".into(), |
| target_path: CapabilityPath::try_from("/svc/hippo").unwrap(), |
| dependency_type: DependencyType::Strong, |
| availability: Availability::Required, |
| }), |
| &root_instance, |
| ); |
| } |
| |
| // Checks that `route_capability` returns immediately when routing a capability from a |
| // `ComponentInstanceForAnalyzer`. In addition, updates to that method should |
| // be reviewed to make sure that this property holds; otherwise, `ComponentModelForAnalyzer`'s |
| // sync methods may panic. |
| #[fuchsia::test] |
| fn route_storage_and_backing_directory_is_sync() { |
| let components = vec![("root", ComponentDeclBuilder::new().build())]; |
| |
| let config = Arc::new(RuntimeConfig::default()); |
| let cm_url = cm_types::Url::new(make_test_url("root").to_string()) |
| .expect("failed to parse root component url"); |
| let url = Url::parse(cm_url.as_str()).expect("failed to parse root component url"); |
| let build_model_result = ModelBuilderForAnalyzer::new(url).build( |
| make_decl_map(components), |
| config, |
| Arc::new(ComponentIdIndex::default()), |
| RunnerRegistry::default(), |
| ); |
| assert_eq!(build_model_result.errors.len(), 0); |
| assert!(build_model_result.model.is_some()); |
| let model = build_model_result.model.unwrap(); |
| assert_eq!(model.len(), 1); |
| |
| let root_instance = |
| model.get_instance(&NodePath::absolute_from_vec(vec![])).expect("root instance"); |
| |
| // Panics if the future returned by `route_storage_and_backing_directory` was not ready immediately. |
| // If no panic, discard the result. |
| let _ = ComponentModelForAnalyzer::route_storage_and_backing_directory_sync( |
| UseStorageDecl { |
| source_name: "cache".into(), |
| target_path: "/storage".try_into().unwrap(), |
| availability: Availability::Required, |
| }, |
| &root_instance, |
| ); |
| } |
| |
| // Builds a model with structure `root -- child` in which the child environment extends the root's. |
| // Checks that the child has access to the inherited runner and resolver registrations through its |
| // environment. |
| #[fuchsia::test] |
| fn environment_inherits() -> Result<()> { |
| let child_env_name = "child_env"; |
| let child_runner_registration = RunnerRegistration { |
| source_name: "child_env_runner".into(), |
| source: RegistrationSource::Self_, |
| target_name: "child_env_runner".into(), |
| }; |
| let child_resolver_registration = ResolverRegistration { |
| resolver: "child_env_resolver".into(), |
| source: RegistrationSource::Self_, |
| scheme: "child_resolver_scheme".into(), |
| }; |
| |
| let components = vec![ |
| ( |
| "root", |
| ComponentDeclBuilder::new() |
| .add_child( |
| ChildDeclBuilder::new_lazy_child("child").environment(child_env_name), |
| ) |
| .add_environment( |
| EnvironmentDeclBuilder::new() |
| .name(child_env_name) |
| .extends(fdecl::EnvironmentExtends::Realm) |
| .add_resolver(child_resolver_registration.clone()) |
| .add_runner(child_runner_registration.clone()) |
| .build(), |
| ) |
| .build(), |
| ), |
| ("child", ComponentDeclBuilder::new().build()), |
| ]; |
| |
| // Set up the RuntimeConfig to register the `fuchsia-boot` resolver as a built-in, |
| // in addition to `builtin_runner`. |
| let mut config = RuntimeConfig::default(); |
| config.builtin_boot_resolver = component_internal::BuiltinBootResolver::Boot; |
| |
| let builtin_runner_name = CapabilityName("builtin_runner".into()); |
| let builtin_runner_registration = RunnerRegistration { |
| source_name: builtin_runner_name.clone(), |
| source: RegistrationSource::Self_, |
| target_name: builtin_runner_name.clone(), |
| }; |
| |
| let cm_url = cm_types::Url::new(make_test_url("root").to_string()) |
| .expect("failed to parse root component url"); |
| let url = Url::parse(cm_url.as_str()).expect("failed to parse root component url"); |
| let build_model_result = ModelBuilderForAnalyzer::new(url).build( |
| make_decl_map(components), |
| Arc::new(config), |
| Arc::new(ComponentIdIndex::default()), |
| RunnerRegistry::from_decl(&vec![builtin_runner_registration]), |
| ); |
| assert_eq!(build_model_result.errors.len(), 0); |
| assert!(build_model_result.model.is_some()); |
| let model = build_model_result.model.unwrap(); |
| assert_eq!(model.len(), 2); |
| |
| let child_instance = model |
| .get_instance(&NodePath::absolute_from_vec(vec!["child"])) |
| .expect("child instance"); |
| |
| let get_child_runner_result = child_instance |
| .environment() |
| .get_registered_runner(&child_runner_registration.target_name)?; |
| assert!(get_child_runner_result.is_some()); |
| let (child_runner_registrar, child_runner) = get_child_runner_result.unwrap(); |
| match child_runner_registrar { |
| ExtendedInstanceInterface::Component(instance) => { |
| assert_eq!(instance.abs_moniker(), &AbsoluteMoniker::from(vec![])); |
| } |
| ExtendedInstanceInterface::AboveRoot(_) => { |
| panic!("expected child_env_runner to be registered by the root instance") |
| } |
| } |
| assert_eq!(child_runner_registration, child_runner); |
| |
| let get_child_resolver_result = child_instance |
| .environment |
| .get_registered_resolver(&child_resolver_registration.scheme)?; |
| assert!(get_child_resolver_result.is_some()); |
| let (child_resolver_registrar, child_resolver) = get_child_resolver_result.unwrap(); |
| match child_resolver_registrar { |
| ExtendedInstanceInterface::Component(instance) => { |
| assert_eq!(instance.abs_moniker(), &AbsoluteMoniker::from(vec![])); |
| } |
| ExtendedInstanceInterface::AboveRoot(_) => { |
| panic!("expected child_env_resolver to be registered by the root instance") |
| } |
| } |
| assert_eq!(child_resolver_registration, child_resolver); |
| |
| let get_builtin_runner_result = child_instance |
| .environment() |
| .get_registered_runner(&CapabilityName::from(builtin_runner_name))?; |
| assert!(get_builtin_runner_result.is_some()); |
| let (builtin_runner_registrar, _builtin_runner) = get_builtin_runner_result.unwrap(); |
| match builtin_runner_registrar { |
| ExtendedInstanceInterface::Component(_) => { |
| panic!("expected builtin runner to be registered above the root") |
| } |
| ExtendedInstanceInterface::AboveRoot(_) => {} |
| } |
| |
| let get_builtin_resolver_result = |
| child_instance.environment.get_registered_resolver(&BOOT_SCHEME.to_string())?; |
| assert!(get_builtin_resolver_result.is_some()); |
| let (builtin_resolver_registrar, _builtin_resolver) = get_builtin_resolver_result.unwrap(); |
| match builtin_resolver_registrar { |
| ExtendedInstanceInterface::Component(_) => { |
| panic!("expected boot resolver to be registered above the root") |
| } |
| ExtendedInstanceInterface::AboveRoot(_) => {} |
| } |
| |
| Ok(()) |
| } |
| |
| fn decl(id: &str) -> ComponentDecl { |
| // Identify decls by a single child named `id`. |
| ComponentDeclBuilder::new().add_child(ChildDeclBuilder::new_lazy_child(id)).build() |
| } |
| |
| #[fuchsia::test] |
| fn get_decl_by_url_none() { |
| let beta_beta_urls = vec![ |
| Url::parse("fuchsia-pkg://test.fuchsia.com/beta#beta.cm").unwrap(), |
| Url::parse("fuchsia-pkg://test.fuchsia.com/beta/0#beta.cm").unwrap(), |
| Url::parse("fuchsia-pkg://test.fuchsia.com/beta?hash=0000000000000000000000000000000000000000000000000000000000000000#beta.cm").unwrap(), |
| Url::parse("fuchsia-pkg://test.fuchsia.com/beta/0?hash=0000000000000000000000000000000000000000000000000000000000000000#beta.cm").unwrap(), |
| ]; |
| let decls_by_url_no_beta_beta = hashmap! { |
| Url::parse("fuchsia-pkg://test.fuchsia.com/alpha#beta.cm").unwrap() => decl("alpha_beta"), |
| Url::parse("fuchsia-pkg://test.fuchsia.com/beta/0#alpha.cm").unwrap() => decl("beta_alpha"), |
| Url::parse("fuchsia-pkg://test.fuchsia.com/gamma?hash=0000000000000000000000000000000000000000000000000000000000000000#beta.cm").unwrap() => decl("gamma_beta"), |
| }; |
| |
| for beta_beta_url in beta_beta_urls.iter() { |
| let result = |
| ModelBuilderForAnalyzer::get_decl_by_url(&decls_by_url_no_beta_beta, beta_beta_url); |
| assert!(result.is_ok()); |
| assert_eq!(None, result.ok().unwrap()); |
| } |
| } |
| |
| #[fuchsia::test] |
| fn get_decl_by_url_fuchsia_boot() { |
| let fuchsia_boot_url = Url::parse("fuchsia-boot:///#meta/boot.cm").unwrap(); |
| let fuchsia_boot_component = decl("boot"); |
| let decls_by_url_with_fuchsia_boot = hashmap! { |
| Url::parse("fuchsia-pkg://test.fuchsia.com/alpha#beta.cm").unwrap() => decl("alpha_beta"), |
| Url::parse("fuchsia-pkg://test.fuchsia.com/beta/0#alpha.cm").unwrap() => decl("beta_alpha"), |
| Url::parse("fuchsia-pkg://test.fuchsia.com/gamma?hash=0000000000000000000000000000000000000000000000000000000000000000#beta.cm").unwrap() => decl("gamma_beta"), |
| fuchsia_boot_url.clone() => fuchsia_boot_component.clone(), |
| }; |
| |
| let result = ModelBuilderForAnalyzer::get_decl_by_url( |
| &decls_by_url_with_fuchsia_boot, |
| &fuchsia_boot_url, |
| ); |
| |
| assert!(result.is_ok()); |
| assert_eq!(Some(&fuchsia_boot_component), result.ok().unwrap()); |
| } |
| |
| #[fuchsia::test] |
| fn get_decl_by_url_bad_url() { |
| let bad_url = |
| Url::parse("fuchsia-pkg:///test.fuchsia.com/alpha?hash=notahexvalue#meta/alpha.cm") |
| .unwrap(); |
| let empty_decls_by_url = hashmap! {}; |
| |
| let result = ModelBuilderForAnalyzer::get_decl_by_url(&empty_decls_by_url, &bad_url); |
| |
| assert!(result.is_err()); |
| } |
| |
| #[fuchsia::test] |
| fn get_decl_by_url_strong() { |
| let beta_beta_url = Url::parse("fuchsia-pkg://test.fuchsia.com/beta/0?hash=0000000000000000000000000000000000000000000000000000000000000000#beta.cm").unwrap(); |
| let beta_beta_decl = decl("beta_beta"); |
| let decls_by_url_with_beta_beta = hashmap! { |
| Url::parse("fuchsia-pkg://test.fuchsia.com/alpha#beta.cm").unwrap() => decl("alpha_beta"), |
| Url::parse("fuchsia-pkg://test.fuchsia.com/beta/0#alpha.cm").unwrap() => decl("beta_alpha"), |
| Url::parse("fuchsia-pkg://test.fuchsia.com/gamma?hash=0000000000000000000000000000000000000000000000000000000000000000#beta.cm").unwrap() => decl("gamma_beta"), |
| beta_beta_url.clone() => beta_beta_decl.clone(), |
| }; |
| |
| let result = |
| ModelBuilderForAnalyzer::get_decl_by_url(&decls_by_url_with_beta_beta, &beta_beta_url); |
| |
| assert!(result.is_ok()); |
| assert_eq!(Some(&beta_beta_decl), result.ok().unwrap()); |
| } |
| |
| #[fuchsia::test] |
| fn get_decl_by_url_strongest() { |
| let beta_beta_strong_url = Url::parse("fuchsia-pkg://test.fuchsia.com/beta/0?hash=0000000000000000000000000000000000000000000000000000000000000000#beta.cm").unwrap(); |
| let beta_beta_strong_decl = decl("beta_beta_strong"); |
| let beta_beta_weak_url_1 = |
| Url::parse("fuchsia-pkg://test.fuchsia.com/beta/0#beta.cm").unwrap(); |
| let beta_beta_weak_decl_1 = decl("beta_beta_weak_1"); |
| let beta_beta_weak_url_2 = Url::parse("fuchsia-pkg://test.fuchsia.com/beta?hash=0000000000000000000000000000000000000000000000000000000000000000#beta.cm").unwrap(); |
| let beta_beta_weak_decl_2 = decl("beta_beta_weak_2"); |
| let beta_beta_weak_url_3 = |
| Url::parse("fuchsia-pkg://test.fuchsia.com/beta#beta.cm").unwrap(); |
| let beta_beta_weak_decl_3 = decl("beta_beta_weak_3"); |
| let decls_by_url_with_4_beta_betas = hashmap! { |
| beta_beta_weak_url_1 => beta_beta_weak_decl_1, |
| beta_beta_weak_url_2 => beta_beta_weak_decl_2, |
| beta_beta_weak_url_3 => beta_beta_weak_decl_3, |
| beta_beta_strong_url.clone() => beta_beta_strong_decl.clone(), |
| }; |
| |
| let result = ModelBuilderForAnalyzer::get_decl_by_url( |
| &decls_by_url_with_4_beta_betas, |
| &beta_beta_strong_url, |
| ); |
| |
| assert!(result.is_ok()); |
| assert_eq!(Some(&beta_beta_strong_decl), result.ok().unwrap()); |
| } |
| |
| #[fuchsia::test] |
| fn get_decl_by_url_weak() { |
| let beta_beta_strong_url = Url::parse("fuchsia-pkg://test.fuchsia.com/beta/0?hash=0000000000000000000000000000000000000000000000000000000000000000#beta.cm").unwrap(); |
| let beta_beta_weak_url = Url::parse("fuchsia-pkg://test.fuchsia.com/beta/0?hash=0000000000000000000000000000000000000000000000000000000000000000#beta.cm").unwrap(); |
| let beta_beta_decl = decl("beta_beta"); |
| let decls_by_url_with_strong_beta_beta = hashmap! { |
| Url::parse("fuchsia-pkg://test.fuchsia.com/alpha#beta.cm").unwrap() => decl("alpha_beta"), |
| Url::parse("fuchsia-pkg://test.fuchsia.com/beta/0#alpha.cm").unwrap() => decl("beta_alpha"), |
| Url::parse("fuchsia-pkg://test.fuchsia.com/gamma?hash=0000000000000000000000000000000000000000000000000000000000000000#beta.cm").unwrap() => decl("gamma_beta"), |
| beta_beta_strong_url.clone() => beta_beta_decl.clone(), |
| }; |
| |
| let result = ModelBuilderForAnalyzer::get_decl_by_url( |
| &decls_by_url_with_strong_beta_beta, |
| &beta_beta_weak_url, |
| ); |
| |
| assert!(result.is_ok()); |
| assert_eq!(Some(&beta_beta_decl), result.ok().unwrap()); |
| } |
| |
| #[fuchsia::test] |
| fn get_decl_by_url_weak_any() { |
| let beta_beta_url_1 = Url::parse("fuchsia-pkg://test.fuchsia.com/beta/0?hash=0000000000000000000000000000000000000000000000000000000000000000#beta.cm").unwrap(); |
| let beta_beta_decl_1 = decl("beta_beta_strong"); |
| let beta_beta_url_2 = Url::parse("fuchsia-pkg://test.fuchsia.com/beta/0#beta.cm").unwrap(); |
| let beta_beta_decl_2 = decl("beta_beta_weak_1"); |
| let beta_beta_url_3 = Url::parse("fuchsia-pkg://test.fuchsia.com/beta?hash=0000000000000000000000000000000000000000000000000000000000000000#beta.cm").unwrap(); |
| let beta_beta_decl_3 = decl("beta_beta_weak_2"); |
| let beta_beta_weakest_url = |
| Url::parse("fuchsia-pkg://test.fuchsia.com/beta#beta.cm").unwrap(); |
| let decls_by_url_3_weak_matches = hashmap! { |
| Url::parse("fuchsia-pkg://test.fuchsia.com/alpha#beta.cm").unwrap() => decl("alpha_beta"), |
| Url::parse("fuchsia-pkg://test.fuchsia.com/beta/0#alpha.cm").unwrap() => decl("beta_alpha"), |
| Url::parse("fuchsia-pkg://test.fuchsia.com/gamma?hash=0000000000000000000000000000000000000000000000000000000000000000#beta.cm").unwrap() => decl("gamma_beta"), |
| beta_beta_url_1 => beta_beta_decl_1.clone(), |
| beta_beta_url_2 => beta_beta_decl_2.clone(), |
| beta_beta_url_3 => beta_beta_decl_3.clone(), |
| }; |
| |
| let result = ModelBuilderForAnalyzer::get_decl_by_url( |
| &decls_by_url_3_weak_matches, |
| &beta_beta_weakest_url, |
| ); |
| |
| assert!(result.is_ok()); |
| let actual_decl = result.ok().unwrap().unwrap(); |
| assert!( |
| &beta_beta_decl_1 == actual_decl |
| || &beta_beta_decl_2 == actual_decl |
| || &beta_beta_decl_3 == actual_decl |
| ); |
| } |
| } |