Skip to main content

runtime_capabilities/fidl/
dictionary.rs

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