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