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