Skip to main content

routing/bedrock/
dict_ext.rs

1// Copyright 2024 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::RoutingError;
6use async_trait::async_trait;
7use capability_source::{CapabilitySource, RemotedAtSource};
8use cm_rust::CapabilityTypeName;
9use cm_types::{IterablePath, RelativePath};
10use fidl_fuchsia_component_runtime::RouteRequest;
11use itertools::Itertools;
12use moniker::ExtendedMoniker;
13use router_error::RouterError;
14use runtime_capabilities::{
15    Capability, CapabilityBound, Dictionary, Routable, Router, WeakInstanceToken,
16};
17use std::fmt::Debug;
18
19#[async_trait]
20pub trait DictExt {
21    /// Returns the capability at the path, if it exists. Returns `None` if path is empty.
22    fn get_capability(&self, path: &impl IterablePath) -> Option<Capability>;
23
24    /// Looks up a top-level router in this [Dictionary] with return type `T`. If it's not found
25    /// (or it's not a router) returns a router that always returns `not_found_error`. If `path`
26    /// has one segment and a router was found, returns that router.
27    ///
28    /// If `path` is a multi-segment path, the returned router performs a [Dictionary] lookup with
29    /// the remaining path relative to the top-level router (see [LazyGet::lazy_get]).
30    ///
31    /// REQUIRES: `path` is not empty.
32    fn get_router_or_not_found<T>(
33        &self,
34        path: &impl IterablePath,
35        not_found_error: RoutingError,
36    ) -> Router<T>
37    where
38        T: CapabilityBound,
39        Router<T>: TryFrom<Capability>;
40
41    /// Inserts the capability at the path. Intermediary dictionaries are created as needed. If
42    /// there's already a capability at the path, then the preexisting value is returned.
43    fn insert_capability(
44        &self,
45        path: &impl IterablePath,
46        capability: Capability,
47    ) -> Option<Capability>;
48
49    /// Removes the capability at the path, if it exists, and returns it.
50    fn remove_capability(&self, path: &impl IterablePath) -> Option<Capability>;
51
52    /// Looks up the element at `path`. When encountering an intermediate router, use `request` to
53    /// request the underlying capability from it. In contrast, `get_capability` will return
54    /// `None`.
55    ///
56    /// Note that the return value can contain any capability type, instead of a parameterized `T`.
57    /// This is because some callers work with a generic capability and don't care about the
58    /// specific type.
59    async fn get_with_request<'a>(
60        &self,
61        moniker: &ExtendedMoniker,
62        path: &'a impl IterablePath,
63        request: RouteRequest,
64        target: WeakInstanceToken,
65    ) -> Result<Option<Capability>, RouterError>;
66
67    /// Identical to `get_with_request`, except it returns the source of the capability at `path`
68    /// instead of the capability itself.
69    async fn get_with_request_debug<'a>(
70        &self,
71        moniker: &ExtendedMoniker,
72        path: &'a impl IterablePath,
73        request: RouteRequest,
74        target: WeakInstanceToken,
75    ) -> Result<CapabilitySource, RouterError>;
76}
77
78#[async_trait]
79impl DictExt for Dictionary {
80    fn get_capability(&self, path: &impl IterablePath) -> Option<Capability> {
81        let mut segments = path.iter_segments();
82        let Some(mut current_name) = segments.next() else { return Some(self.clone().into()) };
83        let mut current_dict = self.clone();
84        loop {
85            match segments.next() {
86                Some(next_name) => {
87                    let sub_dict =
88                        current_dict.get(current_name).and_then(|value| value.to_dictionary())?;
89                    current_dict = sub_dict;
90
91                    current_name = next_name;
92                }
93                None => return current_dict.get(current_name),
94            }
95        }
96    }
97
98    fn get_router_or_not_found<T>(
99        &self,
100        path: &impl IterablePath,
101        not_found_error: RoutingError,
102    ) -> Router<T>
103    where
104        T: CapabilityBound,
105        Router<T>: TryFrom<Capability>,
106    {
107        let mut segments = path.iter_segments();
108        let root = segments.next().expect("path must be nonempty");
109
110        #[derive(Debug)]
111        struct ErrorRouter {
112            not_found_error: RouterError,
113        }
114
115        #[async_trait]
116        impl<T: CapabilityBound> Routable<T> for ErrorRouter {
117            async fn route(
118                &self,
119                _request: RouteRequest,
120                _target: WeakInstanceToken,
121            ) -> Result<Option<T>, RouterError> {
122                Err(self.not_found_error.clone())
123            }
124
125            async fn route_debug(
126                &self,
127                _request: RouteRequest,
128                _target: WeakInstanceToken,
129            ) -> Result<CapabilitySource, RouterError> {
130                Err(self.not_found_error.clone())
131            }
132        }
133
134        /// This uses the same algorithm as [LazyGet], but that is implemented for
135        /// [Router<Dictionary>] while this is implemented for [Router]. This duplication will go
136        /// away once [Router] is replaced with [Router].
137        #[derive(Debug)]
138        struct ScopedDictRouter<P: IterablePath + Debug + 'static> {
139            router: Router<Dictionary>,
140            path: P,
141            not_found_error: RoutingError,
142        }
143
144        #[async_trait]
145        impl<P: IterablePath + Debug + 'static, T: CapabilityBound> Routable<T> for ScopedDictRouter<P> {
146            async fn route(
147                &self,
148                request: RouteRequest,
149                target: WeakInstanceToken,
150            ) -> Result<Option<T>, RouterError> {
151                let get_init_request = || request_with_dictionary_replacement(&request);
152
153                let init_request = (get_init_request)()?;
154                match self.router.route(init_request, target.clone()).await? {
155                    Some(dict) => {
156                        let moniker: ExtendedMoniker = self.not_found_error.clone().into();
157                        match dict.get_with_request(&moniker, &self.path, request, target).await {
158                            Err(router_error)
159                                if let Ok(RoutingError::BedrockNotPresentInDictionary {
160                                    ..
161                                }) = router_error.clone().try_into() =>
162                            {
163                                Err(self.not_found_error.clone().into())
164                            }
165                            Err(e) => Err(e),
166                            Ok(None) => Ok(None),
167                            Ok(Some(cap)) => {
168                                let actual_type_name = cap.debug_typename();
169                                let cap = T::try_from(cap).map_err(|_| {
170                                    RoutingError::BedrockWrongCapabilityType {
171                                        expected: T::debug_typename().into(),
172                                        actual: actual_type_name.into(),
173                                        moniker,
174                                    }
175                                })?;
176                                Ok(Some(cap))
177                            }
178                        }
179                    }
180                    None => Ok(None),
181                }
182            }
183
184            async fn route_debug(
185                &self,
186                request: RouteRequest,
187                target: WeakInstanceToken,
188            ) -> Result<CapabilitySource, RouterError> {
189                let get_init_request = || request_with_dictionary_replacement(&request);
190
191                // When performing a debug route, we only want to call `route_debug` on the
192                // capability at `path`. Here we're looking up the containing dictionary, so we do
193                // non-debug routing, to obtain the actual Dictionary and not its debug info.
194                let init_request = (get_init_request)()?;
195                match self.router.route(init_request, target.clone()).await? {
196                    Some(dict) => {
197                        let moniker: ExtendedMoniker = self.not_found_error.clone().into();
198                        match dict
199                            .get_with_request_debug(&moniker, &self.path, request, target)
200                            .await
201                        {
202                            Err(router_error)
203                                if let Ok(RoutingError::BedrockNotPresentInDictionary {
204                                    ..
205                                }) = router_error.clone().try_into() =>
206                            {
207                                Err(self.not_found_error.clone().into())
208                            }
209                            other_result => other_result,
210                        }
211                    }
212                    None => {
213                        // The above route was non-debug, but the routing operation failed. Call
214                        // the router again with the same arguments but with `route_debug` so that
215                        // we return the debug info to the caller (which ought to be
216                        // [`CapabilitySource::Void`]).
217                        let init_request = (get_init_request)()?;
218                        self.router.route_debug(init_request, target).await
219                    }
220                }
221            }
222        }
223
224        if segments.next().is_none() {
225            // No nested lookup necessary.
226            let Some(router) = self.get(root).and_then(|cap| Router::<T>::try_from(cap).ok())
227            else {
228                return Router::<T>::new(ErrorRouter { not_found_error: not_found_error.into() });
229            };
230            return router;
231        }
232
233        let Some(cap) = self.get(root) else {
234            return Router::<T>::new(ErrorRouter { not_found_error: not_found_error.into() });
235        };
236        let router = match cap {
237            Capability::Dictionary(d) => Router::<Dictionary>::new_ok(d),
238            Capability::DictionaryRouter(r) => r,
239            _ => {
240                return Router::<T>::new(ErrorRouter { not_found_error: not_found_error.into() });
241            }
242        };
243
244        let mut segments = path.iter_segments();
245        let _ = segments.next().unwrap();
246        let path = RelativePath::from(segments.collect::<Vec<_>>());
247
248        Router::<T>::new(ScopedDictRouter { router, path, not_found_error: not_found_error.into() })
249    }
250
251    fn insert_capability(
252        &self,
253        path: &impl IterablePath,
254        capability: Capability,
255    ) -> Option<Capability> {
256        let mut segments = path.iter_segments();
257        let mut current_name = segments.next().expect("path must be non-empty");
258        let mut current_dict = self.clone();
259        loop {
260            match segments.next() {
261                Some(next_name) => {
262                    let sub_dict = {
263                        match current_dict.get(current_name) {
264                            Some(Capability::Dictionary(dict)) => dict,
265                            Some(Capability::DictionaryRouter(preexisting_router)) => {
266                                let mut path = vec![next_name];
267                                while let Some(name) = segments.next() {
268                                    path.push(name);
269                                }
270                                let path = RelativePath::from(path);
271                                let new_router = Router::new(AdditiveDictionaryRouter {
272                                    preexisting_router,
273                                    path,
274                                    capability,
275                                });
276
277                                // Replace the entry in current_dict.
278                                return current_dict.insert(current_name.into(), new_router.into());
279                            }
280                            None => {
281                                let dict = Dictionary::new();
282                                current_dict.insert(
283                                    current_name.into(),
284                                    Capability::Dictionary(dict.clone()),
285                                );
286                                dict
287                            }
288                            _ => return None,
289                        }
290                    };
291                    current_dict = sub_dict;
292
293                    current_name = next_name;
294                }
295                None => {
296                    return current_dict.insert(current_name.into(), capability);
297                }
298            }
299        }
300    }
301
302    fn remove_capability(&self, path: &impl IterablePath) -> Option<Capability> {
303        let mut segments = path.iter_segments();
304        let mut current_name = segments.next().expect("path must be non-empty");
305        let mut current_dict = self.clone();
306        loop {
307            match segments.next() {
308                Some(next_name) => {
309                    let sub_dict =
310                        current_dict.get(current_name).and_then(|value| value.to_dictionary());
311                    if sub_dict.is_none() {
312                        // The capability doesn't exist, there's nothing to remove.
313                        return None;
314                    }
315                    current_dict = sub_dict.unwrap();
316                    current_name = next_name;
317                }
318                None => {
319                    return current_dict.remove(current_name);
320                }
321            }
322        }
323    }
324
325    async fn get_with_request<'a>(
326        &self,
327        moniker: &ExtendedMoniker,
328        path: &'a impl IterablePath,
329        request: RouteRequest,
330        target: WeakInstanceToken,
331    ) -> Result<Option<Capability>, RouterError> {
332        let mut current_dict = self.clone();
333        let num_segments = path.iter_segments().count();
334        for (next_idx, next_name) in path.iter_segments().enumerate() {
335            // Get the capability.
336            let capability = current_dict.get(next_name);
337
338            // The capability doesn't exist.
339            let Some(capability) = capability else {
340                return Err(RoutingError::BedrockNotPresentInDictionary {
341                    name: path.iter_segments().join("/"),
342                    moniker: moniker.clone(),
343                }
344                .into());
345            };
346
347            if next_idx < num_segments - 1 {
348                // Not at the end of the path yet, so there's more nesting. We expect to have found
349                // a [Dictionary], or a [Dictionary] router -- traverse into this [Dictionary].
350                match capability {
351                    Capability::Dictionary(d) => {
352                        current_dict = d;
353                    }
354                    Capability::DictionaryRouter(r) => {
355                        let request = request_with_dictionary_replacement(&request)?;
356                        let Some(new_dictionary) = r.route(request, target.clone()).await? else {
357                            return Ok(None);
358                        };
359                        current_dict = new_dictionary;
360                    }
361                    _ => {
362                        return Err(RoutingError::BedrockWrongCapabilityType {
363                            expected: Dictionary::debug_typename().into(),
364                            actual: capability.debug_typename().into(),
365                            moniker: moniker.clone(),
366                        }
367                        .into());
368                    }
369                }
370                continue;
371            }
372
373            // We've reached the end of our path. The last capability should have type
374            // `T` or `Router<T>`.
375            //
376            // There's a bit of repetition here because this function supports multiple router
377            // types.
378            match capability {
379                Capability::DictionaryRouter(r) => {
380                    return r.route(request, target).await.map(|option| option.map(Into::into));
381                }
382                Capability::ConnectorRouter(r) => {
383                    return r.route(request, target).await.map(|option| option.map(Into::into));
384                }
385                Capability::DataRouter(r) => {
386                    return r.route(request, target).await.map(|option| option.map(Into::into));
387                }
388                Capability::DirConnectorRouter(r) => {
389                    return r.route(request, target).await.map(|option| option.map(Into::into));
390                }
391                other_capability => return Ok(Some(other_capability.into())),
392            };
393        }
394        unreachable!("get_with_request: All cases are handled in the loop");
395    }
396
397    async fn get_with_request_debug<'a>(
398        &self,
399        moniker: &ExtendedMoniker,
400        path: &'a impl IterablePath,
401        request: RouteRequest,
402        target: WeakInstanceToken,
403    ) -> Result<CapabilitySource, RouterError> {
404        let mut current_dict = self.clone();
405        let mut closest_moniker = moniker.clone();
406        let num_segments = path.iter_segments().count();
407        for (next_idx, next_name) in path.iter_segments().enumerate() {
408            // Get the capability.
409            let capability = current_dict.get(next_name);
410
411            // The capability doesn't exist.
412            let Some(capability) = capability else {
413                return Err(RoutingError::BedrockNotPresentInDictionary {
414                    name: path.iter_segments().join("/"),
415                    moniker: moniker.clone(),
416                }
417                .into());
418            };
419
420            if next_idx < num_segments - 1 {
421                // Not at the end of the path yet, so there's more nesting. We expect to have found
422                // a [Dictionary], or a [Dictionary] router -- traverse into this [Dictionary].
423                match capability {
424                    Capability::Dictionary(d) => {
425                        current_dict = d;
426                    }
427                    Capability::DictionaryRouter(r) => {
428                        // We want to do two routes of this: one debug and one non-debug. The debug
429                        // route is needed so we can determine where this dictionary comes from
430                        // (which is needed below if we find a non-router capability), and the
431                        // non-debug route here is needed to recurse into.
432                        let req = request_with_dictionary_replacement(&request)?;
433                        let maybe_new_dictionary = r.route(req.clone(), target.clone()).await?;
434                        let source = r.route_debug(req.clone(), target.clone()).await?;
435
436                        let Some(new_dictionary) = maybe_new_dictionary else {
437                            // The capability is not available! Let's return the source of it
438                            // (which ought to be [`CapabilitySource::Void`])
439                            return Ok(source);
440                        };
441                        current_dict = new_dictionary;
442                        closest_moniker = source.source_moniker();
443                    }
444                    _ => {
445                        return Err(RoutingError::BedrockWrongCapabilityType {
446                            expected: Dictionary::debug_typename().into(),
447                            actual: capability.debug_typename().into(),
448                            moniker: moniker.clone(),
449                        }
450                        .into());
451                    }
452                }
453                continue;
454            }
455
456            // We've reached the end of our path. The last capability should have type
457            // `T` or `Router<T>`.
458            //
459            // There's a bit of repetition here because this function supports multiple router
460            // types.
461            match capability {
462                Capability::DictionaryRouter(r) => {
463                    return r.route_debug(request, target).await;
464                }
465                Capability::ConnectorRouter(r) => {
466                    return r.route_debug(request, target).await;
467                }
468                Capability::DataRouter(r) => {
469                    return r.route_debug(request, target).await;
470                }
471                Capability::DirConnectorRouter(r) => {
472                    return r.route_debug(request, target).await;
473                }
474                _other_capability => {
475                    // This is a debug route, and we've found a non-router capability. We must
476                    // return debug information for the debug route, and the only reason there
477                    // would be a non-router capability in a dictionary would be if a user
478                    // created one, so we can safely report that this was a remotely created
479                    // capability.
480                    //
481                    // We attribute this to the provider of the most recent dictionary we routed,
482                    // which should be the component that put this non-router capability in a
483                    // dictionary.
484                    let remoted_at_moniker = match closest_moniker {
485                        ExtendedMoniker::ComponentInstance(m) => m,
486                        // Component manager always generates routers, so we should never find
487                        // a non-router capability at the point where this moniker would be for
488                        // component manager.
489                        ExtendedMoniker::ComponentManager => {
490                            panic!("component manager generated a non-router capability")
491                        }
492                    };
493                    let type_name: Option<CapabilityTypeName> = request
494                        .build_type_name
495                        .as_ref()
496                        .map(|s| std::str::FromStr::from_str(s.as_str()))
497                        .transpose()
498                        .expect("invalid type name");
499                    return Ok(CapabilitySource::RemotedAt(RemotedAtSource {
500                        moniker: remoted_at_moniker,
501                        type_name,
502                    }));
503                }
504            };
505        }
506        unreachable!("get_with_request_debug: All cases are handled in the loop");
507    }
508}
509
510/// Creates a clone of `request` that is identical except `"type"` is set to `dictionary`
511/// if it is not already. If `request` is `None`, `None` will be returned.
512///
513/// This is convenient for router lookups of nested paths, since all lookups except the last
514/// segment are dictionary lookups.
515pub(super) fn request_with_dictionary_replacement(
516    request: &RouteRequest,
517) -> Result<RouteRequest, RoutingError> {
518    if request == &RouteRequest::default() {
519        return Ok(RouteRequest::default());
520    }
521    let mut request_clone = request.clone();
522    request_clone.build_type_name = Some(CapabilityTypeName::Dictionary.to_string());
523    Ok(request_clone)
524}
525
526struct AdditiveDictionaryRouter {
527    preexisting_router: Router<Dictionary>,
528    path: RelativePath,
529    capability: Capability,
530}
531
532#[async_trait]
533impl Routable<Dictionary> for AdditiveDictionaryRouter {
534    async fn route(
535        &self,
536        request: RouteRequest,
537        target: WeakInstanceToken,
538    ) -> Result<Option<Dictionary>, RouterError> {
539        let dictionary = match self.preexisting_router.route(request, target).await {
540            Ok(Some(dictionary)) => dictionary.shallow_copy(),
541            other_response => return other_response,
542        };
543        let _ = dictionary.insert_capability(&self.path, self.capability.clone());
544        Ok(Some(dictionary))
545    }
546
547    async fn route_debug(
548        &self,
549        request: RouteRequest,
550        target: WeakInstanceToken,
551    ) -> Result<CapabilitySource, RouterError> {
552        self.preexisting_router.route_debug(request, target).await
553    }
554}