1use 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 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 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 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 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 return Err(error);
159 }
160 Ok(directory)
161 }
162}
163
164#[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 Small,
199 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 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 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 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 assert_eq!(adjusted_len(&dict, test_type), 1);
369
370 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 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 assert_eq!(cap, Data::Int64(1).into());
466
467 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 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 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 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(©, test_type), 2);
623 let copy = copy.lock();
624 assert!(copy.entries.iter().all(|(_, value)| matches!(value, Capability::Data(_))));
625 }
626
627 {
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 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 assert_eq!(adjusted_len(&dict_dup, test_type), 1);
667 }
668
669 #[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 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 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 {
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 {
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 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 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 assert!(dict.is_empty());
899 }
900
901 #[fuchsia::test]
903 async fn read_batches() {
904 const NUM_ENTRIES: u32 = fsandbox::MAX_DICTIONARY_ITERATOR_CHUNK * 2 + 1;
908
909 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 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 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 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 const NUM_ENTRIES: u32 = fsandbox::MAX_DICTIONARY_ITERATOR_CHUNK * 2 + 1;
970
971 const EXPECTED_CHUNK_LENGTHS: &[u32] =
973 &[fsandbox::MAX_DICTIONARY_ITERATOR_CHUNK, fsandbox::MAX_DICTIONARY_ITERATOR_CHUNK, 1];
974
975 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 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 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 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 {
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 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 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 {
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 {
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 {
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 {
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 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 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 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 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 *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 store.dictionary_remove(dict_id, key_for(i).as_str(), None).await.unwrap().unwrap();
1443
1444 let updates: Vec<_> = iter::from_fn(move || match update_rx.try_recv() {
1446 Ok(e) => Some(e),
1447 Err(mpsc::TryRecvError::Disconnected) => None,
1448 Err(mpsc::TryRecvError::Empty) => unreachable!(),
1450 })
1451 .collect();
1452 let expected_updates = [
1453 Update::Idle,
1454 Update::Add(key_for(1)),
1456 Update::Add(key_for(2)),
1457 Update::Remove(key_for(2)),
1458 Update::Add(key_for(2)),
1459 Update::Remove(key_for(1)),
1461 Update::Add(key_for(3)),
1463 Update::Remove(key_for(2)),
1464 Update::Remove(key_for(3)),
1465 Update::Add(key_for(4)),
1467 ];
1468 match test_type {
1469 TestType::Small => {
1470 assert_eq!(updates, expected_updates);
1471 }
1472 TestType::Big => {
1473 let updates = &updates[HYBRID_SWITCH_INSERTION_LEN..];
1475 let nexpected = expected_updates.len() - 1;
1476 assert_eq!(updates[..nexpected], expected_updates[..nexpected]);
1477
1478 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_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 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 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 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 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 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 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 assert!(dict.is_empty());
1690 assert!(inner_dict.is_empty());
1691
1692 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 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 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}