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