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