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