Skip to main content

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;
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 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    // The rights of the previous step (if any) of the route must be
192    // compatible with this step of the route.
193    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    // The rights of the request must be compatible with the
200    // rights of this step of the route.
201    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
232/// Builds a router that ensures the capability request has an availability
233/// strength that is at least the provided `availability`. A default `Request`
234/// is populated with `metadata_fn` if the client passes a `None` `Request`.
235pub 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    /// The [Availability] attribute for this route.
272    /// REQUIRED.
273    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    /// The identity of the component on behalf of whom this routing request is performed, if the
302    /// caller passes a `None` request.
303    /// Either this or `target_above_root` is REQUIRED.
304    pub fn target(mut self, t: &Arc<C>) -> Self {
305        self.target = Some(WeakExtendedInstanceInterface::Component(t.as_weak()));
306        self
307    }
308
309    /// The identity of the "above root" instance that is component manager itself.
310    /// Either this or `target` is REQUIRED.
311    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    /// Object used to generate diagnostic information about the route that is logged if the route
317    /// fails. This is usually a [cm_rust] type that is castable to [RouteRequestErrorInfo]
318    /// REQUIRED.
319    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    /// The [ErrorReporter] used to log errors if routing fails.
328    /// REQUIRED.
329    pub fn error_reporter(mut self, r: R) -> Self {
330        self.error_reporter = Some(r);
331        self
332    }
333
334    /// Build the [PorcelainRouter] with attributes configured by this builder.
335    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
361/// See [WithPorcelain::with_porcelain] for documentation.
362pub trait WithPorcelain<
363    T: CapabilityBound,
364    R: ErrorReporter,
365    C: ComponentInstanceInterface + 'static,
366>
367{
368    /// Returns a [PorcelainBuilder] you use to construct a new router with porcelain properties
369    /// that augments the `self`. See [PorcelainBuilder] for documentation of the supported
370    /// properties.
371    ///
372    /// If a `None` request is passed into the built router, the router will supply a default
373    /// request based on the values passed to the builder.
374    fn with_porcelain_with_default(
375        self,
376        type_: CapabilityTypeName,
377    ) -> PorcelainBuilder<T, R, C, true>;
378
379    /// Returns a [PorcelainBuilder] you use to construct a new router with porcelain properties
380    /// that augments the `self`. See [PorcelainBuilder] for documentation of the supported
381    /// properties.
382    ///
383    /// If a `None` request is passed into the built router, the router will throw an `InvalidArgs`
384    /// error.
385    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}