Skip to main content

routing/
lib.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5pub mod availability;
6pub mod bedrock;
7pub mod capability_source;
8pub mod component_instance;
9pub mod config;
10pub mod error;
11pub mod policy;
12pub mod resolving;
13pub mod rights;
14pub mod subdir;
15
16use crate::bedrock::request_metadata::directory_metadata;
17use crate::capability_source::CapabilitySource;
18use crate::component_instance::{ComponentInstanceInterface, ResolvedInstanceInterface};
19use crate::error::RoutingError;
20use cm_rust::{
21    Availability, ExposeDecl, ExposeDeclCommon, ExposeTarget, OfferDecl, OfferDeclCommon,
22    OfferTarget, StorageDecl, StorageDirectorySource, UseDecl,
23};
24use cm_types::{IterablePath, Name, RelativePath};
25use fidl_fuchsia_io::RW_STAR_DIR;
26use itertools::Itertools;
27use moniker::{ChildName, ExtendedMoniker};
28use sandbox::{
29    Capability, CapabilityBound, Dict, DirConnector, Request, Routable, Router, RouterResponse,
30};
31use std::fmt::Debug;
32use std::sync::Arc;
33
34pub use bedrock::dict_ext::{DictExt, GenericRouterResponse};
35pub use bedrock::lazy_get::LazyGet;
36pub use bedrock::weak_instance_token_ext::{WeakInstanceTokenExt, test_invalid_instance_token};
37pub use bedrock::with_porcelain::WithPorcelain;
38
39#[derive(Clone)]
40pub struct SandboxPath {
41    path: String,
42}
43
44impl SandboxPath {
45    pub fn resolver(scheme: &str) -> Self {
46        Self { path: format!("component_input/environment/resolvers/{}", &scheme) }
47    }
48
49    pub fn used_path(target_path: &impl IterablePath) -> Self {
50        let path: RelativePath = target_path.iter_segments().collect::<Vec<_>>().into();
51        Self { path: format!("program_input/namespace/{}", path) }
52    }
53}
54
55impl From<&UseDecl> for SandboxPath {
56    fn from(use_decl: &UseDecl) -> Self {
57        let path = match use_decl {
58            UseDecl::Config(u) => format!("program_input/config/{}", u.target_name),
59            UseDecl::Dictionary(u) => format!("program_input/namespace{}", u.target_path),
60            UseDecl::Directory(u) => format!("program_input/namespace{}", u.target_path),
61            UseDecl::EventStream(u) => format!("program_input/namespace{}", u.target_path),
62            UseDecl::Protocol(u) => match (&u.target_path, &u.numbered_handle) {
63                (Some(target_path), None) => format!("program_input/namespace{}", target_path),
64                (None, Some(numbered_handle)) => {
65                    format!("program_input/numbered_handles/{}", Name::from(*numbered_handle))
66                }
67                _ => panic!("invalid use decl"),
68            },
69            UseDecl::Runner(_u) => "program_input/runner".to_string(),
70            UseDecl::Service(u) => format!("program_input/namespace{}", u.target_path),
71            UseDecl::Storage(u) => format!("program_input/namespace{}", u.target_path),
72        };
73        Self { path }
74    }
75}
76
77impl From<&OfferDecl> for SandboxPath {
78    fn from(offer_decl: &OfferDecl) -> Self {
79        let path = match offer_decl.target() {
80            OfferTarget::Child(child_ref) if child_ref.collection.is_some() => {
81                panic!("dynamic offers not supported")
82            }
83            OfferTarget::Child(child_ref) => {
84                format!("child_inputs/{}/parent/{}", child_ref.name, offer_decl.target_name())
85            }
86            OfferTarget::Collection(name) => {
87                format!("collection_inputs/{}/parent/{}", name, offer_decl.target_name())
88            }
89            OfferTarget::Capability(name) => {
90                format!("declared_dictionaries/{}/{}", name, offer_decl.target_name())
91            }
92        };
93        Self { path }
94    }
95}
96
97impl From<&ExposeDecl> for SandboxPath {
98    fn from(expose_decl: &ExposeDecl) -> Self {
99        let path = match expose_decl.target() {
100            ExposeTarget::Parent => {
101                format!("component_output/parent/{}", expose_decl.target_name())
102            }
103            ExposeTarget::Framework => {
104                format!("component_output/framework/{}", expose_decl.target_name())
105            }
106        };
107        Self { path }
108    }
109}
110
111impl From<SandboxPath> for RelativePath {
112    fn from(path: SandboxPath) -> Self {
113        RelativePath::new(&path.path).expect("invalid path string")
114    }
115}
116
117/// Calls `route` on the router at the given path within the component sandbox. Panics if the
118/// sandbox does not hold a router at that path.
119pub async fn debug_route_sandbox_path<C: ComponentInstanceInterface + 'static>(
120    component: &Arc<C>,
121    sandbox_path: impl Into<SandboxPath>,
122) -> Result<CapabilitySource, RoutingError> {
123    debug_route_sandbox_path_with_request(component, sandbox_path, None).await
124}
125
126/// Calls `route` on the router with the given request at the given path within the component
127/// sandbox. Panics if the sandbox does not hold a router at that path.
128pub async fn debug_route_sandbox_path_with_request<C: ComponentInstanceInterface + 'static>(
129    component: &Arc<C>,
130    sandbox_path: impl Into<SandboxPath>,
131    request: Option<Request>,
132) -> Result<CapabilitySource, RoutingError> {
133    let sandbox_path = sandbox_path.into();
134    let path: RelativePath = sandbox_path.clone().into();
135    let sandbox = component.component_sandbox().await.map_err(RoutingError::from)?;
136    let sandbox_dictionary: Dict = sandbox.into();
137    let maybe_response = sandbox_dictionary
138        .get_with_request(
139            &ExtendedMoniker::ComponentManager,
140            &path,
141            request,
142            true,
143            component.as_weak().into(),
144        )
145        .await
146        .map_err(|e| RoutingError::try_from(e).expect("invalid routing error"))?;
147    match maybe_response {
148        Some(GenericRouterResponse::Debug(data)) => {
149            Ok(data.try_into().expect("failed to deserialize capability source"))
150        }
151        None => Err(RoutingError::BedrockNotPresentInDictionary {
152            name: sandbox_path.path,
153            moniker: component.moniker().clone().into(),
154        }),
155        other_value => {
156            panic!("unexpected response to route: {other_value:?}")
157        }
158    }
159}
160
161/// Routes the backing directory for the storage declaration on the component.
162pub async fn debug_route_storage_backing_directory<C: ComponentInstanceInterface + 'static>(
163    component: &Arc<C>,
164    storage_decl: StorageDecl,
165) -> Result<CapabilitySource, RoutingError> {
166    let component_sandbox = component.component_sandbox().await?;
167    let source_dictionary = match storage_decl.source {
168        StorageDirectorySource::Parent => component_sandbox.component_input.capabilities(),
169        StorageDirectorySource::Self_ => component_sandbox.program_output_dict.clone(),
170        StorageDirectorySource::Child(static_name) => {
171            let child_name = ChildName::parse(static_name)
172                .expect("invalid child name, this should be prevented by manifest validation");
173            let child_component = component
174                .lock_resolved_state()
175                .await?
176                .get_child(&child_name)
177                .expect("resolver registration references nonexistent static child, this should be prevented by manifest validation");
178            let child_sandbox = child_component.component_sandbox().await?;
179            child_sandbox.component_output.capabilities().clone()
180        }
181    };
182    route_capability_inner::<DirConnector, _>(
183        &source_dictionary,
184        &storage_decl.backing_dir,
185        directory_metadata(Availability::Required, Some(RW_STAR_DIR.into()), None),
186        component,
187    )
188    .await
189}
190
191async fn route_capability_inner<T, C>(
192    dictionary: &Dict,
193    path: &impl IterablePath,
194    metadata: Dict,
195    target: &Arc<C>,
196) -> Result<CapabilitySource, RoutingError>
197where
198    C: ComponentInstanceInterface + 'static,
199    T: CapabilityBound + Debug,
200    Router<T>: TryFrom<Capability>,
201{
202    let router = dictionary.get_router_or_not_found(
203        path,
204        RoutingError::BedrockNotPresentInDictionary {
205            moniker: target.moniker().clone().into(),
206            name: path.iter_segments().join("/"),
207        },
208    );
209    perform_route::<T, C>(router, metadata, target).await
210}
211
212async fn perform_route<T, C>(
213    router: impl Routable<T>,
214    metadata: Dict,
215    target: &Arc<C>,
216) -> Result<CapabilitySource, RoutingError>
217where
218    C: ComponentInstanceInterface + 'static,
219    T: CapabilityBound + Debug,
220    Router<T>: TryFrom<Capability>,
221{
222    let request = Request { metadata };
223    let data = match router
224        .route(Some(request), true, target.as_weak().into())
225        .await
226        .map_err(|e| RoutingError::try_from(e).unwrap_or(RoutingError::UnexpectedError))?
227    {
228        RouterResponse::<T>::Debug(d) => d,
229        d => panic!("Debug route did not return a debug response: {d:?}"),
230    };
231    Ok(data.try_into().unwrap())
232}