1use 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 fn new_program_dictionary_router(
39 &self,
40 component: WeakComponentInstanceInterface<C>,
41 path: Path,
42 capability: ComponentCapability,
43 ) -> Router<Dictionary>;
44
45 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 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
88fn 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 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 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 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
446fn 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}