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