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::{Metadata, METADATA_KEY_TYPE};
6use crate::component_instance::{ComponentInstanceInterface, WeakExtendedInstanceInterface};
7use crate::error::{ErrorReporter, RouteRequestErrorInfo, RoutingError};
8use async_trait::async_trait;
9use cm_rust::CapabilityTypeName;
10use cm_types::{Availability, Name};
11use fidl_fuchsia_component_sandbox as fsandbox;
12use moniker::ExtendedMoniker;
13use router_error::RouterError;
14use sandbox::{Capability, CapabilityBound, Data, Dict, Request, Routable, Router, RouterResponse};
15use std::collections::HashMap;
16use std::sync::{Arc, LazyLock};
17use strum::IntoEnumIterator;
18
19struct PorcelainRouter<T: CapabilityBound, R, C: ComponentInstanceInterface, const D: bool> {
20    router: Router<T>,
21    porcelain_type: CapabilityTypeName,
22    availability: Availability,
23    target: WeakExtendedInstanceInterface<C>,
24    route_request: RouteRequestErrorInfo,
25    error_reporter: R,
26}
27
28#[async_trait]
29impl<
30        T: CapabilityBound,
31        R: ErrorReporter,
32        C: ComponentInstanceInterface + 'static,
33        const D: bool,
34    > Routable<T> for PorcelainRouter<T, R, C, D>
35{
36    async fn route(
37        &self,
38        request: Option<Request>,
39        debug: bool,
40    ) -> Result<RouterResponse<T>, RouterError> {
41        match self.do_route(request, debug, D).await {
42            Ok(res) => Ok(res),
43            Err(err) => {
44                self.error_reporter
45                    .report(&self.route_request, &err, self.target.clone().into())
46                    .await;
47                Err(err)
48            }
49        }
50    }
51}
52
53impl<
54        T: CapabilityBound,
55        R: ErrorReporter,
56        C: ComponentInstanceInterface + 'static,
57        const D: bool,
58    > PorcelainRouter<T, R, C, D>
59{
60    #[inline]
61    async fn do_route(
62        &self,
63        request: Option<Request>,
64        debug: bool,
65        supply_default: bool,
66    ) -> Result<RouterResponse<T>, RouterError> {
67        let PorcelainRouter {
68            router,
69            porcelain_type,
70            availability,
71            target,
72            route_request: _,
73            error_reporter: _,
74        } = self;
75        let request = if let Some(request) = request {
76            request
77        } else {
78            if !supply_default {
79                Err(RouterError::InvalidArgs)?;
80            }
81            let metadata = Dict::new();
82            metadata
83                .insert(
84                    Name::new(METADATA_KEY_TYPE).unwrap(),
85                    Capability::Data(Data::String(porcelain_type.to_string())),
86                )
87                .expect("failed to build default metadata?");
88            metadata.set_metadata(*availability);
89            Request { target: target.clone().into(), metadata }
90        };
91
92        let moniker: ExtendedMoniker = match &self.target {
93            WeakExtendedInstanceInterface::Component(t) => t.moniker.clone().into(),
94            WeakExtendedInstanceInterface::AboveRoot(_) => ExtendedMoniker::ComponentManager,
95        };
96        check_porcelain_type(&moniker, &request, *porcelain_type)?;
97        let updated_availability = check_availability(&moniker, &request, *availability)?;
98
99        // Everything checks out, forward the request.
100        request.metadata.set_metadata(updated_availability);
101        router.route(Some(request), debug).await
102    }
103}
104
105fn check_porcelain_type(
106    moniker: &ExtendedMoniker,
107    request: &Request,
108    expected_type: CapabilityTypeName,
109) -> Result<(), RouterError> {
110    let Capability::Data(Data::String(capability_type)) = request
111        .metadata
112        .get(&cm_types::Name::new(METADATA_KEY_TYPE).unwrap())
113        .map_err(|()| RoutingError::BedrockNotCloneable { moniker: moniker.clone() })?
114        .ok_or_else(|| RoutingError::BedrockMissingCapabilityType {
115            moniker: moniker.clone(),
116            type_name: expected_type.to_string(),
117        })?
118    else {
119        return Err(RoutingError::BedrockNotPresentInDictionary {
120            moniker: moniker.clone(),
121            name: String::from("type"),
122        }
123        .into());
124    };
125    if capability_type != expected_type.to_string() {
126        Err(RoutingError::BedrockWrongCapabilityType {
127            moniker: moniker.clone(),
128            actual: capability_type,
129            expected: expected_type.to_string(),
130        })?;
131    }
132    Ok(())
133}
134
135fn check_availability(
136    moniker: &ExtendedMoniker,
137    request: &Request,
138    availability: Availability,
139) -> Result<Availability, RouterError> {
140    // The availability of the request must be compatible with the
141    // availability of this step of the route.
142    let request_availability = request
143        .metadata
144        .get_metadata()
145        .ok_or(fsandbox::RouterError::InvalidArgs)
146        .inspect_err(|e| {
147            log::error!("request {:?} did not have availability metadata: {e:?}", request.target)
148        })?;
149    crate::availability::advance(&moniker, request_availability, availability)
150        .map_err(|e| RoutingError::from(e).into())
151}
152
153pub type DefaultMetadataFn = Arc<dyn Fn(Availability) -> Dict + Send + Sync + 'static>;
154
155/// Builds a router that ensures the capability request has an availability
156/// strength that is at least the provided `availability`. A default `Request`
157/// is populated with `metadata_fn` if the client passes a `None` `Request`.
158pub struct PorcelainBuilder<
159    T: CapabilityBound,
160    R: ErrorReporter,
161    C: ComponentInstanceInterface + 'static,
162    const D: bool,
163> {
164    router: Router<T>,
165    porcelain_type: CapabilityTypeName,
166    availability: Option<Availability>,
167    target: Option<WeakExtendedInstanceInterface<C>>,
168    error_info: Option<RouteRequestErrorInfo>,
169    error_reporter: Option<R>,
170}
171
172impl<
173        T: CapabilityBound,
174        R: ErrorReporter,
175        C: ComponentInstanceInterface + 'static,
176        const D: bool,
177    > PorcelainBuilder<T, R, C, D>
178{
179    fn new(router: Router<T>, porcelain_type: CapabilityTypeName) -> Self {
180        Self {
181            router,
182            porcelain_type,
183            availability: None,
184            target: None,
185            error_info: None,
186            error_reporter: None,
187        }
188    }
189
190    /// The [Availability] attribute for this route.
191    /// REQUIRED.
192    pub fn availability(mut self, a: Availability) -> Self {
193        self.availability = Some(a);
194        self
195    }
196
197    /// The identity of the component on behalf of whom this routing request is performed, if the
198    /// caller passes a `None` request.
199    /// Either this or `target_above_root` is REQUIRED.
200    pub fn target(mut self, t: &Arc<C>) -> Self {
201        self.target = Some(WeakExtendedInstanceInterface::Component(t.as_weak()));
202        self
203    }
204
205    /// The identity of the "above root" instance that is component manager itself.
206    /// Either this or `target` is REQUIRED.
207    pub fn target_above_root(mut self, t: &Arc<C::TopInstance>) -> Self {
208        self.target = Some(WeakExtendedInstanceInterface::AboveRoot(Arc::downgrade(t)));
209        self
210    }
211
212    /// Object used to generate diagnostic information about the route that is logged if the route
213    /// fails. This is usually a [cm_rust] type that is castable to [RouteRequestErrorInfo]
214    /// REQUIRED.
215    pub fn error_info<S>(mut self, r: S) -> Self
216    where
217        RouteRequestErrorInfo: From<S>,
218    {
219        self.error_info = Some(RouteRequestErrorInfo::from(r));
220        self
221    }
222
223    /// The [ErrorReporter] used to log errors if routing fails.
224    /// REQUIRED.
225    pub fn error_reporter(mut self, r: R) -> Self {
226        self.error_reporter = Some(r);
227        self
228    }
229
230    /// Build the [PorcelainRouter] with attributes configured by this builder.
231    pub fn build(self) -> Router<T> {
232        Router::new(PorcelainRouter::<T, R, C, D> {
233            router: self.router,
234            porcelain_type: self.porcelain_type,
235            availability: self.availability.expect("must set availability"),
236            target: self.target.expect("must set target"),
237            route_request: self.error_info.expect("must set route_request"),
238            error_reporter: self.error_reporter.expect("must set error_reporter"),
239        })
240    }
241}
242
243impl<
244        R: ErrorReporter,
245        T: CapabilityBound,
246        C: ComponentInstanceInterface + 'static,
247        const D: bool,
248    > From<PorcelainBuilder<T, R, C, D>> for Capability
249where
250    Router<T>: Into<Capability>,
251{
252    fn from(b: PorcelainBuilder<T, R, C, D>) -> Self {
253        b.build().into()
254    }
255}
256
257/// See [WithPorcelain::with_porcelain] for documentation.
258pub trait WithPorcelain<
259    T: CapabilityBound,
260    R: ErrorReporter,
261    C: ComponentInstanceInterface + 'static,
262>
263{
264    /// Returns a [PorcelainBuilder] you use to construct a new router with porcelain properties
265    /// that augments the `self`. See [PorcelainBuilder] for documentation of the supported
266    /// properties.
267    ///
268    /// If a `None` request is passed into the built router, the router will supply a default
269    /// request based on the values passed to the builder.
270    fn with_porcelain_with_default(
271        self,
272        type_: CapabilityTypeName,
273    ) -> PorcelainBuilder<T, R, C, true>;
274
275    /// Returns a [PorcelainBuilder] you use to construct a new router with porcelain properties
276    /// that augments the `self`. See [PorcelainBuilder] for documentation of the supported
277    /// properties.
278    ///
279    /// If a `None` request is passed into the built router, the router will throw an `InvalidArgs`
280    /// error.
281    fn with_porcelain_no_default(
282        self,
283        type_: CapabilityTypeName,
284    ) -> PorcelainBuilder<T, R, C, false>;
285}
286
287impl<T: CapabilityBound, R: ErrorReporter, C: ComponentInstanceInterface + 'static>
288    WithPorcelain<T, R, C> for Router<T>
289{
290    fn with_porcelain_with_default(
291        self,
292        type_: CapabilityTypeName,
293    ) -> PorcelainBuilder<T, R, C, true> {
294        PorcelainBuilder::<T, R, C, true>::new(self, type_)
295    }
296
297    fn with_porcelain_no_default(
298        self,
299        type_: CapabilityTypeName,
300    ) -> PorcelainBuilder<T, R, C, false> {
301        PorcelainBuilder::<T, R, C, false>::new(self, type_)
302    }
303}
304
305pub fn metadata_for_porcelain_type(
306    typename: CapabilityTypeName,
307) -> Arc<dyn Fn(Availability) -> Dict + Send + Sync + 'static> {
308    type MetadataMap =
309        HashMap<CapabilityTypeName, Arc<dyn Fn(Availability) -> Dict + Send + Sync + 'static>>;
310    static CLOSURES: LazyLock<MetadataMap> = LazyLock::new(|| {
311        fn entry_for_typename(
312            typename: CapabilityTypeName,
313        ) -> (CapabilityTypeName, Arc<dyn Fn(Availability) -> Dict + Send + Sync + 'static>)
314        {
315            let v = Arc::new(move |availability: Availability| {
316                let metadata = Dict::new();
317                metadata
318                    .insert(
319                        Name::new(METADATA_KEY_TYPE).unwrap(),
320                        Capability::Data(Data::String(typename.to_string())),
321                    )
322                    .expect("failed to build default metadata?");
323                metadata.set_metadata(availability);
324                metadata
325            });
326            (typename, v)
327        }
328        CapabilityTypeName::iter().map(entry_for_typename).collect()
329    });
330    CLOSURES.get(&typename).unwrap().clone()
331}
332
333#[cfg(test)]
334mod tests {
335    use super::*;
336    use crate::bedrock::sandbox_construction::ComponentSandbox;
337    use crate::capability_source::{BuiltinCapabilities, NamespaceCapabilities};
338    use crate::component_instance::{ExtendedInstanceInterface, TopInstanceInterface};
339    use crate::error::ComponentInstanceError;
340    use crate::policy::GlobalPolicyChecker;
341    use crate::{environment, ResolvedInstanceInterface};
342    use assert_matches::assert_matches;
343    use cm_rust_testing::UseBuilder;
344    use cm_types::Url;
345    use moniker::{BorrowedChildName, Moniker};
346    use router_error::{DowncastErrorForTest, RouterError};
347    use sandbox::{Data, Dict};
348    use std::sync::{Arc, Mutex};
349
350    #[derive(Debug)]
351    struct FakeComponent {
352        moniker: Moniker,
353    }
354
355    #[derive(Debug)]
356    struct FakeTopInstance {
357        ns: NamespaceCapabilities,
358        builtin: BuiltinCapabilities,
359    }
360
361    impl TopInstanceInterface for FakeTopInstance {
362        fn namespace_capabilities(&self) -> &NamespaceCapabilities {
363            &self.ns
364        }
365        fn builtin_capabilities(&self) -> &BuiltinCapabilities {
366            &self.builtin
367        }
368    }
369
370    #[async_trait]
371    impl ComponentInstanceInterface for FakeComponent {
372        type TopInstance = FakeTopInstance;
373
374        fn child_moniker(&self) -> Option<&BorrowedChildName> {
375            panic!()
376        }
377
378        fn moniker(&self) -> &Moniker {
379            &self.moniker
380        }
381
382        fn url(&self) -> &Url {
383            panic!()
384        }
385
386        fn environment(&self) -> &environment::Environment<Self> {
387            panic!()
388        }
389
390        fn config_parent_overrides(&self) -> Option<&Vec<cm_rust::ConfigOverride>> {
391            panic!()
392        }
393
394        fn policy_checker(&self) -> &GlobalPolicyChecker {
395            panic!()
396        }
397
398        fn component_id_index(&self) -> &component_id_index::Index {
399            panic!()
400        }
401
402        fn try_get_parent(
403            &self,
404        ) -> Result<ExtendedInstanceInterface<Self>, ComponentInstanceError> {
405            panic!()
406        }
407
408        async fn lock_resolved_state<'a>(
409            self: &'a Arc<Self>,
410        ) -> Result<Box<dyn ResolvedInstanceInterface<Component = Self> + 'a>, ComponentInstanceError>
411        {
412            panic!()
413        }
414
415        async fn component_sandbox(
416            self: &Arc<Self>,
417        ) -> Result<ComponentSandbox, ComponentInstanceError> {
418            panic!()
419        }
420    }
421
422    #[derive(Clone)]
423    struct TestErrorReporter {
424        reported: Arc<Mutex<bool>>,
425    }
426
427    impl TestErrorReporter {
428        fn new() -> Self {
429            Self { reported: Arc::new(Mutex::new(false)) }
430        }
431    }
432
433    #[async_trait]
434    impl ErrorReporter for TestErrorReporter {
435        async fn report(
436            &self,
437            _request: &RouteRequestErrorInfo,
438            _err: &RouterError,
439            _route_target: sandbox::WeakInstanceToken,
440        ) {
441            let mut reported = self.reported.lock().unwrap();
442            if *reported {
443                panic!("report() was called twice");
444            }
445            *reported = true;
446        }
447    }
448
449    fn fake_component() -> Arc<FakeComponent> {
450        Arc::new(FakeComponent { moniker: Moniker::root() })
451    }
452
453    fn error_info() -> cm_rust::UseDecl {
454        UseBuilder::protocol().name("name").build()
455    }
456
457    #[fuchsia::test]
458    async fn success() {
459        let source = Data::String("hello".to_string());
460        let base = Router::<Data>::new_ok(source);
461        let component = fake_component();
462        let proxy = base
463            .with_porcelain_with_default(CapabilityTypeName::Protocol)
464            .availability(Availability::Optional)
465            .target(&component)
466            .error_info(&error_info())
467            .error_reporter(TestErrorReporter::new())
468            .build();
469        let metadata = Dict::new();
470        metadata
471            .insert(
472                Name::new(METADATA_KEY_TYPE).unwrap(),
473                Capability::Data(Data::String(CapabilityTypeName::Protocol.to_string())),
474            )
475            .unwrap();
476        metadata.set_metadata(Availability::Optional);
477
478        let capability = proxy
479            .route(Some(Request { target: component.as_weak().into(), metadata }), false)
480            .await
481            .unwrap();
482        let capability = match capability {
483            RouterResponse::<Data>::Capability(d) => d,
484            _ => panic!(),
485        };
486        assert_eq!(capability, Data::String("hello".to_string()));
487    }
488
489    #[fuchsia::test]
490    async fn type_missing() {
491        let reporter = TestErrorReporter::new();
492        let reported = reporter.reported.clone();
493        let source = Data::String("hello".to_string());
494        let base = Router::<Data>::new_ok(source);
495        let component = fake_component();
496        let proxy = base
497            .with_porcelain_with_default(CapabilityTypeName::Protocol)
498            .availability(Availability::Optional)
499            .target(&component)
500            .error_info(&error_info())
501            .error_reporter(reporter)
502            .build();
503        let metadata = Dict::new();
504        metadata.set_metadata(Availability::Optional);
505
506        let error = proxy
507            .route(Some(Request { target: component.as_weak().into(), metadata }), false)
508            .await
509            .unwrap_err();
510        assert_matches!(
511            error,
512            RouterError::NotFound(err)
513            if matches!(
514                err.downcast_for_test::<RoutingError>(),
515                RoutingError::BedrockMissingCapabilityType {
516                    moniker,
517                    type_name,
518                } if moniker == &Moniker::root().into() && type_name == "protocol"
519            )
520        );
521        assert!(*reported.lock().unwrap());
522    }
523
524    #[fuchsia::test]
525    async fn type_mismatch() {
526        let reporter = TestErrorReporter::new();
527        let reported = reporter.reported.clone();
528        let source = Data::String("hello".to_string());
529        let base = Router::<Data>::new_ok(source);
530        let component = fake_component();
531        let proxy = base
532            .with_porcelain_with_default(CapabilityTypeName::Protocol)
533            .availability(Availability::Optional)
534            .target(&component)
535            .error_info(&error_info())
536            .error_reporter(reporter)
537            .build();
538        let metadata = Dict::new();
539        metadata
540            .insert(
541                Name::new(METADATA_KEY_TYPE).unwrap(),
542                Capability::Data(Data::String(CapabilityTypeName::Service.to_string())),
543            )
544            .unwrap();
545        metadata.set_metadata(Availability::Optional);
546
547        let error = proxy
548            .route(Some(Request { target: component.as_weak().into(), metadata }), false)
549            .await
550            .unwrap_err();
551        assert_matches!(
552            error,
553            RouterError::NotFound(err)
554            if matches!(
555                err.downcast_for_test::<RoutingError>(),
556                RoutingError::BedrockWrongCapabilityType {
557                    moniker,
558                    expected,
559                    actual
560                } if moniker == &Moniker::root().into()
561                    && expected == "protocol" && actual == "service"
562            )
563        );
564        assert!(*reported.lock().unwrap());
565    }
566
567    #[fuchsia::test]
568    async fn availability_mismatch() {
569        let reporter = TestErrorReporter::new();
570        let reported = reporter.reported.clone();
571        let source = Data::String("hello".to_string());
572        let base = Router::<Data>::new_ok(source);
573        let component = fake_component();
574        let proxy = base
575            .with_porcelain_with_default(CapabilityTypeName::Protocol)
576            .availability(Availability::Optional)
577            .target(&component)
578            .error_info(&error_info())
579            .error_reporter(reporter)
580            .build();
581        let metadata = Dict::new();
582        metadata
583            .insert(
584                Name::new(METADATA_KEY_TYPE).unwrap(),
585                Capability::Data(Data::String(CapabilityTypeName::Protocol.to_string())),
586            )
587            .unwrap();
588        metadata.set_metadata(Availability::Required);
589
590        let error = proxy
591            .route(Some(Request { target: component.as_weak().into(), metadata }), false)
592            .await
593            .unwrap_err();
594        assert_matches!(
595            error,
596            RouterError::NotFound(err)
597            if matches!(
598                err.downcast_for_test::<RoutingError>(),
599                RoutingError::AvailabilityRoutingError(
600                        crate::error::AvailabilityRoutingError::TargetHasStrongerAvailability {
601                        moniker
602                    }
603                ) if moniker == &Moniker::root().into()
604            )
605        );
606        assert!(*reported.lock().unwrap());
607    }
608}