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