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