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