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::{
17    Capability, CapabilityBound, Dict, Request, Routable, Router, RouterResponse, WeakInstanceToken,
18};
19use std::collections::HashMap;
20use std::sync::{Arc, LazyLock};
21use strum::IntoEnumIterator;
22use {fidl_fuchsia_component_internal as finternal, fidl_fuchsia_component_sandbox as fsandbox};
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 the scope is already set then it's a smaller scope (because we can't expose these),
120            // so only set our scope if the request doesn't have one yet.
121            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        // Everything checks out, forward the request.
129        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    // The availability of the request must be compatible with the
161    // availability of this step of the route.
162    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    let request_rights = RightsWalker::new(request_rights, moniker.clone());
192    let router_rights = RightsWalker::new(*rights, moniker.clone());
193    // The rights of the previous step (if any) of the route must be
194    // compatible with this step of the route.
195    if let Some(IntermediateRights(intermediate_rights)) = intermediate_rights {
196        let intermediate_rights_walker = RightsWalker::new(intermediate_rights, moniker.clone());
197        intermediate_rights_walker
198            .validate_next(&router_rights)
199            .map_err(|e| router_error::RouterError::from(RoutingError::from(e)))?;
200    };
201    request.metadata.set_metadata(IntermediateRights(*rights));
202    // The rights of the request must be compatible with the
203    // rights of this step of the route.
204    request_rights.validate_next(&router_rights).map_err(RoutingError::from)?;
205    Ok(())
206}
207
208fn check_and_compute_subdir(
209    moniker: &ExtendedMoniker,
210    request: &Request,
211    subdir: &Option<SubDir>,
212) -> Result<Option<SubDir>, RouterError> {
213    let Some(mut subdir_from_decl) = subdir.clone() else {
214        return Ok(None);
215    };
216
217    let request_subdir: Option<SubDir> = request.metadata.get_metadata();
218
219    if let Some(request_subdir) = request_subdir {
220        let success = subdir_from_decl.as_mut().extend(request_subdir.clone().into());
221        if !success {
222            return Err(RoutingError::PathTooLong {
223                moniker: moniker.clone(),
224                path: subdir_from_decl.to_string(),
225                keyword: request_subdir.to_string(),
226            }
227            .into());
228        }
229    }
230    Ok(Some(subdir_from_decl))
231}
232
233pub type DefaultMetadataFn = Arc<dyn Fn(Availability) -> Dict + Send + Sync + 'static>;
234
235/// Builds a router that ensures the capability request has an availability
236/// strength that is at least the provided `availability`. A default `Request`
237/// is populated with `metadata_fn` if the client passes a `None` `Request`.
238pub struct PorcelainBuilder<
239    T: CapabilityBound,
240    R: ErrorReporter,
241    C: ComponentInstanceInterface + 'static,
242    const D: bool,
243> {
244    router: Router<T>,
245    porcelain_type: CapabilityTypeName,
246    availability: Option<Availability>,
247    rights: Option<Rights>,
248    subdir: Option<SubDir>,
249    inherit_rights: Option<bool>,
250    event_stream_route_metadata: Option<finternal::EventStreamRouteMetadata>,
251    target: Option<WeakExtendedInstanceInterface<C>>,
252    error_info: Option<RouteRequestErrorInfo>,
253    error_reporter: Option<R>,
254}
255
256impl<T: CapabilityBound, R: ErrorReporter, C: ComponentInstanceInterface + 'static, const D: bool>
257    PorcelainBuilder<T, R, C, D>
258{
259    fn new(router: Router<T>, porcelain_type: CapabilityTypeName) -> Self {
260        Self {
261            router,
262            porcelain_type,
263            availability: None,
264            rights: None,
265            subdir: None,
266            inherit_rights: None,
267            event_stream_route_metadata: None,
268            target: None,
269            error_info: None,
270            error_reporter: None,
271        }
272    }
273
274    /// The [Availability] attribute for this route.
275    /// REQUIRED.
276    pub fn availability(mut self, a: Availability) -> Self {
277        self.availability = Some(a);
278        self
279    }
280
281    pub fn rights(mut self, rights: Option<Rights>) -> Self {
282        self.rights = rights;
283        self
284    }
285
286    pub fn subdir(mut self, subdir: SubDir) -> Self {
287        self.subdir = Some(subdir);
288        self
289    }
290
291    pub fn inherit_rights(mut self, inherit_rights: bool) -> Self {
292        self.inherit_rights = Some(inherit_rights);
293        self
294    }
295
296    pub fn event_stream_route_metadata(
297        mut self,
298        esrm: finternal::EventStreamRouteMetadata,
299    ) -> Self {
300        self.event_stream_route_metadata = Some(esrm);
301        self
302    }
303
304    /// The identity of the component on behalf of whom this routing request is performed, if the
305    /// caller passes a `None` request.
306    /// Either this or `target_above_root` is REQUIRED.
307    pub fn target(mut self, t: &Arc<C>) -> Self {
308        self.target = Some(WeakExtendedInstanceInterface::Component(t.as_weak()));
309        self
310    }
311
312    /// The identity of the "above root" instance that is component manager itself.
313    /// Either this or `target` is REQUIRED.
314    pub fn target_above_root(mut self, t: &Arc<C::TopInstance>) -> Self {
315        self.target = Some(WeakExtendedInstanceInterface::AboveRoot(Arc::downgrade(t)));
316        self
317    }
318
319    /// Object used to generate diagnostic information about the route that is logged if the route
320    /// fails. This is usually a [cm_rust] type that is castable to [RouteRequestErrorInfo]
321    /// REQUIRED.
322    pub fn error_info<S>(mut self, r: S) -> Self
323    where
324        RouteRequestErrorInfo: From<S>,
325    {
326        self.error_info = Some(RouteRequestErrorInfo::from(r));
327        self
328    }
329
330    /// The [ErrorReporter] used to log errors if routing fails.
331    /// REQUIRED.
332    pub fn error_reporter(mut self, r: R) -> Self {
333        self.error_reporter = Some(r);
334        self
335    }
336
337    /// Build the [PorcelainRouter] with attributes configured by this builder.
338    pub fn build(self) -> Router<T> {
339        Router::new(PorcelainRouter::<T, R, C, D> {
340            router: self.router,
341            porcelain_type: self.porcelain_type,
342            availability: self.availability.expect("must set availability"),
343            rights: self.rights,
344            subdir: self.subdir,
345            inherit_rights: self.inherit_rights,
346            event_stream_route_metadata: self.event_stream_route_metadata,
347            target: self.target.expect("must set target"),
348            route_request: self.error_info.expect("must set route_request"),
349            error_reporter: self.error_reporter.expect("must set error_reporter"),
350        })
351    }
352}
353
354impl<R: ErrorReporter, T: CapabilityBound, C: ComponentInstanceInterface + 'static, const D: bool>
355    From<PorcelainBuilder<T, R, C, D>> for Capability
356where
357    Router<T>: Into<Capability>,
358{
359    fn from(b: PorcelainBuilder<T, R, C, D>) -> Self {
360        b.build().into()
361    }
362}
363
364/// See [WithPorcelain::with_porcelain] for documentation.
365pub trait WithPorcelain<
366    T: CapabilityBound,
367    R: ErrorReporter,
368    C: ComponentInstanceInterface + 'static,
369>
370{
371    /// Returns a [PorcelainBuilder] you use to construct a new router with porcelain properties
372    /// that augments the `self`. See [PorcelainBuilder] for documentation of the supported
373    /// properties.
374    ///
375    /// If a `None` request is passed into the built router, the router will supply a default
376    /// request based on the values passed to the builder.
377    fn with_porcelain_with_default(
378        self,
379        type_: CapabilityTypeName,
380    ) -> PorcelainBuilder<T, R, C, true>;
381
382    /// Returns a [PorcelainBuilder] you use to construct a new router with porcelain properties
383    /// that augments the `self`. See [PorcelainBuilder] for documentation of the supported
384    /// properties.
385    ///
386    /// If a `None` request is passed into the built router, the router will throw an `InvalidArgs`
387    /// error.
388    fn with_porcelain_no_default(
389        self,
390        type_: CapabilityTypeName,
391    ) -> PorcelainBuilder<T, R, C, false>;
392}
393
394impl<T: CapabilityBound, R: ErrorReporter, C: ComponentInstanceInterface + 'static>
395    WithPorcelain<T, R, C> for Router<T>
396{
397    fn with_porcelain_with_default(
398        self,
399        type_: CapabilityTypeName,
400    ) -> PorcelainBuilder<T, R, C, true> {
401        PorcelainBuilder::<T, R, C, true>::new(self, type_)
402    }
403
404    fn with_porcelain_no_default(
405        self,
406        type_: CapabilityTypeName,
407    ) -> PorcelainBuilder<T, R, C, false> {
408        PorcelainBuilder::<T, R, C, false>::new(self, type_)
409    }
410}
411
412pub fn metadata_for_porcelain_type(
413    typename: CapabilityTypeName,
414) -> Arc<dyn Fn(Availability) -> Dict + Send + Sync + 'static> {
415    type MetadataMap =
416        HashMap<CapabilityTypeName, Arc<dyn Fn(Availability) -> Dict + Send + Sync + 'static>>;
417    static CLOSURES: LazyLock<MetadataMap> = LazyLock::new(|| {
418        fn entry_for_typename(
419            typename: CapabilityTypeName,
420        ) -> (CapabilityTypeName, Arc<dyn Fn(Availability) -> Dict + Send + Sync + 'static>)
421        {
422            let v = Arc::new(move |availability: Availability| {
423                let metadata = Dict::new();
424                metadata.set_metadata(typename);
425                metadata.set_metadata(availability);
426                metadata
427            });
428            (typename, v)
429        }
430        CapabilityTypeName::iter().map(entry_for_typename).collect()
431    });
432    CLOSURES.get(&typename).unwrap().clone()
433}
434
435#[cfg(test)]
436mod tests {
437    use super::*;
438    use crate::ResolvedInstanceInterface;
439    use crate::bedrock::sandbox_construction::ComponentSandbox;
440    use crate::capability_source::{BuiltinCapabilities, NamespaceCapabilities};
441    use crate::component_instance::{ExtendedInstanceInterface, TopInstanceInterface};
442    use crate::error::ComponentInstanceError;
443    use crate::policy::GlobalPolicyChecker;
444    use assert_matches::assert_matches;
445    use cm_rust_testing::UseBuilder;
446    use cm_types::Url;
447    use fuchsia_sync::Mutex;
448    use moniker::Moniker;
449    use router_error::{DowncastErrorForTest, RouterError};
450    use sandbox::{Data, Dict};
451    use std::sync::Arc;
452
453    #[derive(Debug)]
454    struct FakeComponent {
455        moniker: Moniker,
456    }
457
458    #[derive(Debug)]
459    struct FakeTopInstance {
460        ns: NamespaceCapabilities,
461        builtin: BuiltinCapabilities,
462    }
463
464    impl TopInstanceInterface for FakeTopInstance {
465        fn namespace_capabilities(&self) -> &NamespaceCapabilities {
466            &self.ns
467        }
468        fn builtin_capabilities(&self) -> &BuiltinCapabilities {
469            &self.builtin
470        }
471    }
472
473    #[async_trait]
474    impl ComponentInstanceInterface for FakeComponent {
475        type TopInstance = FakeTopInstance;
476
477        fn moniker(&self) -> &Moniker {
478            &self.moniker
479        }
480
481        fn url(&self) -> &Url {
482            panic!()
483        }
484
485        fn config_parent_overrides(&self) -> Option<&[cm_rust::ConfigOverride]> {
486            panic!()
487        }
488
489        fn policy_checker(&self) -> &GlobalPolicyChecker {
490            panic!()
491        }
492
493        fn component_id_index(&self) -> &component_id_index::Index {
494            panic!()
495        }
496
497        fn try_get_parent(
498            &self,
499        ) -> Result<ExtendedInstanceInterface<Self>, ComponentInstanceError> {
500            panic!()
501        }
502
503        async fn lock_resolved_state<'a>(
504            self: &'a Arc<Self>,
505        ) -> Result<Box<dyn ResolvedInstanceInterface<Component = Self> + 'a>, ComponentInstanceError>
506        {
507            panic!()
508        }
509
510        async fn component_sandbox(
511            self: &Arc<Self>,
512        ) -> Result<ComponentSandbox, ComponentInstanceError> {
513            panic!()
514        }
515    }
516
517    #[derive(Clone)]
518    struct TestErrorReporter {
519        reported: Arc<Mutex<bool>>,
520    }
521
522    impl TestErrorReporter {
523        fn new() -> Self {
524            Self { reported: Arc::new(Mutex::new(false)) }
525        }
526    }
527
528    #[async_trait]
529    impl ErrorReporter for TestErrorReporter {
530        async fn report(
531            &self,
532            _request: &RouteRequestErrorInfo,
533            _err: &RouterError,
534            _route_target: sandbox::WeakInstanceToken,
535        ) {
536            let mut reported = self.reported.lock();
537            if *reported {
538                panic!("report() was called twice");
539            }
540            *reported = true;
541        }
542    }
543
544    fn fake_component() -> Arc<FakeComponent> {
545        Arc::new(FakeComponent { moniker: Moniker::root() })
546    }
547
548    fn error_info() -> cm_rust::UseDecl {
549        UseBuilder::protocol().name("name").build()
550    }
551
552    #[fuchsia::test]
553    async fn success() {
554        let source = Data::String("hello".into());
555        let base = Router::<Data>::new_ok(source);
556        let component = fake_component();
557        let proxy = base
558            .with_porcelain_with_default(CapabilityTypeName::Protocol)
559            .availability(Availability::Optional)
560            .target(&component)
561            .error_info(&error_info())
562            .error_reporter(TestErrorReporter::new())
563            .build();
564        let metadata = Dict::new();
565        metadata.set_metadata(CapabilityTypeName::Protocol);
566        metadata.set_metadata(Availability::Optional);
567
568        let capability = proxy
569            .route(Some(Request { metadata }), false, component.as_weak().into())
570            .await
571            .unwrap();
572        let capability = match capability {
573            RouterResponse::<Data>::Capability(d) => d,
574            _ => panic!(),
575        };
576        assert_eq!(capability, Data::String("hello".into()));
577    }
578
579    #[fuchsia::test]
580    async fn type_missing() {
581        let reporter = TestErrorReporter::new();
582        let reported = reporter.reported.clone();
583        let source = Data::String("hello".into());
584        let base = Router::<Data>::new_ok(source);
585        let component = fake_component();
586        let proxy = base
587            .with_porcelain_with_default(CapabilityTypeName::Protocol)
588            .availability(Availability::Optional)
589            .target(&component)
590            .error_info(&error_info())
591            .error_reporter(reporter)
592            .build();
593        let metadata = Dict::new();
594        metadata.set_metadata(Availability::Optional);
595
596        let error = proxy
597            .route(Some(Request { metadata }), false, component.as_weak().into())
598            .await
599            .unwrap_err();
600        assert_matches!(
601            error,
602            RouterError::NotFound(err)
603            if matches!(
604                err.downcast_for_test::<RoutingError>(),
605                RoutingError::BedrockMissingCapabilityType {
606                    moniker,
607                    type_name,
608                } if moniker == &Moniker::root().into() && type_name == "protocol"
609            )
610        );
611        assert!(*reported.lock());
612    }
613
614    #[fuchsia::test]
615    async fn type_mismatch() {
616        let reporter = TestErrorReporter::new();
617        let reported = reporter.reported.clone();
618        let source = Data::String("hello".into());
619        let base = Router::<Data>::new_ok(source);
620        let component = fake_component();
621        let proxy = base
622            .with_porcelain_with_default(CapabilityTypeName::Protocol)
623            .availability(Availability::Optional)
624            .target(&component)
625            .error_info(&error_info())
626            .error_reporter(reporter)
627            .build();
628        let metadata = Dict::new();
629        metadata.set_metadata(CapabilityTypeName::Service);
630        metadata.set_metadata(Availability::Optional);
631
632        let error = proxy
633            .route(Some(Request { metadata }), false, component.as_weak().into())
634            .await
635            .unwrap_err();
636        assert_matches!(
637            error,
638            RouterError::NotFound(err)
639            if matches!(
640                err.downcast_for_test::<RoutingError>(),
641                RoutingError::BedrockWrongCapabilityType {
642                    moniker,
643                    expected,
644                    actual
645                } if moniker == &Moniker::root().into()
646                    && expected == "protocol" && actual == "service"
647            )
648        );
649        assert!(*reported.lock());
650    }
651
652    #[fuchsia::test]
653    async fn availability_mismatch() {
654        let reporter = TestErrorReporter::new();
655        let reported = reporter.reported.clone();
656        let source = Data::String("hello".into());
657        let base = Router::<Data>::new_ok(source);
658        let component = fake_component();
659        let proxy = base
660            .with_porcelain_with_default(CapabilityTypeName::Protocol)
661            .availability(Availability::Optional)
662            .target(&component)
663            .error_info(&error_info())
664            .error_reporter(reporter)
665            .build();
666        let metadata = Dict::new();
667        metadata.set_metadata(CapabilityTypeName::Protocol);
668        metadata.set_metadata(Availability::Required);
669
670        let error = proxy
671            .route(Some(Request { metadata }), false, component.as_weak().into())
672            .await
673            .unwrap_err();
674        assert_matches!(
675            error,
676            RouterError::NotFound(err)
677            if matches!(
678                err.downcast_for_test::<RoutingError>(),
679                RoutingError::AvailabilityRoutingError(
680                        crate::error::AvailabilityRoutingError::TargetHasStrongerAvailability {
681                        moniker
682                    }
683                ) if moniker == &Moniker::root().into()
684            )
685        );
686        assert!(*reported.lock());
687    }
688}