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::DictExt;
6use crate::bedrock::with_policy_check::WithPolicyCheck;
7use crate::capability_source::{CapabilitySource, ComponentCapability, ComponentSource};
8use crate::component_instance::{ComponentInstanceInterface, WeakComponentInstanceInterface};
9use async_trait::async_trait;
10use cm_rust::NativeIntoFidl;
11use cm_types::Path;
12use log::warn;
13use router_error::RouterError;
14use sandbox::{Connector, Data, Dict, DirConnector, Request, Routable, Router, RouterResponse};
15use std::sync::Arc;
16
17pub trait ProgramOutputGenerator<C: ComponentInstanceInterface + 'static> {
18    /// Get a router for [Dict] that forwards the request to a [Router] served at `path`
19    /// in the program's outgoing directory.
20    fn new_program_dictionary_router(
21        &self,
22        component: WeakComponentInstanceInterface<C>,
23        path: Path,
24        capability: ComponentCapability,
25    ) -> Router<Dict>;
26
27    /// Get an outgoing directory router for `capability` that returns [Connector]. `capability`
28    /// should be a type that maps to [Connector].
29    fn new_outgoing_dir_connector_router(
30        &self,
31        component: &Arc<C>,
32        decl: &cm_rust::ComponentDecl,
33        capability: &cm_rust::CapabilityDecl,
34    ) -> Router<Connector>;
35
36    /// Get an outgoing directory router for `capability` that returns [DirConnector]. `capability`
37    /// should be a type that maps to [DirConnector].
38    fn new_outgoing_dir_dir_connector_router(
39        &self,
40        component: &Arc<C>,
41        decl: &cm_rust::ComponentDecl,
42        capability: &cm_rust::CapabilityDecl,
43    ) -> Router<DirConnector>;
44}
45
46pub fn build_program_output_dictionary<C: ComponentInstanceInterface + 'static>(
47    component: &Arc<C>,
48    decl: &cm_rust::ComponentDecl,
49    router_gen: &impl ProgramOutputGenerator<C>,
50) -> (Dict, Dict) {
51    let program_output_dict = Dict::new();
52    let declared_dictionaries = Dict::new();
53    for capability in &decl.capabilities {
54        extend_dict_with_capability(
55            component,
56            decl,
57            capability,
58            &program_output_dict,
59            &declared_dictionaries,
60            router_gen,
61        );
62    }
63    (program_output_dict, declared_dictionaries)
64}
65
66/// Adds `capability` to the program output dict given the resolved `decl`. The program output dict
67/// is a dict of routers, keyed by capability name.
68fn extend_dict_with_capability<C: ComponentInstanceInterface + 'static>(
69    component: &Arc<C>,
70    decl: &cm_rust::ComponentDecl,
71    capability: &cm_rust::CapabilityDecl,
72    program_output_dict: &Dict,
73    declared_dictionaries: &Dict,
74    router_gen: &impl ProgramOutputGenerator<C>,
75) {
76    match capability {
77        cm_rust::CapabilityDecl::Service(_) => {
78            let router =
79                router_gen.new_outgoing_dir_dir_connector_router(component, decl, capability);
80            let router = router.with_policy_check::<C>(
81                CapabilitySource::Component(ComponentSource {
82                    capability: ComponentCapability::from(capability.clone()),
83                    moniker: component.moniker().clone(),
84                }),
85                component.policy_checker().clone(),
86            );
87            match program_output_dict.insert_capability(capability.name(), router.into()) {
88                Ok(()) => (),
89                Err(e) => {
90                    warn!("failed to add {} to program output dict: {e:?}", capability.name())
91                }
92            }
93        }
94        cm_rust::CapabilityDecl::Directory(_) => {
95            let router =
96                router_gen.new_outgoing_dir_dir_connector_router(component, decl, capability);
97            let router = router.with_policy_check::<C>(
98                CapabilitySource::Component(ComponentSource {
99                    capability: ComponentCapability::from(capability.clone()),
100                    moniker: component.moniker().clone(),
101                }),
102                component.policy_checker().clone(),
103            );
104            match program_output_dict.insert_capability(capability.name(), router.into()) {
105                Ok(()) => (),
106                Err(e) => {
107                    warn!("failed to add {} to program output dict: {e:?}", capability.name())
108                }
109            }
110        }
111        cm_rust::CapabilityDecl::Protocol(_)
112        | cm_rust::CapabilityDecl::Runner(_)
113        | cm_rust::CapabilityDecl::Resolver(_) => {
114            let router = router_gen.new_outgoing_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            match program_output_dict.insert_capability(capability.name(), router.into()) {
123                Ok(()) => (),
124                Err(e) => {
125                    warn!("failed to add {} to program output dict: {e:?}", capability.name())
126                }
127            }
128        }
129        cm_rust::CapabilityDecl::Dictionary(d) => {
130            extend_dict_with_dictionary(
131                component,
132                d,
133                program_output_dict,
134                declared_dictionaries,
135                router_gen,
136            );
137        }
138        cm_rust::CapabilityDecl::Config(c) => {
139            let data = sandbox::Data::Bytes(
140                fidl::persist(&c.value.clone().native_into_fidl()).unwrap().into(),
141            );
142            match program_output_dict
143                .insert_capability(capability.name(), Router::<Data>::new_ok(data).into())
144            {
145                Ok(()) => (),
146                Err(e) => {
147                    warn!("failed to add {} to program output dict: {e:?}", capability.name())
148                }
149            }
150        }
151        cm_rust::CapabilityDecl::EventStream(_) | cm_rust::CapabilityDecl::Storage(_) => {
152            // Capabilities which will never appear in a program output dictionary
153            return;
154        }
155    }
156}
157
158fn extend_dict_with_dictionary<C: ComponentInstanceInterface + 'static>(
159    component: &Arc<C>,
160    decl: &cm_rust::DictionaryDecl,
161    program_output_dict: &Dict,
162    declared_dictionaries: &Dict,
163    router_gen: &impl ProgramOutputGenerator<C>,
164) {
165    let router;
166    let declared_dict;
167    if let Some(source_path) = decl.source_path.as_ref() {
168        // Dictionary backed by program's outgoing directory.
169        router = router_gen.new_program_dictionary_router(
170            component.as_weak(),
171            source_path.clone(),
172            ComponentCapability::Dictionary(decl.clone()),
173        );
174        declared_dict = None;
175    } else {
176        let dict = Dict::new();
177        router = make_simple_dict_router(dict.clone(), component, decl);
178        declared_dict = Some(dict);
179    }
180    if let Some(dict) = declared_dict {
181        match declared_dictionaries.insert_capability(&decl.name, dict.into()) {
182            Ok(()) => (),
183            Err(e) => warn!("failed to add {} to declared dicts: {e:?}", decl.name),
184        };
185    }
186    match program_output_dict.insert_capability(&decl.name, router.into()) {
187        Ok(()) => (),
188        Err(e) => warn!("failed to add {} to program output dict: {e:?}", decl.name),
189    }
190}
191
192/// Makes a router that always returns the given dictionary.
193fn make_simple_dict_router<C: ComponentInstanceInterface + 'static>(
194    dict: Dict,
195    component: &Arc<C>,
196    decl: &cm_rust::DictionaryDecl,
197) -> Router<Dict> {
198    struct DictRouter {
199        dict: Dict,
200        source: CapabilitySource,
201    }
202    #[async_trait]
203    impl Routable<Dict> for DictRouter {
204        async fn route(
205            &self,
206            _request: Option<Request>,
207            debug: bool,
208        ) -> Result<RouterResponse<Dict>, RouterError> {
209            if debug {
210                Ok(RouterResponse::Debug(
211                    self.source
212                        .clone()
213                        .try_into()
214                        .expect("failed to convert capability source to dictionary"),
215                ))
216            } else {
217                Ok(RouterResponse::Capability(self.dict.clone().into()))
218            }
219        }
220    }
221    let source = CapabilitySource::Component(ComponentSource {
222        capability: ComponentCapability::Dictionary(decl.clone()),
223        moniker: component.moniker().clone(),
224    });
225    Router::<Dict>::new(DictRouter { dict, source })
226}