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