sandbox/
dict.rs

1// Copyright 2023 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::{Capability, CapabilityBound};
6use derivative::Derivative;
7use fidl_fuchsia_component_sandbox as fsandbox;
8use std::borrow::Borrow;
9use std::collections::BTreeMap;
10use std::collections::btree_map::Entry;
11use std::sync::{Arc, Mutex, MutexGuard};
12
13#[cfg(target_os = "fuchsia")]
14use fuchsia_async as fasync;
15
16pub type Key = cm_types::Name;
17pub type BorrowedKey = cm_types::BorrowedName;
18
19/// A capability that represents a dictionary of capabilities.
20#[derive(Debug, Clone)]
21pub struct Dict {
22    inner: Arc<Mutex<DictInner>>,
23}
24
25#[derive(Derivative)]
26#[derivative(Debug)]
27pub(crate) struct DictInner {
28    /// The contents of the [Dict].
29    pub(crate) entries: HybridMap,
30
31    /// When an external request tries to access a non-existent entry, this closure will be invoked
32    /// with the name of the entry.
33    #[derivative(Debug = "ignore")]
34    // Currently this is only used on target, but it's compatible with host.
35    #[allow(dead_code)]
36    pub(crate) not_found: Option<Box<dyn Fn(&str) -> () + 'static + Send + Sync>>,
37
38    /// Tasks that serve the Dictionary protocol. This is an `Option` because dictionaries are not
39    /// necessarily used in async contexts but a TaskGroup will fail construction if there is no
40    /// async executor.
41    #[cfg(target_os = "fuchsia")]
42    #[derivative(Debug = "ignore")]
43    task_group: Option<fasync::TaskGroup>,
44
45    /// Functions that will be invoked whenever the contents of this dictionary changes.
46    #[derivative(Debug = "ignore")]
47    update_notifiers: UpdateNotifiers,
48}
49
50impl CapabilityBound for Dict {
51    fn debug_typename() -> &'static str {
52        "Dictionary"
53    }
54}
55
56impl Drop for DictInner {
57    fn drop(&mut self) {
58        // When this dictionary doesn't exist anymore, then neither do its entries (or at least
59        // these references of them). Notify anyone listening that all of the entries have been
60        // removed.
61        let entries = std::mem::replace(&mut self.entries, HybridMap::default());
62        for (key, _) in entries.into_iter() {
63            self.update_notifiers.update(EntryUpdate::Remove(&key))
64        }
65    }
66}
67
68/// Represents a change to a dictionary, where an entry is either added or removed.
69#[derive(Debug, Copy, Clone)]
70pub enum EntryUpdate<'a> {
71    Add(&'a BorrowedKey, &'a Capability),
72    Remove(&'a BorrowedKey),
73    Idle,
74}
75
76/// Represents whether an update notifier should be retained and thus continue to be called for
77/// future updates, or if it should be dropped and no longer be called.
78pub enum UpdateNotifierRetention {
79    Retain,
80    Drop_,
81}
82
83/// A function that will be called when the contents of a dictionary changes. Note that this
84/// function will be called while the internal dictionary structure is locked, so it must not
85/// interact with the dictionary on which it is registered. It shouldn't even hold a strong
86/// reference to the dictionary it's registered on, as that'll create a cycle and make LSAN sad.
87pub type UpdateNotifierFn =
88    Box<dyn for<'a> FnMut(EntryUpdate<'a>) -> UpdateNotifierRetention + Sync + Send>;
89
90impl Default for Dict {
91    fn default() -> Self {
92        Self {
93            inner: Arc::new(Mutex::new(DictInner {
94                entries: HybridMap::default(),
95                not_found: None,
96                #[cfg(target_os = "fuchsia")]
97                task_group: None,
98                update_notifiers: UpdateNotifiers::default(),
99            })),
100        }
101    }
102}
103
104impl Dict {
105    /// Creates an empty dictionary.
106    pub fn new() -> Self {
107        Self::default()
108    }
109
110    /// Creates an empty dictionary. When an external request tries to access a non-existent entry,
111    /// the name of the entry will be sent using `not_found`.
112    pub fn new_with_not_found(not_found: impl Fn(&str) -> () + 'static + Send + Sync) -> Self {
113        Self {
114            inner: Arc::new(Mutex::new(DictInner {
115                entries: HybridMap::default(),
116                not_found: Some(Box::new(not_found)),
117                #[cfg(target_os = "fuchsia")]
118                task_group: None,
119                update_notifiers: UpdateNotifiers::default(),
120            })),
121        }
122    }
123
124    pub(crate) fn lock(&self) -> MutexGuard<'_, DictInner> {
125        self.inner.lock().unwrap()
126    }
127
128    /// Registers a new update notifier function with this dictionary. The function will be called
129    /// whenever an entry is added to or removed from this dictionary. The function will be
130    /// immediately called with an `EntryUpdate::Add` value for any entries already in this
131    /// dictionary.
132    ///
133    /// Note that this function will be called while the internal dictionary structure is locked,
134    /// so it must not interact with the dictionary on which it is registered. It shouldn't even
135    /// hold a strong reference to the dictionary it's registered on, as that'll create a cycle and
136    /// make LSAN sad.
137    pub fn register_update_notifier(&self, mut notifier_fn: UpdateNotifierFn) {
138        let mut guard = self.lock();
139        for (key, value) in guard.entries.iter() {
140            if let UpdateNotifierRetention::Drop_ = (notifier_fn)(EntryUpdate::Add(key, value)) {
141                // The notifier has signaled that it doesn't want to process any more updates.
142                return;
143            }
144        }
145        if let UpdateNotifierRetention::Retain = (notifier_fn)(EntryUpdate::Idle) {
146            guard.update_notifiers.0.push(notifier_fn);
147        }
148    }
149
150    /// Inserts an entry, mapping `key` to `capability`. If an entry already exists at `key`, a
151    /// `fsandbox::DictionaryError::AlreadyExists` will be returned.
152    pub fn insert(
153        &self,
154        key: Key,
155        capability: Capability,
156    ) -> Result<(), fsandbox::CapabilityStoreError> {
157        let DictInner { entries, update_notifiers, .. } = &mut *self.lock();
158        entries.insert(key, capability, update_notifiers)
159    }
160
161    pub fn append(&self, other: &Dict) -> Result<(), ()> {
162        let DictInner { entries, update_notifiers, .. } = &mut *self.lock();
163        let other = other.lock();
164        entries.append(&other.entries, update_notifiers)
165    }
166
167    /// Returns a clone of the capability associated with `key`. If there is no entry for `key`,
168    /// `None` is returned.
169    ///
170    /// If the value could not be cloned, returns an error.
171    pub fn get<Q: ?Sized>(&self, key: &Q) -> Result<Option<Capability>, ()>
172    where
173        Key: Borrow<Q> + Ord,
174        Q: Ord,
175    {
176        self.lock().entries.get(key)
177    }
178
179    /// Returns a clone of the capability associated with `key`, or populates it with `default` if
180    /// it is not present.
181    ///
182    /// If the value could not be cloned, returns an error.
183    pub fn get_or_insert(
184        &self,
185        key: &Key,
186        default: impl FnOnce() -> Capability,
187    ) -> Result<Capability, ()> {
188        let DictInner { entries, update_notifiers, .. } = &mut *self.lock();
189        match entries.get(key)? {
190            Some(v) => Ok(v),
191            None => {
192                let v = (default)();
193                entries.insert(key.clone(), v.try_clone()?, update_notifiers).unwrap();
194                Ok(v)
195            }
196        }
197    }
198
199    /// Removes `key` from the entries, returning the capability at `key` if the key was already in
200    /// the entries.
201    pub fn remove(&self, key: &BorrowedKey) -> Option<Capability> {
202        let DictInner { entries, update_notifiers, .. } = &mut *self.lock();
203        entries.remove(key, update_notifiers)
204    }
205
206    /// Returns an iterator over a clone of the entries, sorted by key.
207    ///
208    /// If a capability is not cloneable, an error returned for the value.
209    pub fn enumerate(&self) -> impl Iterator<Item = (Key, Result<Capability, ()>)> + use<> {
210        self.lock()
211            .entries
212            .iter()
213            .map(|(k, v)| (k.clone(), v.try_clone()))
214            .collect::<Vec<_>>()
215            .into_iter()
216    }
217
218    /// Returns an iterator over the keys, in sorted order.
219    pub fn keys(&self) -> impl Iterator<Item = Key> + use<> {
220        self.lock().entries.iter().map(|(k, _)| k.clone()).collect::<Vec<_>>().into_iter()
221    }
222
223    /// Removes all entries from the Dict and returns them as an iterator.
224    pub fn drain(&self) -> impl Iterator<Item = (Key, Capability)> + use<> {
225        let DictInner { entries, update_notifiers, .. } = &mut *self.lock();
226        let entries = std::mem::replace(entries, HybridMap::default());
227        for (key, _) in entries.iter() {
228            update_notifiers.update(EntryUpdate::Remove(&key))
229        }
230        entries.into_iter()
231    }
232
233    /// Creates a new Dict with entries cloned from this Dict.
234    ///
235    /// This is a shallow copy. Values are cloned, not copied, so are new references to the same
236    /// underlying data.
237    ///
238    /// If any value in the dictionary could not be cloned, returns an error.
239    pub fn shallow_copy(&self) -> Result<Self, ()> {
240        let copy = Self::new();
241        {
242            let DictInner { entries, .. } = &*self.lock();
243            let mut copy = copy.lock();
244            copy.entries = entries.shallow_copy()?;
245        }
246        Ok(copy)
247    }
248
249    /// Sends the name of an entry to the not found handler.
250    // Currently this is only used on target, but it's compatible with host.
251    #[allow(dead_code)]
252    pub(crate) fn not_found(&self, entry: &str) {
253        if let Some(not_found_handler) = &self.lock().not_found {
254            not_found_handler(entry);
255        }
256    }
257}
258
259impl DictInner {
260    #[cfg(target_os = "fuchsia")]
261    pub(crate) fn tasks(&mut self) -> &mut fasync::TaskGroup {
262        self.task_group.get_or_insert_with(|| fasync::TaskGroup::new())
263    }
264}
265
266pub(crate) const HYBRID_SWITCH_INSERTION_LEN: usize = 11;
267pub(crate) const HYBRID_SWITCH_REMOVAL_LEN: usize = 5;
268
269/// A map collection whose representation is a `Vec` for small `N` and a `BTreeMap` for larger `N`,
270/// where the threshold is defined by the constant `HYBRID_SWITCH_INSERTION_LEN` for insertion and
271/// `HYBRID_SWITCH_REMOVAL_LEN` for removal. This is a more space efficient representation for
272/// small `N` than a `BTreeMap`, which has a big impact because component_manager creates lots of
273/// `Dict` objects for component sandboxes.
274///
275/// Details: Rust's `BTreeMap` implementation uses a `B` value of 6, which means each node in the
276/// `BTreeMap` reserves space for 11 entries. Inserting 1 entry will allocate a node with space for
277/// 11 entries. Each entry is 48 bytes, 48 * 11 + some metadata = 544 bytes. Rounding up to the
278/// nearest scudo bucket size means each node consumes 656 bytes of memory.
279///
280/// A BTreeMap with only 2 entries uses 656 bytes when 112 would be sufficient (48 * 2 = 96 fits in
281/// the 112 byte scudo bucket).
282#[derive(Debug)]
283pub(crate) enum HybridMap {
284    Vec(Vec<(Key, Capability)>),
285    Map(BTreeMap<Key, Capability>),
286}
287
288impl Default for HybridMap {
289    fn default() -> Self {
290        Self::Vec(Vec::default())
291    }
292}
293
294impl HybridMap {
295    pub fn get<Q: ?Sized>(&self, key: &Q) -> Result<Option<Capability>, ()>
296    where
297        Key: Borrow<Q> + Ord,
298        Q: Ord,
299    {
300        match self {
301            Self::Vec(vec) => match Self::sorted_vec_index_of(vec, key) {
302                Ok(index) => Ok(Some(vec[index].1.try_clone()?)),
303                Err(_) => Ok(None),
304            },
305            Self::Map(map) => match map.get(key) {
306                Some(capability) => Ok(Some(capability.try_clone()?)),
307                None => Ok(None),
308            },
309        }
310    }
311
312    pub fn insert(
313        &mut self,
314        key: Key,
315        capability: Capability,
316        update_notifiers: &mut UpdateNotifiers,
317    ) -> Result<(), fsandbox::CapabilityStoreError> {
318        match self {
319            Self::Vec(vec) => match Self::sorted_vec_index_of(vec, &key) {
320                Ok(index) => {
321                    update_notifiers.update(EntryUpdate::Remove(&key));
322                    update_notifiers.update(EntryUpdate::Add(&key, &capability));
323                    vec[index].1 = capability;
324                    Err(fsandbox::CapabilityStoreError::ItemAlreadyExists)
325                }
326                Err(index) => {
327                    update_notifiers.update(EntryUpdate::Add(&key, &capability));
328                    if vec.len() + 1 >= HYBRID_SWITCH_INSERTION_LEN {
329                        self.switch_to_map();
330                        let Self::Map(map) = self else { unreachable!() };
331                        map.insert(key, capability);
332                        Ok(())
333                    } else {
334                        vec.reserve_exact(1);
335                        vec.insert(index, (key, capability));
336                        Ok(())
337                    }
338                }
339            },
340            Self::Map(map) => match map.entry(key.clone()) {
341                Entry::Occupied(mut occupied) => {
342                    update_notifiers.update(EntryUpdate::Remove(&key));
343                    update_notifiers.update(EntryUpdate::Add(&key, &capability));
344                    occupied.insert(capability);
345                    Err(fsandbox::CapabilityStoreError::ItemAlreadyExists)
346                }
347                Entry::Vacant(vacant) => {
348                    update_notifiers.update(EntryUpdate::Add(&key, &capability));
349                    vacant.insert(capability);
350                    Ok(())
351                }
352            },
353        }
354    }
355
356    pub fn remove(
357        &mut self,
358        key: &BorrowedKey,
359        update_notifiers: &mut UpdateNotifiers,
360    ) -> Option<Capability> {
361        let result = match self {
362            Self::Vec(vec) => match Self::sorted_vec_index_of(vec, key) {
363                Ok(index) => {
364                    update_notifiers.update(EntryUpdate::Remove(key));
365                    Some(vec.remove(index).1)
366                }
367                Err(_) => None,
368            },
369            Self::Map(map) => {
370                let result = map.remove(key);
371                if result.is_some() {
372                    update_notifiers.update(EntryUpdate::Remove(key));
373                    if self.len() <= HYBRID_SWITCH_REMOVAL_LEN {
374                        self.switch_to_vec();
375                    }
376                }
377                result
378            }
379        };
380        result
381    }
382
383    pub fn append(
384        &mut self,
385        other: &Self,
386        update_notifiers: &mut UpdateNotifiers,
387    ) -> Result<(), ()> {
388        if other.is_empty() {
389            // If other is empty then return early.
390            return Ok(());
391        }
392
393        // If any clone would fail, throw an error early and don't modify the dict
394        let to_insert: Result<Vec<_>, _> =
395            other.iter().map(|(k, v)| v.try_clone().map(|v| (k.clone(), v))).collect();
396        let to_insert = to_insert?;
397        for (k, _) in &to_insert {
398            let contains_key = match self {
399                Self::Vec(vec) => matches!(Self::sorted_vec_index_of(vec, k), Ok(_)),
400                Self::Map(map) => map.contains_key(k),
401            };
402            if contains_key {
403                return Err(());
404            }
405        }
406
407        if self.len() + other.len() >= HYBRID_SWITCH_INSERTION_LEN {
408            // If at some point we will need to switch to a map then do it now.
409            self.switch_to_map();
410        } else if let Self::Vec(vec) = self {
411            // We're currently a Vec and won't need to convert to a Map so grow the Vec to the final
412            // size now.
413            vec.reserve(other.len());
414        }
415        for (k, v) in to_insert {
416            self.insert(k, v, update_notifiers).expect("append: insert should have succeeded");
417        }
418        Ok(())
419    }
420
421    pub fn shallow_copy(&self) -> Result<Self, ()> {
422        match self {
423            Self::Vec(vec) => {
424                let mut new_vec = Vec::with_capacity(vec.len());
425                for (key, value) in vec.iter() {
426                    new_vec.push((key.clone(), value.try_clone()?));
427                }
428                Ok(HybridMap::Vec(new_vec))
429            }
430            Self::Map(map) => {
431                let mut new_map = BTreeMap::new();
432                for (key, value) in map.iter() {
433                    new_map.insert(key.clone(), value.try_clone()?);
434                }
435                Ok(HybridMap::Map(new_map))
436            }
437        }
438    }
439
440    pub fn len(&self) -> usize {
441        match self {
442            Self::Map(map) => map.len(),
443            Self::Vec(vec) => vec.len(),
444        }
445    }
446
447    pub fn is_empty(&self) -> bool {
448        match self {
449            Self::Map(map) => map.is_empty(),
450            Self::Vec(vec) => vec.is_empty(),
451        }
452    }
453
454    pub fn iter(&self) -> impl Iterator<Item = (&Key, &Capability)> {
455        match self {
456            Self::Vec(vec) => itertools::Either::Left(vec.iter().map(|kv| (&kv.0, &kv.1))),
457            Self::Map(map) => itertools::Either::Right(map.iter()),
458        }
459    }
460
461    pub fn into_iter(self) -> impl Iterator<Item = (Key, Capability)> {
462        match self {
463            Self::Vec(vec) => itertools::Either::Left(vec.into_iter()),
464            Self::Map(map) => itertools::Either::Right(map.into_iter()),
465        }
466    }
467
468    fn switch_to_map(&mut self) {
469        match self {
470            Self::Map(_) => {}
471            Self::Vec(vec) => {
472                let vec = std::mem::replace(vec, Vec::new());
473                let map = BTreeMap::from_iter(vec.into_iter());
474                *self = Self::Map(map);
475            }
476        }
477    }
478
479    fn switch_to_vec(&mut self) {
480        match self {
481            Self::Vec(_) => {}
482            Self::Map(map) => {
483                let map = std::mem::replace(map, Default::default());
484                let vec = Vec::from_iter(map.into_iter());
485                *self = Self::Vec(vec);
486            }
487        }
488    }
489
490    #[inline]
491    fn sorted_vec_index_of<Q: ?Sized>(vec: &Vec<(Key, Capability)>, key: &Q) -> Result<usize, usize>
492    where
493        Key: Borrow<Q> + Ord,
494        Q: Ord,
495    {
496        vec.binary_search_by(|probe| probe.0.borrow().cmp(&key))
497    }
498}
499
500#[derive(Default)]
501pub(crate) struct UpdateNotifiers(Vec<UpdateNotifierFn>);
502
503impl UpdateNotifiers {
504    fn update<'a>(&'a mut self, update: EntryUpdate<'a>) {
505        self.0.retain_mut(|notifier_fn| match (notifier_fn)(update) {
506            UpdateNotifierRetention::Retain => true,
507            UpdateNotifierRetention::Drop_ => false,
508        });
509    }
510}
511
512// Tests are located in fidl/dict.rs.