Skip to main content

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