| // Copyright 2024 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use { |
| crate::sandbox_util::DictExt, |
| cm_types::{IterablePath, Name}, |
| fidl_fuchsia_component_sandbox as fsandbox, |
| lazy_static::lazy_static, |
| sandbox::{Capability, Dict}, |
| std::{fmt, marker::PhantomData}, |
| }; |
| |
| /// This trait is implemented by types that wrap a [Dict] and wish to present an abstracted |
| /// interface over the [Dict]. |
| /// |
| /// All such types are defined in this module, so this trait is private. |
| /// |
| /// See also: [StructuredDictMap] |
| trait StructuredDict: Into<Dict> + Default + Clone + fmt::Debug { |
| /// Converts from [Dict] to `Self`. |
| /// |
| /// REQUIRES: [Dict] is a valid representation of `Self`. |
| /// |
| /// IMPORTANT: The caller should know that [Dict] is a valid representation of [Self]. This |
| /// function is not guaranteed to perform any validation. |
| fn from_dict(dict: Dict) -> Self; |
| } |
| |
| /// A collection type for mapping [Name] to [StructuredDict], using [Dict] as the underlying |
| /// representation. |
| /// |
| /// For example, this can be used to store a map of child or collection names to [ComponentInput]s |
| /// (where [ComponentInput] is the type that implements [StructuredDict]). |
| /// |
| /// Because the representation of this type is [Dict], this type itself implements |
| /// [StructuredDict]. |
| #[derive(Clone, Debug, Default)] |
| #[allow(private_bounds)] |
| pub struct StructuredDictMap<T: StructuredDict> { |
| inner: Dict, |
| phantom: PhantomData<T>, |
| } |
| |
| impl<T: StructuredDict> StructuredDict for StructuredDictMap<T> { |
| fn from_dict(dict: Dict) -> Self { |
| Self { inner: dict, phantom: Default::default() } |
| } |
| } |
| |
| #[allow(private_bounds)] |
| impl<T: StructuredDict> StructuredDictMap<T> { |
| pub fn insert(&mut self, key: Name, value: T) -> Result<(), fsandbox::DictionaryError> { |
| let dict: Dict = value.into(); |
| self.inner.insert(key, dict.into()) |
| } |
| |
| pub fn get(&self, key: &Name) -> Option<T> { |
| self.inner.get(key).map(|cap| { |
| let Capability::Dictionary(dict) = cap else { |
| unreachable!("structured map entry must be a dict: {cap:?}"); |
| }; |
| T::from_dict(dict.clone()) |
| }) |
| } |
| |
| pub fn remove(&mut self, key: &Name) -> Option<T> { |
| self.inner.remove(key).map(|cap| { |
| let Capability::Dictionary(dict) = cap else { |
| unreachable!("structured map entry must be a dict: {cap:?}"); |
| }; |
| T::from_dict(dict.clone()) |
| }) |
| } |
| } |
| |
| impl<T: StructuredDict> From<StructuredDictMap<T>> for Dict { |
| fn from(m: StructuredDictMap<T>) -> Self { |
| m.inner |
| } |
| } |
| |
| // Dictionary keys for different kinds of sandboxes. |
| lazy_static! { |
| /// Dictionary of capabilities from the parent. |
| static ref PARENT: Name = "parent".parse().unwrap(); |
| |
| /// Dictionary of capabilities from a component's environment. |
| static ref ENVIRONMENT: Name = "environment".parse().unwrap(); |
| |
| /// Dictionary of debug capabilities in a component's environment. |
| static ref DEBUG: Name = "debug".parse().unwrap(); |
| } |
| |
| /// Contains the capabilities component receives from its parent and environment. Stored as a |
| /// [Dict] containing two nested [Dict]s for the parent and environment. |
| #[derive(Clone, Debug)] |
| pub struct ComponentInput(Dict); |
| |
| impl Default for ComponentInput { |
| fn default() -> Self { |
| Self::new(ComponentEnvironment::new()) |
| } |
| } |
| |
| impl StructuredDict for ComponentInput { |
| fn from_dict(dict: Dict) -> Self { |
| Self(dict) |
| } |
| } |
| |
| impl ComponentInput { |
| pub fn new(environment: ComponentEnvironment) -> Self { |
| let mut dict = Dict::new(); |
| dict.insert(PARENT.clone(), Dict::new().into()).ok(); |
| dict.insert(ENVIRONMENT.clone(), Dict::from(environment).into()).ok(); |
| Self(dict) |
| } |
| |
| /// Creates a new ComponentInput with entries cloned from this ComponentInput. |
| /// |
| /// This is a shallow copy. Values are cloned, not copied, so are new references to the same |
| /// underlying data. |
| pub fn shallow_copy(&self) -> Self { |
| // Note: We call [Dict::copy] on the nested [Dict]s, not the root [Dict], because |
| // [Dict::copy] only goes one level deep and we want to copy the contents of the |
| // inner sandboxes. |
| let mut dict = Dict::new(); |
| dict.insert(PARENT.clone(), self.capabilities().shallow_copy().into()).ok(); |
| dict.insert(ENVIRONMENT.clone(), Dict::from(self.environment()).shallow_copy().into()).ok(); |
| Self(dict) |
| } |
| |
| /// Returns the sub-dictionary containing capabilities routed by the component's parent. |
| pub fn capabilities(&self) -> Dict { |
| let cap = self.0.get(&*PARENT).unwrap(); |
| let Capability::Dictionary(dict) = cap else { |
| unreachable!("parent entry must be a dict: {cap:?}"); |
| }; |
| dict |
| } |
| |
| /// Returns the sub-dictionary containing capabilities routed by the component's environment. |
| pub fn environment(&self) -> ComponentEnvironment { |
| let cap = self.0.get(&*ENVIRONMENT).unwrap(); |
| let Capability::Dictionary(dict) = cap else { |
| unreachable!("environment entry must be a dict: {cap:?}"); |
| }; |
| ComponentEnvironment(dict) |
| } |
| |
| pub fn insert_capability( |
| &self, |
| path: &impl IterablePath, |
| capability: Capability, |
| ) -> Result<(), fsandbox::DictionaryError> { |
| self.capabilities().insert_capability(path, capability.into()) |
| } |
| } |
| |
| impl From<ComponentInput> for Dict { |
| fn from(e: ComponentInput) -> Self { |
| e.0 |
| } |
| } |
| |
| /// The capabilities a component has in its environment. Stored as a [Dict] containing a nested |
| /// [Dict] holding the environment's debug capabilities. |
| #[derive(Clone, Debug)] |
| pub struct ComponentEnvironment(Dict); |
| |
| impl Default for ComponentEnvironment { |
| fn default() -> Self { |
| let mut dict = Dict::new(); |
| dict.insert(DEBUG.clone(), Dict::new().into()).ok(); |
| Self(dict) |
| } |
| } |
| |
| impl StructuredDict for ComponentEnvironment { |
| fn from_dict(dict: Dict) -> Self { |
| Self(dict) |
| } |
| } |
| |
| impl ComponentEnvironment { |
| pub fn new() -> Self { |
| Self::default() |
| } |
| |
| /// Capabilities listed in the `debug_capabilities` portion of its environment. |
| pub fn debug(&self) -> Dict { |
| let cap = self.0.get(&*DEBUG).unwrap(); |
| let Capability::Dictionary(dict) = cap else { |
| unreachable!("debug entry must be a dict: {cap:?}"); |
| }; |
| dict.clone() |
| } |
| |
| pub fn shallow_copy(&self) -> Self { |
| // Note: We call [Dict::copy] on the nested [Dict]s, not the root [Dict], because |
| // [Dict::copy] only goes one level deep and we want to copy the contents of the |
| // inner sandboxes. |
| let mut dict = Dict::new(); |
| dict.insert(DEBUG.clone(), self.debug().shallow_copy().into()).ok(); |
| Self(dict) |
| } |
| } |
| |
| impl From<ComponentEnvironment> for Dict { |
| fn from(e: ComponentEnvironment) -> Self { |
| e.0 |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use assert_matches::assert_matches; |
| use sandbox::DictKey; |
| |
| impl StructuredDict for Dict { |
| fn from_dict(dict: Dict) -> Self { |
| dict |
| } |
| } |
| |
| #[fuchsia::test] |
| async fn structured_dict_map() { |
| let dict1 = { |
| let mut dict = Dict::new(); |
| dict.insert("a".parse().unwrap(), Dict::new().into()) |
| .expect("dict entry already exists"); |
| dict |
| }; |
| let dict2 = { |
| let mut dict = Dict::new(); |
| dict.insert("b".parse().unwrap(), Dict::new().into()) |
| .expect("dict entry already exists"); |
| dict |
| }; |
| let dict2_alt = { |
| let mut dict = Dict::new(); |
| dict.insert("c".parse().unwrap(), Dict::new().into()) |
| .expect("dict entry already exists"); |
| dict |
| }; |
| let name1 = Name::new("1").unwrap(); |
| let name2 = Name::new("2").unwrap(); |
| |
| let mut map: StructuredDictMap<Dict> = Default::default(); |
| assert_matches!(map.get(&name1), None); |
| assert!(map.insert(name1.clone(), dict1).is_ok()); |
| let d = map.get(&name1).unwrap(); |
| let key = DictKey::new("a").unwrap(); |
| assert!(d.get(&key).is_some()); |
| |
| assert!(map.insert(name2.clone(), dict2).is_ok()); |
| let d = map.remove(&name2).unwrap(); |
| assert_matches!(map.remove(&name2), None); |
| let key = DictKey::new("b").unwrap(); |
| assert!(d.get(&key).is_some()); |
| |
| assert!(map.insert(name2.clone(), dict2_alt).is_ok()); |
| let d = map.get(&name2).unwrap(); |
| let key = DictKey::new("c").unwrap(); |
| assert!(d.get(&key).is_some()); |
| } |
| } |