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