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