Skip to main content

routing/
intermediate_router.rs

1// Copyright 2026 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::error::{ComponentInstanceError, PrettyPrintRef, RouteVerb, RoutingError};
6use crate::rights::{Rights, validate_rights};
7use async_trait::async_trait;
8use capability_source::CapabilitySource;
9use cm_rust::{CapabilityTypeName, FidlIntoNative, NativeIntoFidl};
10use cm_rust_derive::FidlDecl;
11use cm_types::RelativePath;
12use fidl_fuchsia_component_decl as fdecl;
13use fidl_fuchsia_component_runtime as fruntime;
14use fidl_fuchsia_io as fio;
15use moniker::{ChildName, Moniker};
16use router_error::RouterError;
17use runtime_capabilities::{
18    Capability, CapabilityBound, Connector, Data, Dictionary, DirConnector, Routable, Router,
19    RouterErrorInfo, WeakInstanceToken,
20};
21use std::sync::{Arc, Weak};
22
23#[cfg(target_os = "fuchsia")]
24use {cm_types::IterablePath, fuchsia_trace as trace, itertools::Itertools};
25
26pub enum WeakDictionaryOrRouter {
27    Dictionary(Weak<Dictionary>),
28    Router(Weak<Router<Dictionary>>),
29}
30
31impl From<Weak<Dictionary>> for WeakDictionaryOrRouter {
32    fn from(d: Weak<Dictionary>) -> Self {
33        Self::Dictionary(d)
34    }
35}
36
37impl From<Weak<Router<Dictionary>>> for WeakDictionaryOrRouter {
38    fn from(r: Weak<Router<Dictionary>>) -> Self {
39        Self::Router(r)
40    }
41}
42
43/// RoutingError is big, and we're going to hold a lot of IntermediateRouter types, so instead of
44/// pre-creating a RoutingError we hold the pieces we need to make a
45/// RoutingError::RouteSourceNotFound and construct it as needed.
46struct NotFoundErrorContext {
47    verb: RouteVerb,
48    source: PrettyPrintRef,
49    type_name: CapabilityTypeName,
50}
51
52impl NotFoundErrorContext {
53    fn to_router_error(&self, intermediate_router: &IntermediateRouter) -> RouterError {
54        RoutingError::RouteSourceNotFound {
55            moniker: intermediate_router.moniker.clone(),
56            verb: self.verb,
57            counter_verb: match &self.source {
58                PrettyPrintRef::Parent => RouteVerb::Offer,
59                PrettyPrintRef::Child(_)
60                | PrettyPrintRef::ChildInCollection(_, _)
61                | PrettyPrintRef::Collection(_) => RouteVerb::Expose,
62                PrettyPrintRef::Self_ | PrettyPrintRef::Capability(_) => RouteVerb::Declare,
63                PrettyPrintRef::Framework
64                | PrettyPrintRef::Debug
65                | PrettyPrintRef::Void
66                | PrettyPrintRef::Environment => RouteVerb::Contain,
67            },
68            source: self.source.clone(),
69            capability_type: self.type_name,
70            capability_name: intermediate_router.source_path.clone(),
71        }
72        .into()
73    }
74}
75
76#[derive(FidlDecl, Debug, Clone, PartialEq)]
77#[fidl_decl(fidl_table = "fruntime::RouteRequest")]
78pub struct RouteRequest {
79    pub build_type_name: cm_rust::CapabilityTypeName,
80    pub availability: Option<cm_rust::Availability>,
81    pub directory_rights: Option<fio::Flags>,
82    pub directory_intermediate_rights: Option<fio::Flags>,
83    pub inherit_rights: Option<bool>,
84    pub sub_directory_path: Option<RelativePath>,
85    pub isolated_storage_path: Option<RelativePath>,
86    pub storage_sub_directory_path: Option<RelativePath>,
87    pub storage_source_moniker: Option<Moniker>,
88    pub event_stream_scope_moniker: Option<Moniker>,
89    pub event_stream_scope: Option<Box<[cm_rust::EventScope]>>,
90    pub skip_policy_checks: Option<bool>,
91}
92
93impl Default for RouteRequest {
94    fn default() -> Self {
95        Self {
96            // Having Default implemented reduces verbosity in places where we assemble this
97            // struct, but it's also more convenient to not have `build_type_name` wrapped in an
98            // `Option`. We set the default to `Protocol` here, but anyone using this should
99            // overwrite that.
100            build_type_name: cm_rust::CapabilityTypeName::Protocol,
101            availability: None,
102            directory_rights: None,
103            directory_intermediate_rights: None,
104            inherit_rights: None,
105            sub_directory_path: None,
106            isolated_storage_path: None,
107            storage_sub_directory_path: None,
108            storage_source_moniker: None,
109            event_stream_scope_moniker: None,
110            event_stream_scope: None,
111            skip_policy_checks: None,
112        }
113    }
114}
115
116/// A router that attempts to find a router to forward to in a source dictionary, validates (and
117/// potentially mutates) the route request, and then forwards the request to next router.
118pub struct IntermediateRouter {
119    /// The dictionary (or router for a dictionary) which will hold the router that we forward to.
120    source_dictionary: WeakDictionaryOrRouter,
121
122    /// The path in the source dictionary at which we should find the router to forward to.
123    source_path: RelativePath,
124
125    /// The request that will be used if the route request is empty. The route request will also be
126    /// compared against this "default" request, and if it's requesting something different/larger
127    /// in scope than the default then the request will be rejected. For example, any incoming
128    /// requests must have the same build type name as the default request and they must not
129    /// request greater directory rights than are listed in the default request.
130    default_request: RouteRequest,
131
132    /// The "default" instance token to use when performing routes in order to find the router we
133    /// will forward the request to.
134    default_token: Arc<WeakInstanceToken>,
135
136    /// The moniker of the component which created this router.
137    moniker: Moniker,
138
139    /// Additional context about this step in the route, which is used to generate error values
140    /// when the source router cannot be found.
141    not_found_context: NotFoundErrorContext,
142
143    /// Whether or not to emit tracing events for routes. This field is ignored during host
144    /// routing, where tracing events are never emitted.
145    #[allow(unused)]
146    enable_tracing: bool,
147}
148
149enum RouterCapabilityOrSource<C: CapabilityBound> {
150    Router(Arc<Router<C>>),
151    Capability(Arc<C>),
152    Source(Box<CapabilitySource>),
153}
154
155impl IntermediateRouter {
156    /// Returns an IntermediateRouter wrapped in a Router type, with an appropriate type for the
157    /// `default_request.build_type_name`.
158    pub fn new(
159        source_dictionary: WeakDictionaryOrRouter,
160        source_path: RelativePath,
161        default_request: RouteRequest,
162        default_token: Arc<WeakInstanceToken>,
163        moniker: Moniker,
164        route_verb: RouteVerb,
165        source: fdecl::Ref,
166    ) -> Capability {
167        assert!(source_path.len() != 0);
168        let type_name = default_request.build_type_name;
169
170        let enable_tracing = route_verb == RouteVerb::Use;
171
172        let self_ = Self {
173            source_dictionary,
174            source_path,
175            default_request,
176            default_token,
177            moniker,
178            not_found_context: NotFoundErrorContext {
179                verb: route_verb,
180                source: source.into(),
181                type_name,
182            },
183            enable_tracing,
184        };
185
186        match type_name {
187            CapabilityTypeName::Protocol
188            | CapabilityTypeName::Runner
189            | CapabilityTypeName::Resolver => Router::<Connector>::new(self_).into(),
190            CapabilityTypeName::Service
191            | CapabilityTypeName::Directory
192            | CapabilityTypeName::Storage => Router::<DirConnector>::new(self_).into(),
193            CapabilityTypeName::EventStream | CapabilityTypeName::Dictionary => {
194                Router::<Dictionary>::new(self_).into()
195            }
196            CapabilityTypeName::Config => Router::<Data>::new(self_).into(),
197        }
198    }
199
200    /// Returns the moniker that owns the source dictionary.
201    fn get_upgrade_failure_moniker(&self) -> Moniker {
202        match &self.not_found_context.source {
203            PrettyPrintRef::Parent => self.moniker.parent().unwrap_or_else(|| Moniker::root()),
204
205            PrettyPrintRef::Child(name) => {
206                self.moniker.child(ChildName::new(name.clone().into(), None))
207            }
208            PrettyPrintRef::ChildInCollection(name, collection) => {
209                self.moniker.child(ChildName::new(name.clone(), Some(collection.clone())))
210            }
211
212            PrettyPrintRef::Collection(_)
213            | PrettyPrintRef::Capability(_)
214            | PrettyPrintRef::Debug
215            | PrettyPrintRef::Environment
216            | PrettyPrintRef::Framework
217            | PrettyPrintRef::Self_
218            | PrettyPrintRef::Void => self.moniker.clone(),
219        }
220    }
221
222    /// Upgrades and returns the dictionary which holds the router we will forward the request to.
223    /// Initiates a routing operation for that dictionary if necessary.
224    async fn upgrade_source_dictionary(
225        &self,
226        dictionary_request: &fruntime::RouteRequest,
227    ) -> Result<Arc<Dictionary>, RouterError> {
228        match &self.source_dictionary {
229            WeakDictionaryOrRouter::Dictionary(dictionary) => dictionary.upgrade().ok_or(
230                RoutingError::from(ComponentInstanceError::InstanceNotFound {
231                    moniker: self.get_upgrade_failure_moniker(),
232                })
233                .into(),
234            ),
235            WeakDictionaryOrRouter::Router(router) => {
236                let router = router.upgrade().ok_or(RoutingError::from(
237                    ComponentInstanceError::InstanceNotFound {
238                        moniker: self.get_upgrade_failure_moniker(),
239                    },
240                ))?;
241                let dictionary = router
242                    .route(dictionary_request.clone(), self.default_token.clone())
243                    .await?
244                    .expect("routers for source dictionaries should never return None");
245                Ok(dictionary)
246            }
247        }
248    }
249
250    /// Upgrades self.source_dictionary and attempts to find a router at self.source_path in it of
251    /// type Arc<Router<C>>. It does this by walking self.source_path, stepping down into each
252    /// successive dictionary (routing dictionary routers as needed).
253    async fn get_source_router<C: CapabilityBound>(
254        &self,
255        request: &fruntime::RouteRequest,
256        debug: bool,
257    ) -> Result<RouterCapabilityOrSource<C>, RouterError>
258    where
259        Arc<Router<C>>: TryFrom<Capability>,
260        Arc<C>: TryFrom<Capability>,
261        Router<C>: CapabilityBound,
262    {
263        let dictionary_request = fruntime::RouteRequest {
264            build_type_name: Some(CapabilityTypeName::Dictionary.to_string()),
265            ..request.clone()
266        };
267        // Get the dictionary holding the source router (if the dictionary still exists).
268        let mut source_dictionary = self.upgrade_source_dictionary(&dictionary_request).await?;
269
270        // Get the source router from the dictionary
271        let mut path_to_walk = self.source_path.clone();
272        let mut most_recent_source = None;
273        while path_to_walk.len() > 1 {
274            let next_step = path_to_walk.pop_front().expect("we checked that this isn't empty");
275            match source_dictionary.get(&next_step) {
276                Some(Capability::Dictionary(d)) => source_dictionary = d,
277                Some(Capability::DictionaryRouter(r)) => {
278                    match r.route(dictionary_request.clone(), self.default_token.clone()).await? {
279                        Some(d) => {
280                            if debug {
281                                most_recent_source = Some(
282                                    r.route_debug(
283                                        dictionary_request.clone(),
284                                        self.default_token.clone(),
285                                    )
286                                    .await?,
287                                );
288                            }
289                            source_dictionary = d;
290                        }
291                        None => {
292                            // The next step along our path is unavailable! If this is a debug
293                            // route, we'll want the source of this unavailable dictionary.
294                            let source = r
295                                .route_debug(dictionary_request.clone(), self.default_token.clone())
296                                .await?;
297                            return Ok(RouterCapabilityOrSource::Source(Box::new(source)));
298                        }
299                    }
300                }
301                Some(capability) => {
302                    return Err(RoutingError::BedrockWrongCapabilityType {
303                        actual: capability.debug_typename().to_string(),
304                        expected: Dictionary::debug_typename().to_string(),
305                        moniker: self.moniker.clone().into(),
306                    }
307                    .into());
308                }
309                None => {
310                    return Err(self.not_found_context.to_router_error(self));
311                }
312            }
313        }
314        let capability_name = path_to_walk
315            .pop_front()
316            .expect("we stopped the above loop before fully draining the path");
317        let maybe_source_router = source_dictionary
318            .get(&capability_name)
319            .ok_or_else(|| self.not_found_context.to_router_error(self))?;
320
321        let maybe_c: Option<Arc<C>> = maybe_source_router.clone().try_into().ok();
322        if let Some(c) = maybe_c {
323            if !debug {
324                return Ok(RouterCapabilityOrSource::Capability(c));
325            } else {
326                let source = most_recent_source.ok_or_else(|| RoutingError::SourceUnknown {
327                    capability_id: capability_name.to_string(),
328                    moniker: self.moniker.clone().into(),
329                })?;
330                return Ok(RouterCapabilityOrSource::Source(Box::new(source)));
331            }
332        }
333
334        let capability_type_name = maybe_source_router.debug_typename();
335        let router: Arc<Router<C>> = maybe_source_router.try_into().map_err(|_| {
336            RoutingError::BedrockWrongCapabilityType {
337                actual: capability_type_name.to_string(),
338                expected: Router::<C>::debug_typename().to_string(),
339                moniker: self.moniker.clone().into(),
340            }
341        })?;
342        Ok(RouterCapabilityOrSource::Router(router))
343    }
344
345    /// Check to see if `request` does not request anything different or larger in scope than
346    /// `self.default_request`. `request` will be set to `self.default_request` if it is empty.
347    fn handle_new_request(&self, request: &mut fruntime::RouteRequest) -> Result<(), RouterError> {
348        if *request == fruntime::RouteRequest::default() {
349            *request = self.default_request.clone().native_into_fidl();
350            return Ok(());
351        }
352
353        self.check_build_type_name(request)?;
354        self.handle_availability(request)?;
355        self.handle_directory_rights(request)?;
356        self.handle_sub_directory(request)?;
357        self.handle_event_stream_scope(request);
358
359        Ok(())
360    }
361
362    fn check_build_type_name(
363        &self,
364        request: &mut fruntime::RouteRequest,
365    ) -> Result<(), RouterError> {
366        if request.build_type_name != Some(self.default_request.build_type_name.to_string()) {
367            Err(RoutingError::BedrockWrongCapabilityType {
368                moniker: self.moniker.clone().into(),
369                actual: request
370                    .build_type_name
371                    .as_ref()
372                    .map(Clone::clone)
373                    .unwrap_or_else(|| "".to_string()),
374                expected: self.default_request.build_type_name.to_string(),
375            })?;
376        }
377        Ok(())
378    }
379
380    fn handle_availability(&self, request: &mut fruntime::RouteRequest) -> Result<(), RouterError> {
381        if self.default_request.availability.is_none() {
382            return Ok(());
383        }
384        let request_availability: fidl_fuchsia_component_decl::Availability = *request
385            .availability
386            .as_ref()
387            .ok_or_else(|| RoutingError::RouteRequestMissingField {
388                moniker: self.moniker.clone().into(),
389                missing_field: "availability".to_string(),
390            })?;
391        let request_availability: cm_rust::Availability = request_availability.fidl_into_native();
392        let default_availability =
393            self.default_request.availability.expect("default request is missing availability");
394        let new_availability = crate::availability::advance(
395            &self.moniker.clone().into(),
396            request_availability,
397            default_availability,
398        )
399        .map_err(|e| RoutingError::from(e))?;
400        request.availability = Some(new_availability.native_into_fidl());
401        Ok(())
402    }
403
404    fn handle_directory_rights(
405        &self,
406        request: &mut fruntime::RouteRequest,
407    ) -> Result<(), RouterError> {
408        let Some(directory_rights) = self.default_request.directory_rights else {
409            return Ok(());
410        };
411        let rights = Rights::from(directory_rights);
412        validate_rights(self.moniker.clone().into(), rights.into(), request)?;
413        request.directory_intermediate_rights = Some(fio::Flags::from(rights));
414        Ok(())
415    }
416
417    fn handle_sub_directory(
418        &self,
419        request: &mut fruntime::RouteRequest,
420    ) -> Result<(), RouterError> {
421        let Some(mut new_subdir) = self.default_request.sub_directory_path.clone() else {
422            return Ok(());
423        };
424
425        let Some(current_subdir) = request.sub_directory_path.as_ref() else {
426            request.sub_directory_path =
427                self.default_request.sub_directory_path.clone().map(|p| p.to_string());
428            return Ok(());
429        };
430        let current_subdir = RelativePath::new(current_subdir).map_err(|e| {
431            RoutingError::RouteRequestFailedToParseField {
432                moniker: self.moniker.clone().into(),
433                field: "sub_directory_path".to_string(),
434                parse_error: format!("{e:?}"),
435            }
436        })?;
437
438        let success = new_subdir.extend(current_subdir);
439        if !success {
440            return Err(RoutingError::PathTooLong {
441                moniker: self.moniker.clone().into(),
442                path: self.default_request.sub_directory_path.clone().unwrap().to_string(),
443                keyword: request.sub_directory_path.clone().unwrap(),
444            }
445            .into());
446        }
447
448        request.sub_directory_path = Some(new_subdir.native_into_fidl());
449        Ok(())
450    }
451
452    fn handle_event_stream_scope(&self, request: &mut fruntime::RouteRequest) {
453        if request.event_stream_scope_moniker.is_some() {
454            // If the scope is already set then it's a smaller scope (because we can't expose
455            // these), so only set our scope if the request doesn't have one yet.
456            return;
457        }
458        let Some(new_moniker) = self.default_request.event_stream_scope_moniker.as_ref() else {
459            return;
460        };
461        let Some(new_scope) = self.default_request.event_stream_scope.as_ref() else {
462            return;
463        };
464        request.event_stream_scope_moniker = Some(new_moniker.clone().native_into_fidl());
465        request.event_stream_scope = Some(new_scope.clone().native_into_fidl());
466    }
467}
468
469#[async_trait]
470impl<C: CapabilityBound> Routable<C> for IntermediateRouter
471where
472    Arc<Router<C>>: TryFrom<Capability>,
473    Arc<C>: TryFrom<Capability>,
474    Router<C>: CapabilityBound,
475    C: std::fmt::Debug,
476{
477    async fn route(
478        &self,
479        mut request: fruntime::RouteRequest,
480        target: Arc<WeakInstanceToken>,
481    ) -> Result<Option<Arc<C>>, RouterError> {
482        #[cfg(target_os = "fuchsia")]
483        if self.enable_tracing {
484            trace::duration_begin!(
485                "component_manager", "route_capability",
486                "target" => self.moniker.as_str(),
487                "type" => self.default_request.build_type_name.to_string().as_str(),
488                "capability" => self.source_path.iter_segments().join("/").as_str()
489            );
490        }
491
492        self.handle_new_request(&mut request)?;
493        let result = match self.get_source_router(&request, false).await? {
494            RouterCapabilityOrSource::Capability(c) => Ok(Some(c)),
495            RouterCapabilityOrSource::Source(source) => {
496                match &*source {
497                    CapabilitySource::Void(_) => (),
498                    other_source => {
499                        panic!(
500                            "should only return source for non-debug routes when source is void, but the source is {other_source:?}"
501                        );
502                    }
503                }
504                Err(RoutingError::SourceCapabilityIsVoid { moniker: source.source_moniker() }
505                    .into())
506            }
507            RouterCapabilityOrSource::Router(router) => router.route(request, target).await,
508        };
509
510        #[cfg(target_os = "fuchsia")]
511        if self.enable_tracing {
512            trace::duration_end!(
513                "component_manager", "route_capability",
514                "target" => self.moniker.as_str(),
515                "type" => self.default_request.build_type_name.to_string().as_str(),
516                "capability" => self.source_path.iter_segments().join("/").as_str()
517            );
518        }
519
520        result
521    }
522
523    async fn route_debug(
524        &self,
525        mut request: fruntime::RouteRequest,
526        target: Arc<WeakInstanceToken>,
527    ) -> Result<CapabilitySource, RouterError> {
528        #[cfg(target_os = "fuchsia")]
529        if self.enable_tracing {
530            trace::duration_begin!(
531                "component_manager", "route_capability_debug",
532                "target" => self.moniker.as_str(),
533                "type" => self.default_request.build_type_name.to_string().as_str(),
534                "capability" => self.source_path.iter_segments().join("/").as_str()
535            );
536        }
537
538        self.handle_new_request(&mut request)?;
539        let result = match self.get_source_router(&request, true).await? {
540            RouterCapabilityOrSource::Capability(_) => {
541                panic!("returned capability for debug operation")
542            }
543            RouterCapabilityOrSource::Source(source) => Ok(*source),
544            RouterCapabilityOrSource::Router(router) => router.route_debug(request, target).await,
545        };
546
547        #[cfg(target_os = "fuchsia")]
548        if self.enable_tracing {
549            trace::duration_end!(
550                "component_manager", "route_capability_debug",
551                "target" => self.moniker.as_str(),
552                "type" => self.default_request.build_type_name.to_string().as_str(),
553                "capability" => self.source_path.iter_segments().join("/").as_str()
554            );
555        }
556
557        result
558    }
559
560    fn error_info(&self) -> Option<RouterErrorInfo> {
561        Some(RouterErrorInfo {
562            capability_type: self.not_found_context.type_name,
563            name: self.source_path.basename().unwrap().to_owned(),
564            availability: self.default_request.availability.unwrap(),
565        })
566    }
567}