| // 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}, |
| node_path::NodePath, |
| route::{RouteMap, RouteSegment, VerifyRouteResult}, |
| }, |
| anyhow::{anyhow, Result}, |
| cm_rust::{ |
| CapabilityDecl, CapabilityPath, CapabilityTypeName, ComponentDecl, ExposeDecl, |
| ExposeDeclCommon, ProgramDecl, ResolverRegistration, UseDecl, UseStorageDecl, |
| }, |
| fidl::endpoints::ProtocolMarker, |
| fidl_fuchsia_sys2 as fsys, fuchsia_zircon_status as zx_status, |
| futures::FutureExt, |
| moniker::{AbsoluteMoniker, AbsoluteMonikerBase, PartialChildMoniker, RelativeMoniker}, |
| 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::{ComponentInstanceError, RoutingError}, |
| policy::GlobalPolicyChecker, |
| route_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), |
| } |
| |
| /// 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: String, |
| } |
| |
| /// 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: impl Into<String>) -> Self { |
| Self { default_root_url: default_root_url.into() } |
| } |
| |
| pub fn build( |
| self, |
| decls_by_url: HashMap<String, ComponentDecl>, |
| runtime_config: Arc<RuntimeConfig>, |
| component_id_index: Arc<ComponentIdIndex>, |
| runner_registry: RunnerRegistry, |
| ) -> BuildModelResult { |
| let mut result = BuildModelResult::new(); |
| |
| // 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: String = match &runtime_config.root_component_url { |
| Some(url) => url.clone().into(), |
| None => self.default_root_url.clone().into(), |
| }; |
| |
| // If `root_url` matches a `ComponentDecl` in `decls_by_url`, construct the root |
| // instance and then recursively add child instances to the model. |
| match decls_by_url.get(&root_url) { |
| Some(root_decl) => { |
| let root_instance = ComponentInstanceForAnalyzer::new_root( |
| root_decl.clone(), |
| root_url, |
| Arc::clone(&model.top_instance), |
| Arc::clone(&runtime_config), |
| model.policy_checker.clone(), |
| Arc::clone(&model.component_id_index), |
| runner_registry, |
| ); |
| |
| Self::add_descendants(&root_instance, &decls_by_url, &mut model, &mut result); |
| |
| model |
| .instances |
| .insert(NodePath::from(root_instance.abs_moniker().clone()), root_instance); |
| |
| result.model = Some(Arc::new(model)); |
| } |
| None => { |
| result.errors.push(anyhow!(BuildAnalyzerModelError::ComponentDeclNotFound( |
| root_url, |
| "".to_string() |
| ))); |
| } |
| }; |
| |
| result |
| } |
| |
| // Adds all descendants of `instance` to `model`, also inserting each new instance |
| // in the `children` map of its parent. |
| fn add_descendants( |
| instance: &Arc<ComponentInstanceForAnalyzer>, |
| decls_by_url: &HashMap<String, ComponentDecl>, |
| model: &mut ComponentModelForAnalyzer, |
| result: &mut BuildModelResult, |
| ) { |
| for child in instance.decl.children.iter() { |
| match Self::get_absolute_child_url(&child.url, instance) { |
| Ok(url) => { |
| let absolute_url = url.to_string(); |
| if child.name.is_empty() { |
| result.errors.push(anyhow!(BuildAnalyzerModelError::InvalidChildDecl( |
| absolute_url.to_string(), |
| NodePath::from(instance.abs_moniker().clone()).to_string(), |
| ))); |
| continue; |
| } |
| |
| match decls_by_url.get(&absolute_url) { |
| Some(child_decl) => match ComponentInstanceForAnalyzer::new_for_child( |
| child, |
| absolute_url, |
| child_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, model, result); |
| |
| instance.add_child( |
| PartialChildMoniker::new(child.name.clone(), None), |
| Arc::clone(&child_instance), |
| ); |
| |
| model.instances.insert( |
| NodePath::from(child_instance.abs_moniker().clone()), |
| child_instance, |
| ); |
| } |
| Err(err) => { |
| result.errors.push(anyhow!(err)); |
| } |
| }, |
| 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), |
| } |
| } |
| } |
| |
| /// `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> { |
| let abs_moniker = AbsoluteMoniker::parse_string_without_instances(&id.to_string()) |
| .expect("failed to parse moniker from id"); |
| match self.instances.get(id) { |
| Some(instance) => Ok(Arc::clone(instance)), |
| None => Err(ComponentInstanceError::instance_not_found(abs_moniker.to_partial())), |
| } |
| } |
| |
| /// 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), |
| 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), |
| 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), |
| 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), |
| 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, |
| ), |
| 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::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::NAME) => { |
| match source_component.decl.find_storage_source(source_capability_name) { |
| Some(_) => Ok(()), |
| None => Err(AnalyzerModelError::InvalidSourceCapability( |
| source_capability_name.to_string(), |
| fsys::StorageAdminMarker::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. |
| 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") |
| } |
| |
| // 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>, |
| RelativeMoniker, |
| <<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") |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use { |
| super::*, |
| crate::environment::BOOT_SCHEME, |
| anyhow::Result, |
| cm_rust::{ |
| CapabilityName, DependencyType, RegistrationSource, RunnerRegistration, |
| UseProtocolDecl, UseSource, |
| }, |
| cm_rust_testing::{ChildDeclBuilder, ComponentDeclBuilder, EnvironmentDeclBuilder}, |
| fidl_fuchsia_component_internal as component_internal, |
| routing::{ |
| component_instance::WeakExtendedInstanceInterface, environment::EnvironmentInterface, |
| }, |
| std::{ |
| convert::{TryFrom, TryInto}, |
| iter::FromIterator, |
| }, |
| }; |
| |
| const TEST_URL_PREFIX: &str = "test:///"; |
| |
| fn make_test_url(component_name: &str) -> String { |
| format!("{}{}", TEST_URL_PREFIX, component_name) |
| } |
| |
| fn make_decl_map( |
| components: Vec<(&'static str, ComponentDecl)>, |
| ) -> HashMap<String, 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. |
| #[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 build_model_result = ModelBuilderForAnalyzer::new( |
| cm_types::Url::new(make_test_url("root")).expect("failed to parse root component 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_string_without_instances(&other_id.to_string()) |
| .expect("failed to parse moniker from id") |
| .to_partial() |
| ) |
| .to_string() |
| ); |
| |
| assert_eq!(root_instance.abs_moniker(), &AbsoluteMoniker::root()); |
| assert_eq!( |
| child_instance.abs_moniker(), |
| &AbsoluteMoniker::parse_string_without_instances("/child") |
| .expect("failed to parse moniker from id") |
| ); |
| |
| 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()) |
| } |
| _ => panic!("child instance's parent should be root component"), |
| } |
| |
| let get_child = root_instance.resolve().map(|locked| { |
| locked.get_live_child(&PartialChildMoniker::new("child".to_string(), None)) |
| })?; |
| assert!(get_child.is_some()); |
| assert_eq!(get_child.unwrap().abs_moniker(), child_instance.abs_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()) |
| } |
| _ => 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. |
| #[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 = format!("{}#child", root_url); |
| |
| 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( |
| cm_types::Url::new(root_url).expect("failed to parse root component 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); |
| } |
| |
| // 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. |
| #[test] |
| fn route_capability_is_sync() { |
| let components = vec![("root", ComponentDeclBuilder::new().build())]; |
| |
| let config = Arc::new(RuntimeConfig::default()); |
| let build_model_result = ModelBuilderForAnalyzer::new( |
| cm_types::Url::new(make_test_url("root")).expect("failed to parse root component 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, |
| }), |
| &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. |
| #[test] |
| fn route_storage_and_backing_directory_is_sync() { |
| let components = vec![("root", ComponentDeclBuilder::new().build())]; |
| |
| let config = Arc::new(RuntimeConfig::default()); |
| let build_model_result = ModelBuilderForAnalyzer::new( |
| cm_types::Url::new(make_test_url("root")).expect("failed to parse root component 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(), |
| }, |
| &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. |
| #[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(fsys::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 build_model_result = ModelBuilderForAnalyzer::new( |
| cm_types::Url::new(make_test_url("root")).expect("failed to parse root component 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(()) |
| } |
| } |