Skip to main content

routing/bedrock/
program_output_dict.rs

1// Copyright 2023 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
5use crate::bedrock::structured_dict::ComponentInput;
6use crate::bedrock::with_policy_check::WithPolicyCheck;
7use crate::component_instance::{
8    ComponentInstanceInterface, ExtendedInstanceInterface, WeakComponentInstanceInterface,
9    WeakExtendedInstanceInterface,
10};
11use crate::error::RoutingError;
12use crate::{DictExt, LazyGet, WeakInstanceTokenExt};
13use async_trait::async_trait;
14use capability_source::{CapabilitySource, ComponentCapability, ComponentSource};
15use cm_rust::{CapabilityTypeName, NativeIntoFidl};
16use cm_types::{Path, RelativePath};
17use component_id_index::InstanceId;
18use fidl_fuchsia_component_decl as fdecl;
19use fidl_fuchsia_component_runtime::RouteRequest;
20use fidl_fuchsia_io as fio;
21use moniker::{ChildName, ExtendedMoniker, Moniker};
22use router_error::RouterError;
23use runtime_capabilities::{
24    Connector, Data, Dictionary, DirConnector, Routable, Router, WeakInstanceToken,
25};
26use std::collections::HashMap;
27use std::marker::PhantomData;
28use std::path::PathBuf;
29use std::sync::Arc;
30
31pub trait ProgramOutputGenerator<C: ComponentInstanceInterface + 'static> {
32    /// Get a router for [Dictionary] that forwards the request to a [Router] served at `path`
33    /// in the program's outgoing directory.
34    fn new_program_dictionary_router(
35        &self,
36        component: WeakComponentInstanceInterface<C>,
37        path: Path,
38        capability: ComponentCapability,
39    ) -> Router<Dictionary>;
40
41    /// Get an outgoing directory router for `capability` that returns [Connector]. `capability`
42    /// should be a type that maps to [Connector].
43    fn new_outgoing_dir_connector_router(
44        &self,
45        component: &Arc<C>,
46        decl: &cm_rust::ComponentDecl,
47        capability: &cm_rust::CapabilityDecl,
48    ) -> Router<Connector>;
49
50    /// Get an outgoing directory router for `capability` that returns [DirConnector]. `capability`
51    /// should be a type that maps to [DirConnector].
52    fn new_outgoing_dir_dir_connector_router(
53        &self,
54        component: &Arc<C>,
55        decl: &cm_rust::ComponentDecl,
56        capability: &cm_rust::CapabilityDecl,
57    ) -> Router<DirConnector>;
58}
59
60pub fn build_program_output_dictionary<C: ComponentInstanceInterface + 'static>(
61    component: &Arc<C>,
62    decl: &cm_rust::ComponentDecl,
63    component_input: &ComponentInput,
64    child_outgoing_dictionary_routers: &HashMap<ChildName, Router<Dictionary>>,
65    router_gen: &impl ProgramOutputGenerator<C>,
66) -> (Dictionary, Dictionary) {
67    let program_output_dict = Dictionary::new();
68    let declared_dictionaries = Dictionary::new();
69    for capability in &decl.capabilities {
70        extend_dict_with_capability(
71            component,
72            decl,
73            capability,
74            &program_output_dict,
75            &declared_dictionaries,
76            component_input,
77            child_outgoing_dictionary_routers,
78            router_gen,
79        );
80    }
81    (program_output_dict, declared_dictionaries)
82}
83
84/// Adds `capability` to the program output dict given the resolved `decl`. The program output dict
85/// is a dict of routers, keyed by capability name.
86fn extend_dict_with_capability<C: ComponentInstanceInterface + 'static>(
87    component: &Arc<C>,
88    decl: &cm_rust::ComponentDecl,
89    capability: &cm_rust::CapabilityDecl,
90    program_output_dict: &Dictionary,
91    declared_dictionaries: &Dictionary,
92    component_input: &ComponentInput,
93    child_outgoing_dictionary_routers: &HashMap<ChildName, Router<Dictionary>>,
94    router_gen: &impl ProgramOutputGenerator<C>,
95) {
96    match capability {
97        cm_rust::CapabilityDecl::Service(_) => {
98            let router =
99                router_gen.new_outgoing_dir_dir_connector_router(component, decl, capability);
100            let router = router.with_policy_check::<C>(
101                CapabilitySource::Component(ComponentSource {
102                    capability: ComponentCapability::from(capability.clone()),
103                    moniker: component.moniker().clone(),
104                }),
105                component.policy_checker().clone(),
106            );
107            let prev = program_output_dict.insert_capability(capability.name(), router.into());
108            assert!(prev.is_none(), "failed to insert {}: preexisting value", capability.name());
109        }
110        cm_rust::CapabilityDecl::Directory(_) => {
111            let router =
112                router_gen.new_outgoing_dir_dir_connector_router(component, decl, capability);
113            let router = router.with_policy_check::<C>(
114                CapabilitySource::Component(ComponentSource {
115                    capability: ComponentCapability::from(capability.clone()),
116                    moniker: component.moniker().clone(),
117                }),
118                component.policy_checker().clone(),
119            );
120            let prev = program_output_dict.insert_capability(capability.name(), router.into());
121            assert!(prev.is_none(), "failed to insert {}: preexisting value", capability.name());
122        }
123        cm_rust::CapabilityDecl::Storage(cm_rust::StorageDecl {
124            name,
125            source,
126            backing_dir,
127            subdir,
128            storage_id,
129        }) => {
130            let router: Router<DirConnector> = match source {
131                cm_rust::StorageDirectorySource::Parent => {
132                    component_input.capabilities().get_router_or_not_found(
133                        backing_dir,
134                        RoutingError::storage_from_parent_not_found(
135                            component.moniker(),
136                            backing_dir.clone(),
137                        ),
138                    )
139                }
140                cm_rust::StorageDirectorySource::Self_ => program_output_dict
141                    .get_router_or_not_found(
142                        backing_dir,
143                        RoutingError::BedrockNotPresentInDictionary {
144                            name: backing_dir.to_string(),
145                            moniker: ExtendedMoniker::ComponentInstance(
146                                component.moniker().clone(),
147                            ),
148                        },
149                    ),
150                cm_rust::StorageDirectorySource::Child(child_name) => {
151                    let child_name = ChildName::parse(child_name).expect("invalid child name");
152                    let Some(child_component_output) =
153                        child_outgoing_dictionary_routers.get(&child_name)
154                    else {
155                        panic!(
156                            "use declaration in manifest for component {} has a source of a nonexistent child {}, this should be prevented by manifest validation",
157                            component.moniker(),
158                            child_name
159                        );
160                    };
161                    child_component_output.clone().lazy_get(
162                        backing_dir.to_owned(),
163                        RoutingError::storage_from_child_expose_not_found(
164                            &child_name,
165                            &component.moniker(),
166                            backing_dir.clone(),
167                        ),
168                    )
169                }
170            };
171
172            #[derive(Debug)]
173            struct StorageBackingDirRouter<C: ComponentInstanceInterface + 'static> {
174                subdir: RelativePath,
175                storage_id: fdecl::StorageId,
176                backing_dir_router: Router<DirConnector>,
177                storage_source_moniker: Moniker,
178                backing_dir_target: WeakInstanceToken,
179                _component_type: PhantomData<C>,
180            }
181
182            impl<C: ComponentInstanceInterface + 'static> StorageBackingDirRouter<C> {
183                fn prepare_route(
184                    &self,
185                    mut request: RouteRequest,
186                    target: WeakInstanceToken,
187                ) -> Result<RouteRequest, RouterError> {
188                    fn generate_moniker_based_storage_path(
189                        subdir: Option<String>,
190                        moniker: &Moniker,
191                        instance_id: Option<&InstanceId>,
192                    ) -> PathBuf {
193                        let mut dir_path = vec![];
194                        if let Some(subdir) = subdir {
195                            dir_path.push(subdir);
196                        }
197
198                        if let Some(id) = instance_id {
199                            dir_path.push(id.to_string());
200                            return dir_path.into_iter().collect();
201                        }
202
203                        let path = moniker.path();
204                        let mut path = path.iter();
205                        if let Some(p) = path.next() {
206                            dir_path.push(format!("{p}:0"));
207                        }
208                        while let Some(p) = path.next() {
209                            dir_path.push("children".to_string());
210                            dir_path.push(format!("{p}:0"));
211                        }
212
213                        // Storage capabilities used to have a hardcoded set of types, which would be appended
214                        // here. To maintain compatibility with the old paths (and thus not lose data when this was
215                        // migrated) we append "data" here. This works because this is the only type of storage
216                        // that was actually used in the wild.
217                        //
218                        // This is only temporary, until the storage instance id migration changes this layout.
219                        dir_path.push("data".to_string());
220                        dir_path.into_iter().collect()
221                    }
222                    let StorageBackingDirRouter {
223                        subdir,
224                        storage_id,
225                        backing_dir_router: _,
226                        storage_source_moniker,
227                        backing_dir_target: _,
228                        _component_type: _,
229                    } = self;
230                    let instance: ExtendedInstanceInterface<C> = target.upgrade().unwrap();
231                    let instance = match instance {
232                        ExtendedInstanceInterface::Component(c) => c,
233                        ExtendedInstanceInterface::AboveRoot(_) => {
234                            panic!("unexpected component manager instance")
235                        }
236                    };
237                    let index = instance.component_id_index();
238                    let instance_id = index.id_for_moniker(instance.moniker());
239                    match storage_id {
240                        fdecl::StorageId::StaticInstanceId if instance_id.is_none() => {
241                            return Err(RouterError::from(RoutingError::ComponentNotInIdIndex {
242                                source_moniker: storage_source_moniker.clone(),
243                                target_name: instance.moniker().leaf().map(Into::into),
244                            }));
245                        }
246                        _ => (),
247                    }
248                    let moniker = match WeakInstanceTokenExt::<C>::moniker(&target) {
249                        ExtendedMoniker::ComponentInstance(m) => m,
250                        ExtendedMoniker::ComponentManager => {
251                            panic!("component manager is the target of a storage capability")
252                        }
253                    };
254                    let moniker = match moniker.strip_prefix(&storage_source_moniker) {
255                        Ok(v) => v,
256                        Err(_) => moniker,
257                    };
258                    let subdir_opt = if subdir.is_dot() { None } else { Some(subdir.to_string()) };
259                    let isolated_storage_path =
260                        generate_moniker_based_storage_path(subdir_opt, &moniker, instance_id);
261                    request.isolated_storage_path =
262                        Some(format!("{}", isolated_storage_path.display()));
263                    request.build_type_name = Some(CapabilityTypeName::Directory.to_string());
264                    request.directory_rights = Some(fio::PERM_READABLE | fio::PERM_WRITABLE);
265                    request.inherit_rights = Some(false);
266                    request.storage_sub_directory_path = Some(subdir.to_string());
267                    request.storage_source_moniker = Some(storage_source_moniker.to_string());
268                    Ok(request)
269                }
270            }
271
272            #[async_trait]
273            impl<C: ComponentInstanceInterface + 'static> Routable<DirConnector>
274                for StorageBackingDirRouter<C>
275            {
276                async fn route(
277                    &self,
278                    request: RouteRequest,
279                    target: WeakInstanceToken,
280                ) -> Result<Option<DirConnector>, RouterError> {
281                    let request = self.prepare_route(request, target)?;
282                    self.backing_dir_router.route(request, self.backing_dir_target.clone()).await
283                }
284
285                async fn route_debug(
286                    &self,
287                    request: RouteRequest,
288                    target: WeakInstanceToken,
289                ) -> Result<CapabilitySource, RouterError> {
290                    let request = self.prepare_route(request, target)?;
291                    self.backing_dir_router
292                        .route_debug(request, self.backing_dir_target.clone())
293                        .await
294                }
295            }
296
297            let router = router.with_policy_check::<C>(
298                CapabilitySource::Component(ComponentSource {
299                    capability: ComponentCapability::from(capability.clone()),
300                    moniker: component.moniker().clone(),
301                }),
302                component.policy_checker().clone(),
303            );
304            let router = Router::new(StorageBackingDirRouter::<C> {
305                subdir: subdir.clone(),
306                storage_id: storage_id.clone(),
307                backing_dir_router: router,
308                storage_source_moniker: component.moniker().clone(),
309                backing_dir_target: WeakInstanceToken {
310                    inner: Arc::new(WeakExtendedInstanceInterface::Component(component.as_weak())),
311                },
312                _component_type: Default::default(),
313            });
314            let prev = program_output_dict.insert_capability(name, router.into());
315            assert!(prev.is_none(), "failed to insert {}: preexisting value", capability.name());
316        }
317        cm_rust::CapabilityDecl::Protocol(_)
318        | cm_rust::CapabilityDecl::Runner(_)
319        | cm_rust::CapabilityDecl::Resolver(_) => {
320            let router = router_gen.new_outgoing_dir_connector_router(component, decl, capability);
321            let router = router.with_policy_check::<C>(
322                CapabilitySource::Component(ComponentSource {
323                    capability: ComponentCapability::from(capability.clone()),
324                    moniker: component.moniker().clone(),
325                }),
326                component.policy_checker().clone(),
327            );
328            let prev = program_output_dict.insert_capability(capability.name(), router.into());
329            assert!(prev.is_none(), "failed to insert {}: preexisting value", capability.name());
330        }
331        cm_rust::CapabilityDecl::Dictionary(d) => {
332            extend_dict_with_dictionary(
333                component,
334                d,
335                program_output_dict,
336                declared_dictionaries,
337                router_gen,
338            );
339        }
340        cm_rust::CapabilityDecl::Config(c) => {
341            let data =
342                Data::Bytes(fidl::persist(&c.value.clone().native_into_fidl()).unwrap().into());
343            struct ConfigRouter {
344                data: Data,
345                source: CapabilitySource,
346            }
347            #[async_trait]
348            impl Routable<Data> for ConfigRouter {
349                async fn route(
350                    &self,
351                    _request: RouteRequest,
352                    _target: WeakInstanceToken,
353                ) -> Result<Option<Data>, RouterError> {
354                    Ok(Some(self.data.clone()))
355                }
356                async fn route_debug(
357                    &self,
358                    _request: RouteRequest,
359                    _target: WeakInstanceToken,
360                ) -> Result<CapabilitySource, RouterError> {
361                    Ok(self.source.clone())
362                }
363            }
364            let source = CapabilitySource::Component(ComponentSource {
365                capability: ComponentCapability::from(capability.clone()),
366                moniker: component.moniker().clone(),
367            });
368            let router = Router::new(ConfigRouter { data, source: source.clone() });
369            let router = router.with_policy_check::<C>(source, component.policy_checker().clone());
370            let prev = program_output_dict.insert_capability(capability.name(), router.into());
371            assert!(prev.is_none(), "failed to insert {}: preexisting value", capability.name());
372        }
373        cm_rust::CapabilityDecl::EventStream(_) => {
374            // Capabilities not supported in bedrock program output dict yet.
375            return;
376        }
377    }
378}
379
380fn extend_dict_with_dictionary<C: ComponentInstanceInterface + 'static>(
381    component: &Arc<C>,
382    decl: &cm_rust::DictionaryDecl,
383    program_output_dict: &Dictionary,
384    declared_dictionaries: &Dictionary,
385    router_gen: &impl ProgramOutputGenerator<C>,
386) {
387    let router;
388    let declared_dict;
389    if let Some(source_path) = decl.source_path.as_ref() {
390        // Dictionary backed by program's outgoing directory.
391        router = router_gen.new_program_dictionary_router(
392            component.as_weak(),
393            source_path.clone(),
394            ComponentCapability::Dictionary(decl.clone()),
395        );
396        declared_dict = None;
397    } else {
398        let dict = Dictionary::new();
399        router = make_simple_dict_router(dict.clone(), component, decl);
400        declared_dict = Some(dict);
401    }
402    if let Some(dict) = declared_dict {
403        let prev = declared_dictionaries.insert_capability(&decl.name, dict.into());
404        assert!(prev.is_none(), "failed to insert {}: preexisting value", &decl.name);
405    }
406    let prev = program_output_dict.insert_capability(&decl.name, router.into());
407    assert!(prev.is_none(), "failed to insert {}: preexisting value", &decl.name);
408}
409
410/// Makes a router that always returns the given dictionary.
411fn make_simple_dict_router<C: ComponentInstanceInterface + 'static>(
412    dict: Dictionary,
413    component: &Arc<C>,
414    decl: &cm_rust::DictionaryDecl,
415) -> Router<Dictionary> {
416    struct DictRouter {
417        dict: Dictionary,
418        source: CapabilitySource,
419    }
420    #[async_trait]
421    impl Routable<Dictionary> for DictRouter {
422        async fn route(
423            &self,
424            _request: RouteRequest,
425            _target: WeakInstanceToken,
426        ) -> Result<Option<Dictionary>, RouterError> {
427            Ok(Some(self.dict.clone().into()))
428        }
429
430        async fn route_debug(
431            &self,
432            _request: RouteRequest,
433            _target: WeakInstanceToken,
434        ) -> Result<CapabilitySource, RouterError> {
435            Ok(self.source.clone())
436        }
437    }
438    let source = CapabilitySource::Component(ComponentSource {
439        capability: ComponentCapability::Dictionary(decl.clone()),
440        moniker: component.moniker().clone(),
441    });
442    Router::<Dictionary>::new(DictRouter { dict, source })
443}