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(Arc::new(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(Arc::new(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(Arc::new(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(Arc::new(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!(
599            dict.insert("data1".parse().unwrap(), Capability::Data(Arc::new(Data::Int64(1))))
600                .is_none()
601        );
602        store
603            .import(
604                1,
605                dict.clone().into_fsandbox_capability(WeakInstanceToken::new_invalid()).into(),
606            )
607            .await
608            .unwrap()
609            .unwrap();
610        store.dictionary_copy(1, 2).await.unwrap().unwrap();
611
612        // Insert a Data into the copy.
613        store.import(3, Data::Int64(1).into()).await.unwrap().unwrap();
614        store
615            .dictionary_insert(2, &fsandbox::DictionaryItem { key: "k".into(), value: 3 })
616            .await
617            .unwrap()
618            .unwrap();
619
620        // The copy should have two Data values.
621        let copy = store.export(2).await.unwrap().unwrap();
622        let copy = Capability::try_from(copy).unwrap();
623        let Capability::Dictionary(copy) = copy else { panic!() };
624        {
625            assert_eq!(adjusted_len(&copy, test_type), 2);
626            let copy = copy.lock();
627            assert!(copy.entries.iter().all(|(_, value)| matches!(value, Capability::Data(_))));
628        }
629
630        // The original Dictionary should have only one Data.
631        {
632            assert_eq!(adjusted_len(&dict, test_type), 1);
633            let dict = dict.lock();
634            assert!(dict.entries.iter().all(|(_, value)| matches!(value, Capability::Data(_))));
635        }
636    }
637
638    #[test_case(TestType::Small)]
639    #[test_case(TestType::Big)]
640    #[fuchsia::test]
641    async fn duplicate(test_type: TestType) {
642        let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
643        let _server = fasync::Task::spawn(async move {
644            let receiver_scope = fasync::Scope::new();
645            serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
646        });
647
648        let dict = new_dict(test_type);
649        store
650            .import(1, dict.clone().into_fsandbox_capability(WeakInstanceToken::new_invalid()))
651            .await
652            .unwrap()
653            .unwrap();
654        store.duplicate(1, 2).await.unwrap().unwrap();
655
656        // Add a Data into the duplicate.
657        store.import(3, Data::Int64(1).into()).await.unwrap().unwrap();
658        store
659            .dictionary_insert(2, &fsandbox::DictionaryItem { key: "k".into(), value: 3 })
660            .await
661            .unwrap()
662            .unwrap();
663        let dict_dup = store.export(2).await.unwrap().unwrap();
664        let dict_dup = Capability::try_from(dict_dup).unwrap();
665        let Capability::Dictionary(dict_dup) = dict_dup else { panic!() };
666        assert_eq!(adjusted_len(&dict_dup, test_type), 1);
667
668        // The original dict should now have an entry because it shares entries with the clone.
669        assert_eq!(adjusted_len(&dict_dup, test_type), 1);
670    }
671
672    /// Tests basic functionality of read APIs.
673    #[test_case(TestType::Small)]
674    #[test_case(TestType::Big)]
675    #[fuchsia::test]
676    async fn read(test_type: TestType) {
677        let dict = new_dict(test_type);
678        let id_gen = sandbox::CapabilityIdGenerator::new();
679
680        let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
681        let _server = fasync::Task::spawn(async move {
682            let receiver_scope = fasync::Scope::new();
683            serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
684        });
685        let dict_ref =
686            Capability::Dictionary(dict).into_fsandbox_capability(WeakInstanceToken::new_invalid());
687        let dict_id = id_gen.next();
688        store.import(dict_id, dict_ref).await.unwrap().unwrap();
689
690        // Create two Data capabilities.
691        let mut data_caps = vec![];
692        for i in 1..3 {
693            let id = id_gen.next();
694            store.import(id, Data::Int64(i.try_into().unwrap()).into()).await.unwrap().unwrap();
695            data_caps.push(id);
696        }
697
698        // Add the Data capabilities to the dict.
699        store
700            .dictionary_insert(
701                dict_id,
702                &fsandbox::DictionaryItem { key: "Cap1".into(), value: data_caps.remove(0) },
703            )
704            .await
705            .unwrap()
706            .unwrap();
707        store
708            .dictionary_insert(
709                dict_id,
710                &fsandbox::DictionaryItem { key: "Cap2".into(), value: data_caps.remove(0) },
711            )
712            .await
713            .unwrap()
714            .unwrap();
715        let (ch, _) = fidl::Channel::create();
716        let handle = ch.into_handle();
717        let id = id_gen.next();
718        store.import(id, fsandbox::Capability::Handle(handle)).await.unwrap().unwrap();
719        store
720            .dictionary_insert(dict_id, &fsandbox::DictionaryItem { key: "Cap3".into(), value: id })
721            .await
722            .unwrap()
723            .unwrap();
724
725        // Keys
726        {
727            let (iterator, server_end) = create_proxy();
728            store.dictionary_keys(dict_id, server_end).await.unwrap().unwrap();
729            let keys = iterator.get_next().await.unwrap();
730            assert!(iterator.get_next().await.unwrap().is_empty());
731            match test_type {
732                TestType::Small => assert_eq!(keys, ["Cap1", "Cap2", "Cap3"]),
733                TestType::Big => {
734                    assert_eq!(keys[0..3], ["Cap1", "Cap2", "Cap3"]);
735                    assert_eq!(keys.len(), 3 + HYBRID_SWITCH_INSERTION_LEN);
736                }
737            }
738        }
739        // Enumerate
740        {
741            let (iterator, server_end) = create_proxy();
742            store.dictionary_enumerate(dict_id, server_end).await.unwrap().unwrap();
743            let start_id = 100;
744            let ofs: u32 = match test_type {
745                TestType::Small => 0,
746                TestType::Big => HYBRID_SWITCH_INSERTION_LEN as u32,
747            };
748            let limit = 4 + ofs;
749            let (mut items, end_id) = iterator.get_next(start_id, limit).await.unwrap().unwrap();
750            assert_eq!(end_id, 103 + ofs as u64);
751            let (last, end_id) = iterator.get_next(end_id, limit).await.unwrap().unwrap();
752            assert!(last.is_empty());
753            assert_eq!(end_id, 103 + ofs as u64);
754
755            assert_matches!(
756                items.remove(0),
757                fsandbox::DictionaryOptionalItem {
758                    key,
759                    value: Some(value)
760                }
761                if key == "Cap1" && value.id == 100
762            );
763            assert_matches!(
764                store.export(100).await.unwrap().unwrap(),
765                fsandbox::Capability::Data(fsandbox::Data::Int64(1))
766            );
767            assert_matches!(
768                items.remove(0),
769                fsandbox::DictionaryOptionalItem {
770                    key,
771                    value: Some(value)
772                }
773                if key == "Cap2" && value.id == 101
774            );
775            assert_matches!(
776                store.export(101).await.unwrap().unwrap(),
777                fsandbox::Capability::Data(fsandbox::Data::Int64(2))
778            );
779            assert_matches!(
780                items.remove(0),
781                fsandbox::DictionaryOptionalItem {
782                    key,
783                    value: Some(value)
784                }
785                if key == "Cap3" && value.id == 102
786            );
787            match test_type {
788                TestType::Small => {}
789                TestType::Big => {
790                    assert_eq!(items.len(), HYBRID_SWITCH_INSERTION_LEN);
791                }
792            }
793        }
794    }
795
796    #[test_case(TestType::Small)]
797    #[test_case(TestType::Big)]
798    #[fuchsia::test]
799    async fn drain(test_type: TestType) {
800        let dict = new_dict(test_type);
801
802        let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
803        let _server = fasync::Task::spawn(async move {
804            let receiver_scope = fasync::Scope::new();
805            serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
806        });
807        let dict_ref = Capability::Dictionary(dict.clone())
808            .into_fsandbox_capability(WeakInstanceToken::new_invalid());
809        let dict_id = 1;
810        store.import(dict_id, dict_ref).await.unwrap().unwrap();
811
812        // Create two Data capabilities.
813        let mut data_caps = vec![];
814        for i in 1..3 {
815            let value = 10 + i;
816            store.import(value, Data::Int64(i.try_into().unwrap()).into()).await.unwrap().unwrap();
817            data_caps.push(value);
818        }
819
820        // Add the Data capabilities to the dict.
821        store
822            .dictionary_insert(
823                dict_id,
824                &fsandbox::DictionaryItem { key: "Cap1".into(), value: data_caps.remove(0) },
825            )
826            .await
827            .unwrap()
828            .unwrap();
829        store
830            .dictionary_insert(
831                dict_id,
832                &fsandbox::DictionaryItem { key: "Cap2".into(), value: data_caps.remove(0) },
833            )
834            .await
835            .unwrap()
836            .unwrap();
837        let (ch, _) = fidl::Channel::create();
838        let handle = ch.into_handle();
839        let handle_koid = handle.koid().unwrap();
840        let value = 20;
841        store.import(value, fsandbox::Capability::Handle(handle)).await.unwrap().unwrap();
842        store
843            .dictionary_insert(dict_id, &fsandbox::DictionaryItem { key: "Cap3".into(), value })
844            .await
845            .unwrap()
846            .unwrap();
847
848        let (iterator, server_end) = create_proxy();
849        store.dictionary_drain(dict_id, Some(server_end)).await.unwrap().unwrap();
850        let ofs: u32 = match test_type {
851            TestType::Small => 0,
852            TestType::Big => HYBRID_SWITCH_INSERTION_LEN as u32,
853        };
854        let start_id = 100;
855        let limit = 4 + ofs;
856        let (mut items, end_id) = iterator.get_next(start_id, limit).await.unwrap().unwrap();
857        assert_eq!(end_id, 103 + ofs as u64);
858        let (last, end_id) = iterator.get_next(end_id, limit).await.unwrap().unwrap();
859        assert!(last.is_empty());
860        assert_eq!(end_id, 103 + ofs as u64);
861
862        assert_matches!(
863            items.remove(0),
864            fsandbox::DictionaryItem {
865                key,
866                value: 100
867            }
868            if key == "Cap1"
869        );
870        assert_matches!(
871            store.export(100).await.unwrap().unwrap(),
872            fsandbox::Capability::Data(fsandbox::Data::Int64(1))
873        );
874        assert_matches!(
875            items.remove(0),
876            fsandbox::DictionaryItem {
877                key,
878                value: 101
879            }
880            if key == "Cap2"
881        );
882        assert_matches!(
883            store.export(101).await.unwrap().unwrap(),
884            fsandbox::Capability::Data(fsandbox::Data::Int64(2))
885        );
886        assert_matches!(
887            items.remove(0),
888            fsandbox::DictionaryItem {
889                key,
890                value: 102
891            }
892            if key == "Cap3"
893        );
894        assert_matches!(
895            store.export(102).await.unwrap().unwrap(),
896            fsandbox::Capability::Handle(handle)
897            if handle.koid().unwrap() == handle_koid
898        );
899
900        // Dictionary should now be empty.
901        assert!(dict.is_empty());
902    }
903
904    /// Tests batching for read APIs.
905    #[fuchsia::test]
906    async fn read_batches() {
907        // Number of entries in the Dictionary that will be enumerated.
908        //
909        // This value was chosen such that that GetNext returns multiple chunks of different sizes.
910        const NUM_ENTRIES: u32 = fsandbox::MAX_DICTIONARY_ITERATOR_CHUNK * 2 + 1;
911
912        // Number of items we expect in each chunk, for every chunk we expect to get.
913        const EXPECTED_CHUNK_LENGTHS: &[u32] =
914            &[fsandbox::MAX_DICTIONARY_ITERATOR_CHUNK, fsandbox::MAX_DICTIONARY_ITERATOR_CHUNK, 1];
915
916        let id_gen = sandbox::CapabilityIdGenerator::new();
917
918        // Create a Dictionary with [NUM_ENTRIES] entries that have Data values.
919        let dict = Dictionary::new();
920        for i in 0..NUM_ENTRIES {
921            assert!(
922                dict.insert(
923                    format!("{}", i).parse().unwrap(),
924                    Capability::Data(Arc::new(Data::Int64(1)))
925                )
926                .is_none()
927            );
928        }
929
930        let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
931        let _server = fasync::Task::spawn(async move {
932            let receiver_scope = fasync::Scope::new();
933            serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
934        });
935        let dict_ref = Capability::Dictionary(dict.clone())
936            .into_fsandbox_capability(WeakInstanceToken::new_invalid());
937        let dict_id = id_gen.next();
938        store.import(dict_id, dict_ref).await.unwrap().unwrap();
939
940        let (key_iterator, server_end) = create_proxy();
941        store.dictionary_keys(dict_id, server_end).await.unwrap().unwrap();
942        let (item_iterator, server_end) = create_proxy();
943        store.dictionary_enumerate(dict_id, server_end).await.unwrap().unwrap();
944
945        // Get all the entries from the Dictionary with `GetNext`.
946        let mut num_got_items: u32 = 0;
947        let mut start_id = 100;
948        let limit = fsandbox::MAX_DICTIONARY_ITERATOR_CHUNK;
949        for expected_len in EXPECTED_CHUNK_LENGTHS {
950            let keys = key_iterator.get_next().await.unwrap();
951            let (items, end_id) = item_iterator.get_next(start_id, limit).await.unwrap().unwrap();
952            if keys.is_empty() && items.is_empty() {
953                break;
954            }
955            assert_eq!(*expected_len, keys.len() as u32);
956            assert_eq!(*expected_len, items.len() as u32);
957            assert_eq!(u64::from(*expected_len), end_id - start_id);
958            start_id = end_id;
959            num_got_items += *expected_len;
960        }
961
962        // GetNext should return no items once all items have been returned.
963        let (items, _) = item_iterator.get_next(start_id, limit).await.unwrap().unwrap();
964        assert!(items.is_empty());
965        assert!(key_iterator.get_next().await.unwrap().is_empty());
966
967        assert_eq!(num_got_items, NUM_ENTRIES);
968    }
969
970    #[fuchsia::test]
971    async fn drain_batches() {
972        // Number of entries in the Dictionary that will be enumerated.
973        //
974        // This value was chosen such that that GetNext returns multiple chunks of different sizes.
975        const NUM_ENTRIES: u32 = fsandbox::MAX_DICTIONARY_ITERATOR_CHUNK * 2 + 1;
976
977        // Number of items we expect in each chunk, for every chunk we expect to get.
978        const EXPECTED_CHUNK_LENGTHS: &[u32] =
979            &[fsandbox::MAX_DICTIONARY_ITERATOR_CHUNK, fsandbox::MAX_DICTIONARY_ITERATOR_CHUNK, 1];
980
981        // Create a Dictionary with [NUM_ENTRIES] entries that have Data values.
982        let dict = Dictionary::new();
983        for i in 0..NUM_ENTRIES {
984            assert!(
985                dict.insert(
986                    format!("{}", i).parse().unwrap(),
987                    Capability::Data(Arc::new(Data::Int64(1)))
988                )
989                .is_none()
990            );
991        }
992
993        let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
994        let _server = fasync::Task::spawn(async move {
995            let receiver_scope = fasync::Scope::new();
996            serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
997        });
998        let dict_ref = Capability::Dictionary(dict.clone())
999            .into_fsandbox_capability(WeakInstanceToken::new_invalid());
1000        let dict_id = 1;
1001        store.import(dict_id, dict_ref).await.unwrap().unwrap();
1002
1003        let (item_iterator, server_end) = create_proxy();
1004        store.dictionary_drain(dict_id, Some(server_end)).await.unwrap().unwrap();
1005
1006        // Get all the entries from the Dictionary with `GetNext`.
1007        let mut num_got_items: u32 = 0;
1008        let mut start_id = 100;
1009        let limit = fsandbox::MAX_DICTIONARY_ITERATOR_CHUNK;
1010        for expected_len in EXPECTED_CHUNK_LENGTHS {
1011            let (items, end_id) = item_iterator.get_next(start_id, limit).await.unwrap().unwrap();
1012            if items.is_empty() {
1013                break;
1014            }
1015            assert_eq!(*expected_len, items.len() as u32);
1016            assert_eq!(u64::from(*expected_len), end_id - start_id);
1017            start_id = end_id;
1018            num_got_items += *expected_len;
1019        }
1020
1021        // GetNext should return no items once all items have been returned.
1022        let (items, _) = item_iterator.get_next(start_id, limit).await.unwrap().unwrap();
1023        assert!(items.is_empty());
1024
1025        assert_eq!(num_got_items, NUM_ENTRIES);
1026
1027        // Dictionary should now be empty.
1028        assert!(dict.is_empty());
1029    }
1030
1031    #[fuchsia::test]
1032    async fn read_error() {
1033        let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
1034        let _server = fasync::Task::spawn(async move {
1035            let receiver_scope = fasync::Scope::new();
1036            serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
1037        });
1038
1039        store.import(2, Data::Int64(1).into()).await.unwrap().unwrap();
1040
1041        let (_, server_end) = create_proxy();
1042        assert_matches!(
1043            store.dictionary_keys(1, server_end).await.unwrap(),
1044            Err(fsandbox::CapabilityStoreError::IdNotFound)
1045        );
1046        let (_, server_end) = create_proxy();
1047        assert_matches!(
1048            store.dictionary_enumerate(1, server_end).await.unwrap(),
1049            Err(fsandbox::CapabilityStoreError::IdNotFound)
1050        );
1051        assert_matches!(
1052            store.dictionary_drain(1, None).await.unwrap(),
1053            Err(fsandbox::CapabilityStoreError::IdNotFound)
1054        );
1055
1056        let (_, server_end) = create_proxy();
1057        assert_matches!(
1058            store.dictionary_keys(2, server_end).await.unwrap(),
1059            Err(fsandbox::CapabilityStoreError::WrongType)
1060        );
1061        let (_, server_end) = create_proxy();
1062        assert_matches!(
1063            store.dictionary_enumerate(2, server_end).await.unwrap(),
1064            Err(fsandbox::CapabilityStoreError::WrongType)
1065        );
1066        assert_matches!(
1067            store.dictionary_drain(2, None).await.unwrap(),
1068            Err(fsandbox::CapabilityStoreError::WrongType)
1069        );
1070    }
1071
1072    #[fuchsia::test]
1073    async fn read_iterator_error() {
1074        let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
1075        let _server = fasync::Task::spawn(async move {
1076            let receiver_scope = fasync::Scope::new();
1077            serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
1078        });
1079
1080        store.dictionary_create(1).await.unwrap().unwrap();
1081
1082        {
1083            let (iterator, server_end) = create_proxy();
1084            store.dictionary_enumerate(1, server_end).await.unwrap().unwrap();
1085            assert_matches!(
1086                iterator.get_next(2, fsandbox::MAX_DICTIONARY_ITERATOR_CHUNK + 1).await.unwrap(),
1087                Err(fsandbox::CapabilityStoreError::InvalidArgs)
1088            );
1089            let (iterator, server_end) = create_proxy();
1090            store.dictionary_enumerate(1, server_end).await.unwrap().unwrap();
1091            assert_matches!(
1092                iterator.get_next(2, 0).await.unwrap(),
1093                Err(fsandbox::CapabilityStoreError::InvalidArgs)
1094            );
1095
1096            let (iterator, server_end) = create_proxy();
1097            store.dictionary_drain(1, Some(server_end)).await.unwrap().unwrap();
1098            assert_matches!(
1099                iterator.get_next(2, fsandbox::MAX_DICTIONARY_ITERATOR_CHUNK + 1).await.unwrap(),
1100                Err(fsandbox::CapabilityStoreError::InvalidArgs)
1101            );
1102            let (iterator, server_end) = create_proxy();
1103            store.dictionary_drain(1, Some(server_end)).await.unwrap().unwrap();
1104            assert_matches!(
1105                iterator.get_next(2, 0).await.unwrap(),
1106                Err(fsandbox::CapabilityStoreError::InvalidArgs)
1107            );
1108        }
1109
1110        store.import(4, Data::Int64(1).into()).await.unwrap().unwrap();
1111        for i in 0..3 {
1112            store.import(2, Data::Int64(1).into()).await.unwrap().unwrap();
1113            store
1114                .dictionary_insert(1, &fsandbox::DictionaryItem { key: format!("k{i}"), value: 2 })
1115                .await
1116                .unwrap()
1117                .unwrap();
1118        }
1119
1120        // Range overlaps with id 4
1121        {
1122            let (iterator, server_end) = create_proxy();
1123            store.dictionary_enumerate(1, server_end).await.unwrap().unwrap();
1124            assert_matches!(
1125                iterator.get_next(2, 3).await.unwrap(),
1126                Err(fsandbox::CapabilityStoreError::IdAlreadyExists)
1127            );
1128
1129            let (iterator, server_end) = create_proxy();
1130            store.dictionary_drain(1, Some(server_end)).await.unwrap().unwrap();
1131            assert_matches!(
1132                iterator.get_next(2, 3).await.unwrap(),
1133                Err(fsandbox::CapabilityStoreError::IdAlreadyExists)
1134            );
1135        }
1136    }
1137
1138    #[fuchsia::test]
1139    async fn try_into_open_error_not_supported() {
1140        let dict = Dictionary::new();
1141        assert!(dict.insert(CAP_KEY.clone(), Capability::Data(Arc::new(Data::Int64(1)))).is_none());
1142        let scope = ExecutionScope::new();
1143        assert_matches!(
1144            dict.try_into_directory_entry(scope, WeakInstanceToken::new_invalid()).err(),
1145            Some(ConversionError::NotSupported)
1146        );
1147    }
1148
1149    struct MockDir(Counter);
1150    impl DirectoryEntry for MockDir {
1151        fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), Status> {
1152            request.open_remote(self)
1153        }
1154    }
1155    impl GetEntryInfo for MockDir {
1156        fn entry_info(&self) -> EntryInfo {
1157            EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)
1158        }
1159    }
1160    impl RemoteLike for MockDir {
1161        fn open(
1162            self: Arc<Self>,
1163            _scope: ExecutionScope,
1164            relative_path: Path,
1165            _flags: fio::Flags,
1166            _object_request: ObjectRequestRef<'_>,
1167        ) -> Result<(), Status> {
1168            assert_eq!(relative_path.as_ref(), "bar");
1169            self.0.inc();
1170            Ok(())
1171        }
1172    }
1173
1174    #[fuchsia::test]
1175    async fn try_into_open_success() {
1176        let dict = Dictionary::new();
1177        let mock_dir = Arc::new(MockDir(Counter::new(0)));
1178        assert!(
1179            dict.insert(
1180                CAP_KEY.clone(),
1181                DirConnector::from_directory_entry(mock_dir.clone(), fio::PERM_READABLE).into(),
1182            )
1183            .is_none()
1184        );
1185        let scope = ExecutionScope::new();
1186        let remote =
1187            dict.try_into_directory_entry(scope.clone(), WeakInstanceToken::new_invalid()).unwrap();
1188
1189        let dir_client_end = serve_directory(remote.clone(), &scope, fio::PERM_READABLE).unwrap();
1190
1191        assert_eq!(mock_dir.0.get(), 0);
1192        let (client_end, server_end) = Channel::create();
1193        let dir = dir_client_end.channel();
1194        fdio::service_connect_at(dir, &format!("{}/bar", *CAP_KEY), server_end).unwrap();
1195        fasync::Channel::from_channel(client_end).on_closed().await.unwrap();
1196        assert_eq!(mock_dir.0.get(), 1);
1197    }
1198
1199    #[fuchsia::test]
1200    async fn try_into_open_success_nested() {
1201        let inner_dict = Dictionary::new();
1202        let mock_dir = Arc::new(MockDir(Counter::new(0)));
1203        assert!(
1204            inner_dict
1205                .insert(
1206                    CAP_KEY.clone(),
1207                    DirConnector::from_directory_entry(mock_dir.clone(), fio::PERM_READABLE).into(),
1208                )
1209                .is_none()
1210        );
1211        let dict = Dictionary::new();
1212        assert!(dict.insert(CAP_KEY.clone(), Capability::Dictionary(inner_dict)).is_none());
1213
1214        let scope = ExecutionScope::new();
1215        let remote = dict
1216            .try_into_directory_entry(scope.clone(), WeakInstanceToken::new_invalid())
1217            .expect("convert dict into Open capability");
1218
1219        let dir_client_end = serve_directory(remote.clone(), &scope, fio::PERM_READABLE).unwrap();
1220
1221        // List the outer directory and verify the contents.
1222        let dir = dir_client_end.into_proxy();
1223        assert_eq!(
1224            fuchsia_fs::directory::readdir(&dir).await.unwrap(),
1225            vec![directory::DirEntry {
1226                name: CAP_KEY.to_string(),
1227                kind: fio::DirentType::Directory
1228            },]
1229        );
1230
1231        // Open the inner most capability.
1232        assert_eq!(mock_dir.0.get(), 0);
1233        let (client_end, server_end) = Channel::create();
1234        let dir = dir.into_channel().unwrap().into_zx_channel();
1235        fdio::service_connect_at(&dir, &format!("{}/{}/bar", *CAP_KEY, *CAP_KEY), server_end)
1236            .unwrap();
1237        fasync::Channel::from_channel(client_end).on_closed().await.unwrap();
1238        assert_eq!(mock_dir.0.get(), 1)
1239    }
1240
1241    #[fuchsia::test]
1242    async fn switch_between_vec_and_map() {
1243        let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
1244        let _server = fasync::Task::spawn(async move {
1245            let receiver_scope = fasync::Scope::new();
1246            serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
1247        });
1248
1249        let dict = Dictionary::new();
1250        let dict_ref = Capability::Dictionary(dict.clone())
1251            .into_fsandbox_capability(WeakInstanceToken::new_invalid());
1252        let dict_id = 1;
1253        store.import(dict_id, dict_ref).await.unwrap().unwrap();
1254
1255        // Just one less one the switchover point. Count down instead of up to test sorting.
1256        {
1257            for i in (1..=HYBRID_SWITCH_INSERTION_LEN - 1).rev() {
1258                let data = Data::Int64(1).into();
1259                let value = (i + 10) as u64;
1260                store.import(value, data).await.unwrap().unwrap();
1261                store
1262                    .dictionary_insert(
1263                        dict_id,
1264                        &fsandbox::DictionaryItem { key: key_for(i).into(), value },
1265                    )
1266                    .await
1267                    .unwrap()
1268                    .unwrap();
1269            }
1270
1271            let entries = &dict.lock().entries;
1272            let HybridMap::Vec(v) = entries else { panic!() };
1273            v.iter().for_each(|(_, v)| assert_matches!(v, Capability::Data(_)));
1274            let actual_keys: Vec<Key> = v.iter().map(|(k, _)| k.clone()).collect();
1275            let expected: Vec<Key> =
1276                (1..=HYBRID_SWITCH_INSERTION_LEN - 1).map(|i| key_for(i)).collect();
1277            assert_eq!(actual_keys, expected);
1278        }
1279
1280        // Add one more, and the switch happens.
1281        {
1282            let i = HYBRID_SWITCH_INSERTION_LEN;
1283            let data = Data::Int64(1).into();
1284            let value = (i + 10) as u64;
1285            store.import(value, data).await.unwrap().unwrap();
1286            store
1287                .dictionary_insert(
1288                    dict_id,
1289                    &fsandbox::DictionaryItem { key: key_for(i).into(), value },
1290                )
1291                .await
1292                .unwrap()
1293                .unwrap();
1294
1295            let entries = &dict.lock().entries;
1296            let HybridMap::Map(m) = entries else { panic!() };
1297            m.iter().for_each(|(_, m)| assert_matches!(m, Capability::Data(_)));
1298            let actual_keys: Vec<Key> = m.iter().map(|(k, _)| k.clone()).collect();
1299            let expected: Vec<Key> =
1300                (1..=HYBRID_SWITCH_INSERTION_LEN).map(|i| key_for(i)).collect();
1301            assert_eq!(actual_keys, expected);
1302        }
1303
1304        // Now go in reverse: remove just one less than the switchover point for removal.
1305        {
1306            for i in (HYBRID_SWITCH_INSERTION_LEN - HYBRID_SWITCH_REMOVAL_LEN + 1
1307                ..=HYBRID_SWITCH_INSERTION_LEN)
1308                .rev()
1309            {
1310                store.dictionary_remove(dict_id, key_for(i).as_str(), None).await.unwrap().unwrap();
1311            }
1312
1313            let entries = &dict.lock().entries;
1314            let HybridMap::Map(m) = entries else { panic!() };
1315            m.iter().for_each(|(_, v)| assert_matches!(v, Capability::Data(_)));
1316            let actual_keys: Vec<Key> = m.iter().map(|(k, _)| k.clone()).collect();
1317            let expected: Vec<Key> =
1318                (1..=HYBRID_SWITCH_REMOVAL_LEN + 1).map(|i| key_for(i)).collect();
1319            assert_eq!(actual_keys, expected);
1320        }
1321
1322        // Finally, remove one more, and it switches back to a map.
1323        {
1324            let i = HYBRID_SWITCH_REMOVAL_LEN + 1;
1325            store.dictionary_remove(dict_id, key_for(i).as_str(), None).await.unwrap().unwrap();
1326
1327            let entries = &dict.lock().entries;
1328            let HybridMap::Vec(v) = entries else { panic!() };
1329            v.iter().for_each(|(_, v)| assert_matches!(v, Capability::Data(_)));
1330            let actual_keys: Vec<Key> = v.iter().map(|(k, _)| k.clone()).collect();
1331            let expected: Vec<Key> = (1..=HYBRID_SWITCH_REMOVAL_LEN).map(|i| key_for(i)).collect();
1332            assert_eq!(actual_keys, expected);
1333        }
1334    }
1335
1336    #[test_case(TestType::Small)]
1337    #[test_case(TestType::Big)]
1338    #[fuchsia::test]
1339    async fn register_update_notifier(test_type: TestType) {
1340        // We would like to use futures::channel::oneshot here but because the sender is captured
1341        // by the `FnMut` to `register_update_notifier`, it would not compile because `send` would
1342        // consume the sender. std::sync::mpsc is used instead of futures::channel::mpsc because
1343        // the register_update_notifier callback is not async-aware.
1344        use std::sync::mpsc;
1345
1346        let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
1347        let _server = fasync::Task::spawn(async move {
1348            let receiver_scope = fasync::Scope::new();
1349            serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
1350        });
1351
1352        #[derive(PartialEq)]
1353        enum Update {
1354            Add(Key),
1355            Remove(Key),
1356            Idle,
1357        }
1358        impl fmt::Debug for Update {
1359            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1360                match self {
1361                    Self::Add(k) => write!(f, "Add({k})"),
1362                    Self::Remove(k) => write!(f, "Remove({k})"),
1363                    Self::Idle => write!(f, "Idle"),
1364                }
1365            }
1366        }
1367
1368        let dict = new_dict(test_type);
1369        let (update_tx, update_rx) = mpsc::channel();
1370        let subscribed = Arc::new(Mutex::new(true));
1371        let subscribed2 = subscribed.clone();
1372        dict.register_update_notifier(Box::new(move |update: EntryUpdate<'_>| {
1373            let u = match update {
1374                EntryUpdate::Add(k, v) => {
1375                    assert_matches!(v, Capability::Data(_));
1376                    Update::Add(k.into())
1377                }
1378                EntryUpdate::Remove(k) => Update::Remove(k.into()),
1379                EntryUpdate::Idle => Update::Idle,
1380            };
1381            update_tx.send(u).unwrap();
1382            if *subscribed2.lock() {
1383                UpdateNotifierRetention::Retain
1384            } else {
1385                UpdateNotifierRetention::Drop_
1386            }
1387        }));
1388        let dict_ref = Capability::Dictionary(dict.clone())
1389            .into_fsandbox_capability(WeakInstanceToken::new_invalid());
1390        let dict_id = 1;
1391        store.import(dict_id, dict_ref).await.unwrap().unwrap();
1392
1393        // 1. Three inserts, one of which overlaps
1394        let i = 1;
1395        let data = Data::Int64(1).into();
1396        let value = (i + 10) as u64;
1397        store.import(value, data).await.unwrap().unwrap();
1398        store
1399            .dictionary_insert(dict_id, &fsandbox::DictionaryItem { key: key_for(i).into(), value })
1400            .await
1401            .unwrap()
1402            .unwrap();
1403
1404        for expected_result in [Ok(()), Err(fsandbox::CapabilityStoreError::ItemAlreadyExists)] {
1405            let i = 2;
1406            let data = Data::Int64(1).into();
1407            let value = (i + 10) as u64;
1408            store.import(value, data).await.unwrap().unwrap();
1409            let result = store
1410                .dictionary_insert(
1411                    dict_id,
1412                    &fsandbox::DictionaryItem { key: key_for(i).into(), value },
1413                )
1414                .await
1415                .unwrap();
1416            assert_eq!(result, expected_result);
1417        }
1418
1419        // 2. Remove the same item twice. Second time is a no-op
1420        for expected_result in [Ok(()), Err(fsandbox::CapabilityStoreError::ItemNotFound)] {
1421            let i = 1;
1422            let result =
1423                store.dictionary_remove(dict_id, &key_for(i).as_str(), None).await.unwrap();
1424            assert_eq!(result, expected_result);
1425        }
1426
1427        // 3. One more insert, then drain
1428        let i = 3;
1429        let data = Data::Int64(1).into();
1430        let value = (i + 10) as u64;
1431        store.import(value, data).await.unwrap().unwrap();
1432        store
1433            .dictionary_insert(dict_id, &fsandbox::DictionaryItem { key: key_for(i).into(), value })
1434            .await
1435            .unwrap()
1436            .unwrap();
1437        store.dictionary_drain(dict_id, None).await.unwrap().unwrap();
1438
1439        // 4. Unsubscribe to updates
1440        *subscribed.lock() = false;
1441        let i = 4;
1442        let data = Data::Int64(1).into();
1443        let value = (i + 10) as u64;
1444        store.import(value, data).await.unwrap().unwrap();
1445        store
1446            .dictionary_insert(dict_id, &fsandbox::DictionaryItem { key: key_for(i).into(), value })
1447            .await
1448            .unwrap()
1449            .unwrap();
1450        // This Remove shouldn't appear in the updates because we unsubscribed
1451        store.dictionary_remove(dict_id, key_for(i).as_str(), None).await.unwrap().unwrap();
1452
1453        // Check the updates
1454        let updates: Vec<_> = iter::from_fn(move || match update_rx.try_recv() {
1455            Ok(e) => Some(e),
1456            Err(mpsc::TryRecvError::Disconnected) => None,
1457            // The producer should die before we get here because we unsubscribed
1458            Err(mpsc::TryRecvError::Empty) => unreachable!(),
1459        })
1460        .collect();
1461        let expected_updates = [
1462            Update::Idle,
1463            // 1.
1464            Update::Add(key_for(1)),
1465            Update::Add(key_for(2)),
1466            Update::Remove(key_for(2)),
1467            Update::Add(key_for(2)),
1468            // 2.
1469            Update::Remove(key_for(1)),
1470            // 3.
1471            Update::Add(key_for(3)),
1472            Update::Remove(key_for(2)),
1473            Update::Remove(key_for(3)),
1474            // 4.
1475            Update::Add(key_for(4)),
1476        ];
1477        match test_type {
1478            TestType::Small => {
1479                assert_eq!(updates, expected_updates);
1480            }
1481            TestType::Big => {
1482                // Skip over items populated to make the dict big
1483                let updates = &updates[HYBRID_SWITCH_INSERTION_LEN..];
1484                let nexpected = expected_updates.len() - 1;
1485                assert_eq!(updates[..nexpected], expected_updates[..nexpected]);
1486
1487                // Skip over these items again when they are drained
1488                let expected_updates = &expected_updates[nexpected..];
1489                let updates = &updates[nexpected + HYBRID_SWITCH_INSERTION_LEN..];
1490                assert_eq!(updates, expected_updates);
1491            }
1492        }
1493    }
1494
1495    #[fuchsia::test]
1496    async fn live_update_add_nodes() {
1497        let dict = Dictionary::new();
1498        let scope = ExecutionScope::new();
1499        let remote = dict
1500            .clone()
1501            .try_into_directory_entry(scope.clone(), WeakInstanceToken::new_invalid())
1502            .unwrap();
1503        let dir_proxy =
1504            serve_directory(remote.clone(), &scope, fio::PERM_READABLE).unwrap().into_proxy();
1505        let mut watcher = fuchsia_fs::directory::Watcher::new(&dir_proxy)
1506            .await
1507            .expect("failed to create watcher");
1508
1509        // Assert that the directory is empty, because the dictionary is empty.
1510        assert_eq!(fuchsia_fs::directory::readdir(&dir_proxy).await.unwrap(), vec![]);
1511        assert_eq!(
1512            watcher.next().await,
1513            Some(Ok(fuchsia_fs::directory::WatchMessage {
1514                event: fuchsia_fs::directory::WatchEvent::EXISTING,
1515                filename: ".".into(),
1516            })),
1517        );
1518        assert_eq!(
1519            watcher.next().await,
1520            Some(Ok(fuchsia_fs::directory::WatchMessage {
1521                event: fuchsia_fs::directory::WatchEvent::IDLE,
1522                filename: "".into(),
1523            })),
1524        );
1525
1526        // Add an item to the dictionary, and assert that the projected directory contains the
1527        // added item.
1528        let fs = pseudo_directory! {};
1529        let dir_connector = DirConnector::from_directory_entry(fs, fio::PERM_READABLE);
1530        assert!(dict.insert("a".parse().unwrap(), dir_connector.clone().into()).is_none());
1531
1532        assert_eq!(
1533            fuchsia_fs::directory::readdir(&dir_proxy).await.unwrap(),
1534            vec![directory::DirEntry { name: "a".to_string(), kind: fio::DirentType::Directory },]
1535        );
1536        assert_eq!(
1537            watcher.next().await,
1538            Some(Ok(fuchsia_fs::directory::WatchMessage {
1539                event: fuchsia_fs::directory::WatchEvent::ADD_FILE,
1540                filename: "a".into(),
1541            })),
1542        );
1543
1544        // Add an item to the dictionary, and assert that the projected directory contains the
1545        // added item.
1546        assert!(dict.insert("b".parse().unwrap(), dir_connector.into()).is_none());
1547        let mut readdir_results = fuchsia_fs::directory::readdir(&dir_proxy).await.unwrap();
1548        readdir_results.sort_by(|entry_1, entry_2| entry_1.name.cmp(&entry_2.name));
1549        assert_eq!(
1550            readdir_results,
1551            vec![
1552                directory::DirEntry { name: "a".to_string(), kind: fio::DirentType::Directory },
1553                directory::DirEntry { name: "b".to_string(), kind: fio::DirentType::Directory },
1554            ]
1555        );
1556        assert_eq!(
1557            watcher.next().await,
1558            Some(Ok(fuchsia_fs::directory::WatchMessage {
1559                event: fuchsia_fs::directory::WatchEvent::ADD_FILE,
1560                filename: "b".into(),
1561            })),
1562        );
1563    }
1564
1565    #[fuchsia::test]
1566    async fn live_update_remove_nodes() {
1567        let dict = Dictionary::new();
1568        let fs = pseudo_directory! {};
1569        let dir_connector = DirConnector::from_directory_entry(fs, fio::PERM_READABLE);
1570        assert!(dict.insert("a".parse().unwrap(), dir_connector.clone().into()).is_none());
1571        assert!(dict.insert("b".parse().unwrap(), dir_connector.clone().into()).is_none());
1572        assert!(dict.insert("c".parse().unwrap(), dir_connector.clone().into()).is_none());
1573
1574        let scope = ExecutionScope::new();
1575        let remote = dict
1576            .clone()
1577            .try_into_directory_entry(scope.clone(), WeakInstanceToken::new_invalid())
1578            .unwrap();
1579        let dir_proxy =
1580            serve_directory(remote.clone(), &scope, fio::PERM_READABLE).unwrap().into_proxy();
1581        let mut watcher = fuchsia_fs::directory::Watcher::new(&dir_proxy)
1582            .await
1583            .expect("failed to create watcher");
1584
1585        // The dictionary already had three entries in it when the directory proxy was created, so
1586        // we should see those in the directory. We check both readdir and via the watcher API.
1587        let mut readdir_results = fuchsia_fs::directory::readdir(&dir_proxy).await.unwrap();
1588        readdir_results.sort_by(|entry_1, entry_2| entry_1.name.cmp(&entry_2.name));
1589        assert_eq!(
1590            readdir_results,
1591            vec![
1592                directory::DirEntry { name: "a".to_string(), kind: fio::DirentType::Directory },
1593                directory::DirEntry { name: "b".to_string(), kind: fio::DirentType::Directory },
1594                directory::DirEntry { name: "c".to_string(), kind: fio::DirentType::Directory },
1595            ]
1596        );
1597        let mut existing_files = vec![];
1598        loop {
1599            match watcher.next().await {
1600                Some(Ok(fuchsia_fs::directory::WatchMessage { event, filename }))
1601                    if event == fuchsia_fs::directory::WatchEvent::EXISTING =>
1602                {
1603                    existing_files.push(filename)
1604                }
1605                Some(Ok(fuchsia_fs::directory::WatchMessage { event, filename: _ }))
1606                    if event == fuchsia_fs::directory::WatchEvent::IDLE =>
1607                {
1608                    break;
1609                }
1610                other_message => panic!("unexpected message: {:?}", other_message),
1611            }
1612        }
1613        existing_files.sort();
1614        let expected_files: Vec<std::path::PathBuf> =
1615            vec![".".into(), "a".into(), "b".into(), "c".into()];
1616        assert_eq!(existing_files, expected_files,);
1617
1618        // Remove each entry from the dictionary, and observe the directory watcher API inform us
1619        // that it has been removed.
1620        let _ =
1621            dict.remove(&BorrowedKey::new("a").unwrap()).expect("capability was not in dictionary");
1622        assert_eq!(
1623            watcher.next().await,
1624            Some(Ok(fuchsia_fs::directory::WatchMessage {
1625                event: fuchsia_fs::directory::WatchEvent::REMOVE_FILE,
1626                filename: "a".into(),
1627            })),
1628        );
1629
1630        let _ =
1631            dict.remove(&BorrowedKey::new("b").unwrap()).expect("capability was not in dictionary");
1632        assert_eq!(
1633            watcher.next().await,
1634            Some(Ok(fuchsia_fs::directory::WatchMessage {
1635                event: fuchsia_fs::directory::WatchEvent::REMOVE_FILE,
1636                filename: "b".into(),
1637            })),
1638        );
1639
1640        let _ =
1641            dict.remove(&BorrowedKey::new("c").unwrap()).expect("capability was not in dictionary");
1642        assert_eq!(
1643            watcher.next().await,
1644            Some(Ok(fuchsia_fs::directory::WatchMessage {
1645                event: fuchsia_fs::directory::WatchEvent::REMOVE_FILE,
1646                filename: "c".into(),
1647            })),
1648        );
1649
1650        // At this point there are no entries left in the dictionary, so the directory should be
1651        // empty too.
1652        assert_eq!(fuchsia_fs::directory::readdir(&dir_proxy).await.unwrap(), vec![],);
1653    }
1654
1655    #[fuchsia::test]
1656    async fn into_directory_oneshot() {
1657        let dict = Dictionary::new();
1658        let inner_dict = Dictionary::new();
1659        let fs = pseudo_directory! {};
1660        let dir_connector = DirConnector::from_directory_entry(fs, fio::PERM_READABLE);
1661        assert!(inner_dict.insert("x".parse().unwrap(), dir_connector.clone().into()).is_none());
1662        assert!(dict.insert("a".parse().unwrap(), dir_connector.clone().into()).is_none());
1663        assert!(dict.insert("b".parse().unwrap(), dir_connector.clone().into()).is_none());
1664        assert!(
1665            dict.insert("c".parse().unwrap(), Capability::Dictionary(inner_dict.clone())).is_none()
1666        );
1667
1668        let scope = ExecutionScope::new();
1669        let remote = dict
1670            .clone()
1671            .try_into_directory_entry_oneshot(scope.clone(), WeakInstanceToken::new_invalid())
1672            .unwrap();
1673        let dir_proxy =
1674            serve_directory(remote.clone(), &scope, fio::PERM_READABLE).unwrap().into_proxy();
1675
1676        // The dictionary already had three entries in it when the directory proxy was created, so
1677        // we should see those in the directory.
1678        let mut readdir_results = fuchsia_fs::directory::readdir(&dir_proxy).await.unwrap();
1679        readdir_results.sort_by(|entry_1, entry_2| entry_1.name.cmp(&entry_2.name));
1680        assert_eq!(
1681            readdir_results,
1682            vec![
1683                directory::DirEntry { name: "a".to_string(), kind: fio::DirentType::Directory },
1684                directory::DirEntry { name: "b".to_string(), kind: fio::DirentType::Directory },
1685                directory::DirEntry { name: "c".to_string(), kind: fio::DirentType::Directory },
1686            ]
1687        );
1688
1689        let (inner_proxy, server) = create_proxy::<fio::DirectoryMarker>();
1690        dir_proxy.open("c", Default::default(), &Default::default(), server.into()).unwrap();
1691        let readdir_results = fuchsia_fs::directory::readdir(&inner_proxy).await.unwrap();
1692        assert_eq!(
1693            readdir_results,
1694            vec![directory::DirEntry { name: "x".to_string(), kind: fio::DirentType::Directory }]
1695        );
1696
1697        // The Dict should be empty because `try_into_directory_entry_oneshot consumed it.
1698        assert!(dict.is_empty());
1699        assert!(inner_dict.is_empty());
1700
1701        // Adding to the empty Dictionary has no impact on the directory.
1702        assert!(dict.insert("z".parse().unwrap(), dir_connector.clone().into()).is_none());
1703        let mut readdir_results = fuchsia_fs::directory::readdir(&dir_proxy).await.unwrap();
1704        readdir_results.sort_by(|entry_1, entry_2| entry_1.name.cmp(&entry_2.name));
1705        assert_eq!(
1706            readdir_results,
1707            vec![
1708                directory::DirEntry { name: "a".to_string(), kind: fio::DirentType::Directory },
1709                directory::DirEntry { name: "b".to_string(), kind: fio::DirentType::Directory },
1710                directory::DirEntry { name: "c".to_string(), kind: fio::DirentType::Directory },
1711            ]
1712        );
1713    }
1714
1715    /// Generates a key from an integer such that if i < j, key_for(i) < key_for(j).
1716    /// (A simple string conversion doesn't work because 1 < 10 but "1" > "10" in terms of
1717    /// string comparison.)
1718    fn key_for(i: usize) -> Key {
1719        iter::repeat("A").take(i).collect::<String>().parse().unwrap()
1720    }
1721
1722    fn new_dict(test_type: TestType) -> Arc<Dictionary> {
1723        let dict = Dictionary::new();
1724        match test_type {
1725            TestType::Small => {}
1726            TestType::Big => {
1727                for i in 1..=HYBRID_SWITCH_INSERTION_LEN {
1728                    // These items will come last in the order as long as all other keys begin with
1729                    // a capital letter
1730                    assert!(
1731                        dict.insert(
1732                            format!("_{i}").parse().unwrap(),
1733                            Capability::Data(Arc::new(Data::Int64(1)))
1734                        )
1735                        .is_none()
1736                    );
1737                }
1738            }
1739        }
1740        dict
1741    }
1742
1743    fn adjusted_len(dict: &Dictionary, test_type: TestType) -> usize {
1744        let ofs = match test_type {
1745            TestType::Small => 0,
1746            TestType::Big => HYBRID_SWITCH_INSERTION_LEN,
1747        };
1748        dict.lock().entries.len() - ofs
1749    }
1750}