Skip to main content

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