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 log::warn;
23use moniker::{ChildName, ExtendedMoniker, Moniker};
24use router_error::RouterError;
25use sandbox::{
26    Connector, Data, Dict, DirConnector, Request, Routable, Router, RouterResponse,
27    WeakInstanceToken,
28};
29use std::collections::HashMap;
30use std::marker::PhantomData;
31use std::path::PathBuf;
32use std::sync::Arc;
33use {fidl_fuchsia_component_decl as fdecl, fidl_fuchsia_io as fio};
34
35pub trait ProgramOutputGenerator<C: ComponentInstanceInterface + 'static> {
36    /// Get a router for [Dict] 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<Dict>;
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<Dict>>,
69    router_gen: &impl ProgramOutputGenerator<C>,
70) -> (Dict, Dict) {
71    let program_output_dict = Dict::new();
72    let declared_dictionaries = Dict::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: &Dict,
95    declared_dictionaries: &Dict,
96    component_input: &ComponentInput,
97    child_outgoing_dictionary_routers: &HashMap<ChildName, Router<Dict>>,
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            #[async_trait]
195            impl<C: ComponentInstanceInterface + 'static> Routable<DirConnector>
196                for StorageBackingDirRouter<C>
197            {
198                async fn route(
199                    &self,
200                    request: Option<Request>,
201                    debug: bool,
202                    target: WeakInstanceToken,
203                ) -> Result<RouterResponse<DirConnector>, RouterError> {
204                    fn generate_moniker_based_storage_path(
205                        subdir: Option<String>,
206                        moniker: &Moniker,
207                        instance_id: Option<&InstanceId>,
208                    ) -> PathBuf {
209                        let mut dir_path = vec![];
210                        if let Some(subdir) = subdir {
211                            dir_path.push(subdir);
212                        }
213
214                        if let Some(id) = instance_id {
215                            dir_path.push(id.to_string());
216                            return dir_path.into_iter().collect();
217                        }
218
219                        let path = moniker.path();
220                        let mut path = path.iter();
221                        if let Some(p) = path.next() {
222                            dir_path.push(format!("{p}:0"));
223                        }
224                        while let Some(p) = path.next() {
225                            dir_path.push("children".to_string());
226                            dir_path.push(format!("{p}:0"));
227                        }
228
229                        // Storage capabilities used to have a hardcoded set of types, which would be appended
230                        // here. To maintain compatibility with the old paths (and thus not lose data when this was
231                        // migrated) we append "data" here. This works because this is the only type of storage
232                        // that was actually used in the wild.
233                        //
234                        // This is only temporary, until the storage instance id migration changes this layout.
235                        dir_path.push("data".to_string());
236                        dir_path.into_iter().collect()
237                    }
238                    let request = request.ok_or(RouterError::InvalidArgs)?;
239                    let StorageBackingDirRouter {
240                        subdir,
241                        storage_id,
242                        backing_dir_router,
243                        storage_source_moniker,
244                        backing_dir_target,
245                        _component_type: _,
246                    } = self;
247                    let instance: ExtendedInstanceInterface<C> = target.upgrade().unwrap();
248                    let instance = match instance {
249                        ExtendedInstanceInterface::Component(c) => c,
250                        ExtendedInstanceInterface::AboveRoot(_) => {
251                            panic!("unexpected component manager instance")
252                        }
253                    };
254                    let index = instance.component_id_index();
255                    let instance_id = index.id_for_moniker(instance.moniker());
256                    match storage_id {
257                        fdecl::StorageId::StaticInstanceId if instance_id.is_none() => {
258                            return Err(RouterError::from(RoutingError::ComponentNotInIdIndex {
259                                source_moniker: storage_source_moniker.clone(),
260                                target_name: instance.moniker().leaf().map(Into::into),
261                            }));
262                        }
263                        _ => (),
264                    }
265                    let moniker = match WeakInstanceTokenExt::<C>::moniker(&target) {
266                        ExtendedMoniker::ComponentInstance(m) => m,
267                        ExtendedMoniker::ComponentManager => {
268                            panic!("component manager is the target of a storage capability")
269                        }
270                    };
271                    let moniker = match moniker.strip_prefix(&storage_source_moniker) {
272                        Ok(v) => v,
273                        Err(_) => moniker,
274                    };
275                    let subdir_opt = if subdir.is_dot() { None } else { Some(subdir.to_string()) };
276                    let isolated_storage_path =
277                        generate_moniker_based_storage_path(subdir_opt, &moniker, instance_id);
278                    request.metadata.set_metadata(IsolatedStoragePath(isolated_storage_path));
279                    request.metadata.set_metadata(CapabilityTypeName::Directory);
280                    request.metadata.set_metadata(Rights::from(fio::RW_STAR_DIR));
281                    request.metadata.set_metadata(InheritRights(false));
282                    request.metadata.set_metadata(StorageSubdir(subdir.clone()));
283                    request
284                        .metadata
285                        .set_metadata(StorageSourceMoniker(storage_source_moniker.clone()));
286                    backing_dir_router.route(Some(request), debug, backing_dir_target.clone()).await
287                }
288            }
289
290            let router = router.with_policy_check::<C>(
291                CapabilitySource::Component(ComponentSource {
292                    capability: ComponentCapability::from(capability.clone()),
293                    moniker: component.moniker().clone(),
294                }),
295                component.policy_checker().clone(),
296            );
297            let router = Router::new(StorageBackingDirRouter::<C> {
298                subdir: subdir.clone(),
299                storage_id: storage_id.clone(),
300                backing_dir_router: router,
301                storage_source_moniker: component.moniker().clone(),
302                backing_dir_target: WeakInstanceToken {
303                    inner: Arc::new(WeakExtendedInstanceInterface::Component(component.as_weak())),
304                },
305                _component_type: Default::default(),
306            });
307            match program_output_dict.insert_capability(name, router.into()) {
308                Ok(()) => (),
309                Err(e) => {
310                    warn!("failed to add {} to program output dict: {e:?}", name)
311                }
312            }
313        }
314        cm_rust::CapabilityDecl::Protocol(_)
315        | cm_rust::CapabilityDecl::Runner(_)
316        | cm_rust::CapabilityDecl::Resolver(_) => {
317            let router = router_gen.new_outgoing_dir_connector_router(component, decl, capability);
318            let router = router.with_policy_check::<C>(
319                CapabilitySource::Component(ComponentSource {
320                    capability: ComponentCapability::from(capability.clone()),
321                    moniker: component.moniker().clone(),
322                }),
323                component.policy_checker().clone(),
324            );
325            match program_output_dict.insert_capability(capability.name(), router.into()) {
326                Ok(()) => (),
327                Err(e) => {
328                    warn!("failed to add {} to program output dict: {e:?}", capability.name())
329                }
330            }
331        }
332        cm_rust::CapabilityDecl::Dictionary(d) => {
333            extend_dict_with_dictionary(
334                component,
335                d,
336                program_output_dict,
337                declared_dictionaries,
338                router_gen,
339            );
340        }
341        cm_rust::CapabilityDecl::Config(c) => {
342            let data = sandbox::Data::Bytes(
343                fidl::persist(&c.value.clone().native_into_fidl()).unwrap().into(),
344            );
345            struct ConfigRouter {
346                data: Data,
347                source: CapabilitySource,
348            }
349            #[async_trait]
350            impl Routable<Data> for ConfigRouter {
351                async fn route(
352                    &self,
353                    _request: Option<Request>,
354                    debug: bool,
355                    _target: WeakInstanceToken,
356                ) -> Result<RouterResponse<Data>, RouterError> {
357                    if debug {
358                        Ok(RouterResponse::Debug(
359                            self.source
360                                .clone()
361                                .try_into()
362                                .expect("failed to convert capability source to dictionary"),
363                        ))
364                    } else {
365                        Ok(RouterResponse::Capability(self.data.clone()))
366                    }
367                }
368            }
369            let source = CapabilitySource::Component(ComponentSource {
370                capability: ComponentCapability::from(capability.clone()),
371                moniker: component.moniker().clone(),
372            });
373            let router = Router::new(ConfigRouter { data, source: source.clone() });
374            let router = router.with_policy_check::<C>(source, component.policy_checker().clone());
375            match program_output_dict.insert_capability(capability.name(), router.into()) {
376                Ok(()) => (),
377                Err(e) => {
378                    warn!("failed to add {} to program output dict: {e:?}", capability.name())
379                }
380            }
381        }
382        cm_rust::CapabilityDecl::EventStream(_) => {
383            // Capabilities not supported in bedrock program output dict yet.
384            return;
385        }
386    }
387}
388
389fn extend_dict_with_dictionary<C: ComponentInstanceInterface + 'static>(
390    component: &Arc<C>,
391    decl: &cm_rust::DictionaryDecl,
392    program_output_dict: &Dict,
393    declared_dictionaries: &Dict,
394    router_gen: &impl ProgramOutputGenerator<C>,
395) {
396    let router;
397    let declared_dict;
398    if let Some(source_path) = decl.source_path.as_ref() {
399        // Dictionary backed by program's outgoing directory.
400        router = router_gen.new_program_dictionary_router(
401            component.as_weak(),
402            source_path.clone(),
403            ComponentCapability::Dictionary(decl.clone()),
404        );
405        declared_dict = None;
406    } else {
407        let dict = Dict::new();
408        router = make_simple_dict_router(dict.clone(), component, decl);
409        declared_dict = Some(dict);
410    }
411    if let Some(dict) = declared_dict {
412        match declared_dictionaries.insert_capability(&decl.name, dict.into()) {
413            Ok(()) => (),
414            Err(e) => warn!("failed to add {} to declared dicts: {e:?}", decl.name),
415        };
416    }
417    match program_output_dict.insert_capability(&decl.name, router.into()) {
418        Ok(()) => (),
419        Err(e) => warn!("failed to add {} to program output dict: {e:?}", decl.name),
420    }
421}
422
423/// Makes a router that always returns the given dictionary.
424fn make_simple_dict_router<C: ComponentInstanceInterface + 'static>(
425    dict: Dict,
426    component: &Arc<C>,
427    decl: &cm_rust::DictionaryDecl,
428) -> Router<Dict> {
429    struct DictRouter {
430        dict: Dict,
431        source: CapabilitySource,
432    }
433    #[async_trait]
434    impl Routable<Dict> for DictRouter {
435        async fn route(
436            &self,
437            _request: Option<Request>,
438            debug: bool,
439            _target: WeakInstanceToken,
440        ) -> Result<RouterResponse<Dict>, RouterError> {
441            if debug {
442                Ok(RouterResponse::Debug(
443                    self.source
444                        .clone()
445                        .try_into()
446                        .expect("failed to convert capability source to dictionary"),
447                ))
448            } else {
449                Ok(RouterResponse::Capability(self.dict.clone().into()))
450            }
451        }
452    }
453    let source = CapabilitySource::Component(ComponentSource {
454        capability: ComponentCapability::Dictionary(decl.clone()),
455        moniker: component.moniker().clone(),
456    });
457    Router::<Dict>::new(DictRouter { dict, source })
458}