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        dict.insert(CAP_KEY.clone(), Capability::Data(Data::Int64(1))).unwrap();
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        dict.insert(CAP_KEY.clone(), Capability::Data(Data::Int64(1))).unwrap();
537        assert_eq!(adjusted_len(&dict, test_type), 1);
538        let (ch, _) = fidl::Channel::create();
539        let handle = Handle::new(ch.into_handle());
540        dict.insert("h".parse().unwrap(), Capability::Handle(handle)).unwrap();
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        dict.insert("data1".parse().unwrap(), Capability::Data(Data::Int64(1))).unwrap();
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            dict.insert(format!("{}", i).parse().unwrap(), Capability::Data(Data::Int64(1)))
928                .unwrap();
929        }
930
931        let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
932        let _server = fasync::Task::spawn(async move {
933            let receiver_scope = fasync::Scope::new();
934            serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
935        });
936        let dict_ref = Capability::Dictionary(dict.clone())
937            .into_fsandbox_capability(WeakInstanceToken::new_invalid());
938        let dict_id = id_gen.next();
939        store.import(dict_id, dict_ref).await.unwrap().unwrap();
940
941        let (key_iterator, server_end) = create_proxy();
942        store.dictionary_keys(dict_id, server_end).await.unwrap().unwrap();
943        let (item_iterator, server_end) = create_proxy();
944        store.dictionary_enumerate(dict_id, server_end).await.unwrap().unwrap();
945
946        // Get all the entries from the Dictionary with `GetNext`.
947        let mut num_got_items: u32 = 0;
948        let mut start_id = 100;
949        let limit = fsandbox::MAX_DICTIONARY_ITERATOR_CHUNK;
950        for expected_len in EXPECTED_CHUNK_LENGTHS {
951            let keys = key_iterator.get_next().await.unwrap();
952            let (items, end_id) = item_iterator.get_next(start_id, limit).await.unwrap().unwrap();
953            if keys.is_empty() && items.is_empty() {
954                break;
955            }
956            assert_eq!(*expected_len, keys.len() as u32);
957            assert_eq!(*expected_len, items.len() as u32);
958            assert_eq!(u64::from(*expected_len), end_id - start_id);
959            start_id = end_id;
960            num_got_items += *expected_len;
961        }
962
963        // GetNext should return no items once all items have been returned.
964        let (items, _) = item_iterator.get_next(start_id, limit).await.unwrap().unwrap();
965        assert!(items.is_empty());
966        assert!(key_iterator.get_next().await.unwrap().is_empty());
967
968        assert_eq!(num_got_items, NUM_ENTRIES);
969    }
970
971    #[fuchsia::test]
972    async fn drain_batches() {
973        // Number of entries in the Dictionary that will be enumerated.
974        //
975        // This value was chosen such that that GetNext returns multiple chunks of different sizes.
976        const NUM_ENTRIES: u32 = fsandbox::MAX_DICTIONARY_ITERATOR_CHUNK * 2 + 1;
977
978        // Number of items we expect in each chunk, for every chunk we expect to get.
979        const EXPECTED_CHUNK_LENGTHS: &[u32] =
980            &[fsandbox::MAX_DICTIONARY_ITERATOR_CHUNK, fsandbox::MAX_DICTIONARY_ITERATOR_CHUNK, 1];
981
982        // Create a Dictionary with [NUM_ENTRIES] entries that have Data values.
983        let dict = Dictionary::new();
984        for i in 0..NUM_ENTRIES {
985            dict.insert(format!("{}", i).parse().unwrap(), Capability::Data(Data::Int64(1)))
986                .unwrap();
987        }
988
989        let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
990        let _server = fasync::Task::spawn(async move {
991            let receiver_scope = fasync::Scope::new();
992            serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
993        });
994        let dict_ref = Capability::Dictionary(dict.clone())
995            .into_fsandbox_capability(WeakInstanceToken::new_invalid());
996        let dict_id = 1;
997        store.import(dict_id, dict_ref).await.unwrap().unwrap();
998
999        let (item_iterator, server_end) = create_proxy();
1000        store.dictionary_drain(dict_id, Some(server_end)).await.unwrap().unwrap();
1001
1002        // Get all the entries from the Dictionary with `GetNext`.
1003        let mut num_got_items: u32 = 0;
1004        let mut start_id = 100;
1005        let limit = fsandbox::MAX_DICTIONARY_ITERATOR_CHUNK;
1006        for expected_len in EXPECTED_CHUNK_LENGTHS {
1007            let (items, end_id) = item_iterator.get_next(start_id, limit).await.unwrap().unwrap();
1008            if items.is_empty() {
1009                break;
1010            }
1011            assert_eq!(*expected_len, items.len() as u32);
1012            assert_eq!(u64::from(*expected_len), end_id - start_id);
1013            start_id = end_id;
1014            num_got_items += *expected_len;
1015        }
1016
1017        // GetNext should return no items once all items have been returned.
1018        let (items, _) = item_iterator.get_next(start_id, limit).await.unwrap().unwrap();
1019        assert!(items.is_empty());
1020
1021        assert_eq!(num_got_items, NUM_ENTRIES);
1022
1023        // Dictionary should now be empty.
1024        assert!(dict.is_empty());
1025    }
1026
1027    #[fuchsia::test]
1028    async fn read_error() {
1029        let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
1030        let _server = fasync::Task::spawn(async move {
1031            let receiver_scope = fasync::Scope::new();
1032            serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
1033        });
1034
1035        store.import(2, Data::Int64(1).into()).await.unwrap().unwrap();
1036
1037        let (_, server_end) = create_proxy();
1038        assert_matches!(
1039            store.dictionary_keys(1, server_end).await.unwrap(),
1040            Err(fsandbox::CapabilityStoreError::IdNotFound)
1041        );
1042        let (_, server_end) = create_proxy();
1043        assert_matches!(
1044            store.dictionary_enumerate(1, server_end).await.unwrap(),
1045            Err(fsandbox::CapabilityStoreError::IdNotFound)
1046        );
1047        assert_matches!(
1048            store.dictionary_drain(1, None).await.unwrap(),
1049            Err(fsandbox::CapabilityStoreError::IdNotFound)
1050        );
1051
1052        let (_, server_end) = create_proxy();
1053        assert_matches!(
1054            store.dictionary_keys(2, server_end).await.unwrap(),
1055            Err(fsandbox::CapabilityStoreError::WrongType)
1056        );
1057        let (_, server_end) = create_proxy();
1058        assert_matches!(
1059            store.dictionary_enumerate(2, server_end).await.unwrap(),
1060            Err(fsandbox::CapabilityStoreError::WrongType)
1061        );
1062        assert_matches!(
1063            store.dictionary_drain(2, None).await.unwrap(),
1064            Err(fsandbox::CapabilityStoreError::WrongType)
1065        );
1066    }
1067
1068    #[fuchsia::test]
1069    async fn read_iterator_error() {
1070        let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
1071        let _server = fasync::Task::spawn(async move {
1072            let receiver_scope = fasync::Scope::new();
1073            serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
1074        });
1075
1076        store.dictionary_create(1).await.unwrap().unwrap();
1077
1078        {
1079            let (iterator, server_end) = create_proxy();
1080            store.dictionary_enumerate(1, server_end).await.unwrap().unwrap();
1081            assert_matches!(
1082                iterator.get_next(2, fsandbox::MAX_DICTIONARY_ITERATOR_CHUNK + 1).await.unwrap(),
1083                Err(fsandbox::CapabilityStoreError::InvalidArgs)
1084            );
1085            let (iterator, server_end) = create_proxy();
1086            store.dictionary_enumerate(1, server_end).await.unwrap().unwrap();
1087            assert_matches!(
1088                iterator.get_next(2, 0).await.unwrap(),
1089                Err(fsandbox::CapabilityStoreError::InvalidArgs)
1090            );
1091
1092            let (iterator, server_end) = create_proxy();
1093            store.dictionary_drain(1, Some(server_end)).await.unwrap().unwrap();
1094            assert_matches!(
1095                iterator.get_next(2, fsandbox::MAX_DICTIONARY_ITERATOR_CHUNK + 1).await.unwrap(),
1096                Err(fsandbox::CapabilityStoreError::InvalidArgs)
1097            );
1098            let (iterator, server_end) = create_proxy();
1099            store.dictionary_drain(1, Some(server_end)).await.unwrap().unwrap();
1100            assert_matches!(
1101                iterator.get_next(2, 0).await.unwrap(),
1102                Err(fsandbox::CapabilityStoreError::InvalidArgs)
1103            );
1104        }
1105
1106        store.import(4, Data::Int64(1).into()).await.unwrap().unwrap();
1107        for i in 0..3 {
1108            store.import(2, Data::Int64(1).into()).await.unwrap().unwrap();
1109            store
1110                .dictionary_insert(1, &fsandbox::DictionaryItem { key: format!("k{i}"), value: 2 })
1111                .await
1112                .unwrap()
1113                .unwrap();
1114        }
1115
1116        // Range overlaps with id 4
1117        {
1118            let (iterator, server_end) = create_proxy();
1119            store.dictionary_enumerate(1, server_end).await.unwrap().unwrap();
1120            assert_matches!(
1121                iterator.get_next(2, 3).await.unwrap(),
1122                Err(fsandbox::CapabilityStoreError::IdAlreadyExists)
1123            );
1124
1125            let (iterator, server_end) = create_proxy();
1126            store.dictionary_drain(1, Some(server_end)).await.unwrap().unwrap();
1127            assert_matches!(
1128                iterator.get_next(2, 3).await.unwrap(),
1129                Err(fsandbox::CapabilityStoreError::IdAlreadyExists)
1130            );
1131        }
1132    }
1133
1134    #[fuchsia::test]
1135    async fn try_into_open_error_not_supported() {
1136        let dict = Dictionary::new();
1137        dict.insert(CAP_KEY.clone(), Capability::Data(Data::Int64(1)))
1138            .expect("dict entry already exists");
1139        let scope = ExecutionScope::new();
1140        assert_matches!(
1141            dict.try_into_directory_entry(scope, WeakInstanceToken::new_invalid()).err(),
1142            Some(ConversionError::NotSupported)
1143        );
1144    }
1145
1146    struct MockDir(Counter);
1147    impl DirectoryEntry for MockDir {
1148        fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), Status> {
1149            request.open_remote(self)
1150        }
1151    }
1152    impl GetEntryInfo for MockDir {
1153        fn entry_info(&self) -> EntryInfo {
1154            EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)
1155        }
1156    }
1157    impl RemoteLike for MockDir {
1158        fn open(
1159            self: Arc<Self>,
1160            _scope: ExecutionScope,
1161            relative_path: Path,
1162            _flags: fio::Flags,
1163            _object_request: ObjectRequestRef<'_>,
1164        ) -> Result<(), Status> {
1165            assert_eq!(relative_path.as_ref(), "bar");
1166            self.0.inc();
1167            Ok(())
1168        }
1169    }
1170
1171    #[fuchsia::test]
1172    async fn try_into_open_success() {
1173        let dict = Dictionary::new();
1174        let mock_dir = Arc::new(MockDir(Counter::new(0)));
1175        dict.insert(
1176            CAP_KEY.clone(),
1177            DirConnector::from_directory_entry(mock_dir.clone(), fio::PERM_READABLE).into(),
1178        )
1179        .expect("dict entry already exists");
1180        let scope = ExecutionScope::new();
1181        let remote =
1182            dict.try_into_directory_entry(scope.clone(), WeakInstanceToken::new_invalid()).unwrap();
1183
1184        let dir_client_end = serve_directory(remote.clone(), &scope, fio::PERM_READABLE).unwrap();
1185
1186        assert_eq!(mock_dir.0.get(), 0);
1187        let (client_end, server_end) = Channel::create();
1188        let dir = dir_client_end.channel();
1189        fdio::service_connect_at(dir, &format!("{}/bar", *CAP_KEY), server_end).unwrap();
1190        fasync::Channel::from_channel(client_end).on_closed().await.unwrap();
1191        assert_eq!(mock_dir.0.get(), 1);
1192    }
1193
1194    #[fuchsia::test]
1195    async fn try_into_open_success_nested() {
1196        let inner_dict = Dictionary::new();
1197        let mock_dir = Arc::new(MockDir(Counter::new(0)));
1198        inner_dict
1199            .insert(
1200                CAP_KEY.clone(),
1201                DirConnector::from_directory_entry(mock_dir.clone(), fio::PERM_READABLE).into(),
1202            )
1203            .expect("dict entry already exists");
1204        let dict = Dictionary::new();
1205        dict.insert(CAP_KEY.clone(), Capability::Dictionary(inner_dict)).unwrap();
1206
1207        let scope = ExecutionScope::new();
1208        let remote = dict
1209            .try_into_directory_entry(scope.clone(), WeakInstanceToken::new_invalid())
1210            .expect("convert dict into Open capability");
1211
1212        let dir_client_end = serve_directory(remote.clone(), &scope, fio::PERM_READABLE).unwrap();
1213
1214        // List the outer directory and verify the contents.
1215        let dir = dir_client_end.into_proxy();
1216        assert_eq!(
1217            fuchsia_fs::directory::readdir(&dir).await.unwrap(),
1218            vec![directory::DirEntry {
1219                name: CAP_KEY.to_string(),
1220                kind: fio::DirentType::Directory
1221            },]
1222        );
1223
1224        // Open the inner most capability.
1225        assert_eq!(mock_dir.0.get(), 0);
1226        let (client_end, server_end) = Channel::create();
1227        let dir = dir.into_channel().unwrap().into_zx_channel();
1228        fdio::service_connect_at(&dir, &format!("{}/{}/bar", *CAP_KEY, *CAP_KEY), server_end)
1229            .unwrap();
1230        fasync::Channel::from_channel(client_end).on_closed().await.unwrap();
1231        assert_eq!(mock_dir.0.get(), 1)
1232    }
1233
1234    #[fuchsia::test]
1235    async fn switch_between_vec_and_map() {
1236        let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
1237        let _server = fasync::Task::spawn(async move {
1238            let receiver_scope = fasync::Scope::new();
1239            serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
1240        });
1241
1242        let dict = Dictionary::new();
1243        let dict_ref = Capability::Dictionary(dict.clone())
1244            .into_fsandbox_capability(WeakInstanceToken::new_invalid());
1245        let dict_id = 1;
1246        store.import(dict_id, dict_ref).await.unwrap().unwrap();
1247
1248        // Just one less one the switchover point. Count down instead of up to test sorting.
1249        {
1250            for i in (1..=HYBRID_SWITCH_INSERTION_LEN - 1).rev() {
1251                let data = Data::Int64(1).into();
1252                let value = (i + 10) as u64;
1253                store.import(value, data).await.unwrap().unwrap();
1254                store
1255                    .dictionary_insert(
1256                        dict_id,
1257                        &fsandbox::DictionaryItem { key: key_for(i).into(), value },
1258                    )
1259                    .await
1260                    .unwrap()
1261                    .unwrap();
1262            }
1263
1264            let entries = &dict.lock().entries;
1265            let HybridMap::Vec(v) = entries else { panic!() };
1266            v.iter().for_each(|(_, v)| assert_matches!(v, Capability::Data(_)));
1267            let actual_keys: Vec<Key> = v.iter().map(|(k, _)| k.clone()).collect();
1268            let expected: Vec<Key> =
1269                (1..=HYBRID_SWITCH_INSERTION_LEN - 1).map(|i| key_for(i)).collect();
1270            assert_eq!(actual_keys, expected);
1271        }
1272
1273        // Add one more, and the switch happens.
1274        {
1275            let i = HYBRID_SWITCH_INSERTION_LEN;
1276            let data = Data::Int64(1).into();
1277            let value = (i + 10) as u64;
1278            store.import(value, data).await.unwrap().unwrap();
1279            store
1280                .dictionary_insert(
1281                    dict_id,
1282                    &fsandbox::DictionaryItem { key: key_for(i).into(), value },
1283                )
1284                .await
1285                .unwrap()
1286                .unwrap();
1287
1288            let entries = &dict.lock().entries;
1289            let HybridMap::Map(m) = entries else { panic!() };
1290            m.iter().for_each(|(_, m)| assert_matches!(m, Capability::Data(_)));
1291            let actual_keys: Vec<Key> = m.iter().map(|(k, _)| k.clone()).collect();
1292            let expected: Vec<Key> =
1293                (1..=HYBRID_SWITCH_INSERTION_LEN).map(|i| key_for(i)).collect();
1294            assert_eq!(actual_keys, expected);
1295        }
1296
1297        // Now go in reverse: remove just one less than the switchover point for removal.
1298        {
1299            for i in (HYBRID_SWITCH_INSERTION_LEN - HYBRID_SWITCH_REMOVAL_LEN + 1
1300                ..=HYBRID_SWITCH_INSERTION_LEN)
1301                .rev()
1302            {
1303                store.dictionary_remove(dict_id, key_for(i).as_str(), None).await.unwrap().unwrap();
1304            }
1305
1306            let entries = &dict.lock().entries;
1307            let HybridMap::Map(m) = entries else { panic!() };
1308            m.iter().for_each(|(_, v)| assert_matches!(v, Capability::Data(_)));
1309            let actual_keys: Vec<Key> = m.iter().map(|(k, _)| k.clone()).collect();
1310            let expected: Vec<Key> =
1311                (1..=HYBRID_SWITCH_REMOVAL_LEN + 1).map(|i| key_for(i)).collect();
1312            assert_eq!(actual_keys, expected);
1313        }
1314
1315        // Finally, remove one more, and it switches back to a map.
1316        {
1317            let i = HYBRID_SWITCH_REMOVAL_LEN + 1;
1318            store.dictionary_remove(dict_id, key_for(i).as_str(), None).await.unwrap().unwrap();
1319
1320            let entries = &dict.lock().entries;
1321            let HybridMap::Vec(v) = entries else { panic!() };
1322            v.iter().for_each(|(_, v)| assert_matches!(v, Capability::Data(_)));
1323            let actual_keys: Vec<Key> = v.iter().map(|(k, _)| k.clone()).collect();
1324            let expected: Vec<Key> = (1..=HYBRID_SWITCH_REMOVAL_LEN).map(|i| key_for(i)).collect();
1325            assert_eq!(actual_keys, expected);
1326        }
1327    }
1328
1329    #[test_case(TestType::Small)]
1330    #[test_case(TestType::Big)]
1331    #[fuchsia::test]
1332    async fn register_update_notifier(test_type: TestType) {
1333        // We would like to use futures::channel::oneshot here but because the sender is captured
1334        // by the `FnMut` to `register_update_notifier`, it would not compile because `send` would
1335        // consume the sender. std::sync::mpsc is used instead of futures::channel::mpsc because
1336        // the register_update_notifier callback is not async-aware.
1337        use std::sync::mpsc;
1338
1339        let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
1340        let _server = fasync::Task::spawn(async move {
1341            let receiver_scope = fasync::Scope::new();
1342            serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
1343        });
1344
1345        #[derive(PartialEq)]
1346        enum Update {
1347            Add(Key),
1348            Remove(Key),
1349            Idle,
1350        }
1351        impl fmt::Debug for Update {
1352            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1353                match self {
1354                    Self::Add(k) => write!(f, "Add({k})"),
1355                    Self::Remove(k) => write!(f, "Remove({k})"),
1356                    Self::Idle => write!(f, "Idle"),
1357                }
1358            }
1359        }
1360
1361        let dict = new_dict(test_type);
1362        let (update_tx, update_rx) = mpsc::channel();
1363        let subscribed = Arc::new(Mutex::new(true));
1364        let subscribed2 = subscribed.clone();
1365        dict.register_update_notifier(Box::new(move |update: EntryUpdate<'_>| {
1366            let u = match update {
1367                EntryUpdate::Add(k, v) => {
1368                    assert_matches!(v, Capability::Data(_));
1369                    Update::Add(k.into())
1370                }
1371                EntryUpdate::Remove(k) => Update::Remove(k.into()),
1372                EntryUpdate::Idle => Update::Idle,
1373            };
1374            update_tx.send(u).unwrap();
1375            if *subscribed2.lock() {
1376                UpdateNotifierRetention::Retain
1377            } else {
1378                UpdateNotifierRetention::Drop_
1379            }
1380        }));
1381        let dict_ref = Capability::Dictionary(dict.clone())
1382            .into_fsandbox_capability(WeakInstanceToken::new_invalid());
1383        let dict_id = 1;
1384        store.import(dict_id, dict_ref).await.unwrap().unwrap();
1385
1386        // 1. Three inserts, one of which overlaps
1387        let i = 1;
1388        let data = Data::Int64(1).into();
1389        let value = (i + 10) as u64;
1390        store.import(value, data).await.unwrap().unwrap();
1391        store
1392            .dictionary_insert(dict_id, &fsandbox::DictionaryItem { key: key_for(i).into(), value })
1393            .await
1394            .unwrap()
1395            .unwrap();
1396
1397        for expected_result in [Ok(()), Err(fsandbox::CapabilityStoreError::ItemAlreadyExists)] {
1398            let i = 2;
1399            let data = Data::Int64(1).into();
1400            let value = (i + 10) as u64;
1401            store.import(value, data).await.unwrap().unwrap();
1402            let result = store
1403                .dictionary_insert(
1404                    dict_id,
1405                    &fsandbox::DictionaryItem { key: key_for(i).into(), value },
1406                )
1407                .await
1408                .unwrap();
1409            assert_eq!(result, expected_result);
1410        }
1411
1412        // 2. Remove the same item twice. Second time is a no-op
1413        for expected_result in [Ok(()), Err(fsandbox::CapabilityStoreError::ItemNotFound)] {
1414            let i = 1;
1415            let result =
1416                store.dictionary_remove(dict_id, &key_for(i).as_str(), None).await.unwrap();
1417            assert_eq!(result, expected_result);
1418        }
1419
1420        // 3. One more insert, then drain
1421        let i = 3;
1422        let data = Data::Int64(1).into();
1423        let value = (i + 10) as u64;
1424        store.import(value, data).await.unwrap().unwrap();
1425        store
1426            .dictionary_insert(dict_id, &fsandbox::DictionaryItem { key: key_for(i).into(), value })
1427            .await
1428            .unwrap()
1429            .unwrap();
1430        store.dictionary_drain(dict_id, None).await.unwrap().unwrap();
1431
1432        // 4. Unsubscribe to updates
1433        *subscribed.lock() = false;
1434        let i = 4;
1435        let data = Data::Int64(1).into();
1436        let value = (i + 10) as u64;
1437        store.import(value, data).await.unwrap().unwrap();
1438        store
1439            .dictionary_insert(dict_id, &fsandbox::DictionaryItem { key: key_for(i).into(), value })
1440            .await
1441            .unwrap()
1442            .unwrap();
1443        // This Remove shouldn't appear in the updates because we unsubscribed
1444        store.dictionary_remove(dict_id, key_for(i).as_str(), None).await.unwrap().unwrap();
1445
1446        // Check the updates
1447        let updates: Vec<_> = iter::from_fn(move || match update_rx.try_recv() {
1448            Ok(e) => Some(e),
1449            Err(mpsc::TryRecvError::Disconnected) => None,
1450            // The producer should die before we get here because we unsubscribed
1451            Err(mpsc::TryRecvError::Empty) => unreachable!(),
1452        })
1453        .collect();
1454        let expected_updates = [
1455            Update::Idle,
1456            // 1.
1457            Update::Add(key_for(1)),
1458            Update::Add(key_for(2)),
1459            Update::Remove(key_for(2)),
1460            Update::Add(key_for(2)),
1461            // 2.
1462            Update::Remove(key_for(1)),
1463            // 3.
1464            Update::Add(key_for(3)),
1465            Update::Remove(key_for(2)),
1466            Update::Remove(key_for(3)),
1467            // 4.
1468            Update::Add(key_for(4)),
1469        ];
1470        match test_type {
1471            TestType::Small => {
1472                assert_eq!(updates, expected_updates);
1473            }
1474            TestType::Big => {
1475                // Skip over items populated to make the dict big
1476                let updates = &updates[HYBRID_SWITCH_INSERTION_LEN..];
1477                let nexpected = expected_updates.len() - 1;
1478                assert_eq!(updates[..nexpected], expected_updates[..nexpected]);
1479
1480                // Skip over these items again when they are drained
1481                let expected_updates = &expected_updates[nexpected..];
1482                let updates = &updates[nexpected + HYBRID_SWITCH_INSERTION_LEN..];
1483                assert_eq!(updates, expected_updates);
1484            }
1485        }
1486    }
1487
1488    #[fuchsia::test]
1489    async fn live_update_add_nodes() {
1490        let dict = Dictionary::new();
1491        let scope = ExecutionScope::new();
1492        let remote = dict
1493            .clone()
1494            .try_into_directory_entry(scope.clone(), WeakInstanceToken::new_invalid())
1495            .unwrap();
1496        let dir_proxy =
1497            serve_directory(remote.clone(), &scope, fio::PERM_READABLE).unwrap().into_proxy();
1498        let mut watcher = fuchsia_fs::directory::Watcher::new(&dir_proxy)
1499            .await
1500            .expect("failed to create watcher");
1501
1502        // Assert that the directory is empty, because the dictionary is empty.
1503        assert_eq!(fuchsia_fs::directory::readdir(&dir_proxy).await.unwrap(), vec![]);
1504        assert_eq!(
1505            watcher.next().await,
1506            Some(Ok(fuchsia_fs::directory::WatchMessage {
1507                event: fuchsia_fs::directory::WatchEvent::EXISTING,
1508                filename: ".".into(),
1509            })),
1510        );
1511        assert_eq!(
1512            watcher.next().await,
1513            Some(Ok(fuchsia_fs::directory::WatchMessage {
1514                event: fuchsia_fs::directory::WatchEvent::IDLE,
1515                filename: "".into(),
1516            })),
1517        );
1518
1519        // Add an item to the dictionary, and assert that the projected directory contains the
1520        // added item.
1521        let fs = pseudo_directory! {};
1522        let dir_connector = DirConnector::from_directory_entry(fs, fio::PERM_READABLE);
1523        dict.insert("a".parse().unwrap(), dir_connector.clone().into())
1524            .expect("dict entry already exists");
1525
1526        assert_eq!(
1527            fuchsia_fs::directory::readdir(&dir_proxy).await.unwrap(),
1528            vec![directory::DirEntry { name: "a".to_string(), kind: fio::DirentType::Directory },]
1529        );
1530        assert_eq!(
1531            watcher.next().await,
1532            Some(Ok(fuchsia_fs::directory::WatchMessage {
1533                event: fuchsia_fs::directory::WatchEvent::ADD_FILE,
1534                filename: "a".into(),
1535            })),
1536        );
1537
1538        // Add an item to the dictionary, and assert that the projected directory contains the
1539        // added item.
1540        dict.insert("b".parse().unwrap(), dir_connector.into()).expect("dict entry already exists");
1541        let mut readdir_results = fuchsia_fs::directory::readdir(&dir_proxy).await.unwrap();
1542        readdir_results.sort_by(|entry_1, entry_2| entry_1.name.cmp(&entry_2.name));
1543        assert_eq!(
1544            readdir_results,
1545            vec![
1546                directory::DirEntry { name: "a".to_string(), kind: fio::DirentType::Directory },
1547                directory::DirEntry { name: "b".to_string(), kind: fio::DirentType::Directory },
1548            ]
1549        );
1550        assert_eq!(
1551            watcher.next().await,
1552            Some(Ok(fuchsia_fs::directory::WatchMessage {
1553                event: fuchsia_fs::directory::WatchEvent::ADD_FILE,
1554                filename: "b".into(),
1555            })),
1556        );
1557    }
1558
1559    #[fuchsia::test]
1560    async fn live_update_remove_nodes() {
1561        let dict = Dictionary::new();
1562        let fs = pseudo_directory! {};
1563        let dir_connector = DirConnector::from_directory_entry(fs, fio::PERM_READABLE);
1564        dict.insert("a".parse().unwrap(), dir_connector.clone().into())
1565            .expect("dict entry already exists");
1566        dict.insert("b".parse().unwrap(), dir_connector.clone().into())
1567            .expect("dict entry already exists");
1568        dict.insert("c".parse().unwrap(), dir_connector.clone().into())
1569            .expect("dict entry already exists");
1570
1571        let scope = ExecutionScope::new();
1572        let remote = dict
1573            .clone()
1574            .try_into_directory_entry(scope.clone(), WeakInstanceToken::new_invalid())
1575            .unwrap();
1576        let dir_proxy =
1577            serve_directory(remote.clone(), &scope, fio::PERM_READABLE).unwrap().into_proxy();
1578        let mut watcher = fuchsia_fs::directory::Watcher::new(&dir_proxy)
1579            .await
1580            .expect("failed to create watcher");
1581
1582        // The dictionary already had three entries in it when the directory proxy was created, so
1583        // we should see those in the directory. We check both readdir and via the watcher API.
1584        let mut readdir_results = fuchsia_fs::directory::readdir(&dir_proxy).await.unwrap();
1585        readdir_results.sort_by(|entry_1, entry_2| entry_1.name.cmp(&entry_2.name));
1586        assert_eq!(
1587            readdir_results,
1588            vec![
1589                directory::DirEntry { name: "a".to_string(), kind: fio::DirentType::Directory },
1590                directory::DirEntry { name: "b".to_string(), kind: fio::DirentType::Directory },
1591                directory::DirEntry { name: "c".to_string(), kind: fio::DirentType::Directory },
1592            ]
1593        );
1594        let mut existing_files = vec![];
1595        loop {
1596            match watcher.next().await {
1597                Some(Ok(fuchsia_fs::directory::WatchMessage { event, filename }))
1598                    if event == fuchsia_fs::directory::WatchEvent::EXISTING =>
1599                {
1600                    existing_files.push(filename)
1601                }
1602                Some(Ok(fuchsia_fs::directory::WatchMessage { event, filename: _ }))
1603                    if event == fuchsia_fs::directory::WatchEvent::IDLE =>
1604                {
1605                    break;
1606                }
1607                other_message => panic!("unexpected message: {:?}", other_message),
1608            }
1609        }
1610        existing_files.sort();
1611        let expected_files: Vec<std::path::PathBuf> =
1612            vec![".".into(), "a".into(), "b".into(), "c".into()];
1613        assert_eq!(existing_files, expected_files,);
1614
1615        // Remove each entry from the dictionary, and observe the directory watcher API inform us
1616        // that it has been removed.
1617        let _ =
1618            dict.remove(&BorrowedKey::new("a").unwrap()).expect("capability was not in dictionary");
1619        assert_eq!(
1620            watcher.next().await,
1621            Some(Ok(fuchsia_fs::directory::WatchMessage {
1622                event: fuchsia_fs::directory::WatchEvent::REMOVE_FILE,
1623                filename: "a".into(),
1624            })),
1625        );
1626
1627        let _ =
1628            dict.remove(&BorrowedKey::new("b").unwrap()).expect("capability was not in dictionary");
1629        assert_eq!(
1630            watcher.next().await,
1631            Some(Ok(fuchsia_fs::directory::WatchMessage {
1632                event: fuchsia_fs::directory::WatchEvent::REMOVE_FILE,
1633                filename: "b".into(),
1634            })),
1635        );
1636
1637        let _ =
1638            dict.remove(&BorrowedKey::new("c").unwrap()).expect("capability was not in dictionary");
1639        assert_eq!(
1640            watcher.next().await,
1641            Some(Ok(fuchsia_fs::directory::WatchMessage {
1642                event: fuchsia_fs::directory::WatchEvent::REMOVE_FILE,
1643                filename: "c".into(),
1644            })),
1645        );
1646
1647        // At this point there are no entries left in the dictionary, so the directory should be
1648        // empty too.
1649        assert_eq!(fuchsia_fs::directory::readdir(&dir_proxy).await.unwrap(), vec![],);
1650    }
1651
1652    #[fuchsia::test]
1653    async fn into_directory_oneshot() {
1654        let dict = Dictionary::new();
1655        let inner_dict = Dictionary::new();
1656        let fs = pseudo_directory! {};
1657        let dir_connector = DirConnector::from_directory_entry(fs, fio::PERM_READABLE);
1658        inner_dict.insert("x".parse().unwrap(), dir_connector.clone().into()).unwrap();
1659        dict.insert("a".parse().unwrap(), dir_connector.clone().into()).unwrap();
1660        dict.insert("b".parse().unwrap(), dir_connector.clone().into()).unwrap();
1661        dict.insert("c".parse().unwrap(), Capability::Dictionary(inner_dict.clone())).unwrap();
1662
1663        let scope = ExecutionScope::new();
1664        let remote = dict
1665            .clone()
1666            .try_into_directory_entry_oneshot(scope.clone(), WeakInstanceToken::new_invalid())
1667            .unwrap();
1668        let dir_proxy =
1669            serve_directory(remote.clone(), &scope, fio::PERM_READABLE).unwrap().into_proxy();
1670
1671        // The dictionary already had three entries in it when the directory proxy was created, so
1672        // we should see those in the directory.
1673        let mut readdir_results = fuchsia_fs::directory::readdir(&dir_proxy).await.unwrap();
1674        readdir_results.sort_by(|entry_1, entry_2| entry_1.name.cmp(&entry_2.name));
1675        assert_eq!(
1676            readdir_results,
1677            vec![
1678                directory::DirEntry { name: "a".to_string(), kind: fio::DirentType::Directory },
1679                directory::DirEntry { name: "b".to_string(), kind: fio::DirentType::Directory },
1680                directory::DirEntry { name: "c".to_string(), kind: fio::DirentType::Directory },
1681            ]
1682        );
1683
1684        let (inner_proxy, server) = create_proxy::<fio::DirectoryMarker>();
1685        dir_proxy.open("c", Default::default(), &Default::default(), server.into()).unwrap();
1686        let readdir_results = fuchsia_fs::directory::readdir(&inner_proxy).await.unwrap();
1687        assert_eq!(
1688            readdir_results,
1689            vec![directory::DirEntry { name: "x".to_string(), kind: fio::DirentType::Directory }]
1690        );
1691
1692        // The Dict should be empty because `try_into_directory_entry_oneshot consumed it.
1693        assert!(dict.is_empty());
1694        assert!(inner_dict.is_empty());
1695
1696        // Adding to the empty Dictionary has no impact on the directory.
1697        dict.insert("z".parse().unwrap(), dir_connector.clone().into()).unwrap();
1698        let mut readdir_results = fuchsia_fs::directory::readdir(&dir_proxy).await.unwrap();
1699        readdir_results.sort_by(|entry_1, entry_2| entry_1.name.cmp(&entry_2.name));
1700        assert_eq!(
1701            readdir_results,
1702            vec![
1703                directory::DirEntry { name: "a".to_string(), kind: fio::DirentType::Directory },
1704                directory::DirEntry { name: "b".to_string(), kind: fio::DirentType::Directory },
1705                directory::DirEntry { name: "c".to_string(), kind: fio::DirentType::Directory },
1706            ]
1707        );
1708    }
1709
1710    /// Generates a key from an integer such that if i < j, key_for(i) < key_for(j).
1711    /// (A simple string conversion doesn't work because 1 < 10 but "1" > "10" in terms of
1712    /// string comparison.)
1713    fn key_for(i: usize) -> Key {
1714        iter::repeat("A").take(i).collect::<String>().parse().unwrap()
1715    }
1716
1717    fn new_dict(test_type: TestType) -> Dictionary {
1718        let dict = Dictionary::new();
1719        match test_type {
1720            TestType::Small => {}
1721            TestType::Big => {
1722                for i in 1..=HYBRID_SWITCH_INSERTION_LEN {
1723                    // These items will come last in the order as long as all other keys begin with
1724                    // a capital letter
1725                    dict.insert(format!("_{i}").parse().unwrap(), Capability::Data(Data::Int64(1)))
1726                        .unwrap();
1727                }
1728            }
1729        }
1730        dict
1731    }
1732
1733    fn adjusted_len(dict: &Dictionary, test_type: TestType) -> usize {
1734        let ofs = match test_type {
1735            TestType::Small => 0,
1736            TestType::Big => HYBRID_SWITCH_INSERTION_LEN,
1737        };
1738        dict.lock().entries.len() - ofs
1739    }
1740}