routing/bedrock/
structured_dict.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::DictExt;
6use cm_types::{BorrowedName, IterablePath, Name};
7use fidl_fuchsia_component_sandbox as fsandbox;
8use sandbox::{Capability, Data, Dict};
9use std::fmt;
10use std::marker::PhantomData;
11use std::sync::LazyLock;
12
13/// This trait is implemented by types that wrap a [Dict] and wish to present an abstracted
14/// interface over the [Dict].
15///
16/// All such types are defined in this module, so this trait is private.
17///
18/// See also: [StructuredDictMap]
19trait StructuredDict: Into<Dict> + Default + Clone + fmt::Debug {
20    /// Converts from [Dict] to `Self`.
21    ///
22    /// REQUIRES: [Dict] is a valid representation of `Self`.
23    ///
24    /// IMPORTANT: The caller should know that [Dict] is a valid representation of [Self]. This
25    /// function is not guaranteed to perform any validation.
26    fn from_dict(dict: Dict) -> Self;
27}
28
29/// A collection type for mapping [Name] to [StructuredDict], using [Dict] as the underlying
30/// representation.
31///
32/// For example, this can be used to store a map of child or collection names to [ComponentInput]s
33/// (where [ComponentInput] is the type that implements [StructuredDict]).
34///
35/// Because the representation of this type is [Dict], this type itself implements
36/// [StructuredDict].
37#[derive(Clone, Debug, Default)]
38#[allow(private_bounds)]
39pub struct StructuredDictMap<T: StructuredDict> {
40    inner: Dict,
41    phantom: PhantomData<T>,
42}
43
44impl<T: StructuredDict> StructuredDict for StructuredDictMap<T> {
45    fn from_dict(dict: Dict) -> Self {
46        Self { inner: dict, phantom: Default::default() }
47    }
48}
49
50#[allow(private_bounds)]
51impl<T: StructuredDict> StructuredDictMap<T> {
52    pub fn insert(&self, key: Name, value: T) -> Result<(), fsandbox::CapabilityStoreError> {
53        let dict: Dict = value.into();
54        self.inner.insert(key, dict.into())
55    }
56
57    pub fn get(&self, key: &BorrowedName) -> Option<T> {
58        self.inner.get(key).expect("structured map entry must be cloneable").map(|cap| {
59            let Capability::Dictionary(dict) = cap else {
60                unreachable!("structured map entry must be a dict: {cap:?}");
61            };
62            T::from_dict(dict)
63        })
64    }
65
66    pub fn remove(&self, key: &Name) -> Option<T> {
67        self.inner.remove(&*key).map(|cap| {
68            let Capability::Dictionary(dict) = cap else {
69                unreachable!("structured map entry must be a dict: {cap:?}");
70            };
71            T::from_dict(dict)
72        })
73    }
74
75    pub fn append(&self, other: &Self) -> Result<(), ()> {
76        self.inner.append(&other.inner)
77    }
78
79    pub fn enumerate(&self) -> impl Iterator<Item = (Name, T)> {
80        self.inner.enumerate().map(|(key, capability_res)| match capability_res {
81            Ok(Capability::Dictionary(dict)) => (key, T::from_dict(dict)),
82            Ok(cap) => unreachable!("structured map entry must be a dict: {cap:?}"),
83            Err(_) => panic!("structured map entry must be cloneable"),
84        })
85    }
86}
87
88impl<T: StructuredDict> From<StructuredDictMap<T>> for Dict {
89    fn from(m: StructuredDictMap<T>) -> Self {
90        m.inner
91    }
92}
93
94// Dictionary keys for different kinds of sandboxes.
95/// Dictionary of capabilities from or to the parent.
96static PARENT: LazyLock<Name> = LazyLock::new(|| "parent".parse().unwrap());
97
98/// Dictionary of capabilities from a component's environment.
99static ENVIRONMENT: LazyLock<Name> = LazyLock::new(|| "environment".parse().unwrap());
100
101/// Dictionary of debug capabilities in a component's environment.
102static DEBUG: LazyLock<Name> = LazyLock::new(|| "debug".parse().unwrap());
103
104/// Dictionary of runner capabilities in a component's environment.
105static RUNNERS: LazyLock<Name> = LazyLock::new(|| "runners".parse().unwrap());
106
107/// Dictionary of resolver capabilities in a component's environment.
108static RESOLVERS: LazyLock<Name> = LazyLock::new(|| "resolvers".parse().unwrap());
109
110/// Dictionary of capabilities the component exposes to the framework.
111static FRAMEWORK: LazyLock<Name> = LazyLock::new(|| "framework".parse().unwrap());
112
113/// The stop timeout for a component environment.
114static STOP_TIMEOUT: LazyLock<Name> = LazyLock::new(|| "stop_timeout".parse().unwrap());
115
116/// The name of a component environment.
117static NAME: LazyLock<Name> = LazyLock::new(|| "name".parse().unwrap());
118
119/// Contains the capabilities component receives from its parent and environment. Stored as a
120/// [Dict] containing two nested [Dict]s for the parent and environment.
121#[derive(Clone, Debug)]
122pub struct ComponentInput(Dict);
123
124impl Default for ComponentInput {
125    fn default() -> Self {
126        Self::new(ComponentEnvironment::new())
127    }
128}
129
130impl StructuredDict for ComponentInput {
131    fn from_dict(dict: Dict) -> Self {
132        Self(dict)
133    }
134}
135
136impl ComponentInput {
137    pub fn new(environment: ComponentEnvironment) -> Self {
138        let dict = Dict::new();
139        if let Some(_) = environment.0.keys().next() {
140            dict.insert(ENVIRONMENT.clone(), Dict::from(environment).into()).unwrap();
141        }
142        Self(dict)
143    }
144
145    /// Creates a new ComponentInput with entries cloned from this ComponentInput.
146    ///
147    /// This is a shallow copy. Values are cloned, not copied, so are new references to the same
148    /// underlying data.
149    pub fn shallow_copy(&self) -> Result<Self, ()> {
150        // Note: We call [Dict::copy] on the nested [Dict]s, not the root [Dict], because
151        // [Dict::copy] only goes one level deep and we want to copy the contents of the
152        // inner sandboxes.
153        let dest = Dict::new();
154        shallow_copy(&self.0, &dest, &*PARENT)?;
155        shallow_copy(&self.0, &dest, &*ENVIRONMENT)?;
156        Ok(Self(dest))
157    }
158
159    /// Returns the sub-dictionary containing capabilities routed by the component's parent.
160    pub fn capabilities(&self) -> Dict {
161        get_or_insert(&self.0, &*PARENT)
162    }
163
164    /// Returns the sub-dictionary containing capabilities routed by the component's environment.
165    pub fn environment(&self) -> ComponentEnvironment {
166        ComponentEnvironment(get_or_insert(&self.0, &*ENVIRONMENT))
167    }
168
169    pub fn insert_capability(
170        &self,
171        path: &impl IterablePath,
172        capability: Capability,
173    ) -> Result<(), fsandbox::CapabilityStoreError> {
174        self.capabilities().insert_capability(path, capability.into())
175    }
176}
177
178impl From<ComponentInput> for Dict {
179    fn from(e: ComponentInput) -> Self {
180        e.0
181    }
182}
183
184/// The capabilities a component has in its environment. Stored as a [Dict] containing a nested
185/// [Dict] holding the environment's debug capabilities.
186#[derive(Clone, Debug)]
187pub struct ComponentEnvironment(Dict);
188
189impl Default for ComponentEnvironment {
190    fn default() -> Self {
191        Self(Dict::new())
192    }
193}
194
195impl StructuredDict for ComponentEnvironment {
196    fn from_dict(dict: Dict) -> Self {
197        Self(dict)
198    }
199}
200
201impl ComponentEnvironment {
202    pub fn new() -> Self {
203        Self::default()
204    }
205
206    /// Capabilities listed in the `debug_capabilities` portion of its environment.
207    pub fn debug(&self) -> Dict {
208        get_or_insert(&self.0, &*DEBUG)
209    }
210
211    /// Capabilities listed in the `runners` portion of its environment.
212    pub fn runners(&self) -> Dict {
213        get_or_insert(&self.0, &*RUNNERS)
214    }
215
216    /// Capabilities listed in the `resolvers` portion of its environment.
217    pub fn resolvers(&self) -> Dict {
218        get_or_insert(&self.0, &*RESOLVERS)
219    }
220
221    /// Sets the stop timeout (in milliseconds) for this environment.
222    pub fn set_stop_timeout(&self, timeout: i64) {
223        let _ = self.0.insert(STOP_TIMEOUT.clone(), Data::Int64(timeout).into());
224    }
225
226    /// Returns the stop timeout (in milliseconds) for this environment.
227    pub fn stop_timeout(&self) -> Option<i64> {
228        let Ok(Some(Capability::Data(Data::Int64(timeout)))) = self.0.get(&*STOP_TIMEOUT) else {
229            return None;
230        };
231        Some(timeout)
232    }
233
234    /// Sets the name for this environment.
235    pub fn set_name(&self, name: &Name) {
236        let _ = self.0.insert(NAME.clone(), Data::String(name.as_str().into()).into());
237    }
238
239    /// Returns the name for this environment.
240    pub fn name(&self) -> Option<Name> {
241        let Ok(Some(Capability::Data(Data::String(name)))) = self.0.get(&*NAME) else {
242            return None;
243        };
244        Some(Name::new(name).unwrap())
245    }
246
247    pub fn shallow_copy(&self) -> Result<Self, ()> {
248        // Note: We call [Dict::shallow_copy] on the nested [Dict]s, not the root [Dict], because
249        // [Dict::shallow_copy] only goes one level deep and we want to copy the contents of the
250        // inner sandboxes.
251        let dest = Dict::new();
252        shallow_copy(&self.0, &dest, &*DEBUG)?;
253        shallow_copy(&self.0, &dest, &*RUNNERS)?;
254        shallow_copy(&self.0, &dest, &*RESOLVERS)?;
255        Ok(Self(dest))
256    }
257}
258
259impl From<ComponentEnvironment> for Dict {
260    fn from(e: ComponentEnvironment) -> Self {
261        e.0
262    }
263}
264
265/// Contains the capabilities a component makes available to its parent or the framework. Stored as
266/// a [Dict] containing two nested [Dict]s for the capabilities made available to the parent and to
267/// the framework.
268#[derive(Clone, Debug)]
269pub struct ComponentOutput(Dict);
270
271impl Default for ComponentOutput {
272    fn default() -> Self {
273        Self::new()
274    }
275}
276
277impl StructuredDict for ComponentOutput {
278    fn from_dict(dict: Dict) -> Self {
279        Self(dict)
280    }
281}
282
283impl ComponentOutput {
284    pub fn new() -> Self {
285        Self(Dict::new())
286    }
287
288    /// Creates a new ComponentOutput with entries cloned from this ComponentOutput.
289    ///
290    /// This is a shallow copy. Values are cloned, not copied, so are new references to the same
291    /// underlying data.
292    pub fn shallow_copy(&self) -> Result<Self, ()> {
293        // Note: We call [Dict::copy] on the nested [Dict]s, not the root [Dict], because
294        // [Dict::copy] only goes one level deep and we want to copy the contents of the
295        // inner sandboxes.
296        let dest = Dict::new();
297        shallow_copy(&self.0, &dest, &*PARENT)?;
298        shallow_copy(&self.0, &dest, &*FRAMEWORK)?;
299        Ok(Self(dest))
300    }
301
302    /// Returns the sub-dictionary containing capabilities routed to the component's parent.
303    /// framework. Lazily adds the dictionary if it does not exist yet.
304    pub fn capabilities(&self) -> Dict {
305        get_or_insert(&self.0, &*PARENT)
306    }
307
308    /// Returns the sub-dictionary containing capabilities exposed by the component to the
309    /// framework. Lazily adds the dictionary if it does not exist yet.
310    pub fn framework(&self) -> Dict {
311        get_or_insert(&self.0, &*FRAMEWORK)
312    }
313}
314
315impl From<ComponentOutput> for Dict {
316    fn from(e: ComponentOutput) -> Self {
317        e.0
318    }
319}
320
321fn shallow_copy(src: &Dict, dest: &Dict, key: &Name) -> Result<(), ()> {
322    if let Some(d) = src.get(key).expect("must be cloneable") {
323        let Capability::Dictionary(d) = d else {
324            unreachable!("{key} entry must be a dict: {d:?}");
325        };
326        dest.insert(key.clone(), d.shallow_copy()?.into()).ok();
327    }
328    Ok(())
329}
330
331fn get_or_insert(this: &Dict, key: &Name) -> Dict {
332    let cap = this
333        .get_or_insert(&key, || Capability::Dictionary(Dict::new()))
334        .expect("capabilities must be cloneable");
335    let Capability::Dictionary(dict) = cap else {
336        unreachable!("{key} entry must be a dict: {cap:?}");
337    };
338    dict
339}
340
341#[cfg(test)]
342mod tests {
343    use super::*;
344    use assert_matches::assert_matches;
345    use sandbox::DictKey;
346
347    impl StructuredDict for Dict {
348        fn from_dict(dict: Dict) -> Self {
349            dict
350        }
351    }
352
353    #[fuchsia::test]
354    async fn structured_dict_map() {
355        let dict1 = {
356            let dict = Dict::new();
357            dict.insert("a".parse().unwrap(), Dict::new().into())
358                .expect("dict entry already exists");
359            dict
360        };
361        let dict2 = {
362            let dict = Dict::new();
363            dict.insert("b".parse().unwrap(), Dict::new().into())
364                .expect("dict entry already exists");
365            dict
366        };
367        let dict2_alt = {
368            let dict = Dict::new();
369            dict.insert("c".parse().unwrap(), Dict::new().into())
370                .expect("dict entry already exists");
371            dict
372        };
373        let name1 = Name::new("1").unwrap();
374        let name2 = Name::new("2").unwrap();
375
376        let map: StructuredDictMap<Dict> = Default::default();
377        assert_matches!(map.get(&name1), None);
378        assert!(map.insert(name1.clone(), dict1).is_ok());
379        let d = map.get(&name1).unwrap();
380        let key = DictKey::new("a").unwrap();
381        assert_matches!(d.get(&key), Ok(Some(_)));
382
383        assert!(map.insert(name2.clone(), dict2).is_ok());
384        let d = map.remove(&name2).unwrap();
385        assert_matches!(map.remove(&name2), None);
386        let key = DictKey::new("b").unwrap();
387        assert_matches!(d.get(&key), Ok(Some(_)));
388
389        assert!(map.insert(name2.clone(), dict2_alt).is_ok());
390        let d = map.get(&name2).unwrap();
391        let key = DictKey::new("c").unwrap();
392        assert_matches!(d.get(&key), Ok(Some(_)));
393    }
394}