1use crate::component_instance::{ComponentInstanceInterface, WeakExtendedInstanceInterface};
6use crate::error::{ErrorReporter, RouteRequestErrorInfo, RoutingError};
7use crate::rights::Rights;
8use crate::subdir::SubDir;
9use async_trait::async_trait;
10use capability_source::CapabilitySource;
11use cm_rust::{CapabilityTypeName, EventScope, FidlIntoNative, NativeIntoFidl};
12use cm_types::Availability;
13use fidl_fuchsia_component_runtime::RouteRequest;
14use fidl_fuchsia_component_sandbox as fsandbox;
15use fidl_fuchsia_io as fio;
16use moniker::{ExtendedMoniker, Moniker};
17use router_error::RouterError;
18use runtime_capabilities::{
19 Capability, CapabilityBound, Dictionary, Routable, Router, WeakInstanceToken,
20};
21use std::collections::HashMap;
22use std::sync::{Arc, LazyLock};
23use strum::IntoEnumIterator;
24
25struct PorcelainRouter<T: CapabilityBound, R, C: ComponentInstanceInterface, const D: bool> {
26 router: Router<T>,
27 porcelain_type: CapabilityTypeName,
28 availability: Availability,
29 rights: Option<Rights>,
30 subdir: Option<SubDir>,
31 inherit_rights: Option<bool>,
32 event_stream_scope: Option<(Moniker, Box<[EventScope]>)>,
33 target: WeakExtendedInstanceInterface<C>,
34 route_request: RouteRequestErrorInfo,
35 error_reporter: R,
36}
37
38#[async_trait]
39impl<T: CapabilityBound, R: ErrorReporter, C: ComponentInstanceInterface + 'static, const D: bool>
40 Routable<T> for PorcelainRouter<T, R, C, D>
41{
42 async fn route(
43 &self,
44 request: RouteRequest,
45 target: WeakInstanceToken,
46 ) -> Result<Option<T>, RouterError> {
47 match self.route_inner(request, D, target).await {
48 Ok(res) => Ok(res),
49 Err(err) => {
50 self.error_reporter
51 .report(&self.route_request, &err, self.target.clone().into())
52 .await;
53 Err(err)
54 }
55 }
56 }
57
58 async fn route_debug(
59 &self,
60 request: RouteRequest,
61 target: WeakInstanceToken,
62 ) -> Result<CapabilitySource, RouterError> {
63 match self.route_debug_inner(request, D, target).await {
64 Ok(res) => Ok(res),
65 Err(err) => {
66 self.error_reporter
67 .report(&self.route_request, &err, self.target.clone().into())
68 .await;
69 Err(err)
70 }
71 }
72 }
73}
74
75impl<T: CapabilityBound, R: ErrorReporter, C: ComponentInstanceInterface + 'static, const D: bool>
76 PorcelainRouter<T, R, C, D>
77{
78 async fn route_inner(
79 &self,
80 request: RouteRequest,
81 supply_default: bool,
82 target: WeakInstanceToken,
83 ) -> Result<Option<T>, RouterError> {
84 let request = self.check_and_compute_request(request, supply_default)?;
85 self.router.route(request, target).await
86 }
87
88 async fn route_debug_inner(
89 &self,
90 request: RouteRequest,
91 supply_default: bool,
92 target: WeakInstanceToken,
93 ) -> Result<CapabilitySource, RouterError> {
94 let request = self.check_and_compute_request(request, supply_default)?;
95 self.router.route_debug(request, target).await
96 }
97
98 fn check_and_compute_request(
99 &self,
100 request: RouteRequest,
101 supply_default: bool,
102 ) -> Result<RouteRequest, RouterError> {
103 let PorcelainRouter {
104 router: _,
105 porcelain_type,
106 availability,
107 rights,
108 subdir,
109 inherit_rights,
110 event_stream_scope,
111 target,
112 route_request: _,
113 error_reporter: _,
114 } = self;
115 let mut request = if request != RouteRequest::default() {
116 request
117 } else {
118 if !supply_default {
119 Err(RouterError::InvalidArgs)?;
120 }
121 let mut request = RouteRequest::default();
122 request.build_type_name = Some(porcelain_type.to_string());
123 request.availability = Some(availability.native_into_fidl());
124 if let Some(rights) = rights {
125 request.directory_rights = Some(fio::Flags::from(*rights));
126 }
127 if let Some(inherit_rights) = inherit_rights {
128 request.inherit_rights = Some(*inherit_rights);
129 }
130 if let Some((scope_moniker, scope)) = event_stream_scope.as_ref() {
131 request.event_stream_scope_moniker = Some(scope_moniker.to_string());
132 request.event_stream_scope = Some(scope.clone().native_into_fidl());
133 }
134 request
135 };
136
137 let moniker: ExtendedMoniker = match target {
138 WeakExtendedInstanceInterface::Component(t) => t.moniker.clone().into(),
139 WeakExtendedInstanceInterface::AboveRoot(_) => ExtendedMoniker::ComponentManager,
140 };
141 check_porcelain_type(&moniker, &request, *porcelain_type)?;
142 let updated_availability = check_availability(&moniker, &request, *availability)?;
143
144 check_and_compute_rights(&moniker, &mut request, &rights)?;
145 if let Some(new_subdir) = check_and_compute_subdir(&moniker, &request, &subdir)? {
146 request.sub_directory_path = Some(new_subdir.as_ref().clone().native_into_fidl());
147 }
148 if let Some((new_scope_moniker, new_scope)) = event_stream_scope.as_ref() {
149 if request.event_stream_scope_moniker.is_none() {
152 request.event_stream_scope_moniker =
153 Some(new_scope_moniker.clone().native_into_fidl());
154 request.event_stream_scope = Some(new_scope.clone().native_into_fidl());
155 }
156 }
157
158 request.availability = Some(updated_availability.native_into_fidl());
160 Ok(request)
161 }
162}
163
164fn check_porcelain_type(
165 moniker: &ExtendedMoniker,
166 request: &RouteRequest,
167 expected_type: CapabilityTypeName,
168) -> Result<(), RouterError> {
169 let capability_type: CapabilityTypeName = request
170 .build_type_name
171 .as_ref()
172 .ok_or_else(|| RoutingError::BedrockMissingCapabilityType {
173 type_name: expected_type.to_string(),
174 moniker: moniker.clone(),
175 })?
176 .parse()
177 .map_err(|_| RouterError::InvalidArgs)?;
178 if capability_type != expected_type {
179 Err(RoutingError::BedrockWrongCapabilityType {
180 moniker: moniker.clone(),
181 actual: capability_type.to_string(),
182 expected: expected_type.to_string(),
183 })?;
184 }
185 Ok(())
186}
187
188fn check_availability(
189 moniker: &ExtendedMoniker,
190 request: &RouteRequest,
191 availability: Availability,
192) -> Result<Availability, RouterError> {
193 let request_availability =
196 request.availability.ok_or(fsandbox::RouterError::InvalidArgs).inspect_err(|e| {
197 log::error!("request {:?} did not have availability metadata: {e:?}", request)
198 })?;
199 crate::availability::advance(&moniker, request_availability.fidl_into_native(), availability)
200 .map_err(|e| RoutingError::from(e).into())
201}
202
203fn check_and_compute_rights(
204 moniker: &ExtendedMoniker,
205 request: &mut RouteRequest,
206 rights: &Option<Rights>,
207) -> Result<(), RouterError> {
208 let Some(rights) = rights else {
209 return Ok(());
210 };
211 let inherit = request.inherit_rights.ok_or(RouterError::InvalidArgs)?;
212 let request_rights: Rights = match request.directory_rights {
213 Some(request_rights) => request_rights.into(),
214 None => {
215 if inherit {
216 request.directory_rights = Some(fio::Flags::from(*rights));
217 *rights
218 } else {
219 Err(RouterError::InvalidArgs)?
220 }
221 }
222 };
223 if let Some(intermediate_rights) = request.directory_intermediate_rights {
226 Rights::from(intermediate_rights)
227 .validate_next(&rights, moniker.clone().into())
228 .map_err(|e| router_error::RouterError::from(RoutingError::from(e)))?;
229 };
230 request.directory_intermediate_rights = Some(fio::Flags::from(*rights));
231 request_rights.validate_next(&rights, moniker.clone().into()).map_err(RoutingError::from)?;
234 Ok(())
235}
236
237fn check_and_compute_subdir(
238 moniker: &ExtendedMoniker,
239 request: &RouteRequest,
240 subdir: &Option<SubDir>,
241) -> Result<Option<SubDir>, RouterError> {
242 let Some(mut subdir_from_decl) = subdir.clone() else {
243 return Ok(None);
244 };
245
246 let request_subdir: Option<SubDir> =
247 request.sub_directory_path.as_ref().map(|s| SubDir::new(s).expect("invalid sub directory"));
248
249 if let Some(request_subdir) = request_subdir {
250 let success = subdir_from_decl.as_mut().extend(request_subdir.clone().into());
251 if !success {
252 return Err(RoutingError::PathTooLong {
253 moniker: moniker.clone(),
254 path: subdir_from_decl.to_string(),
255 keyword: request_subdir.to_string(),
256 }
257 .into());
258 }
259 }
260 Ok(Some(subdir_from_decl))
261}
262
263pub type DefaultMetadataFn = Arc<dyn Fn(Availability) -> Dictionary + Send + Sync + 'static>;
264
265pub struct PorcelainBuilder<
269 T: CapabilityBound,
270 R: ErrorReporter,
271 C: ComponentInstanceInterface + 'static,
272 const D: bool,
273> {
274 router: Router<T>,
275 porcelain_type: CapabilityTypeName,
276 availability: Option<Availability>,
277 rights: Option<Rights>,
278 subdir: Option<SubDir>,
279 inherit_rights: Option<bool>,
280 event_stream_scope: Option<(Moniker, Box<[EventScope]>)>,
281 target: Option<WeakExtendedInstanceInterface<C>>,
282 error_info: Option<RouteRequestErrorInfo>,
283 error_reporter: Option<R>,
284}
285
286impl<T: CapabilityBound, R: ErrorReporter, C: ComponentInstanceInterface + 'static, const D: bool>
287 PorcelainBuilder<T, R, C, D>
288{
289 fn new(router: Router<T>, porcelain_type: CapabilityTypeName) -> Self {
290 Self {
291 router,
292 porcelain_type,
293 availability: None,
294 rights: None,
295 subdir: None,
296 inherit_rights: None,
297 event_stream_scope: None,
298 target: None,
299 error_info: None,
300 error_reporter: None,
301 }
302 }
303
304 pub fn availability(mut self, a: Availability) -> Self {
307 self.availability = Some(a);
308 self
309 }
310
311 pub fn rights(mut self, rights: Option<Rights>) -> Self {
312 self.rights = rights;
313 self
314 }
315
316 pub fn subdir(mut self, subdir: SubDir) -> Self {
317 self.subdir = Some(subdir);
318 self
319 }
320
321 pub fn inherit_rights(mut self, inherit_rights: bool) -> Self {
322 self.inherit_rights = Some(inherit_rights);
323 self
324 }
325
326 pub fn event_stream_scope(mut self, scope: (Moniker, Box<[EventScope]>)) -> Self {
327 self.event_stream_scope = Some(scope);
328 self
329 }
330
331 pub fn target(mut self, t: &Arc<C>) -> Self {
335 self.target = Some(WeakExtendedInstanceInterface::Component(t.as_weak()));
336 self
337 }
338
339 pub fn target_above_root(mut self, t: &Arc<C::TopInstance>) -> Self {
342 self.target = Some(WeakExtendedInstanceInterface::AboveRoot(Arc::downgrade(t)));
343 self
344 }
345
346 pub fn error_info<S>(mut self, r: S) -> Self
350 where
351 RouteRequestErrorInfo: From<S>,
352 {
353 self.error_info = Some(RouteRequestErrorInfo::from(r));
354 self
355 }
356
357 pub fn error_reporter(mut self, r: R) -> Self {
360 self.error_reporter = Some(r);
361 self
362 }
363
364 pub fn build(self) -> Router<T> {
366 Router::new(PorcelainRouter::<T, R, C, D> {
367 router: self.router,
368 porcelain_type: self.porcelain_type,
369 availability: self.availability.expect("must set availability"),
370 rights: self.rights,
371 subdir: self.subdir,
372 inherit_rights: self.inherit_rights,
373 event_stream_scope: self.event_stream_scope,
374 target: self.target.expect("must set target"),
375 route_request: self.error_info.expect("must set route_request"),
376 error_reporter: self.error_reporter.expect("must set error_reporter"),
377 })
378 }
379}
380
381impl<R: ErrorReporter, T: CapabilityBound, C: ComponentInstanceInterface + 'static, const D: bool>
382 From<PorcelainBuilder<T, R, C, D>> for Capability
383where
384 Router<T>: Into<Capability>,
385{
386 fn from(b: PorcelainBuilder<T, R, C, D>) -> Self {
387 b.build().into()
388 }
389}
390
391pub trait WithPorcelain<
393 T: CapabilityBound,
394 R: ErrorReporter,
395 C: ComponentInstanceInterface + 'static,
396>
397{
398 fn with_porcelain_with_default(
405 self,
406 type_: CapabilityTypeName,
407 ) -> PorcelainBuilder<T, R, C, true>;
408
409 fn with_porcelain_no_default(
416 self,
417 type_: CapabilityTypeName,
418 ) -> PorcelainBuilder<T, R, C, false>;
419}
420
421impl<T: CapabilityBound, R: ErrorReporter, C: ComponentInstanceInterface + 'static>
422 WithPorcelain<T, R, C> for Router<T>
423{
424 fn with_porcelain_with_default(
425 self,
426 type_: CapabilityTypeName,
427 ) -> PorcelainBuilder<T, R, C, true> {
428 PorcelainBuilder::<T, R, C, true>::new(self, type_)
429 }
430
431 fn with_porcelain_no_default(
432 self,
433 type_: CapabilityTypeName,
434 ) -> PorcelainBuilder<T, R, C, false> {
435 PorcelainBuilder::<T, R, C, false>::new(self, type_)
436 }
437}
438
439pub fn metadata_for_porcelain_type(
440 typename: CapabilityTypeName,
441) -> Arc<dyn Fn(Availability) -> RouteRequest + Send + Sync + 'static> {
442 type MetadataMap = HashMap<
443 CapabilityTypeName,
444 Arc<dyn Fn(Availability) -> RouteRequest + Send + Sync + 'static>,
445 >;
446 static CLOSURES: LazyLock<MetadataMap> = LazyLock::new(|| {
447 fn entry_for_typename(
448 typename: CapabilityTypeName,
449 ) -> (CapabilityTypeName, Arc<dyn Fn(Availability) -> RouteRequest + Send + Sync + 'static>)
450 {
451 let v = Arc::new(move |availability: Availability| RouteRequest {
452 build_type_name: Some(typename.to_string()),
453 availability: Some(availability.native_into_fidl()),
454 ..Default::default()
455 });
456 (typename, v)
457 }
458 CapabilityTypeName::iter().map(entry_for_typename).collect()
459 });
460 CLOSURES.get(&typename).unwrap().clone()
461}
462
463#[cfg(test)]
464mod tests {
465 use super::*;
466 use crate::ResolvedInstanceInterface;
467 use crate::bedrock::sandbox_construction::ComponentSandbox;
468 use crate::component_instance::{ExtendedInstanceInterface, TopInstanceInterface};
469 use crate::error::ComponentInstanceError;
470 use crate::policy::GlobalPolicyChecker;
471 use assert_matches::assert_matches;
472 use capability_source::{BuiltinCapabilities, NamespaceCapabilities};
473 use cm_rust_testing::UseBuilder;
474 use cm_types::Url;
475 use fuchsia_sync::Mutex;
476 use moniker::Moniker;
477 use router_error::RouterError;
478 use runtime_capabilities::Data;
479 use std::sync::Arc;
480
481 #[derive(Debug)]
482 struct FakeComponent {
483 moniker: Moniker,
484 }
485
486 #[derive(Debug)]
487 struct FakeTopInstance {
488 ns: NamespaceCapabilities,
489 builtin: BuiltinCapabilities,
490 }
491
492 impl TopInstanceInterface for FakeTopInstance {
493 fn namespace_capabilities(&self) -> &NamespaceCapabilities {
494 &self.ns
495 }
496 fn builtin_capabilities(&self) -> &BuiltinCapabilities {
497 &self.builtin
498 }
499 }
500
501 #[async_trait]
502 impl ComponentInstanceInterface for FakeComponent {
503 type TopInstance = FakeTopInstance;
504
505 fn moniker(&self) -> &Moniker {
506 &self.moniker
507 }
508
509 fn url(&self) -> &Url {
510 panic!()
511 }
512
513 fn config_parent_overrides(&self) -> Option<&[cm_rust::ConfigOverride]> {
514 panic!()
515 }
516
517 fn policy_checker(&self) -> &GlobalPolicyChecker {
518 panic!()
519 }
520
521 fn component_id_index(&self) -> &component_id_index::Index {
522 panic!()
523 }
524
525 fn try_get_parent(
526 &self,
527 ) -> Result<ExtendedInstanceInterface<Self>, ComponentInstanceError> {
528 panic!()
529 }
530
531 async fn lock_resolved_state<'a>(
532 self: &'a Arc<Self>,
533 ) -> Result<Box<dyn ResolvedInstanceInterface<Component = Self> + 'a>, ComponentInstanceError>
534 {
535 panic!()
536 }
537
538 async fn component_sandbox(
539 self: &Arc<Self>,
540 ) -> Result<ComponentSandbox, ComponentInstanceError> {
541 panic!()
542 }
543 }
544
545 #[derive(Clone)]
546 struct TestErrorReporter {
547 reported: Arc<Mutex<bool>>,
548 }
549
550 impl TestErrorReporter {
551 fn new() -> Self {
552 Self { reported: Arc::new(Mutex::new(false)) }
553 }
554 }
555
556 #[async_trait]
557 impl ErrorReporter for TestErrorReporter {
558 async fn report(
559 &self,
560 _request: &RouteRequestErrorInfo,
561 _err: &RouterError,
562 _route_target: WeakInstanceToken,
563 ) {
564 let mut reported = self.reported.lock();
565 if *reported {
566 panic!("report() was called twice");
567 }
568 *reported = true;
569 }
570 }
571
572 fn fake_component() -> Arc<FakeComponent> {
573 Arc::new(FakeComponent { moniker: Moniker::root() })
574 }
575
576 fn error_info() -> cm_rust::UseDecl {
577 UseBuilder::protocol().name("name").build()
578 }
579
580 #[fuchsia::test]
581 async fn success() {
582 let source = Data::String("hello".into());
583 let base = Router::<Data>::new_ok(source);
584 let component = fake_component();
585 let proxy = base
586 .with_porcelain_with_default(CapabilityTypeName::Protocol)
587 .availability(Availability::Optional)
588 .target(&component)
589 .error_info(&error_info())
590 .error_reporter(TestErrorReporter::new())
591 .build();
592 let request = RouteRequest {
593 build_type_name: Some(CapabilityTypeName::Protocol.to_string()),
594 availability: Some(Availability::Optional.native_into_fidl()),
595 ..Default::default()
596 };
597
598 let capability = proxy.route(request, component.as_weak().into()).await.unwrap();
599 let capability = match capability {
600 Some(d) => d,
601 _ => panic!(),
602 };
603 assert_eq!(capability, Data::String("hello".into()));
604 }
605
606 #[fuchsia::test]
607 async fn type_missing() {
608 let reporter = TestErrorReporter::new();
609 let reported = reporter.reported.clone();
610 let source = Data::String("hello".into());
611 let base = Router::<Data>::new_ok(source);
612 let component = fake_component();
613 let proxy = base
614 .with_porcelain_with_default(CapabilityTypeName::Protocol)
615 .availability(Availability::Optional)
616 .target(&component)
617 .error_info(&error_info())
618 .error_reporter(reporter)
619 .build();
620 let request = RouteRequest {
621 availability: Some(Availability::Optional.native_into_fidl()),
622 ..Default::default()
623 };
624
625 let error = proxy.route(request, component.as_weak().into()).await.unwrap_err();
626 assert_matches!(
627 error,
628 RouterError::NotFound(err)
629 if matches!(
630 err.as_any().downcast_ref::<RoutingError>(),
631 Some(RoutingError::BedrockMissingCapabilityType {
632 moniker,
633 type_name,
634 }) if moniker == &Moniker::root().into() && type_name == "protocol"
635 )
636 );
637 assert!(*reported.lock());
638 }
639
640 #[fuchsia::test]
641 async fn type_mismatch() {
642 let reporter = TestErrorReporter::new();
643 let reported = reporter.reported.clone();
644 let source = Data::String("hello".into());
645 let base = Router::<Data>::new_ok(source);
646 let component = fake_component();
647 let proxy = base
648 .with_porcelain_with_default(CapabilityTypeName::Protocol)
649 .availability(Availability::Optional)
650 .target(&component)
651 .error_info(&error_info())
652 .error_reporter(reporter)
653 .build();
654 let request = RouteRequest {
655 build_type_name: Some(CapabilityTypeName::Service.to_string()),
656 availability: Some(Availability::Optional.native_into_fidl()),
657 ..Default::default()
658 };
659
660 let error = proxy.route(request, component.as_weak().into()).await.unwrap_err();
661 assert_matches!(
662 error,
663 RouterError::NotFound(err)
664 if matches!(
665 err.as_any().downcast_ref::<RoutingError>(),
666 Some(RoutingError::BedrockWrongCapabilityType {
667 moniker,
668 expected,
669 actual
670 }) if moniker == &Moniker::root().into()
671 && expected == "protocol" && actual == "service"
672 )
673 );
674 assert!(*reported.lock());
675 }
676
677 #[fuchsia::test]
678 async fn availability_mismatch() {
679 let reporter = TestErrorReporter::new();
680 let reported = reporter.reported.clone();
681 let source = Data::String("hello".into());
682 let base = Router::<Data>::new_ok(source);
683 let component = fake_component();
684 let proxy = base
685 .with_porcelain_with_default(CapabilityTypeName::Protocol)
686 .availability(Availability::Optional)
687 .target(&component)
688 .error_info(&error_info())
689 .error_reporter(reporter)
690 .build();
691 let request = RouteRequest {
692 build_type_name: Some(CapabilityTypeName::Protocol.to_string()),
693 availability: Some(Availability::Required.native_into_fidl()),
694 ..Default::default()
695 };
696
697 let error = proxy.route(request, component.as_weak().into()).await.unwrap_err();
698 assert_matches!(
699 error,
700 RouterError::NotFound(err)
701 if matches!(
702 err.as_any().downcast_ref::<RoutingError>(),
703 Some(RoutingError::AvailabilityRoutingError(
704 crate::error::AvailabilityRoutingError::TargetHasStrongerAvailability {
705 moniker
706 }
707 )) if moniker == &Moniker::root().into()
708 )
709 );
710 assert!(*reported.lock());
711 }
712}