| // Copyright 2023 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use { |
| crate::{ |
| capability::CapabilitySource, |
| model::{ |
| component::instance::ResolvedInstanceState, |
| component::{ComponentInstance, WeakComponentInstance}, |
| routing::router_ext::RouterExt, |
| structured_dict::{ComponentEnvironment, ComponentInput, StructuredDictMap}, |
| }, |
| sandbox_util::RoutableExt, |
| }, |
| ::routing::{ |
| capability_source::ComponentCapability, |
| component_instance::ComponentInstanceInterface, |
| error::{ComponentInstanceError, RoutingError}, |
| DictExt, WithAvailability, |
| }, |
| async_trait::async_trait, |
| cm_rust::{ |
| CapabilityDecl, ExposeDeclCommon, OfferDeclCommon, SourceName, SourcePath, UseDeclCommon, |
| }, |
| cm_types::{IterablePath, Name, RelativePath, SeparatedPath}, |
| errors::{CapabilityProviderError, ComponentProviderError, OpenError, OpenOutgoingDirError}, |
| fidl::endpoints::{create_proxy, DiscoverableProtocolMarker}, |
| fidl_fuchsia_component_decl as fdecl, fidl_fuchsia_component_sandbox as fsandbox, |
| fidl_fuchsia_io as fio, fidl_fuchsia_sys2 as fsys, |
| futures::FutureExt, |
| itertools::Itertools, |
| moniker::{ChildName, Moniker}, |
| router_error::RouterError, |
| sandbox::Routable, |
| sandbox::{Capability, Dict, Request, Router, Unit}, |
| std::{collections::HashMap, fmt::Debug, sync::Arc}, |
| tracing::warn, |
| vfs::execution_scope::ExecutionScope, |
| }; |
| |
| pub fn build_program_output_dictionary( |
| component: &Arc<ComponentInstance>, |
| children: &HashMap<ChildName, Arc<ComponentInstance>>, |
| decl: &cm_rust::ComponentDecl, |
| component_input: &ComponentInput, |
| program_output_dict: &Dict, |
| declared_dictionaries: &Dict, |
| ) { |
| for capability in &decl.capabilities { |
| extend_dict_with_capability( |
| component, |
| children, |
| decl, |
| capability, |
| component_input, |
| program_output_dict, |
| &declared_dictionaries, |
| ); |
| } |
| } |
| |
| /// Once a component has been resolved and its manifest becomes known, this function produces the |
| /// various dicts the component needs based on the contents of its manifest. |
| pub fn build_component_sandbox( |
| moniker: &Moniker, |
| child_component_output_routers: HashMap<ChildName, Router>, |
| decl: &cm_rust::ComponentDecl, |
| component_input: &ComponentInput, |
| framework_dict: &Dict, |
| capability_sourced_capabilities_dict: &Dict, |
| component_output_dict: &Dict, |
| program_input_dict: &Dict, |
| program_input_dict_additions: &Dict, |
| program_output: &Router, |
| child_inputs: &mut StructuredDictMap<ComponentInput>, |
| collection_inputs: &mut StructuredDictMap<ComponentInput>, |
| environments: &mut StructuredDictMap<ComponentEnvironment>, |
| declared_dictionaries: Dict, |
| ) { |
| for environment_decl in &decl.environments { |
| environments |
| .insert( |
| environment_decl.name.clone(), |
| build_environment( |
| moniker, |
| &child_component_output_routers, |
| component_input, |
| environment_decl, |
| program_input_dict_additions, |
| program_output, |
| ), |
| ) |
| .ok(); |
| } |
| |
| for child in &decl.children { |
| let environment; |
| if let Some(environment_name) = child.environment.as_ref() { |
| environment = environments.get(environment_name).expect( |
| "child references nonexistent environment, \ |
| this should be prevented in manifest validation", |
| ) |
| } else { |
| environment = component_input.environment(); |
| } |
| let input = ComponentInput::new(environment); |
| let name = Name::new(child.name.as_str()).expect("child is static so name is not long"); |
| child_inputs.insert(name, input).ok(); |
| } |
| |
| for collection in &decl.collections { |
| let environment; |
| if let Some(environment_name) = collection.environment.as_ref() { |
| environment = environments.get(environment_name).expect( |
| "collection references nonexistent environment, \ |
| this should be prevented in manifest validation", |
| ) |
| } else { |
| environment = component_input.environment(); |
| } |
| let input = ComponentInput::new(environment); |
| collection_inputs.insert(collection.name.clone(), input).ok(); |
| } |
| |
| for use_ in &decl.uses { |
| extend_dict_with_use( |
| moniker, |
| &child_component_output_routers, |
| component_input, |
| program_input_dict, |
| program_input_dict_additions, |
| program_output, |
| framework_dict, |
| capability_sourced_capabilities_dict, |
| use_, |
| ); |
| } |
| |
| for offer in &decl.offers { |
| // We only support protocol and dictionary capabilities right now |
| if !is_supported_offer(offer) { |
| continue; |
| } |
| let target_dict = match offer.target() { |
| cm_rust::OfferTarget::Child(child_ref) => { |
| assert!(child_ref.collection.is_none(), "unexpected dynamic offer target"); |
| let child_name = Name::new(child_ref.name.as_str()) |
| .expect("child is static so name is not long"); |
| if child_inputs.get(&child_name).is_none() { |
| child_inputs.insert(child_name.clone(), Default::default()).ok(); |
| } |
| child_inputs |
| .get(&child_name) |
| .expect("component input was just added") |
| .capabilities() |
| } |
| cm_rust::OfferTarget::Collection(name) => { |
| if collection_inputs.get(name).is_none() { |
| collection_inputs.insert(name.clone(), Default::default()).ok(); |
| } |
| collection_inputs.get(name).expect("collection input was just added").capabilities() |
| } |
| cm_rust::OfferTarget::Capability(name) => { |
| let dict = match declared_dictionaries.get(name) { |
| Some(dict) => dict, |
| None => { |
| let dict = Capability::Dictionary(Dict::new()); |
| declared_dictionaries.insert(name.clone(), dict.clone()).ok(); |
| dict |
| } |
| }; |
| let Capability::Dictionary(dict) = dict else { |
| panic!("wrong type in dict"); |
| }; |
| dict |
| } |
| }; |
| extend_dict_with_offer( |
| moniker, |
| &child_component_output_routers, |
| component_input, |
| program_output, |
| framework_dict, |
| capability_sourced_capabilities_dict, |
| offer, |
| &target_dict, |
| ); |
| } |
| |
| for expose in &decl.exposes { |
| extend_dict_with_expose( |
| moniker, |
| &child_component_output_routers, |
| program_output, |
| framework_dict, |
| capability_sourced_capabilities_dict, |
| expose, |
| component_output_dict, |
| ); |
| } |
| } |
| |
| /// Adds `capability` to the program output dict given the resolved `decl`. The program output dict |
| /// is a dict of routers, keyed by capability name. |
| fn extend_dict_with_capability( |
| component: &Arc<ComponentInstance>, |
| children: &HashMap<ChildName, Arc<ComponentInstance>>, |
| decl: &cm_rust::ComponentDecl, |
| capability: &cm_rust::CapabilityDecl, |
| component_input: &ComponentInput, |
| program_output_dict: &Dict, |
| declared_dictionaries: &Dict, |
| ) { |
| match capability { |
| CapabilityDecl::Service(_) |
| | CapabilityDecl::Protocol(_) |
| | CapabilityDecl::Directory(_) |
| | CapabilityDecl::Runner(_) |
| | CapabilityDecl::Resolver(_) => { |
| let path = capability.path().expect("must have path"); |
| let router = ResolvedInstanceState::make_program_outgoing_router( |
| component, decl, capability, path, |
| ); |
| let router = router.with_policy_check( |
| CapabilitySource::Component { |
| capability: ComponentCapability::from(capability.clone()), |
| component: component.as_weak(), |
| }, |
| component.policy_checker().clone(), |
| ); |
| match program_output_dict.insert_capability(capability.name(), router.into()) { |
| Ok(()) => (), |
| Err(e) => { |
| warn!("failed to add {} to program output dict: {e:?}", capability.name()) |
| } |
| } |
| } |
| cm_rust::CapabilityDecl::Dictionary(d) => { |
| extend_dict_with_dictionary( |
| component, |
| children, |
| d, |
| component_input, |
| program_output_dict, |
| declared_dictionaries, |
| ); |
| } |
| CapabilityDecl::EventStream(_) | CapabilityDecl::Config(_) | CapabilityDecl::Storage(_) => { |
| // Capabilities not supported in bedrock program output dict yet. |
| return; |
| } |
| } |
| } |
| |
| fn extend_dict_with_dictionary( |
| component: &Arc<ComponentInstance>, |
| children: &HashMap<ChildName, Arc<ComponentInstance>>, |
| decl: &cm_rust::DictionaryDecl, |
| component_input: &ComponentInput, |
| program_output_dict: &Dict, |
| declared_dictionaries: &Dict, |
| ) { |
| let dict = Dict::new(); |
| let router; |
| if let Some(source) = decl.source.as_ref() { |
| let source_path = decl |
| .source_dictionary |
| .as_ref() |
| .expect("source_dictionary must be set if source is set"); |
| let source_dict_router = match &source { |
| cm_rust::DictionarySource::Parent => component_input.capabilities().lazy_get( |
| source_path.to_owned(), |
| RoutingError::use_from_parent_not_found( |
| &component.moniker, |
| source_path.iter_segments().join("/"), |
| ), |
| ), |
| cm_rust::DictionarySource::Self_ => component.program_output().lazy_get( |
| source_path.to_owned(), |
| RoutingError::use_from_self_not_found( |
| &component.moniker, |
| source_path.iter_segments().join("/"), |
| ), |
| ), |
| cm_rust::DictionarySource::Child(child_ref) => { |
| assert!(child_ref.collection.is_none(), "unexpected dynamic offer target"); |
| let child_name = |
| ChildName::parse(child_ref.name.as_str()).expect("invalid child name"); |
| match children.get(&child_name) { |
| Some(child) => child.component_output().lazy_get( |
| source_path.to_owned(), |
| RoutingError::BedrockSourceDictionaryExposeNotFound, |
| ), |
| None => Router::new_error(RoutingError::use_from_child_instance_not_found( |
| &child_name, |
| &component.moniker, |
| source_path.iter_segments().join("/"), |
| )), |
| } |
| } |
| cm_rust::DictionarySource::Program => { |
| struct ProgramRouter { |
| component: WeakComponentInstance, |
| source_path: RelativePath, |
| } |
| #[async_trait] |
| impl Routable for ProgramRouter { |
| async fn route(&self, request: Request) -> Result<Capability, RouterError> { |
| fn open_error(e: OpenOutgoingDirError) -> OpenError { |
| CapabilityProviderError::from(ComponentProviderError::from(e)).into() |
| } |
| |
| let component = self.component.upgrade().map_err(|_| { |
| RoutingError::from(ComponentInstanceError::instance_not_found( |
| self.component.moniker.clone(), |
| )) |
| })?; |
| let open = component.get_outgoing(); |
| |
| let (inner_router, server_end) = |
| create_proxy::<fsandbox::RouterMarker>().unwrap(); |
| open.open( |
| ExecutionScope::new(), |
| fio::OpenFlags::empty(), |
| vfs::path::Path::validate_and_split(self.source_path.to_string()) |
| .expect("path must be valid"), |
| server_end.into_channel(), |
| ); |
| let cap = inner_router |
| .route(request.into()) |
| .await |
| .map_err(|e| open_error(OpenOutgoingDirError::Fidl(e)))? |
| .map_err(RouterError::from)?; |
| let cap = Capability::try_from(cap) |
| .map_err(|_| RoutingError::BedrockRemoteCapability)?; |
| if !matches!(cap, Capability::Dictionary(_)) { |
| Err(RoutingError::BedrockWrongCapabilityType { |
| actual: cap.debug_typename().into(), |
| expected: "Dictionary".into(), |
| })?; |
| } |
| Ok(cap) |
| } |
| } |
| Router::new(ProgramRouter { |
| component: component.as_weak(), |
| source_path: source_path.clone(), |
| }) |
| } |
| }; |
| router = make_dict_extending_router(dict.clone(), source_dict_router); |
| } else { |
| router = Router::new_ok(dict.clone()); |
| } |
| match declared_dictionaries.insert_capability(&decl.name, dict.into()) { |
| Ok(()) => (), |
| Err(e) => warn!("failed to add {} to declared dicts: {e:?}", decl.name), |
| }; |
| match program_output_dict.insert_capability(&decl.name, router.into()) { |
| Ok(()) => (), |
| Err(e) => warn!("failed to add {} to program output dict: {e:?}", decl.name), |
| } |
| } |
| |
| fn build_environment( |
| moniker: &Moniker, |
| child_component_output_routers: &HashMap<ChildName, Router>, |
| component_input: &ComponentInput, |
| environment_decl: &cm_rust::EnvironmentDecl, |
| program_input_dict_additions: &Dict, |
| program_output: &Router, |
| ) -> ComponentEnvironment { |
| let mut environment = ComponentEnvironment::new(); |
| if environment_decl.extends == fdecl::EnvironmentExtends::Realm { |
| environment = component_input.environment().shallow_copy(); |
| } |
| for debug_registration in &environment_decl.debug_capabilities { |
| let cm_rust::DebugRegistration::Protocol(debug_protocol) = debug_registration; |
| let source_path = SeparatedPath { |
| dirname: Default::default(), |
| basename: debug_protocol.source_name.clone(), |
| }; |
| let router = match &debug_protocol.source { |
| cm_rust::RegistrationSource::Parent => use_from_parent_router( |
| component_input, |
| source_path, |
| moniker, |
| program_input_dict_additions, |
| ), |
| cm_rust::RegistrationSource::Self_ => program_output.clone().lazy_get( |
| source_path.clone(), |
| RoutingError::use_from_self_not_found( |
| moniker, |
| source_path.iter_segments().join("/"), |
| ), |
| ), |
| cm_rust::RegistrationSource::Child(child_name) => { |
| let child_name = ChildName::parse(child_name).expect("invalid child name"); |
| let Some(child_component_output) = child_component_output_routers.get(&child_name) |
| else { |
| continue; |
| }; |
| child_component_output.clone().lazy_get( |
| source_path, |
| RoutingError::use_from_child_expose_not_found( |
| &child_name, |
| &moniker, |
| debug_protocol.source_name.clone(), |
| ), |
| ) |
| } |
| }; |
| match environment.debug().insert_capability(&debug_protocol.target_name, router.into()) { |
| Ok(()) => (), |
| Err(e) => warn!( |
| "failed to add {} to debug capabilities dict: {e:?}", |
| debug_protocol.target_name |
| ), |
| } |
| } |
| environment |
| } |
| |
| /// Returns a [Router] that returns a [Dict] whose contents are these union of `dict` and the |
| /// [Dict] returned by `source_dict_router`. |
| /// |
| /// This algorithm returns a new [Dict] each time, leaving `dict` unmodified. |
| fn make_dict_extending_router(dict: Dict, source_dict_router: Router) -> Router { |
| let route_fn = move |request: Request| { |
| let source_dict_router = source_dict_router.clone(); |
| let dict = dict.clone(); |
| async move { |
| let source_dict; |
| match source_dict_router.route(request).await? { |
| Capability::Dictionary(d) => { |
| source_dict = d; |
| } |
| // Optional from void. |
| cap @ Capability::Unit(_) => return Ok(cap), |
| cap => { |
| return Err(RoutingError::BedrockWrongCapabilityType { |
| actual: cap.debug_typename().into(), |
| expected: "Dictionary".into(), |
| } |
| .into()) |
| } |
| } |
| let out_dict = dict.shallow_copy(); |
| for (source_key, source_value) in source_dict.enumerate() { |
| if let Err(_) = out_dict.insert(source_key.clone(), source_value.clone()) { |
| return Err(RoutingError::BedrockSourceDictionaryCollision.into()); |
| } |
| } |
| Ok(out_dict.into()) |
| } |
| .boxed() |
| }; |
| Router::new(route_fn) |
| } |
| |
| /// Extends the given dict based on offer declarations. All offer declarations in `offers` are |
| /// assumed to target `target_dict`. |
| pub fn extend_dict_with_offers( |
| moniker: &Moniker, |
| child_component_output_routers: &HashMap<ChildName, Router>, |
| component_input: &ComponentInput, |
| dynamic_offers: &Vec<cm_rust::OfferDecl>, |
| program_output: &Router, |
| framework_dict: &Dict, |
| capability_sourced_capabilities_dict: &Dict, |
| target_input: &ComponentInput, |
| ) { |
| for offer in dynamic_offers { |
| extend_dict_with_offer( |
| moniker, |
| &child_component_output_routers, |
| component_input, |
| program_output, |
| framework_dict, |
| capability_sourced_capabilities_dict, |
| offer, |
| &target_input.capabilities(), |
| ); |
| } |
| } |
| |
| fn supported_use(use_: &cm_rust::UseDecl) -> Option<&cm_rust::UseProtocolDecl> { |
| match use_ { |
| cm_rust::UseDecl::Protocol(p) => Some(p), |
| _ => None, |
| } |
| } |
| |
| fn extend_dict_with_use( |
| moniker: &Moniker, |
| child_component_output_routers: &HashMap<ChildName, Router>, |
| component_input: &ComponentInput, |
| program_input_dict: &Dict, |
| program_input_dict_additions: &Dict, |
| program_output: &Router, |
| framework_dict: &Dict, |
| capability_sourced_capabilities_dict: &Dict, |
| use_: &cm_rust::UseDecl, |
| ) { |
| let Some(use_protocol) = supported_use(use_) else { |
| return; |
| }; |
| |
| let source_path = use_.source_path(); |
| let router = match use_.source() { |
| cm_rust::UseSource::Parent => use_from_parent_router( |
| component_input, |
| source_path.to_owned(), |
| moniker, |
| program_input_dict_additions, |
| ), |
| cm_rust::UseSource::Self_ => program_output.clone().lazy_get( |
| source_path.to_owned(), |
| RoutingError::use_from_self_not_found(moniker, source_path.iter_segments().join("/")), |
| ), |
| cm_rust::UseSource::Child(child_name) => { |
| let child_name = ChildName::parse(child_name).expect("invalid child name"); |
| let Some(child_component_output) = child_component_output_routers.get(&child_name) |
| else { |
| panic!("use declaration in manifest for component {} has a source of a nonexistent child {}, this should be prevented by manifest validation", moniker, child_name); |
| }; |
| child_component_output.clone().lazy_get( |
| source_path.to_owned(), |
| RoutingError::use_from_child_expose_not_found( |
| &child_name, |
| &moniker, |
| use_.source_name().clone(), |
| ), |
| ) |
| } |
| cm_rust::UseSource::Framework if use_.is_from_dictionary() => { |
| Router::new_error(RoutingError::capability_from_framework_not_found( |
| moniker, |
| source_path.iter_segments().join("/"), |
| )) |
| } |
| cm_rust::UseSource::Framework => framework_dict.clone().lazy_get( |
| source_path.to_owned(), |
| RoutingError::capability_from_framework_not_found( |
| moniker, |
| source_path.iter_segments().join("/"), |
| ), |
| ), |
| cm_rust::UseSource::Capability(capability_name) => { |
| let err = RoutingError::capability_from_capability_not_found( |
| moniker, |
| capability_name.as_str().to_string(), |
| ); |
| if source_path.iter_segments().join("/") == fsys::StorageAdminMarker::PROTOCOL_NAME { |
| capability_sourced_capabilities_dict.clone().lazy_get(capability_name.clone(), err) |
| } else { |
| Router::new_error(err) |
| } |
| } |
| cm_rust::UseSource::Debug => component_input.environment().debug().lazy_get( |
| use_protocol.source_name.clone(), |
| RoutingError::use_from_environment_not_found( |
| moniker, |
| "protocol", |
| &use_protocol.source_name, |
| ), |
| ), |
| // UseSource::Environment is not used for protocol capabilities |
| cm_rust::UseSource::Environment => return, |
| }; |
| match program_input_dict.insert_capability( |
| &use_protocol.target_path, |
| router.with_availability(*use_.availability()).into(), |
| ) { |
| Ok(()) => (), |
| Err(e) => { |
| warn!("failed to insert {} in program input dict: {e:?}", use_protocol.target_path) |
| } |
| } |
| } |
| |
| /// Builds a router that obtains a capability that the program uses from `parent`. |
| /// |
| /// The capability is usually an entry in the `component_input.capabilities` dict unless it is |
| /// overridden by an eponymous capability in the `program_input_dict_additions` when started. |
| fn use_from_parent_router( |
| component_input: &ComponentInput, |
| source_path: impl IterablePath + 'static + Debug, |
| moniker: &Moniker, |
| program_input_dict_additions: &Dict, |
| ) -> Router { |
| let component_input_capability = component_input.capabilities().lazy_get( |
| source_path.clone(), |
| RoutingError::use_from_parent_not_found(moniker, source_path.iter_segments().join("/")), |
| ); |
| |
| let program_input_dict_additions = program_input_dict_additions.clone(); |
| |
| Router::new(move |request| { |
| let source_path = source_path.clone(); |
| let component_input_capability = component_input_capability.clone(); |
| let router = match program_input_dict_additions.get_capability(&source_path) { |
| // There's an addition to the program input dictionary for this |
| // capability, let's use it. |
| Some(Capability::Connector(s)) => Router::new_ok(s), |
| // There's no addition to the program input dictionary for this |
| // capability, let's use the component input dictionary. |
| _ => component_input_capability, |
| }; |
| async move { router.route(request).await }.boxed() |
| }) |
| } |
| |
| fn is_supported_offer(offer: &cm_rust::OfferDecl) -> bool { |
| matches!(offer, cm_rust::OfferDecl::Protocol(_) | cm_rust::OfferDecl::Dictionary(_)) |
| } |
| |
| fn extend_dict_with_offer( |
| moniker: &Moniker, |
| child_component_output_routers: &HashMap<ChildName, Router>, |
| component_input: &ComponentInput, |
| program_output: &Router, |
| framework_dict: &Dict, |
| capability_sourced_capabilities_dict: &Dict, |
| offer: &cm_rust::OfferDecl, |
| target_dict: &Dict, |
| ) { |
| // We only support protocol and dictionary capabilities right now |
| if !is_supported_offer(offer) { |
| return; |
| } |
| let source_path = offer.source_path(); |
| let target_name = offer.target_name(); |
| if target_dict.get_capability(&source_path).is_some() { |
| warn!( |
| "duplicate sources for protocol {} in a dict, unable to populate dict entry", |
| target_name |
| ); |
| target_dict.remove_capability(target_name); |
| return; |
| } |
| let router = match offer.source() { |
| cm_rust::OfferSource::Parent => component_input.capabilities().lazy_get( |
| source_path.to_owned(), |
| RoutingError::offer_from_parent_not_found( |
| moniker, |
| source_path.iter_segments().join("/"), |
| ), |
| ), |
| cm_rust::OfferSource::Self_ => program_output.clone().lazy_get( |
| source_path.to_owned(), |
| RoutingError::offer_from_self_not_found(moniker, source_path.iter_segments().join("/")), |
| ), |
| cm_rust::OfferSource::Child(child_ref) => { |
| let child_name: ChildName = child_ref.clone().try_into().expect("invalid child ref"); |
| let Some(child_component_output) = child_component_output_routers.get(&child_name) |
| else { |
| return; |
| }; |
| child_component_output.clone().lazy_get( |
| source_path.to_owned(), |
| RoutingError::offer_from_child_expose_not_found( |
| &child_name, |
| &moniker, |
| offer.source_name().clone(), |
| ), |
| ) |
| } |
| cm_rust::OfferSource::Framework => { |
| if offer.is_from_dictionary() { |
| warn!( |
| "routing from framework with dictionary path is not supported: {source_path}" |
| ); |
| return; |
| } |
| framework_dict.clone().lazy_get( |
| source_path.to_owned(), |
| RoutingError::capability_from_framework_not_found( |
| moniker, |
| source_path.iter_segments().join("/"), |
| ), |
| ) |
| } |
| cm_rust::OfferSource::Capability(capability_name) => { |
| let err = RoutingError::capability_from_capability_not_found( |
| moniker, |
| capability_name.as_str().to_string(), |
| ); |
| if source_path.iter_segments().join("/") == fsys::StorageAdminMarker::PROTOCOL_NAME { |
| capability_sourced_capabilities_dict.clone().lazy_get(capability_name.clone(), err) |
| } else { |
| Router::new_error(err) |
| } |
| } |
| cm_rust::OfferSource::Void => UnitRouter::new(), |
| // This is only relevant for services, so this arm is never reached. |
| cm_rust::OfferSource::Collection(_name) => return, |
| }; |
| match target_dict |
| .insert_capability(target_name, router.with_availability(*offer.availability()).into()) |
| { |
| Ok(()) => (), |
| Err(e) => warn!("failed to insert {target_name} into target dict: {e:?}"), |
| } |
| } |
| |
| pub fn is_supported_expose(expose: &cm_rust::ExposeDecl) -> bool { |
| matches!(expose, cm_rust::ExposeDecl::Protocol(_) | cm_rust::ExposeDecl::Dictionary(_)) |
| } |
| |
| fn extend_dict_with_expose( |
| moniker: &Moniker, |
| child_component_output_routers: &HashMap<ChildName, Router>, |
| program_output: &Router, |
| framework_dict: &Dict, |
| capability_sourced_capabilities_dict: &Dict, |
| expose: &cm_rust::ExposeDecl, |
| target_dict: &Dict, |
| ) { |
| if !is_supported_expose(expose) { |
| return; |
| } |
| // We only support exposing to the parent right now |
| if expose.target() != &cm_rust::ExposeTarget::Parent { |
| return; |
| } |
| let source_path = expose.source_path(); |
| let target_name = expose.target_name(); |
| |
| let router = match expose.source() { |
| cm_rust::ExposeSource::Self_ => program_output.clone().lazy_get( |
| source_path.to_owned(), |
| RoutingError::expose_from_self_not_found( |
| moniker, |
| source_path.iter_segments().join("/"), |
| ), |
| ), |
| cm_rust::ExposeSource::Child(child_name) => { |
| let child_name = ChildName::parse(child_name).expect("invalid static child name"); |
| if let Some(child_component_output) = child_component_output_routers.get(&child_name) { |
| child_component_output.clone().lazy_get( |
| source_path.to_owned(), |
| RoutingError::expose_from_child_expose_not_found( |
| &child_name, |
| &moniker, |
| expose.source_name().clone(), |
| ), |
| ) |
| } else { |
| return; |
| } |
| } |
| cm_rust::ExposeSource::Framework => { |
| if expose.is_from_dictionary() { |
| warn!( |
| "routing from framework with dictionary path is not supported: {source_path}" |
| ); |
| return; |
| } |
| framework_dict.clone().lazy_get( |
| source_path.to_owned(), |
| RoutingError::capability_from_framework_not_found( |
| moniker, |
| source_path.iter_segments().join("/"), |
| ), |
| ) |
| } |
| cm_rust::ExposeSource::Capability(capability_name) => { |
| let err = RoutingError::capability_from_capability_not_found( |
| moniker, |
| capability_name.as_str().to_string(), |
| ); |
| if source_path.iter_segments().join("/") == fsys::StorageAdminMarker::PROTOCOL_NAME { |
| capability_sourced_capabilities_dict.clone().lazy_get(capability_name.clone(), err) |
| } else { |
| Router::new_error(err) |
| } |
| } |
| cm_rust::ExposeSource::Void => UnitRouter::new(), |
| // This is only relevant for services, so this arm is never reached. |
| cm_rust::ExposeSource::Collection(_name) => return, |
| }; |
| match target_dict |
| .insert_capability(target_name, router.with_availability(*expose.availability()).into()) |
| { |
| Ok(()) => (), |
| Err(e) => warn!("failed to insert {target_name} into target_dict: {e:?}"), |
| } |
| } |
| |
| struct UnitRouter {} |
| |
| impl UnitRouter { |
| fn new() -> Router { |
| Router::new(UnitRouter {}) |
| } |
| } |
| |
| #[async_trait] |
| impl sandbox::Routable for UnitRouter { |
| async fn route(&self, request: Request) -> Result<Capability, RouterError> { |
| match request.availability { |
| cm_rust::Availability::Required | cm_rust::Availability::SameAsTarget => { |
| Err(RoutingError::SourceCapabilityIsVoid.into()) |
| } |
| cm_rust::Availability::Optional | cm_rust::Availability::Transitional => { |
| Ok(Unit {}.into()) |
| } |
| } |
| } |
| } |