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 component_instance;
8pub mod config;
9pub mod error;
10pub mod error_logging_router;
11pub mod intermediate_router;
12pub mod policy;
13pub mod resolving;
14pub mod rights;
15pub mod subdir;
16mod to_request;
17mod to_source;
18
19use crate::bedrock::request_metadata::directory_metadata;
20use crate::component_instance::{ComponentInstanceInterface, ResolvedInstanceInterface};
21use crate::error::RoutingError;
22use capability_source::CapabilitySource;
23use cm_rust::{
24    Availability, CapabilityTypeName, ExposeDecl, ExposeDeclCommon, ExposeTarget, OfferDecl,
25    OfferDeclCommon, OfferTarget, StorageDecl, StorageDirectorySource, UseDecl,
26};
27use cm_types::{IterablePath, Name, RelativePath};
28use fidl_fuchsia_component_decl as fdecl;
29use fidl_fuchsia_component_runtime::RouteRequest;
30use fidl_fuchsia_io::RW_STAR_DIR;
31use itertools::Itertools;
32use moniker::ChildName;
33use runtime_capabilities::{Capability, CapabilityBound, Dictionary, DirConnector, Router};
34use std::fmt::Debug;
35use std::sync::Arc;
36
37pub use bedrock::dict_ext::DictExt;
38pub use bedrock::weak_instance_token_ext::{WeakInstanceTokenExt, test_invalid_instance_token};
39
40#[derive(Clone)]
41pub struct SandboxPath {
42    path: String,
43}
44
45impl SandboxPath {
46    pub fn resolver(scheme: &str) -> Self {
47        Self { path: format!("component_input/environment/resolvers/{}", scheme) }
48    }
49
50    pub fn used_path(target_path: &impl IterablePath) -> Self {
51        let path: RelativePath = target_path.iter_segments().collect::<Vec<_>>().into();
52        Self { path: format!("program_input/namespace/{}", path) }
53    }
54}
55
56impl From<&UseDecl> for SandboxPath {
57    fn from(use_decl: &UseDecl) -> Self {
58        let path = match use_decl {
59            UseDecl::Config(u) => format!("program_input/config/{}", u.target_name),
60            UseDecl::Dictionary(u) => format!("program_input/namespace{}", u.target_path),
61            UseDecl::Directory(u) => format!("program_input/namespace{}", u.target_path),
62            UseDecl::EventStream(u) => format!("program_input/namespace{}", u.target_path),
63            UseDecl::Protocol(u) => match (&u.target_path, &u.numbered_handle) {
64                (Some(target_path), None) => format!("program_input/namespace{}", target_path),
65                (None, Some(numbered_handle)) => {
66                    format!("program_input/numbered_handles/{}", Name::from(*numbered_handle))
67                }
68                _ => panic!("invalid use decl"),
69            },
70            UseDecl::Runner(_u) => "program_input/runner".to_string(),
71            UseDecl::Service(u) => format!("program_input/namespace{}", u.target_path),
72            UseDecl::Storage(u) => format!("program_input/namespace{}", u.target_path),
73        };
74        Self { path }
75    }
76}
77
78impl From<&OfferDecl> for SandboxPath {
79    fn from(offer_decl: &OfferDecl) -> Self {
80        let path = match offer_decl.target() {
81            OfferTarget::Child(child_ref) if child_ref.collection.is_some() => {
82                panic!("dynamic offers not supported")
83            }
84            OfferTarget::Child(child_ref) => {
85                format!("child_inputs/{}/parent/{}", child_ref.name, offer_decl.target_name())
86            }
87            OfferTarget::Collection(name) => {
88                format!("collection_inputs/{}/parent/{}", name, offer_decl.target_name())
89            }
90            OfferTarget::Capability(name) => {
91                format!("declared_dictionaries/{}/{}", name, offer_decl.target_name())
92            }
93        };
94        Self { path }
95    }
96}
97
98impl From<&ExposeDecl> for SandboxPath {
99    fn from(expose_decl: &ExposeDecl) -> Self {
100        let path = match expose_decl.target() {
101            ExposeTarget::Parent => {
102                format!("component_output/parent/{}", expose_decl.target_name())
103            }
104            ExposeTarget::Framework => {
105                format!("component_output/framework/{}", expose_decl.target_name())
106            }
107        };
108        Self { path }
109    }
110}
111
112impl From<SandboxPath> for RelativePath {
113    fn from(path: SandboxPath) -> Self {
114        RelativePath::new(&path.path).expect("invalid path string")
115    }
116}
117
118/// Calls `route` on the router at the given path within the component sandbox. Panics if the
119/// sandbox does not hold a router at that path.
120pub async fn debug_route_sandbox_path<C: ComponentInstanceInterface + 'static>(
121    component: &Arc<C>,
122    sandbox_path: impl Into<SandboxPath>,
123) -> Result<CapabilitySource, RoutingError> {
124    debug_route_sandbox_path_with_request(component, sandbox_path, RouteRequest::default()).await
125}
126
127/// Calls `route` on the router with the given request at the given path within the component
128/// sandbox. Panics if the sandbox does not hold a router at that path.
129pub async fn debug_route_sandbox_path_with_request<C: ComponentInstanceInterface + 'static>(
130    component: &Arc<C>,
131    sandbox_path: impl Into<SandboxPath>,
132    request: RouteRequest,
133) -> Result<CapabilitySource, RoutingError> {
134    let sandbox_path = sandbox_path.into();
135    let path: RelativePath = sandbox_path.clone().into();
136    let mut path_vec: Vec<Name> = path.iter_segments().map(|n| n.to_owned()).collect();
137    let last_name = path_vec.pop().expect("can't open empty path");
138
139    let sandbox = component.component_sandbox().await.map_err(RoutingError::from)?;
140    let mut dictionary: Arc<Dictionary> = sandbox.into();
141
142    for next_name in path_vec.iter() {
143        match dictionary.get(next_name) {
144            Some(Capability::Dictionary(sub_dictionary)) => dictionary = sub_dictionary,
145            Some(Capability::DictionaryRouter(router)) => {
146                let dictionary_request = RouteRequest {
147                    build_type_name: Some(CapabilityTypeName::Dictionary.to_string()),
148                    availability: Some(
149                        request.availability.unwrap_or(fdecl::Availability::Required),
150                    ),
151                    ..request.clone()
152                };
153                match router.route(dictionary_request, component.as_weak().into()).await {
154                    Ok(Some(sub_dictionary)) => dictionary = sub_dictionary,
155                    Ok(None) => {
156                        return Err(RoutingError::BedrockNotPresentInDictionary {
157                            moniker: component.moniker().clone().into(),
158                            name: path.iter_segments().join("/"),
159                        });
160                    }
161                    Err(e) => return Err(e.try_into().unwrap()),
162                }
163            }
164            Some(other_capability) => {
165                return Err(RoutingError::BedrockWrongCapabilityType {
166                    actual: other_capability.debug_typename().to_string(),
167                    expected: "dictionary".to_string(),
168                    moniker: component.moniker().clone().into(),
169                });
170            }
171            None => {
172                return Err(RoutingError::BedrockNotPresentInDictionary {
173                    moniker: component.moniker().clone().into(),
174                    name: path.iter_segments().join("/"),
175                });
176            }
177        }
178    }
179
180    match dictionary.get(&last_name) {
181        None => Err(RoutingError::BedrockNotPresentInDictionary {
182            moniker: component.moniker().clone().into(),
183            name: path.iter_segments().join("/"),
184        }),
185        Some(Capability::ConnectorRouter(router)) => router
186            .route_debug(request, component.as_weak().into())
187            .await
188            .map_err(|e| RoutingError::try_from(e).expect("invalid routing error")),
189        Some(Capability::DirConnectorRouter(router)) => router
190            .route_debug(request, component.as_weak().into())
191            .await
192            .map_err(|e| RoutingError::try_from(e).expect("invalid routing error")),
193        Some(Capability::DictionaryRouter(router)) => router
194            .route_debug(request, component.as_weak().into())
195            .await
196            .map_err(|e| RoutingError::try_from(e).expect("invalid routing error")),
197        Some(Capability::DataRouter(router)) => router
198            .route_debug(request, component.as_weak().into())
199            .await
200            .map_err(|e| RoutingError::try_from(e).expect("invalid routing error")),
201        Some(_other_type) => {
202            panic!("can't debug route a non-router type")
203        }
204    }
205}
206
207/// Routes the backing directory for the storage declaration on the component.
208pub async fn debug_route_storage_backing_directory<C: ComponentInstanceInterface + 'static>(
209    component: &Arc<C>,
210    storage_decl: StorageDecl,
211) -> Result<CapabilitySource, RoutingError> {
212    let component_sandbox = component.component_sandbox().await?;
213    let source_dictionary = match storage_decl.source {
214        StorageDirectorySource::Parent => component_sandbox.component_input.capabilities(),
215        StorageDirectorySource::Self_ => component_sandbox.program_output_dict.clone(),
216        StorageDirectorySource::Child(static_name) => {
217            let child_name = ChildName::parse(static_name)
218                .expect("invalid child name, this should be prevented by manifest validation");
219            let child_component = component
220                .lock_resolved_state()
221                .await?
222                .get_child(&child_name)
223                .expect("resolver registration references nonexistent static child, this should be prevented by manifest validation");
224            let child_sandbox = child_component.component_sandbox().await?;
225            child_sandbox.component_output.capabilities().clone()
226        }
227    };
228    route_capability_inner::<DirConnector, _>(
229        &source_dictionary,
230        &storage_decl.backing_dir,
231        directory_metadata(Availability::Required, Some(RW_STAR_DIR.into()), None),
232        component,
233    )
234    .await
235}
236
237async fn route_capability_inner<T, C>(
238    dictionary: &Arc<Dictionary>,
239    path: &impl IterablePath,
240    request: RouteRequest,
241    target: &Arc<C>,
242) -> Result<CapabilitySource, RoutingError>
243where
244    C: ComponentInstanceInterface + 'static,
245    T: CapabilityBound + Debug,
246    Arc<T>: TryFrom<Capability>,
247    Router<T>: CapabilityBound,
248    Capability: From<Arc<T>>,
249    Capability: From<Arc<Router<T>>>,
250    Arc<Router<T>>: TryFrom<Capability>,
251{
252    let Some(capability) = dictionary.get_capability(path) else {
253        return Err(RoutingError::BedrockNotPresentInDictionary {
254            moniker: target.moniker().clone().into(),
255            name: path.iter_segments().join("/"),
256        });
257    };
258    let actual_type_name = capability.debug_typename();
259    let router: Arc<Router<T>> =
260        capability.try_into().map_err(|_| RoutingError::BedrockWrongCapabilityType {
261            actual: actual_type_name.to_string(),
262            expected: Router::<T>::debug_typename().to_string(),
263            moniker: target.moniker().clone().into(),
264        })?;
265    perform_route::<T, C>(router, request, target).await
266}
267
268async fn perform_route<T, C>(
269    router: Arc<Router<T>>,
270    request: RouteRequest,
271    target: &Arc<C>,
272) -> Result<CapabilitySource, RoutingError>
273where
274    C: ComponentInstanceInterface + 'static,
275    T: CapabilityBound + Debug,
276    Arc<Router<T>>: TryFrom<Capability>,
277{
278    router.route_debug(request, target.as_weak().into()).await.map_err(|e| {
279        RoutingError::try_from(e).expect("failed to convert RouterError to RoutingError")
280    })
281}