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