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(Capability::Dictionary(dict))) => dict,
267                            Ok(Some(Capability::DictionaryRouter(preexisting_router))) => {
268                                let mut path = vec![next_name];
269                                while let Some(name) = segments.next() {
270                                    path.push(name);
271                                }
272                                let path = RelativePath::from(path);
273                                let new_router = Router::new(AdditiveDictionaryRouter {
274                                    preexisting_router,
275                                    path,
276                                    capability,
277                                });
278
279                                // Replace the entry in current_dict.
280                                current_dict.remove(current_name).unwrap();
281                                current_dict
282                                    .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 => other,
471                };
472                return Ok(Some(GenericRouterResponse::Capability(capability)));
473            }
474        }
475        unreachable!("get_with_request: All cases are handled in the loop");
476    }
477}
478
479/// Creates a clone of `request` that is identical except `"type"` is set to `dictionary`
480/// if it is not already. If `request` is `None`, `None` will be returned.
481///
482/// This is convenient for router lookups of nested paths, since all lookups except the last
483/// segment are dictionary lookups.
484pub(super) fn request_with_dictionary_replacement(
485    request: Option<&Request>,
486) -> Result<Option<Request>, RoutingError> {
487    Ok(request.as_ref().map(|r| r.try_clone()).transpose()?.map(|r| {
488        let _ = r.metadata.set_metadata(CapabilityTypeName::Dictionary);
489        r
490    }))
491}
492
493struct AdditiveDictionaryRouter {
494    preexisting_router: Router<Dict>,
495    path: RelativePath,
496    capability: Capability,
497}
498
499#[async_trait]
500impl Routable<Dict> for AdditiveDictionaryRouter {
501    async fn route(
502        &self,
503        request: Option<Request>,
504        debug: bool,
505        target: WeakInstanceToken,
506    ) -> Result<RouterResponse<Dict>, RouterError> {
507        let dictionary = match self.preexisting_router.route(request, debug, target).await {
508            Ok(RouterResponse::<Dict>::Capability(dictionary)) => {
509                dictionary.shallow_copy().unwrap()
510            }
511            other_response => return other_response,
512        };
513        let _ = dictionary.insert_capability(&self.path, self.capability.try_clone().unwrap());
514        Ok(RouterResponse::Capability(dictionary))
515    }
516}