Skip to main content

runtime_capabilities/fidl/
dictionary.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::dictionary::{EntryUpdate, UpdateNotifierRetention};
6use crate::fidl::registry::{self, try_from_handle_in_registry};
7use crate::{
8    Capability, ConversionError, Dictionary, RemotableCapability, RemoteError, WeakInstanceToken,
9};
10use fidl_fuchsia_component_sandbox as fsandbox;
11use fuchsia_sync::Mutex;
12use futures::FutureExt;
13use futures::channel::oneshot;
14use log::warn;
15use std::sync::{Arc, Weak};
16use vfs::directory::entry::DirectoryEntry;
17use vfs::directory::helper::DirectlyMutable;
18use vfs::directory::immutable::simple as pfs;
19use vfs::execution_scope::ExecutionScope;
20use vfs::name::Name;
21
22impl From<Dictionary> for fsandbox::DictionaryRef {
23    fn from(dict: Dictionary) -> Self {
24        fsandbox::DictionaryRef { token: registry::insert_token(dict.into()) }
25    }
26}
27
28impl crate::fidl::IntoFsandboxCapability for Dictionary {
29    fn into_fsandbox_capability(self, _token: WeakInstanceToken) -> fsandbox::Capability {
30        fsandbox::Capability::Dictionary(self.into())
31    }
32}
33
34impl TryFrom<fsandbox::DictionaryRef> for Dictionary {
35    type Error = RemoteError;
36
37    fn try_from(dict: fsandbox::DictionaryRef) -> Result<Self, Self::Error> {
38        let any = try_from_handle_in_registry(dict.token.as_handle_ref())?;
39        let Capability::Dictionary(dict) = any else {
40            panic!("BUG: registry has a non-Dictionary capability under a Dictionary koid");
41        };
42        Ok(dict)
43    }
44}
45
46// Conversion from legacy channel type.
47impl TryFrom<fidl::Channel> for Dictionary {
48    type Error = RemoteError;
49
50    fn try_from(dict: fidl::Channel) -> Result<Self, Self::Error> {
51        let any = try_from_handle_in_registry(dict.as_handle_ref())?;
52        let Capability::Dictionary(dict) = any else {
53            panic!("BUG: registry has a non-Dictionary capability under a Dictionary koid");
54        };
55        Ok(dict)
56    }
57}
58
59impl Dictionary {
60    /// Like [RemotableCapability::try_into_directory_entry], but this version actually consumes
61    /// the contents of the [Dictionary]. In other words, if this function returns `Ok`, `self`
62    /// will be empty. If any items are added to `self` later, they will not appear in the
63    /// directory. This method is useful when the caller has no need to keep the original
64    /// [Dictionary]. Note that even if there is only one reference to the [Dictionary], calling
65    /// [RemotableCapability::try_into_directory_entry] does not have the same effect because the
66    /// `vfs` keeps alive reference to the [Dictionary] -- see the comment in the implementation.
67    ///
68    /// This is transitive: any [Dictionary]s nested in this one will be consumed as well.
69    pub fn try_into_directory_entry_oneshot(
70        self,
71        scope: ExecutionScope,
72        token: WeakInstanceToken,
73    ) -> Result<Arc<dyn DirectoryEntry>, ConversionError> {
74        let directory = if let Some(handler) = self.lock().not_found.take() {
75            pfs::Simple::new_with_not_found_handler(handler)
76        } else {
77            pfs::Simple::new()
78        };
79        for (key, value) in self.drain() {
80            let dir_entry = match value {
81                Capability::Dictionary(value) => {
82                    value.try_into_directory_entry_oneshot(scope.clone(), token.clone())?
83                }
84                value => value.try_into_directory_entry(scope.clone(), token.clone())?,
85            };
86            let key =
87                Name::try_from(key.to_string()).expect("cm_types::Name is always a valid vfs Name");
88            directory
89                .add_entry_impl(key, dir_entry, false)
90                .expect("dictionary values must be unique")
91        }
92
93        Ok(directory)
94    }
95}
96
97impl RemotableCapability for Dictionary {
98    fn try_into_directory_entry(
99        self,
100        scope: ExecutionScope,
101        token: WeakInstanceToken,
102    ) -> Result<Arc<dyn DirectoryEntry>, ConversionError> {
103        let self_clone = self.clone();
104        let directory = pfs::Simple::new_with_not_found_handler(move |path| {
105            // We hold a reference to the dictionary in this closure to solve an ownership problem.
106            // In `try_into_directory_entry` we return a `pfs::Simple` that provides a directory
107            // projection of a dictionary. The directory is live-updated, so that as items are
108            // added to or removed from the dictionary the directory contents are updated to match.
109            //
110            // The live-updating semantics introduce a problem: when all references to a dictionary
111            // reach the end of their lifetime and the dictionary is dropped, all entries in the
112            // dictionary are marked as removed. This means if one creates a dictionary, adds
113            // entries to it, turns it into a directory, and drops the only dictionary reference,
114            // then the directory is immediately emptied of all of its contents.
115            //
116            // Ideally at least one reference to the dictionary would be kept alive as long as the
117            // directory exists. We accomplish that by giving the directory ownership over a
118            // reference to the dictionary here.
119            self_clone.not_found(path);
120        });
121        let weak_dir: Weak<pfs::Simple> = Arc::downgrade(&directory);
122        let (error_sender, error_receiver) = oneshot::channel();
123        let error_sender = Mutex::new(Some(error_sender));
124        // `register_update_notifier` calls the closure with any existing entries before returning,
125        // so there won't be a race with us returning this directory and the entries being added to
126        // it.
127        self.register_update_notifier(Box::new(move |update: EntryUpdate<'_>| {
128            let Some(directory) = weak_dir.upgrade() else {
129                return UpdateNotifierRetention::Drop_;
130            };
131            match update {
132                EntryUpdate::Add(key, value) => {
133                    let dir_entry = match value
134                        .clone()
135                        .try_into_directory_entry(scope.clone(), token.clone())
136                    {
137                        Ok(dir_entry) => dir_entry,
138                        Err(err) => {
139                            if let Some(error_sender) = error_sender.lock().take() {
140                                let _ = error_sender.send(err);
141                            } else {
142                                warn!(
143                                    "value in dictionary cannot be converted to directory entry: \
144                                    {err:?}"
145                                )
146                            }
147                            return UpdateNotifierRetention::Retain;
148                        }
149                    };
150                    let name = Name::try_from(key.to_string())
151                        .expect("cm_types::Name is always a valid vfs Name");
152                    directory
153                        .add_entry_impl(name, dir_entry, false)
154                        .expect("dictionary values must be unique")
155                }
156                EntryUpdate::Remove(key) => {
157                    let name = Name::try_from(key.to_string())
158                        .expect("cm_types::Name is always a valid vfs Name");
159                    let _ = directory.remove_entry_impl(name, false);
160                }
161                EntryUpdate::Idle => (),
162            }
163            UpdateNotifierRetention::Retain
164        }));
165        if let Some(Ok(error)) = error_receiver.now_or_never() {
166            // We encountered an error processing the initial contents of this dictionary. Let's
167            // return that instead of the directory we've created.
168            return Err(error);
169        }
170        Ok(directory)
171    }
172}
173
174// These tests only run on target because the vfs library is not generally available on host.
175#[cfg(test)]
176mod tests {
177    use super::*;
178    use crate::dictionary::{
179        BorrowedKey, HYBRID_SWITCH_INSERTION_LEN, HYBRID_SWITCH_REMOVAL_LEN, HybridMap, Key,
180    };
181    use crate::fidl::IntoFsandboxCapability;
182    use crate::{Data, Dictionary, DirConnector, Handle, serve_capability_store};
183    use assert_matches::assert_matches;
184    use fidl::endpoints::{Proxy, create_proxy, create_proxy_and_stream};
185    use fidl::handle::{Channel, HandleBased, Status};
186    use fidl_fuchsia_io as fio;
187    use fuchsia_async as fasync;
188    use fuchsia_fs::directory;
189    use futures::StreamExt;
190    use std::sync::LazyLock;
191    use std::{fmt, iter};
192    use test_case::test_case;
193    use test_util::Counter;
194    use vfs::directory::entry::{
195        DirectoryEntry, EntryInfo, GetEntryInfo, OpenRequest, serve_directory,
196    };
197    use vfs::execution_scope::ExecutionScope;
198    use vfs::path::Path;
199    use vfs::remote::RemoteLike;
200    use vfs::{ObjectRequestRef, pseudo_directory};
201
202    static CAP_KEY: LazyLock<Key> = LazyLock::new(|| "Cap".parse().unwrap());
203
204    #[derive(Debug, Clone, Copy)]
205    enum TestType {
206        // Test dictionary stored as vector
207        Small,
208        // Test dictionary stored as map
209        Big,
210    }
211
212    #[fuchsia::test]
213    async fn create() {
214        let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
215        let _server = fasync::Task::spawn(async move {
216            let receiver_scope = fasync::Scope::new();
217            serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
218        });
219        let id_gen = sandbox::CapabilityIdGenerator::new();
220
221        let dict_id = id_gen.next();
222        assert_matches!(store.dictionary_create(dict_id).await.unwrap(), Ok(()));
223        assert_matches!(
224            store.dictionary_create(dict_id).await.unwrap(),
225            Err(fsandbox::CapabilityStoreError::IdAlreadyExists)
226        );
227
228        let value = 10;
229        store.import(value, Data::Int64(1).into()).await.unwrap().unwrap();
230        store
231            .dictionary_insert(dict_id, &fsandbox::DictionaryItem { key: "k".into(), value })
232            .await
233            .unwrap()
234            .unwrap();
235
236        // The dictionary has one item.
237        let (iterator, server_end) = create_proxy();
238        store.dictionary_keys(dict_id, server_end).await.unwrap().unwrap();
239        let keys = iterator.get_next().await.unwrap();
240        assert!(iterator.get_next().await.unwrap().is_empty());
241        assert_eq!(keys, ["k"]);
242    }
243
244    #[fuchsia::test]
245    async fn create_error() {
246        let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
247        let _server = fasync::Task::spawn(async move {
248            let receiver_scope = fasync::Scope::new();
249            serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
250        });
251
252        let cap = Capability::Data(Data::Int64(42));
253        assert_matches!(
254            store
255                .import(1, cap.into_fsandbox_capability(WeakInstanceToken::new_invalid()))
256                .await
257                .unwrap(),
258            Ok(())
259        );
260        assert_matches!(
261            store.dictionary_create(1).await.unwrap(),
262            Err(fsandbox::CapabilityStoreError::IdAlreadyExists)
263        );
264    }
265
266    #[fuchsia::test]
267    async fn legacy_import() {
268        let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
269        let _server = fasync::Task::spawn(async move {
270            let receiver_scope = fasync::Scope::new();
271            serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
272        });
273
274        let dict_id = 1;
275        assert_matches!(store.dictionary_create(dict_id).await.unwrap(), Ok(()));
276        assert_matches!(
277            store.dictionary_create(dict_id).await.unwrap(),
278            Err(fsandbox::CapabilityStoreError::IdAlreadyExists)
279        );
280
281        let value = 10;
282        store.import(value, Data::Int64(1).into()).await.unwrap().unwrap();
283        store
284            .dictionary_insert(dict_id, &fsandbox::DictionaryItem { key: "k".into(), value })
285            .await
286            .unwrap()
287            .unwrap();
288
289        // Export and re-import the capability using the legacy import/export APIs.
290        let (client, server) = fidl::Channel::create();
291        store.dictionary_legacy_export(dict_id, server).await.unwrap().unwrap();
292        let dict_id = 2;
293        store.dictionary_legacy_import(dict_id, client).await.unwrap().unwrap();
294
295        // The dictionary has one item.
296        let (iterator, server_end) = create_proxy();
297        store.dictionary_keys(dict_id, server_end).await.unwrap().unwrap();
298        let keys = iterator.get_next().await.unwrap();
299        assert!(iterator.get_next().await.unwrap().is_empty());
300        assert_eq!(keys, ["k"]);
301    }
302
303    #[fuchsia::test]
304    async fn legacy_import_error() {
305        let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
306        let _server = fasync::Task::spawn(async move {
307            let receiver_scope = fasync::Scope::new();
308            serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
309        });
310
311        store.dictionary_create(10).await.unwrap().unwrap();
312        let (dict_ch, server) = fidl::Channel::create();
313        store.dictionary_legacy_export(10, server).await.unwrap().unwrap();
314
315        let cap1 = Capability::Data(Data::Int64(42));
316        store
317            .import(1, cap1.into_fsandbox_capability(WeakInstanceToken::new_invalid()))
318            .await
319            .unwrap()
320            .unwrap();
321        assert_matches!(
322            store.dictionary_legacy_import(1, dict_ch).await.unwrap(),
323            Err(fsandbox::CapabilityStoreError::IdAlreadyExists)
324        );
325
326        let (ch, _) = fidl::Channel::create();
327        assert_matches!(
328            store.dictionary_legacy_import(2, ch.into()).await.unwrap(),
329            Err(fsandbox::CapabilityStoreError::BadCapability)
330        );
331    }
332
333    #[fuchsia::test]
334    async fn legacy_export_error() {
335        let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
336        let _server = fasync::Task::spawn(async move {
337            let receiver_scope = fasync::Scope::new();
338            serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
339        });
340
341        let (_dict_ch, server) = fidl::Channel::create();
342        assert_matches!(
343            store.dictionary_legacy_export(1, server).await.unwrap(),
344            Err(fsandbox::CapabilityStoreError::IdNotFound)
345        );
346    }
347
348    #[test_case(TestType::Small)]
349    #[test_case(TestType::Big)]
350    #[fuchsia::test]
351    async fn insert(test_type: TestType) {
352        let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
353        let _server = fasync::Task::spawn(async move {
354            let receiver_scope = fasync::Scope::new();
355            serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
356        });
357
358        let dict = new_dict(test_type);
359        let dict_ref = Capability::Dictionary(dict.clone())
360            .into_fsandbox_capability(WeakInstanceToken::new_invalid());
361        let dict_id = 1;
362        store.import(dict_id, dict_ref).await.unwrap().unwrap();
363
364        let data = Data::Int64(1).into();
365        let value = 2;
366        store.import(value, data).await.unwrap().unwrap();
367        store
368            .dictionary_insert(
369                dict_id,
370                &fsandbox::DictionaryItem { key: CAP_KEY.to_string(), value },
371            )
372            .await
373            .unwrap()
374            .unwrap();
375
376        // Inserting adds the entry to `entries`.
377        assert_eq!(adjusted_len(&dict, test_type), 1);
378
379        // The entry that was inserted should now be in `entries`.
380        let cap = dict.remove(&*CAP_KEY).expect("not in entries after insert");
381        let Capability::Data(data) = cap else { panic!("Bad capability type: {:#?}", cap) };
382        assert_eq!(data, Data::Int64(1));
383    }
384
385    #[fuchsia::test]
386    async fn insert_error() {
387        let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
388        let _server = fasync::Task::spawn(async move {
389            let receiver_scope = fasync::Scope::new();
390            serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
391        });
392
393        let data = Data::Int64(1).into();
394        let value = 2;
395        store.import(value, data).await.unwrap().unwrap();
396
397        assert_matches!(
398            store
399                .dictionary_insert(1, &fsandbox::DictionaryItem { key: "k".into(), value })
400                .await
401                .unwrap(),
402            Err(fsandbox::CapabilityStoreError::IdNotFound)
403        );
404        assert_matches!(
405            store
406                .dictionary_insert(2, &fsandbox::DictionaryItem { key: "k".into(), value })
407                .await
408                .unwrap(),
409            Err(fsandbox::CapabilityStoreError::WrongType)
410        );
411
412        store.dictionary_create(1).await.unwrap().unwrap();
413        assert_matches!(
414            store
415                .dictionary_insert(1, &fsandbox::DictionaryItem { key: "^bad".into(), value })
416                .await
417                .unwrap(),
418            Err(fsandbox::CapabilityStoreError::InvalidKey)
419        );
420
421        assert_matches!(
422            store
423                .dictionary_insert(1, &fsandbox::DictionaryItem { key: "k".into(), value })
424                .await
425                .unwrap(),
426            Ok(())
427        );
428
429        let data = Data::Int64(1).into();
430        let value = 3;
431        store.import(value, data).await.unwrap().unwrap();
432        assert_matches!(
433            store
434                .dictionary_insert(1, &fsandbox::DictionaryItem { key: "k".into(), value })
435                .await
436                .unwrap(),
437            Err(fsandbox::CapabilityStoreError::ItemAlreadyExists)
438        );
439    }
440
441    #[test_case(TestType::Small)]
442    #[test_case(TestType::Big)]
443    #[fuchsia::test]
444    async fn remove(test_type: TestType) {
445        let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
446        let _server = fasync::Task::spawn(async move {
447            let receiver_scope = fasync::Scope::new();
448            serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
449        });
450
451        let dict = new_dict(test_type);
452
453        // Insert a Data into the Dictionary.
454        assert!(dict.insert(CAP_KEY.clone(), Capability::Data(Data::Int64(1))).is_none());
455        assert_eq!(adjusted_len(&dict, test_type), 1);
456
457        let dict_ref = Capability::Dictionary(dict.clone())
458            .into_fsandbox_capability(WeakInstanceToken::new_invalid());
459        let dict_id = 1;
460        store.import(dict_id, dict_ref).await.unwrap().unwrap();
461
462        let dest_id = 2;
463        store
464            .dictionary_remove(
465                dict_id,
466                &CAP_KEY.to_string(),
467                Some(&fsandbox::WrappedNewCapabilityId { id: dest_id }),
468            )
469            .await
470            .unwrap()
471            .unwrap();
472        let cap = store.export(dest_id).await.unwrap().unwrap();
473        // The value should be the same one that was previously inserted.
474        assert_eq!(cap, Data::Int64(1).into());
475
476        // Removing the entry with Remove should remove it from `entries`.
477        assert_eq!(adjusted_len(&dict, test_type), 0);
478    }
479
480    #[fuchsia::test]
481    async fn remove_error() {
482        let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
483        let _server = fasync::Task::spawn(async move {
484            let receiver_scope = fasync::Scope::new();
485            serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
486        });
487
488        assert_matches!(
489            store.dictionary_remove(1, "k".into(), None).await.unwrap(),
490            Err(fsandbox::CapabilityStoreError::IdNotFound)
491        );
492
493        store.dictionary_create(1).await.unwrap().unwrap();
494
495        let data = Data::Int64(1).into();
496        store.import(2, data).await.unwrap().unwrap();
497
498        assert_matches!(
499            store.dictionary_remove(2, "k".into(), None).await.unwrap(),
500            Err(fsandbox::CapabilityStoreError::WrongType)
501        );
502        store
503            .dictionary_insert(1, &fsandbox::DictionaryItem { key: "k".into(), value: 2 })
504            .await
505            .unwrap()
506            .unwrap();
507        assert_matches!(
508            store
509                .dictionary_remove(1, "k".into(), Some(&fsandbox::WrappedNewCapabilityId { id: 1 }))
510                .await
511                .unwrap(),
512            Err(fsandbox::CapabilityStoreError::IdAlreadyExists)
513        );
514        assert_matches!(
515            store.dictionary_remove(1, "^bad".into(), None).await.unwrap(),
516            Err(fsandbox::CapabilityStoreError::InvalidKey)
517        );
518        assert_matches!(
519            store.dictionary_remove(1, "not_found".into(), None).await.unwrap(),
520            Err(fsandbox::CapabilityStoreError::ItemNotFound)
521        );
522    }
523
524    #[test_case(TestType::Small)]
525    #[test_case(TestType::Big)]
526    #[fuchsia::test]
527    async fn get(test_type: TestType) {
528        let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
529        let _server = fasync::Task::spawn(async move {
530            let receiver_scope = fasync::Scope::new();
531            serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
532        });
533
534        let dict = new_dict(test_type);
535
536        assert!(dict.insert(CAP_KEY.clone(), Capability::Data(Data::Int64(1))).is_none());
537        assert_eq!(adjusted_len(&dict, test_type), 1);
538        let (ch, _) = fidl::Channel::create();
539        let handle = Handle::new(ch.into_handle());
540        assert!(dict.insert("h".parse().unwrap(), Capability::Handle(handle)).is_none());
541
542        let dict_ref = Capability::Dictionary(dict.clone())
543            .into_fsandbox_capability(WeakInstanceToken::new_invalid());
544        let dict_id = 1;
545        store.import(dict_id, dict_ref).await.unwrap().unwrap();
546
547        let dest_id = 2;
548        store.dictionary_get(dict_id, CAP_KEY.as_str(), dest_id).await.unwrap().unwrap();
549        let cap = store.export(dest_id).await.unwrap().unwrap();
550        assert_eq!(cap, Data::Int64(1).into());
551    }
552
553    #[fuchsia::test]
554    async fn get_error() {
555        let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
556        let _server = fasync::Task::spawn(async move {
557            let receiver_scope = fasync::Scope::new();
558            serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
559        });
560
561        assert_matches!(
562            store.dictionary_get(1, "k".into(), 2).await.unwrap(),
563            Err(fsandbox::CapabilityStoreError::IdNotFound)
564        );
565
566        store.dictionary_create(1).await.unwrap().unwrap();
567
568        store.import(2, Data::Int64(1).into()).await.unwrap().unwrap();
569
570        assert_matches!(
571            store.dictionary_get(2, "k".into(), 3).await.unwrap(),
572            Err(fsandbox::CapabilityStoreError::WrongType)
573        );
574        store
575            .dictionary_insert(1, &fsandbox::DictionaryItem { key: "k".into(), value: 2 })
576            .await
577            .unwrap()
578            .unwrap();
579
580        store.import(2, Data::Int64(1).into()).await.unwrap().unwrap();
581        assert_matches!(
582            store.dictionary_get(1, "k".into(), 2).await.unwrap(),
583            Err(fsandbox::CapabilityStoreError::IdAlreadyExists)
584        );
585        assert_matches!(
586            store.dictionary_get(1, "^bad".into(), 3).await.unwrap(),
587            Err(fsandbox::CapabilityStoreError::InvalidKey)
588        );
589        assert_matches!(
590            store.dictionary_get(1, "not_found".into(), 3).await.unwrap(),
591            Err(fsandbox::CapabilityStoreError::ItemNotFound)
592        );
593    }
594
595    #[test_case(TestType::Small)]
596    #[test_case(TestType::Big)]
597    #[fuchsia::test]
598    async fn copy(test_type: TestType) {
599        let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
600        let _server = fasync::Task::spawn(async move {
601            let receiver_scope = fasync::Scope::new();
602            serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
603        });
604
605        // Create a Dictionary with a Data inside, and copy the Dictionary.
606        let dict = new_dict(test_type);
607        assert!(dict.insert("data1".parse().unwrap(), Capability::Data(Data::Int64(1))).is_none());
608        store
609            .import(
610                1,
611                dict.clone().into_fsandbox_capability(WeakInstanceToken::new_invalid()).into(),
612            )
613            .await
614            .unwrap()
615            .unwrap();
616        store.dictionary_copy(1, 2).await.unwrap().unwrap();
617
618        // Insert a Data into the copy.
619        store.import(3, Data::Int64(1).into()).await.unwrap().unwrap();
620        store
621            .dictionary_insert(2, &fsandbox::DictionaryItem { key: "k".into(), value: 3 })
622            .await
623            .unwrap()
624            .unwrap();
625
626        // The copy should have two Data values.
627        let copy = store.export(2).await.unwrap().unwrap();
628        let copy = Capability::try_from(copy).unwrap();
629        let Capability::Dictionary(copy) = copy else { panic!() };
630        {
631            assert_eq!(adjusted_len(&copy, test_type), 2);
632            let copy = copy.lock();
633            assert!(copy.entries.iter().all(|(_, value)| matches!(value, Capability::Data(_))));
634        }
635
636        // The original Dictionary should have only one Data.
637        {
638            assert_eq!(adjusted_len(&dict, test_type), 1);
639            let dict = dict.lock();
640            assert!(dict.entries.iter().all(|(_, value)| matches!(value, Capability::Data(_))));
641        }
642    }
643
644    #[test_case(TestType::Small)]
645    #[test_case(TestType::Big)]
646    #[fuchsia::test]
647    async fn duplicate(test_type: TestType) {
648        let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
649        let _server = fasync::Task::spawn(async move {
650            let receiver_scope = fasync::Scope::new();
651            serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
652        });
653
654        let dict = new_dict(test_type);
655        store
656            .import(1, dict.clone().into_fsandbox_capability(WeakInstanceToken::new_invalid()))
657            .await
658            .unwrap()
659            .unwrap();
660        store.duplicate(1, 2).await.unwrap().unwrap();
661
662        // Add a Data into the duplicate.
663        store.import(3, Data::Int64(1).into()).await.unwrap().unwrap();
664        store
665            .dictionary_insert(2, &fsandbox::DictionaryItem { key: "k".into(), value: 3 })
666            .await
667            .unwrap()
668            .unwrap();
669        let dict_dup = store.export(2).await.unwrap().unwrap();
670        let dict_dup = Capability::try_from(dict_dup).unwrap();
671        let Capability::Dictionary(dict_dup) = dict_dup else { panic!() };
672        assert_eq!(adjusted_len(&dict_dup, test_type), 1);
673
674        // The original dict should now have an entry because it shares entries with the clone.
675        assert_eq!(adjusted_len(&dict_dup, test_type), 1);
676    }
677
678    /// Tests basic functionality of read APIs.
679    #[test_case(TestType::Small)]
680    #[test_case(TestType::Big)]
681    #[fuchsia::test]
682    async fn read(test_type: TestType) {
683        let dict = new_dict(test_type);
684        let id_gen = sandbox::CapabilityIdGenerator::new();
685
686        let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
687        let _server = fasync::Task::spawn(async move {
688            let receiver_scope = fasync::Scope::new();
689            serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
690        });
691        let dict_ref =
692            Capability::Dictionary(dict).into_fsandbox_capability(WeakInstanceToken::new_invalid());
693        let dict_id = id_gen.next();
694        store.import(dict_id, dict_ref).await.unwrap().unwrap();
695
696        // Create two Data capabilities.
697        let mut data_caps = vec![];
698        for i in 1..3 {
699            let id = id_gen.next();
700            store.import(id, Data::Int64(i.try_into().unwrap()).into()).await.unwrap().unwrap();
701            data_caps.push(id);
702        }
703
704        // Add the Data capabilities to the dict.
705        store
706            .dictionary_insert(
707                dict_id,
708                &fsandbox::DictionaryItem { key: "Cap1".into(), value: data_caps.remove(0) },
709            )
710            .await
711            .unwrap()
712            .unwrap();
713        store
714            .dictionary_insert(
715                dict_id,
716                &fsandbox::DictionaryItem { key: "Cap2".into(), value: data_caps.remove(0) },
717            )
718            .await
719            .unwrap()
720            .unwrap();
721        let (ch, _) = fidl::Channel::create();
722        let handle = ch.into_handle();
723        let id = id_gen.next();
724        store.import(id, fsandbox::Capability::Handle(handle)).await.unwrap().unwrap();
725        store
726            .dictionary_insert(dict_id, &fsandbox::DictionaryItem { key: "Cap3".into(), value: id })
727            .await
728            .unwrap()
729            .unwrap();
730
731        // Keys
732        {
733            let (iterator, server_end) = create_proxy();
734            store.dictionary_keys(dict_id, server_end).await.unwrap().unwrap();
735            let keys = iterator.get_next().await.unwrap();
736            assert!(iterator.get_next().await.unwrap().is_empty());
737            match test_type {
738                TestType::Small => assert_eq!(keys, ["Cap1", "Cap2", "Cap3"]),
739                TestType::Big => {
740                    assert_eq!(keys[0..3], ["Cap1", "Cap2", "Cap3"]);
741                    assert_eq!(keys.len(), 3 + HYBRID_SWITCH_INSERTION_LEN);
742                }
743            }
744        }
745        // Enumerate
746        {
747            let (iterator, server_end) = create_proxy();
748            store.dictionary_enumerate(dict_id, server_end).await.unwrap().unwrap();
749            let start_id = 100;
750            let ofs: u32 = match test_type {
751                TestType::Small => 0,
752                TestType::Big => HYBRID_SWITCH_INSERTION_LEN as u32,
753            };
754            let limit = 4 + ofs;
755            let (mut items, end_id) = iterator.get_next(start_id, limit).await.unwrap().unwrap();
756            assert_eq!(end_id, 103 + ofs as u64);
757            let (last, end_id) = iterator.get_next(end_id, limit).await.unwrap().unwrap();
758            assert!(last.is_empty());
759            assert_eq!(end_id, 103 + ofs as u64);
760
761            assert_matches!(
762                items.remove(0),
763                fsandbox::DictionaryOptionalItem {
764                    key,
765                    value: Some(value)
766                }
767                if key == "Cap1" && value.id == 100
768            );
769            assert_matches!(
770                store.export(100).await.unwrap().unwrap(),
771                fsandbox::Capability::Data(fsandbox::Data::Int64(1))
772            );
773            assert_matches!(
774                items.remove(0),
775                fsandbox::DictionaryOptionalItem {
776                    key,
777                    value: Some(value)
778                }
779                if key == "Cap2" && value.id == 101
780            );
781            assert_matches!(
782                store.export(101).await.unwrap().unwrap(),
783                fsandbox::Capability::Data(fsandbox::Data::Int64(2))
784            );
785            assert_matches!(
786                items.remove(0),
787                fsandbox::DictionaryOptionalItem {
788                    key,
789                    value: Some(value)
790                }
791                if key == "Cap3" && value.id == 102
792            );
793            match test_type {
794                TestType::Small => {}
795                TestType::Big => {
796                    assert_eq!(items.len(), HYBRID_SWITCH_INSERTION_LEN);
797                }
798            }
799        }
800    }
801
802    #[test_case(TestType::Small)]
803    #[test_case(TestType::Big)]
804    #[fuchsia::test]
805    async fn drain(test_type: TestType) {
806        let dict = new_dict(test_type);
807
808        let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
809        let _server = fasync::Task::spawn(async move {
810            let receiver_scope = fasync::Scope::new();
811            serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
812        });
813        let dict_ref = Capability::Dictionary(dict.clone())
814            .into_fsandbox_capability(WeakInstanceToken::new_invalid());
815        let dict_id = 1;
816        store.import(dict_id, dict_ref).await.unwrap().unwrap();
817
818        // Create two Data capabilities.
819        let mut data_caps = vec![];
820        for i in 1..3 {
821            let value = 10 + i;
822            store.import(value, Data::Int64(i.try_into().unwrap()).into()).await.unwrap().unwrap();
823            data_caps.push(value);
824        }
825
826        // Add the Data capabilities to the dict.
827        store
828            .dictionary_insert(
829                dict_id,
830                &fsandbox::DictionaryItem { key: "Cap1".into(), value: data_caps.remove(0) },
831            )
832            .await
833            .unwrap()
834            .unwrap();
835        store
836            .dictionary_insert(
837                dict_id,
838                &fsandbox::DictionaryItem { key: "Cap2".into(), value: data_caps.remove(0) },
839            )
840            .await
841            .unwrap()
842            .unwrap();
843        let (ch, _) = fidl::Channel::create();
844        let handle = ch.into_handle();
845        let handle_koid = handle.koid().unwrap();
846        let value = 20;
847        store.import(value, fsandbox::Capability::Handle(handle)).await.unwrap().unwrap();
848        store
849            .dictionary_insert(dict_id, &fsandbox::DictionaryItem { key: "Cap3".into(), value })
850            .await
851            .unwrap()
852            .unwrap();
853
854        let (iterator, server_end) = create_proxy();
855        store.dictionary_drain(dict_id, Some(server_end)).await.unwrap().unwrap();
856        let ofs: u32 = match test_type {
857            TestType::Small => 0,
858            TestType::Big => HYBRID_SWITCH_INSERTION_LEN as u32,
859        };
860        let start_id = 100;
861        let limit = 4 + ofs;
862        let (mut items, end_id) = iterator.get_next(start_id, limit).await.unwrap().unwrap();
863        assert_eq!(end_id, 103 + ofs as u64);
864        let (last, end_id) = iterator.get_next(end_id, limit).await.unwrap().unwrap();
865        assert!(last.is_empty());
866        assert_eq!(end_id, 103 + ofs as u64);
867
868        assert_matches!(
869            items.remove(0),
870            fsandbox::DictionaryItem {
871                key,
872                value: 100
873            }
874            if key == "Cap1"
875        );
876        assert_matches!(
877            store.export(100).await.unwrap().unwrap(),
878            fsandbox::Capability::Data(fsandbox::Data::Int64(1))
879        );
880        assert_matches!(
881            items.remove(0),
882            fsandbox::DictionaryItem {
883                key,
884                value: 101
885            }
886            if key == "Cap2"
887        );
888        assert_matches!(
889            store.export(101).await.unwrap().unwrap(),
890            fsandbox::Capability::Data(fsandbox::Data::Int64(2))
891        );
892        assert_matches!(
893            items.remove(0),
894            fsandbox::DictionaryItem {
895                key,
896                value: 102
897            }
898            if key == "Cap3"
899        );
900        assert_matches!(
901            store.export(102).await.unwrap().unwrap(),
902            fsandbox::Capability::Handle(handle)
903            if handle.koid().unwrap() == handle_koid
904        );
905
906        // Dictionary should now be empty.
907        assert!(dict.is_empty());
908    }
909
910    /// Tests batching for read APIs.
911    #[fuchsia::test]
912    async fn read_batches() {
913        // Number of entries in the Dictionary that will be enumerated.
914        //
915        // This value was chosen such that that GetNext returns multiple chunks of different sizes.
916        const NUM_ENTRIES: u32 = fsandbox::MAX_DICTIONARY_ITERATOR_CHUNK * 2 + 1;
917
918        // Number of items we expect in each chunk, for every chunk we expect to get.
919        const EXPECTED_CHUNK_LENGTHS: &[u32] =
920            &[fsandbox::MAX_DICTIONARY_ITERATOR_CHUNK, fsandbox::MAX_DICTIONARY_ITERATOR_CHUNK, 1];
921
922        let id_gen = sandbox::CapabilityIdGenerator::new();
923
924        // Create a Dictionary with [NUM_ENTRIES] entries that have Data values.
925        let dict = Dictionary::new();
926        for i in 0..NUM_ENTRIES {
927            assert!(
928                dict.insert(format!("{}", i).parse().unwrap(), Capability::Data(Data::Int64(1)))
929                    .is_none()
930            );
931        }
932
933        let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
934        let _server = fasync::Task::spawn(async move {
935            let receiver_scope = fasync::Scope::new();
936            serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
937        });
938        let dict_ref = Capability::Dictionary(dict.clone())
939            .into_fsandbox_capability(WeakInstanceToken::new_invalid());
940        let dict_id = id_gen.next();
941        store.import(dict_id, dict_ref).await.unwrap().unwrap();
942
943        let (key_iterator, server_end) = create_proxy();
944        store.dictionary_keys(dict_id, server_end).await.unwrap().unwrap();
945        let (item_iterator, server_end) = create_proxy();
946        store.dictionary_enumerate(dict_id, server_end).await.unwrap().unwrap();
947
948        // Get all the entries from the Dictionary with `GetNext`.
949        let mut num_got_items: u32 = 0;
950        let mut start_id = 100;
951        let limit = fsandbox::MAX_DICTIONARY_ITERATOR_CHUNK;
952        for expected_len in EXPECTED_CHUNK_LENGTHS {
953            let keys = key_iterator.get_next().await.unwrap();
954            let (items, end_id) = item_iterator.get_next(start_id, limit).await.unwrap().unwrap();
955            if keys.is_empty() && items.is_empty() {
956                break;
957            }
958            assert_eq!(*expected_len, keys.len() as u32);
959            assert_eq!(*expected_len, items.len() as u32);
960            assert_eq!(u64::from(*expected_len), end_id - start_id);
961            start_id = end_id;
962            num_got_items += *expected_len;
963        }
964
965        // GetNext should return no items once all items have been returned.
966        let (items, _) = item_iterator.get_next(start_id, limit).await.unwrap().unwrap();
967        assert!(items.is_empty());
968        assert!(key_iterator.get_next().await.unwrap().is_empty());
969
970        assert_eq!(num_got_items, NUM_ENTRIES);
971    }
972
973    #[fuchsia::test]
974    async fn drain_batches() {
975        // Number of entries in the Dictionary that will be enumerated.
976        //
977        // This value was chosen such that that GetNext returns multiple chunks of different sizes.
978        const NUM_ENTRIES: u32 = fsandbox::MAX_DICTIONARY_ITERATOR_CHUNK * 2 + 1;
979
980        // Number of items we expect in each chunk, for every chunk we expect to get.
981        const EXPECTED_CHUNK_LENGTHS: &[u32] =
982            &[fsandbox::MAX_DICTIONARY_ITERATOR_CHUNK, fsandbox::MAX_DICTIONARY_ITERATOR_CHUNK, 1];
983
984        // Create a Dictionary with [NUM_ENTRIES] entries that have Data values.
985        let dict = Dictionary::new();
986        for i in 0..NUM_ENTRIES {
987            assert!(
988                dict.insert(format!("{}", i).parse().unwrap(), Capability::Data(Data::Int64(1)))
989                    .is_none()
990            );
991        }
992
993        let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
994        let _server = fasync::Task::spawn(async move {
995            let receiver_scope = fasync::Scope::new();
996            serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
997        });
998        let dict_ref = Capability::Dictionary(dict.clone())
999            .into_fsandbox_capability(WeakInstanceToken::new_invalid());
1000        let dict_id = 1;
1001        store.import(dict_id, dict_ref).await.unwrap().unwrap();
1002
1003        let (item_iterator, server_end) = create_proxy();
1004        store.dictionary_drain(dict_id, Some(server_end)).await.unwrap().unwrap();
1005
1006        // Get all the entries from the Dictionary with `GetNext`.
1007        let mut num_got_items: u32 = 0;
1008        let mut start_id = 100;
1009        let limit = fsandbox::MAX_DICTIONARY_ITERATOR_CHUNK;
1010        for expected_len in EXPECTED_CHUNK_LENGTHS {
1011            let (items, end_id) = item_iterator.get_next(start_id, limit).await.unwrap().unwrap();
1012            if items.is_empty() {
1013                break;
1014            }
1015            assert_eq!(*expected_len, items.len() as u32);
1016            assert_eq!(u64::from(*expected_len), end_id - start_id);
1017            start_id = end_id;
1018            num_got_items += *expected_len;
1019        }
1020
1021        // GetNext should return no items once all items have been returned.
1022        let (items, _) = item_iterator.get_next(start_id, limit).await.unwrap().unwrap();
1023        assert!(items.is_empty());
1024
1025        assert_eq!(num_got_items, NUM_ENTRIES);
1026
1027        // Dictionary should now be empty.
1028        assert!(dict.is_empty());
1029    }
1030
1031    #[fuchsia::test]
1032    async fn read_error() {
1033        let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
1034        let _server = fasync::Task::spawn(async move {
1035            let receiver_scope = fasync::Scope::new();
1036            serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
1037        });
1038
1039        store.import(2, Data::Int64(1).into()).await.unwrap().unwrap();
1040
1041        let (_, server_end) = create_proxy();
1042        assert_matches!(
1043            store.dictionary_keys(1, server_end).await.unwrap(),
1044            Err(fsandbox::CapabilityStoreError::IdNotFound)
1045        );
1046        let (_, server_end) = create_proxy();
1047        assert_matches!(
1048            store.dictionary_enumerate(1, server_end).await.unwrap(),
1049            Err(fsandbox::CapabilityStoreError::IdNotFound)
1050        );
1051        assert_matches!(
1052            store.dictionary_drain(1, None).await.unwrap(),
1053            Err(fsandbox::CapabilityStoreError::IdNotFound)
1054        );
1055
1056        let (_, server_end) = create_proxy();
1057        assert_matches!(
1058            store.dictionary_keys(2, server_end).await.unwrap(),
1059            Err(fsandbox::CapabilityStoreError::WrongType)
1060        );
1061        let (_, server_end) = create_proxy();
1062        assert_matches!(
1063            store.dictionary_enumerate(2, server_end).await.unwrap(),
1064            Err(fsandbox::CapabilityStoreError::WrongType)
1065        );
1066        assert_matches!(
1067            store.dictionary_drain(2, None).await.unwrap(),
1068            Err(fsandbox::CapabilityStoreError::WrongType)
1069        );
1070    }
1071
1072    #[fuchsia::test]
1073    async fn read_iterator_error() {
1074        let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
1075        let _server = fasync::Task::spawn(async move {
1076            let receiver_scope = fasync::Scope::new();
1077            serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
1078        });
1079
1080        store.dictionary_create(1).await.unwrap().unwrap();
1081
1082        {
1083            let (iterator, server_end) = create_proxy();
1084            store.dictionary_enumerate(1, server_end).await.unwrap().unwrap();
1085            assert_matches!(
1086                iterator.get_next(2, fsandbox::MAX_DICTIONARY_ITERATOR_CHUNK + 1).await.unwrap(),
1087                Err(fsandbox::CapabilityStoreError::InvalidArgs)
1088            );
1089            let (iterator, server_end) = create_proxy();
1090            store.dictionary_enumerate(1, server_end).await.unwrap().unwrap();
1091            assert_matches!(
1092                iterator.get_next(2, 0).await.unwrap(),
1093                Err(fsandbox::CapabilityStoreError::InvalidArgs)
1094            );
1095
1096            let (iterator, server_end) = create_proxy();
1097            store.dictionary_drain(1, Some(server_end)).await.unwrap().unwrap();
1098            assert_matches!(
1099                iterator.get_next(2, fsandbox::MAX_DICTIONARY_ITERATOR_CHUNK + 1).await.unwrap(),
1100                Err(fsandbox::CapabilityStoreError::InvalidArgs)
1101            );
1102            let (iterator, server_end) = create_proxy();
1103            store.dictionary_drain(1, Some(server_end)).await.unwrap().unwrap();
1104            assert_matches!(
1105                iterator.get_next(2, 0).await.unwrap(),
1106                Err(fsandbox::CapabilityStoreError::InvalidArgs)
1107            );
1108        }
1109
1110        store.import(4, Data::Int64(1).into()).await.unwrap().unwrap();
1111        for i in 0..3 {
1112            store.import(2, Data::Int64(1).into()).await.unwrap().unwrap();
1113            store
1114                .dictionary_insert(1, &fsandbox::DictionaryItem { key: format!("k{i}"), value: 2 })
1115                .await
1116                .unwrap()
1117                .unwrap();
1118        }
1119
1120        // Range overlaps with id 4
1121        {
1122            let (iterator, server_end) = create_proxy();
1123            store.dictionary_enumerate(1, server_end).await.unwrap().unwrap();
1124            assert_matches!(
1125                iterator.get_next(2, 3).await.unwrap(),
1126                Err(fsandbox::CapabilityStoreError::IdAlreadyExists)
1127            );
1128
1129            let (iterator, server_end) = create_proxy();
1130            store.dictionary_drain(1, Some(server_end)).await.unwrap().unwrap();
1131            assert_matches!(
1132                iterator.get_next(2, 3).await.unwrap(),
1133                Err(fsandbox::CapabilityStoreError::IdAlreadyExists)
1134            );
1135        }
1136    }
1137
1138    #[fuchsia::test]
1139    async fn try_into_open_error_not_supported() {
1140        let dict = Dictionary::new();
1141        assert!(dict.insert(CAP_KEY.clone(), Capability::Data(Data::Int64(1))).is_none());
1142        let scope = ExecutionScope::new();
1143        assert_matches!(
1144            dict.try_into_directory_entry(scope, WeakInstanceToken::new_invalid()).err(),
1145            Some(ConversionError::NotSupported)
1146        );
1147    }
1148
1149    struct MockDir(Counter);
1150    impl DirectoryEntry for MockDir {
1151        fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), Status> {
1152            request.open_remote(self)
1153        }
1154    }
1155    impl GetEntryInfo for MockDir {
1156        fn entry_info(&self) -> EntryInfo {
1157            EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)
1158        }
1159    }
1160    impl RemoteLike for MockDir {
1161        fn open(
1162            self: Arc<Self>,
1163            _scope: ExecutionScope,
1164            relative_path: Path,
1165            _flags: fio::Flags,
1166            _object_request: ObjectRequestRef<'_>,
1167        ) -> Result<(), Status> {
1168            assert_eq!(relative_path.as_ref(), "bar");
1169            self.0.inc();
1170            Ok(())
1171        }
1172    }
1173
1174    #[fuchsia::test]
1175    async fn try_into_open_success() {
1176        let dict = Dictionary::new();
1177        let mock_dir = Arc::new(MockDir(Counter::new(0)));
1178        assert!(
1179            dict.insert(
1180                CAP_KEY.clone(),
1181                DirConnector::from_directory_entry(mock_dir.clone(), fio::PERM_READABLE).into(),
1182            )
1183            .is_none()
1184        );
1185        let scope = ExecutionScope::new();
1186        let remote =
1187            dict.try_into_directory_entry(scope.clone(), WeakInstanceToken::new_invalid()).unwrap();
1188
1189        let dir_client_end = serve_directory(remote.clone(), &scope, fio::PERM_READABLE).unwrap();
1190
1191        assert_eq!(mock_dir.0.get(), 0);
1192        let (client_end, server_end) = Channel::create();
1193        let dir = dir_client_end.channel();
1194        fdio::service_connect_at(dir, &format!("{}/bar", *CAP_KEY), server_end).unwrap();
1195        fasync::Channel::from_channel(client_end).on_closed().await.unwrap();
1196        assert_eq!(mock_dir.0.get(), 1);
1197    }
1198
1199    #[fuchsia::test]
1200    async fn try_into_open_success_nested() {
1201        let inner_dict = Dictionary::new();
1202        let mock_dir = Arc::new(MockDir(Counter::new(0)));
1203        assert!(
1204            inner_dict
1205                .insert(
1206                    CAP_KEY.clone(),
1207                    DirConnector::from_directory_entry(mock_dir.clone(), fio::PERM_READABLE).into(),
1208                )
1209                .is_none()
1210        );
1211        let dict = Dictionary::new();
1212        assert!(dict.insert(CAP_KEY.clone(), Capability::Dictionary(inner_dict)).is_none());
1213
1214        let scope = ExecutionScope::new();
1215        let remote = dict
1216            .try_into_directory_entry(scope.clone(), WeakInstanceToken::new_invalid())
1217            .expect("convert dict into Open capability");
1218
1219        let dir_client_end = serve_directory(remote.clone(), &scope, fio::PERM_READABLE).unwrap();
1220
1221        // List the outer directory and verify the contents.
1222        let dir = dir_client_end.into_proxy();
1223        assert_eq!(
1224            fuchsia_fs::directory::readdir(&dir).await.unwrap(),
1225            vec![directory::DirEntry {
1226                name: CAP_KEY.to_string(),
1227                kind: fio::DirentType::Directory
1228            },]
1229        );
1230
1231        // Open the inner most capability.
1232        assert_eq!(mock_dir.0.get(), 0);
1233        let (client_end, server_end) = Channel::create();
1234        let dir = dir.into_channel().unwrap().into_zx_channel();
1235        fdio::service_connect_at(&dir, &format!("{}/{}/bar", *CAP_KEY, *CAP_KEY), server_end)
1236            .unwrap();
1237        fasync::Channel::from_channel(client_end).on_closed().await.unwrap();
1238        assert_eq!(mock_dir.0.get(), 1)
1239    }
1240
1241    #[fuchsia::test]
1242    async fn switch_between_vec_and_map() {
1243        let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
1244        let _server = fasync::Task::spawn(async move {
1245            let receiver_scope = fasync::Scope::new();
1246            serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
1247        });
1248
1249        let dict = Dictionary::new();
1250        let dict_ref = Capability::Dictionary(dict.clone())
1251            .into_fsandbox_capability(WeakInstanceToken::new_invalid());
1252        let dict_id = 1;
1253        store.import(dict_id, dict_ref).await.unwrap().unwrap();
1254
1255        // Just one less one the switchover point. Count down instead of up to test sorting.
1256        {
1257            for i in (1..=HYBRID_SWITCH_INSERTION_LEN - 1).rev() {
1258                let data = Data::Int64(1).into();
1259                let value = (i + 10) as u64;
1260                store.import(value, data).await.unwrap().unwrap();
1261                store
1262                    .dictionary_insert(
1263                        dict_id,
1264                        &fsandbox::DictionaryItem { key: key_for(i).into(), value },
1265                    )
1266                    .await
1267                    .unwrap()
1268                    .unwrap();
1269            }
1270
1271            let entries = &dict.lock().entries;
1272            let HybridMap::Vec(v) = entries else { panic!() };
1273            v.iter().for_each(|(_, v)| assert_matches!(v, Capability::Data(_)));
1274            let actual_keys: Vec<Key> = v.iter().map(|(k, _)| k.clone()).collect();
1275            let expected: Vec<Key> =
1276                (1..=HYBRID_SWITCH_INSERTION_LEN - 1).map(|i| key_for(i)).collect();
1277            assert_eq!(actual_keys, expected);
1278        }
1279
1280        // Add one more, and the switch happens.
1281        {
1282            let i = HYBRID_SWITCH_INSERTION_LEN;
1283            let data = Data::Int64(1).into();
1284            let value = (i + 10) as u64;
1285            store.import(value, data).await.unwrap().unwrap();
1286            store
1287                .dictionary_insert(
1288                    dict_id,
1289                    &fsandbox::DictionaryItem { key: key_for(i).into(), value },
1290                )
1291                .await
1292                .unwrap()
1293                .unwrap();
1294
1295            let entries = &dict.lock().entries;
1296            let HybridMap::Map(m) = entries else { panic!() };
1297            m.iter().for_each(|(_, m)| assert_matches!(m, Capability::Data(_)));
1298            let actual_keys: Vec<Key> = m.iter().map(|(k, _)| k.clone()).collect();
1299            let expected: Vec<Key> =
1300                (1..=HYBRID_SWITCH_INSERTION_LEN).map(|i| key_for(i)).collect();
1301            assert_eq!(actual_keys, expected);
1302        }
1303
1304        // Now go in reverse: remove just one less than the switchover point for removal.
1305        {
1306            for i in (HYBRID_SWITCH_INSERTION_LEN - HYBRID_SWITCH_REMOVAL_LEN + 1
1307                ..=HYBRID_SWITCH_INSERTION_LEN)
1308                .rev()
1309            {
1310                store.dictionary_remove(dict_id, key_for(i).as_str(), None).await.unwrap().unwrap();
1311            }
1312
1313            let entries = &dict.lock().entries;
1314            let HybridMap::Map(m) = entries else { panic!() };
1315            m.iter().for_each(|(_, v)| assert_matches!(v, Capability::Data(_)));
1316            let actual_keys: Vec<Key> = m.iter().map(|(k, _)| k.clone()).collect();
1317            let expected: Vec<Key> =
1318                (1..=HYBRID_SWITCH_REMOVAL_LEN + 1).map(|i| key_for(i)).collect();
1319            assert_eq!(actual_keys, expected);
1320        }
1321
1322        // Finally, remove one more, and it switches back to a map.
1323        {
1324            let i = HYBRID_SWITCH_REMOVAL_LEN + 1;
1325            store.dictionary_remove(dict_id, key_for(i).as_str(), None).await.unwrap().unwrap();
1326
1327            let entries = &dict.lock().entries;
1328            let HybridMap::Vec(v) = entries else { panic!() };
1329            v.iter().for_each(|(_, v)| assert_matches!(v, Capability::Data(_)));
1330            let actual_keys: Vec<Key> = v.iter().map(|(k, _)| k.clone()).collect();
1331            let expected: Vec<Key> = (1..=HYBRID_SWITCH_REMOVAL_LEN).map(|i| key_for(i)).collect();
1332            assert_eq!(actual_keys, expected);
1333        }
1334    }
1335
1336    #[test_case(TestType::Small)]
1337    #[test_case(TestType::Big)]
1338    #[fuchsia::test]
1339    async fn register_update_notifier(test_type: TestType) {
1340        // We would like to use futures::channel::oneshot here but because the sender is captured
1341        // by the `FnMut` to `register_update_notifier`, it would not compile because `send` would
1342        // consume the sender. std::sync::mpsc is used instead of futures::channel::mpsc because
1343        // the register_update_notifier callback is not async-aware.
1344        use std::sync::mpsc;
1345
1346        let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
1347        let _server = fasync::Task::spawn(async move {
1348            let receiver_scope = fasync::Scope::new();
1349            serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
1350        });
1351
1352        #[derive(PartialEq)]
1353        enum Update {
1354            Add(Key),
1355            Remove(Key),
1356            Idle,
1357        }
1358        impl fmt::Debug for Update {
1359            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1360                match self {
1361                    Self::Add(k) => write!(f, "Add({k})"),
1362                    Self::Remove(k) => write!(f, "Remove({k})"),
1363                    Self::Idle => write!(f, "Idle"),
1364                }
1365            }
1366        }
1367
1368        let dict = new_dict(test_type);
1369        let (update_tx, update_rx) = mpsc::channel();
1370        let subscribed = Arc::new(Mutex::new(true));
1371        let subscribed2 = subscribed.clone();
1372        dict.register_update_notifier(Box::new(move |update: EntryUpdate<'_>| {
1373            let u = match update {
1374                EntryUpdate::Add(k, v) => {
1375                    assert_matches!(v, Capability::Data(_));
1376                    Update::Add(k.into())
1377                }
1378                EntryUpdate::Remove(k) => Update::Remove(k.into()),
1379                EntryUpdate::Idle => Update::Idle,
1380            };
1381            update_tx.send(u).unwrap();
1382            if *subscribed2.lock() {
1383                UpdateNotifierRetention::Retain
1384            } else {
1385                UpdateNotifierRetention::Drop_
1386            }
1387        }));
1388        let dict_ref = Capability::Dictionary(dict.clone())
1389            .into_fsandbox_capability(WeakInstanceToken::new_invalid());
1390        let dict_id = 1;
1391        store.import(dict_id, dict_ref).await.unwrap().unwrap();
1392
1393        // 1. Three inserts, one of which overlaps
1394        let i = 1;
1395        let data = Data::Int64(1).into();
1396        let value = (i + 10) as u64;
1397        store.import(value, data).await.unwrap().unwrap();
1398        store
1399            .dictionary_insert(dict_id, &fsandbox::DictionaryItem { key: key_for(i).into(), value })
1400            .await
1401            .unwrap()
1402            .unwrap();
1403
1404        for expected_result in [Ok(()), Err(fsandbox::CapabilityStoreError::ItemAlreadyExists)] {
1405            let i = 2;
1406            let data = Data::Int64(1).into();
1407            let value = (i + 10) as u64;
1408            store.import(value, data).await.unwrap().unwrap();
1409            let result = store
1410                .dictionary_insert(
1411                    dict_id,
1412                    &fsandbox::DictionaryItem { key: key_for(i).into(), value },
1413                )
1414                .await
1415                .unwrap();
1416            assert_eq!(result, expected_result);
1417        }
1418
1419        // 2. Remove the same item twice. Second time is a no-op
1420        for expected_result in [Ok(()), Err(fsandbox::CapabilityStoreError::ItemNotFound)] {
1421            let i = 1;
1422            let result =
1423                store.dictionary_remove(dict_id, &key_for(i).as_str(), None).await.unwrap();
1424            assert_eq!(result, expected_result);
1425        }
1426
1427        // 3. One more insert, then drain
1428        let i = 3;
1429        let data = Data::Int64(1).into();
1430        let value = (i + 10) as u64;
1431        store.import(value, data).await.unwrap().unwrap();
1432        store
1433            .dictionary_insert(dict_id, &fsandbox::DictionaryItem { key: key_for(i).into(), value })
1434            .await
1435            .unwrap()
1436            .unwrap();
1437        store.dictionary_drain(dict_id, None).await.unwrap().unwrap();
1438
1439        // 4. Unsubscribe to updates
1440        *subscribed.lock() = false;
1441        let i = 4;
1442        let data = Data::Int64(1).into();
1443        let value = (i + 10) as u64;
1444        store.import(value, data).await.unwrap().unwrap();
1445        store
1446            .dictionary_insert(dict_id, &fsandbox::DictionaryItem { key: key_for(i).into(), value })
1447            .await
1448            .unwrap()
1449            .unwrap();
1450        // This Remove shouldn't appear in the updates because we unsubscribed
1451        store.dictionary_remove(dict_id, key_for(i).as_str(), None).await.unwrap().unwrap();
1452
1453        // Check the updates
1454        let updates: Vec<_> = iter::from_fn(move || match update_rx.try_recv() {
1455            Ok(e) => Some(e),
1456            Err(mpsc::TryRecvError::Disconnected) => None,
1457            // The producer should die before we get here because we unsubscribed
1458            Err(mpsc::TryRecvError::Empty) => unreachable!(),
1459        })
1460        .collect();
1461        let expected_updates = [
1462            Update::Idle,
1463            // 1.
1464            Update::Add(key_for(1)),
1465            Update::Add(key_for(2)),
1466            Update::Remove(key_for(2)),
1467            Update::Add(key_for(2)),
1468            // 2.
1469            Update::Remove(key_for(1)),
1470            // 3.
1471            Update::Add(key_for(3)),
1472            Update::Remove(key_for(2)),
1473            Update::Remove(key_for(3)),
1474            // 4.
1475            Update::Add(key_for(4)),
1476        ];
1477        match test_type {
1478            TestType::Small => {
1479                assert_eq!(updates, expected_updates);
1480            }
1481            TestType::Big => {
1482                // Skip over items populated to make the dict big
1483                let updates = &updates[HYBRID_SWITCH_INSERTION_LEN..];
1484                let nexpected = expected_updates.len() - 1;
1485                assert_eq!(updates[..nexpected], expected_updates[..nexpected]);
1486
1487                // Skip over these items again when they are drained
1488                let expected_updates = &expected_updates[nexpected..];
1489                let updates = &updates[nexpected + HYBRID_SWITCH_INSERTION_LEN..];
1490                assert_eq!(updates, expected_updates);
1491            }
1492        }
1493    }
1494
1495    #[fuchsia::test]
1496    async fn live_update_add_nodes() {
1497        let dict = Dictionary::new();
1498        let scope = ExecutionScope::new();
1499        let remote = dict
1500            .clone()
1501            .try_into_directory_entry(scope.clone(), WeakInstanceToken::new_invalid())
1502            .unwrap();
1503        let dir_proxy =
1504            serve_directory(remote.clone(), &scope, fio::PERM_READABLE).unwrap().into_proxy();
1505        let mut watcher = fuchsia_fs::directory::Watcher::new(&dir_proxy)
1506            .await
1507            .expect("failed to create watcher");
1508
1509        // Assert that the directory is empty, because the dictionary is empty.
1510        assert_eq!(fuchsia_fs::directory::readdir(&dir_proxy).await.unwrap(), vec![]);
1511        assert_eq!(
1512            watcher.next().await,
1513            Some(Ok(fuchsia_fs::directory::WatchMessage {
1514                event: fuchsia_fs::directory::WatchEvent::EXISTING,
1515                filename: ".".into(),
1516            })),
1517        );
1518        assert_eq!(
1519            watcher.next().await,
1520            Some(Ok(fuchsia_fs::directory::WatchMessage {
1521                event: fuchsia_fs::directory::WatchEvent::IDLE,
1522                filename: "".into(),
1523            })),
1524        );
1525
1526        // Add an item to the dictionary, and assert that the projected directory contains the
1527        // added item.
1528        let fs = pseudo_directory! {};
1529        let dir_connector = DirConnector::from_directory_entry(fs, fio::PERM_READABLE);
1530        assert!(dict.insert("a".parse().unwrap(), dir_connector.clone().into()).is_none());
1531
1532        assert_eq!(
1533            fuchsia_fs::directory::readdir(&dir_proxy).await.unwrap(),
1534            vec![directory::DirEntry { name: "a".to_string(), kind: fio::DirentType::Directory },]
1535        );
1536        assert_eq!(
1537            watcher.next().await,
1538            Some(Ok(fuchsia_fs::directory::WatchMessage {
1539                event: fuchsia_fs::directory::WatchEvent::ADD_FILE,
1540                filename: "a".into(),
1541            })),
1542        );
1543
1544        // Add an item to the dictionary, and assert that the projected directory contains the
1545        // added item.
1546        assert!(dict.insert("b".parse().unwrap(), dir_connector.into()).is_none());
1547        let mut readdir_results = fuchsia_fs::directory::readdir(&dir_proxy).await.unwrap();
1548        readdir_results.sort_by(|entry_1, entry_2| entry_1.name.cmp(&entry_2.name));
1549        assert_eq!(
1550            readdir_results,
1551            vec![
1552                directory::DirEntry { name: "a".to_string(), kind: fio::DirentType::Directory },
1553                directory::DirEntry { name: "b".to_string(), kind: fio::DirentType::Directory },
1554            ]
1555        );
1556        assert_eq!(
1557            watcher.next().await,
1558            Some(Ok(fuchsia_fs::directory::WatchMessage {
1559                event: fuchsia_fs::directory::WatchEvent::ADD_FILE,
1560                filename: "b".into(),
1561            })),
1562        );
1563    }
1564
1565    #[fuchsia::test]
1566    async fn live_update_remove_nodes() {
1567        let dict = Dictionary::new();
1568        let fs = pseudo_directory! {};
1569        let dir_connector = DirConnector::from_directory_entry(fs, fio::PERM_READABLE);
1570        assert!(dict.insert("a".parse().unwrap(), dir_connector.clone().into()).is_none());
1571        assert!(dict.insert("b".parse().unwrap(), dir_connector.clone().into()).is_none());
1572        assert!(dict.insert("c".parse().unwrap(), dir_connector.clone().into()).is_none());
1573
1574        let scope = ExecutionScope::new();
1575        let remote = dict
1576            .clone()
1577            .try_into_directory_entry(scope.clone(), WeakInstanceToken::new_invalid())
1578            .unwrap();
1579        let dir_proxy =
1580            serve_directory(remote.clone(), &scope, fio::PERM_READABLE).unwrap().into_proxy();
1581        let mut watcher = fuchsia_fs::directory::Watcher::new(&dir_proxy)
1582            .await
1583            .expect("failed to create watcher");
1584
1585        // The dictionary already had three entries in it when the directory proxy was created, so
1586        // we should see those in the directory. We check both readdir and via the watcher API.
1587        let mut readdir_results = fuchsia_fs::directory::readdir(&dir_proxy).await.unwrap();
1588        readdir_results.sort_by(|entry_1, entry_2| entry_1.name.cmp(&entry_2.name));
1589        assert_eq!(
1590            readdir_results,
1591            vec![
1592                directory::DirEntry { name: "a".to_string(), kind: fio::DirentType::Directory },
1593                directory::DirEntry { name: "b".to_string(), kind: fio::DirentType::Directory },
1594                directory::DirEntry { name: "c".to_string(), kind: fio::DirentType::Directory },
1595            ]
1596        );
1597        let mut existing_files = vec![];
1598        loop {
1599            match watcher.next().await {
1600                Some(Ok(fuchsia_fs::directory::WatchMessage { event, filename }))
1601                    if event == fuchsia_fs::directory::WatchEvent::EXISTING =>
1602                {
1603                    existing_files.push(filename)
1604                }
1605                Some(Ok(fuchsia_fs::directory::WatchMessage { event, filename: _ }))
1606                    if event == fuchsia_fs::directory::WatchEvent::IDLE =>
1607                {
1608                    break;
1609                }
1610                other_message => panic!("unexpected message: {:?}", other_message),
1611            }
1612        }
1613        existing_files.sort();
1614        let expected_files: Vec<std::path::PathBuf> =
1615            vec![".".into(), "a".into(), "b".into(), "c".into()];
1616        assert_eq!(existing_files, expected_files,);
1617
1618        // Remove each entry from the dictionary, and observe the directory watcher API inform us
1619        // that it has been removed.
1620        let _ =
1621            dict.remove(&BorrowedKey::new("a").unwrap()).expect("capability was not in dictionary");
1622        assert_eq!(
1623            watcher.next().await,
1624            Some(Ok(fuchsia_fs::directory::WatchMessage {
1625                event: fuchsia_fs::directory::WatchEvent::REMOVE_FILE,
1626                filename: "a".into(),
1627            })),
1628        );
1629
1630        let _ =
1631            dict.remove(&BorrowedKey::new("b").unwrap()).expect("capability was not in dictionary");
1632        assert_eq!(
1633            watcher.next().await,
1634            Some(Ok(fuchsia_fs::directory::WatchMessage {
1635                event: fuchsia_fs::directory::WatchEvent::REMOVE_FILE,
1636                filename: "b".into(),
1637            })),
1638        );
1639
1640        let _ =
1641            dict.remove(&BorrowedKey::new("c").unwrap()).expect("capability was not in dictionary");
1642        assert_eq!(
1643            watcher.next().await,
1644            Some(Ok(fuchsia_fs::directory::WatchMessage {
1645                event: fuchsia_fs::directory::WatchEvent::REMOVE_FILE,
1646                filename: "c".into(),
1647            })),
1648        );
1649
1650        // At this point there are no entries left in the dictionary, so the directory should be
1651        // empty too.
1652        assert_eq!(fuchsia_fs::directory::readdir(&dir_proxy).await.unwrap(), vec![],);
1653    }
1654
1655    #[fuchsia::test]
1656    async fn into_directory_oneshot() {
1657        let dict = Dictionary::new();
1658        let inner_dict = Dictionary::new();
1659        let fs = pseudo_directory! {};
1660        let dir_connector = DirConnector::from_directory_entry(fs, fio::PERM_READABLE);
1661        assert!(inner_dict.insert("x".parse().unwrap(), dir_connector.clone().into()).is_none());
1662        assert!(dict.insert("a".parse().unwrap(), dir_connector.clone().into()).is_none());
1663        assert!(dict.insert("b".parse().unwrap(), dir_connector.clone().into()).is_none());
1664        assert!(
1665            dict.insert("c".parse().unwrap(), Capability::Dictionary(inner_dict.clone())).is_none()
1666        );
1667
1668        let scope = ExecutionScope::new();
1669        let remote = dict
1670            .clone()
1671            .try_into_directory_entry_oneshot(scope.clone(), WeakInstanceToken::new_invalid())
1672            .unwrap();
1673        let dir_proxy =
1674            serve_directory(remote.clone(), &scope, fio::PERM_READABLE).unwrap().into_proxy();
1675
1676        // The dictionary already had three entries in it when the directory proxy was created, so
1677        // we should see those in the directory.
1678        let mut readdir_results = fuchsia_fs::directory::readdir(&dir_proxy).await.unwrap();
1679        readdir_results.sort_by(|entry_1, entry_2| entry_1.name.cmp(&entry_2.name));
1680        assert_eq!(
1681            readdir_results,
1682            vec![
1683                directory::DirEntry { name: "a".to_string(), kind: fio::DirentType::Directory },
1684                directory::DirEntry { name: "b".to_string(), kind: fio::DirentType::Directory },
1685                directory::DirEntry { name: "c".to_string(), kind: fio::DirentType::Directory },
1686            ]
1687        );
1688
1689        let (inner_proxy, server) = create_proxy::<fio::DirectoryMarker>();
1690        dir_proxy.open("c", Default::default(), &Default::default(), server.into()).unwrap();
1691        let readdir_results = fuchsia_fs::directory::readdir(&inner_proxy).await.unwrap();
1692        assert_eq!(
1693            readdir_results,
1694            vec![directory::DirEntry { name: "x".to_string(), kind: fio::DirentType::Directory }]
1695        );
1696
1697        // The Dict should be empty because `try_into_directory_entry_oneshot consumed it.
1698        assert!(dict.is_empty());
1699        assert!(inner_dict.is_empty());
1700
1701        // Adding to the empty Dictionary has no impact on the directory.
1702        assert!(dict.insert("z".parse().unwrap(), dir_connector.clone().into()).is_none());
1703        let mut readdir_results = fuchsia_fs::directory::readdir(&dir_proxy).await.unwrap();
1704        readdir_results.sort_by(|entry_1, entry_2| entry_1.name.cmp(&entry_2.name));
1705        assert_eq!(
1706            readdir_results,
1707            vec![
1708                directory::DirEntry { name: "a".to_string(), kind: fio::DirentType::Directory },
1709                directory::DirEntry { name: "b".to_string(), kind: fio::DirentType::Directory },
1710                directory::DirEntry { name: "c".to_string(), kind: fio::DirentType::Directory },
1711            ]
1712        );
1713    }
1714
1715    /// Generates a key from an integer such that if i < j, key_for(i) < key_for(j).
1716    /// (A simple string conversion doesn't work because 1 < 10 but "1" > "10" in terms of
1717    /// string comparison.)
1718    fn key_for(i: usize) -> Key {
1719        iter::repeat("A").take(i).collect::<String>().parse().unwrap()
1720    }
1721
1722    fn new_dict(test_type: TestType) -> Dictionary {
1723        let dict = Dictionary::new();
1724        match test_type {
1725            TestType::Small => {}
1726            TestType::Big => {
1727                for i in 1..=HYBRID_SWITCH_INSERTION_LEN {
1728                    // These items will come last in the order as long as all other keys begin with
1729                    // a capital letter
1730                    assert!(
1731                        dict.insert(
1732                            format!("_{i}").parse().unwrap(),
1733                            Capability::Data(Data::Int64(1))
1734                        )
1735                        .is_none()
1736                    );
1737                }
1738            }
1739        }
1740        dict
1741    }
1742
1743    fn adjusted_len(dict: &Dictionary, test_type: TestType) -> usize {
1744        let ofs = match test_type {
1745            TestType::Small => 0,
1746            TestType::Big => HYBRID_SWITCH_INSERTION_LEN,
1747        };
1748        dict.lock().entries.len() - ofs
1749    }
1750}