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