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