routing/bedrock/
with_porcelain.rs

1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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 the scope is already set then it's a smaller scope (because we can't expose these),
116            // so only set our scope if the request doesn't have one yet.
117            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        // Everything checks out, forward the request.
125        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    // The availability of the request must be compatible with the
157    // availability of this step of the route.
158    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    // The rights of the previous step (if any) of the route must be
193    // compatible with this step of the route.
194    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    // The rights of the request must be compatible with the
202    // rights of this step of the route.
203    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
234/// Builds a router that ensures the capability request has an availability
235/// strength that is at least the provided `availability`. A default `Request`
236/// is populated with `metadata_fn` if the client passes a `None` `Request`.
237pub 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    /// The [Availability] attribute for this route.
274    /// REQUIRED.
275    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    /// The identity of the component on behalf of whom this routing request is performed, if the
304    /// caller passes a `None` request.
305    /// Either this or `target_above_root` is REQUIRED.
306    pub fn target(mut self, t: &Arc<C>) -> Self {
307        self.target = Some(WeakExtendedInstanceInterface::Component(t.as_weak()));
308        self
309    }
310
311    /// The identity of the "above root" instance that is component manager itself.
312    /// Either this or `target` is REQUIRED.
313    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    /// Object used to generate diagnostic information about the route that is logged if the route
319    /// fails. This is usually a [cm_rust] type that is castable to [RouteRequestErrorInfo]
320    /// REQUIRED.
321    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    /// The [ErrorReporter] used to log errors if routing fails.
330    /// REQUIRED.
331    pub fn error_reporter(mut self, r: R) -> Self {
332        self.error_reporter = Some(r);
333        self
334    }
335
336    /// Build the [PorcelainRouter] with attributes configured by this builder.
337    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
363/// See [WithPorcelain::with_porcelain] for documentation.
364pub trait WithPorcelain<
365    T: CapabilityBound,
366    R: ErrorReporter,
367    C: ComponentInstanceInterface + 'static,
368>
369{
370    /// Returns a [PorcelainBuilder] you use to construct a new router with porcelain properties
371    /// that augments the `self`. See [PorcelainBuilder] for documentation of the supported
372    /// properties.
373    ///
374    /// If a `None` request is passed into the built router, the router will supply a default
375    /// request based on the values passed to the builder.
376    fn with_porcelain_with_default(
377        self,
378        type_: CapabilityTypeName,
379    ) -> PorcelainBuilder<T, R, C, true>;
380
381    /// Returns a [PorcelainBuilder] you use to construct a new router with porcelain properties
382    /// that augments the `self`. See [PorcelainBuilder] for documentation of the supported
383    /// properties.
384    ///
385    /// If a `None` request is passed into the built router, the router will throw an `InvalidArgs`
386    /// error.
387    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}