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