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::error::RoutingError;
7use async_trait::async_trait;
8use cm_rust::CapabilityTypeName;
9use cm_types::{IterablePath, RelativePath};
10use fidl_fuchsia_component_sandbox as fsandbox;
11use moniker::ExtendedMoniker;
12use router_error::RouterError;
13use sandbox::{
14    Capability, CapabilityBound, Connector, Data, Dict, DirConnector, DirEntry, Request, Routable,
15    Router, RouterResponse,
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 [Dict] with return type `T`. If it's not found (or it's
25    /// not a router) returns a router that always returns `not_found_error`. If `path` has one
26    /// segment and a router was found, returns that router.
27    ///
28    /// If `path` is a multi-segment path, the returned router performs a [Dict] lookup with the
29    /// 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.
42    fn insert_capability(
43        &self,
44        path: &impl IterablePath,
45        capability: Capability,
46    ) -> Result<(), fsandbox::CapabilityStoreError>;
47
48    /// Removes the capability at the path, if it exists.
49    fn remove_capability(&self, path: &impl IterablePath);
50
51    /// Looks up the element at `path`. When encountering an intermediate router, use `request` to
52    /// request the underlying capability from it. In contrast, `get_capability` will return
53    /// `None`.
54    ///
55    /// Note that the return value can contain any capability type, instead of a parameterized `T`.
56    /// This is because some callers work with a generic capability and don't care about the
57    /// specific type. Callers who do care can use `TryFrom` to cast to the expected
58    /// [RouterResponse] type.
59    async fn get_with_request<'a>(
60        &self,
61        moniker: &ExtendedMoniker,
62        path: &'a impl IterablePath,
63        request: Option<Request>,
64        debug: bool,
65    ) -> Result<Option<GenericRouterResponse>, RouterError>;
66}
67
68/// The analogue of a [RouterResponse] that can hold any type of capability. This is the
69/// return type of [DictExt::get_with_request].
70#[derive(Debug)]
71pub enum GenericRouterResponse {
72    /// Routing succeeded and returned this capability.
73    Capability(Capability),
74
75    /// Routing succeeded, but the capability was marked unavailable.
76    Unavailable,
77
78    /// Routing succeeded in debug mode, `Data` contains the debug data.
79    Debug(Data),
80}
81
82impl<T: CapabilityBound> TryFrom<GenericRouterResponse> for RouterResponse<T> {
83    // Returns the capability's debug typename.
84    type Error = &'static str;
85
86    fn try_from(r: GenericRouterResponse) -> Result<Self, Self::Error> {
87        let r = match r {
88            GenericRouterResponse::Capability(c) => {
89                let debug_name = c.debug_typename();
90                RouterResponse::<T>::Capability(c.try_into().map_err(|_| debug_name)?)
91            }
92            GenericRouterResponse::Unavailable => RouterResponse::<T>::Unavailable,
93            GenericRouterResponse::Debug(d) => RouterResponse::<T>::Debug(d),
94        };
95        Ok(r)
96    }
97}
98
99#[async_trait]
100impl DictExt for Dict {
101    fn get_capability(&self, path: &impl IterablePath) -> Option<Capability> {
102        let mut segments = path.iter_segments();
103        let Some(mut current_name) = segments.next() else { return Some(self.clone().into()) };
104        let mut current_dict = self.clone();
105        loop {
106            match segments.next() {
107                Some(next_name) => {
108                    let sub_dict = current_dict
109                        .get(current_name)
110                        .ok()
111                        .flatten()
112                        .and_then(|value| value.to_dictionary())?;
113                    current_dict = sub_dict;
114
115                    current_name = next_name;
116                }
117                None => return current_dict.get(current_name).ok().flatten(),
118            }
119        }
120    }
121
122    fn get_router_or_not_found<T>(
123        &self,
124        path: &impl IterablePath,
125        not_found_error: RoutingError,
126    ) -> Router<T>
127    where
128        T: CapabilityBound,
129        Router<T>: TryFrom<Capability>,
130    {
131        let mut segments = path.iter_segments();
132        let root = segments.next().expect("path must be nonempty");
133
134        #[derive(Debug)]
135        struct ErrorRouter {
136            not_found_error: RouterError,
137        }
138
139        #[async_trait]
140        impl<T: CapabilityBound> Routable<T> for ErrorRouter {
141            async fn route(
142                &self,
143                _request: Option<Request>,
144                _debug: bool,
145            ) -> Result<RouterResponse<T>, RouterError> {
146                Err(self.not_found_error.clone())
147            }
148        }
149
150        /// This uses the same algorithm as [LazyGet], but that is implemented for
151        /// [Router<Dict>] while this is implemented for [Router]. This duplication will go
152        /// away once [Router] is replaced with [Router].
153        #[derive(Debug)]
154        struct ScopedDictRouter<P: IterablePath + Debug + 'static> {
155            router: Router<Dict>,
156            path: P,
157            not_found_error: RoutingError,
158        }
159
160        #[async_trait]
161        impl<P: IterablePath + Debug + 'static, T: CapabilityBound> Routable<T> for ScopedDictRouter<P> {
162            async fn route(
163                &self,
164                request: Option<Request>,
165                debug: bool,
166            ) -> Result<RouterResponse<T>, RouterError> {
167                let get_init_request = || request_with_dictionary_replacement(request.as_ref());
168
169                // If `debug` is true, that should only apply to the capability at `path`.
170                // Here we're looking up the containing dictionary, so set `debug = false`, to
171                // obtain the actual Dict and not its debug info. For the same reason, we need
172                // to set the capability type on the first request to Dictionary.
173                let init_request = (get_init_request)()?;
174                match self.router.route(init_request, false).await? {
175                    RouterResponse::<Dict>::Capability(dict) => {
176                        let moniker: ExtendedMoniker = self.not_found_error.clone().into();
177                        let resp =
178                            dict.get_with_request(&moniker, &self.path, request, debug).await?;
179                        let resp =
180                            resp.ok_or_else(|| RouterError::from(self.not_found_error.clone()))?;
181                        let resp = resp.try_into().map_err(|debug_name: &'static str| {
182                            RoutingError::BedrockWrongCapabilityType {
183                                expected: T::debug_typename().into(),
184                                actual: debug_name.into(),
185                                moniker,
186                            }
187                        })?;
188                        Ok(resp)
189                    }
190                    RouterResponse::<Dict>::Debug(data) => Ok(RouterResponse::<T>::Debug(data)),
191                    RouterResponse::<Dict>::Unavailable => {
192                        if !debug {
193                            Ok(RouterResponse::<T>::Unavailable)
194                        } else {
195                            // `debug=true` was the input to this function but the call above to
196                            // [`Router::route`] used `debug=false`. Call the router again with the
197                            // same arguments but with `debug=true` so that we return the debug
198                            // info to the caller (which ought to be [`CapabilitySource::Void`]).
199                            let init_request = (get_init_request)()?;
200                            match self.router.route(init_request, true).await? {
201                                RouterResponse::<Dict>::Debug(d) => {
202                                    Ok(RouterResponse::<T>::Debug(d))
203                                }
204                                _ => {
205                                    // This shouldn't happen (we passed debug=true).
206                                    let moniker = self.not_found_error.clone().into();
207                                    Err(RoutingError::BedrockWrongCapabilityType {
208                                        expected: "RouterResponse::Debug".into(),
209                                        actual: "not RouterResponse::Debug".into(),
210                                        moniker,
211                                    }
212                                    .into())
213                                }
214                            }
215                        }
216                    }
217                }
218            }
219        }
220
221        if segments.next().is_none() {
222            // No nested lookup necessary.
223            let Some(router) =
224                self.get(root).ok().flatten().and_then(|cap| Router::<T>::try_from(cap).ok())
225            else {
226                return Router::<T>::new(ErrorRouter { not_found_error: not_found_error.into() });
227            };
228            return router;
229        }
230
231        let Some(cap) = self.get(root).ok().flatten() else {
232            return Router::<T>::new(ErrorRouter { not_found_error: not_found_error.into() });
233        };
234        let router = match cap {
235            Capability::Dictionary(d) => Router::<Dict>::new_ok(d),
236            Capability::DictionaryRouter(r) => r,
237            _ => {
238                return Router::<T>::new(ErrorRouter { not_found_error: not_found_error.into() });
239            }
240        };
241
242        let mut segments = path.iter_segments();
243        let _ = segments.next().unwrap();
244        let path = RelativePath::from(segments.collect::<Vec<_>>());
245
246        Router::<T>::new(ScopedDictRouter { router, path, not_found_error: not_found_error.into() })
247    }
248
249    fn insert_capability(
250        &self,
251        path: &impl IterablePath,
252        capability: Capability,
253    ) -> Result<(), fsandbox::CapabilityStoreError> {
254        let mut segments = path.iter_segments();
255        let mut current_name = segments.next().expect("path must be non-empty");
256        let mut current_dict = self.clone();
257        loop {
258            match segments.next() {
259                Some(next_name) => {
260                    let sub_dict = {
261                        match current_dict.get(current_name) {
262                            Ok(Some(cap)) => cap
263                                .to_dictionary()
264                                .ok_or(fsandbox::CapabilityStoreError::ItemNotFound)?,
265                            Ok(None) => {
266                                let dict = Dict::new();
267                                current_dict.insert(
268                                    current_name.into(),
269                                    Capability::Dictionary(dict.clone()),
270                                )?;
271                                dict
272                            }
273                            Err(_) => return Err(fsandbox::CapabilityStoreError::ItemNotFound),
274                        }
275                    };
276                    current_dict = sub_dict;
277
278                    current_name = next_name;
279                }
280                None => {
281                    return current_dict.insert(current_name.into(), capability);
282                }
283            }
284        }
285    }
286
287    fn remove_capability(&self, path: &impl IterablePath) {
288        let mut segments = path.iter_segments();
289        let mut current_name = segments.next().expect("path must be non-empty");
290        let mut current_dict = self.clone();
291        loop {
292            match segments.next() {
293                Some(next_name) => {
294                    let sub_dict = current_dict
295                        .get(current_name)
296                        .ok()
297                        .flatten()
298                        .and_then(|value| value.to_dictionary());
299                    if sub_dict.is_none() {
300                        // The capability doesn't exist, there's nothing to remove.
301                        return;
302                    }
303                    current_dict = sub_dict.unwrap();
304                    current_name = next_name;
305                }
306                None => {
307                    current_dict.remove(current_name);
308                    return;
309                }
310            }
311        }
312    }
313
314    async fn get_with_request<'a>(
315        &self,
316        moniker: &ExtendedMoniker,
317        path: &'a impl IterablePath,
318        request: Option<Request>,
319        debug: bool,
320    ) -> Result<Option<GenericRouterResponse>, RouterError> {
321        let mut current_dict = self.clone();
322        let num_segments = path.iter_segments().count();
323        for (next_idx, next_name) in path.iter_segments().enumerate() {
324            // Get the capability.
325            let capability = current_dict
326                .get(next_name)
327                .map_err(|_| RoutingError::BedrockNotCloneable { moniker: moniker.clone() })?;
328
329            // The capability doesn't exist.
330            let Some(capability) = capability else {
331                return Ok(None);
332            };
333
334            if next_idx < num_segments - 1 {
335                // Not at the end of the path yet, so there's more nesting. We expect to
336                // have found a [Dict], or a [Dict] router -- traverse into this [Dict].
337                let dict_request = request_with_dictionary_replacement(request.as_ref())?;
338                match capability {
339                    Capability::Dictionary(d) => {
340                        current_dict = d;
341                    }
342                    Capability::DictionaryRouter(r) => match r.route(dict_request, false).await? {
343                        RouterResponse::<Dict>::Capability(d) => {
344                            current_dict = d;
345                        }
346                        RouterResponse::<Dict>::Debug(d) => {
347                            // This shouldn't happen (we passed debug=false). Just pass it up
348                            // the chain so the caller can decide how to deal with it.
349                            return Ok(Some(GenericRouterResponse::Debug(d)));
350                        }
351                        RouterResponse::<Dict>::Unavailable => {
352                            if !debug {
353                                return Ok(Some(GenericRouterResponse::Unavailable));
354                            } else {
355                                // `debug=true` was the input to this function but the call above
356                                // to [`Router::route`] used `debug=false`. Call the router again
357                                // with the same arguments but with `debug=true` so that we return
358                                // the debug info to the caller (which ought to be
359                                // [`CapabilitySource::Void`]).
360                                let dict_request =
361                                    request_with_dictionary_replacement(request.as_ref())?;
362                                match r.route(dict_request, true).await? {
363                                    RouterResponse::<Dict>::Debug(d) => {
364                                        return Ok(Some(GenericRouterResponse::Debug(d)));
365                                    }
366                                    _ => {
367                                        // This shouldn't happen (we passed debug=true).
368                                        return Err(RoutingError::BedrockWrongCapabilityType {
369                                            expected: "RouterResponse::Debug".into(),
370                                            actual: "not RouterResponse::Debug".into(),
371                                            moniker: moniker.clone(),
372                                        }
373                                        .into());
374                                    }
375                                }
376                            }
377                        }
378                    },
379                    _ => {
380                        return Err(RoutingError::BedrockWrongCapabilityType {
381                            expected: Dict::debug_typename().into(),
382                            actual: capability.debug_typename().into(),
383                            moniker: moniker.clone(),
384                        }
385                        .into());
386                    }
387                }
388            } else {
389                // We've reached the end of our path. The last capability should have type
390                // `T` or `Router<T>`.
391                //
392                // There's a bit of repetition here because this function supports multiple router
393                // types.
394                let request = request.as_ref().map(|r| r.try_clone()).transpose()?;
395                let capability: Capability = match capability {
396                    Capability::DictionaryRouter(r) => match r.route(request, debug).await? {
397                        RouterResponse::<Dict>::Capability(c) => c.into(),
398                        RouterResponse::<Dict>::Unavailable => {
399                            return Ok(Some(GenericRouterResponse::Unavailable));
400                        }
401                        RouterResponse::<Dict>::Debug(d) => {
402                            return Ok(Some(GenericRouterResponse::Debug(d)));
403                        }
404                    },
405                    Capability::ConnectorRouter(r) => match r.route(request, debug).await? {
406                        RouterResponse::<Connector>::Capability(c) => c.into(),
407                        RouterResponse::<Connector>::Unavailable => {
408                            return Ok(Some(GenericRouterResponse::Unavailable));
409                        }
410                        RouterResponse::<Connector>::Debug(d) => {
411                            return Ok(Some(GenericRouterResponse::Debug(d)));
412                        }
413                    },
414                    Capability::DataRouter(r) => match r.route(request, debug).await? {
415                        RouterResponse::<Data>::Capability(c) => c.into(),
416                        RouterResponse::<Data>::Unavailable => {
417                            return Ok(Some(GenericRouterResponse::Unavailable));
418                        }
419                        RouterResponse::<Data>::Debug(d) => {
420                            return Ok(Some(GenericRouterResponse::Debug(d)));
421                        }
422                    },
423                    Capability::DirEntryRouter(r) => match r.route(request, debug).await? {
424                        RouterResponse::<DirEntry>::Capability(c) => c.into(),
425                        RouterResponse::<DirEntry>::Unavailable => {
426                            return Ok(Some(GenericRouterResponse::Unavailable));
427                        }
428                        RouterResponse::<DirEntry>::Debug(d) => {
429                            return Ok(Some(GenericRouterResponse::Debug(d)));
430                        }
431                    },
432                    Capability::DirConnectorRouter(r) => match r.route(request, debug).await? {
433                        RouterResponse::<DirConnector>::Capability(c) => c.into(),
434                        RouterResponse::<DirConnector>::Unavailable => {
435                            return Ok(Some(GenericRouterResponse::Unavailable));
436                        }
437                        RouterResponse::<DirConnector>::Debug(d) => {
438                            return Ok(Some(GenericRouterResponse::Debug(d)));
439                        }
440                    },
441                    other => other,
442                };
443                return Ok(Some(GenericRouterResponse::Capability(capability)));
444            }
445        }
446        unreachable!("get_with_request: All cases are handled in the loop");
447    }
448}
449
450/// Creates a clone of `request` that is identical except `"type"` is set to `dictionary`
451/// if it is not already. If `request` is `None`, `None` will be returned.
452///
453/// This is convenient for router lookups of nested paths, since all lookups except the last
454/// segment are dictionary lookups.
455pub(super) fn request_with_dictionary_replacement(
456    request: Option<&Request>,
457) -> Result<Option<Request>, RoutingError> {
458    Ok(request.as_ref().map(|r| r.try_clone()).transpose()?.map(|r| {
459        let _ = r.metadata.set_metadata(CapabilityTypeName::Dictionary);
460        r
461    }))
462}