1use crate::dictionary::{EntryUpdate, UpdateNotifierRetention};
6use crate::fidl::registry::{self, try_from_handle_in_registry};
7use crate::{
8 Capability, ConversionError, Dictionary, RemotableCapability, RemoteError, WeakInstanceToken,
9};
10use fidl_fuchsia_component_sandbox as fsandbox;
11use fuchsia_sync::Mutex;
12use futures::FutureExt;
13use futures::channel::oneshot;
14use log::warn;
15use std::sync::{Arc, Weak};
16use vfs::directory::entry::DirectoryEntry;
17use vfs::directory::helper::DirectlyMutable;
18use vfs::directory::immutable::simple as pfs;
19use vfs::execution_scope::ExecutionScope;
20use vfs::name::Name;
21
22impl From<Dictionary> for fsandbox::DictionaryRef {
23 fn from(dict: Dictionary) -> Self {
24 fsandbox::DictionaryRef { token: registry::insert_token(dict.into()) }
25 }
26}
27
28impl crate::fidl::IntoFsandboxCapability for Dictionary {
29 fn into_fsandbox_capability(self, _token: WeakInstanceToken) -> fsandbox::Capability {
30 fsandbox::Capability::Dictionary(self.into())
31 }
32}
33
34impl TryFrom<fsandbox::DictionaryRef> for Dictionary {
35 type Error = RemoteError;
36
37 fn try_from(dict: fsandbox::DictionaryRef) -> Result<Self, Self::Error> {
38 let any = try_from_handle_in_registry(dict.token.as_handle_ref())?;
39 let Capability::Dictionary(dict) = any else {
40 panic!("BUG: registry has a non-Dictionary capability under a Dictionary koid");
41 };
42 Ok(dict)
43 }
44}
45
46impl TryFrom<fidl::Channel> for Dictionary {
48 type Error = RemoteError;
49
50 fn try_from(dict: fidl::Channel) -> Result<Self, Self::Error> {
51 let any = try_from_handle_in_registry(dict.as_handle_ref())?;
52 let Capability::Dictionary(dict) = any else {
53 panic!("BUG: registry has a non-Dictionary capability under a Dictionary koid");
54 };
55 Ok(dict)
56 }
57}
58
59impl Dictionary {
60 pub fn try_into_directory_entry_oneshot(
70 self,
71 scope: ExecutionScope,
72 token: WeakInstanceToken,
73 ) -> Result<Arc<dyn DirectoryEntry>, ConversionError> {
74 let directory = if let Some(handler) = self.lock().not_found.take() {
75 pfs::Simple::new_with_not_found_handler(handler)
76 } else {
77 pfs::Simple::new()
78 };
79 for (key, value) in self.drain() {
80 let dir_entry = match value {
81 Capability::Dictionary(value) => {
82 value.try_into_directory_entry_oneshot(scope.clone(), token.clone())?
83 }
84 value => value.try_into_directory_entry(scope.clone(), token.clone())?,
85 };
86 let key =
87 Name::try_from(key.to_string()).expect("cm_types::Name is always a valid vfs Name");
88 directory
89 .add_entry_impl(key, dir_entry, false)
90 .expect("dictionary values must be unique")
91 }
92
93 Ok(directory)
94 }
95}
96
97impl RemotableCapability for Dictionary {
98 fn try_into_directory_entry(
99 self,
100 scope: ExecutionScope,
101 token: WeakInstanceToken,
102 ) -> Result<Arc<dyn DirectoryEntry>, ConversionError> {
103 let self_clone = self.clone();
104 let directory = pfs::Simple::new_with_not_found_handler(move |path| {
105 self_clone.not_found(path);
120 });
121 let weak_dir: Weak<pfs::Simple> = Arc::downgrade(&directory);
122 let (error_sender, error_receiver) = oneshot::channel();
123 let error_sender = Mutex::new(Some(error_sender));
124 self.register_update_notifier(Box::new(move |update: EntryUpdate<'_>| {
128 let Some(directory) = weak_dir.upgrade() else {
129 return UpdateNotifierRetention::Drop_;
130 };
131 match update {
132 EntryUpdate::Add(key, value) => {
133 let dir_entry = match value
134 .clone()
135 .try_into_directory_entry(scope.clone(), token.clone())
136 {
137 Ok(dir_entry) => dir_entry,
138 Err(err) => {
139 if let Some(error_sender) = error_sender.lock().take() {
140 let _ = error_sender.send(err);
141 } else {
142 warn!(
143 "value in dictionary cannot be converted to directory entry: \
144 {err:?}"
145 )
146 }
147 return UpdateNotifierRetention::Retain;
148 }
149 };
150 let name = Name::try_from(key.to_string())
151 .expect("cm_types::Name is always a valid vfs Name");
152 directory
153 .add_entry_impl(name, dir_entry, false)
154 .expect("dictionary values must be unique")
155 }
156 EntryUpdate::Remove(key) => {
157 let name = Name::try_from(key.to_string())
158 .expect("cm_types::Name is always a valid vfs Name");
159 let _ = directory.remove_entry_impl(name, false);
160 }
161 EntryUpdate::Idle => (),
162 }
163 UpdateNotifierRetention::Retain
164 }));
165 if let Some(Ok(error)) = error_receiver.now_or_never() {
166 return Err(error);
169 }
170 Ok(directory)
171 }
172}
173
174#[cfg(test)]
176mod tests {
177 use super::*;
178 use crate::dictionary::{
179 BorrowedKey, HYBRID_SWITCH_INSERTION_LEN, HYBRID_SWITCH_REMOVAL_LEN, HybridMap, Key,
180 };
181 use crate::fidl::IntoFsandboxCapability;
182 use crate::{Data, Dictionary, DirConnector, Handle, serve_capability_store};
183 use assert_matches::assert_matches;
184 use fidl::endpoints::{Proxy, create_proxy, create_proxy_and_stream};
185 use fidl::handle::{Channel, HandleBased, Status};
186 use fidl_fuchsia_io as fio;
187 use fuchsia_async as fasync;
188 use fuchsia_fs::directory;
189 use futures::StreamExt;
190 use std::sync::LazyLock;
191 use std::{fmt, iter};
192 use test_case::test_case;
193 use test_util::Counter;
194 use vfs::directory::entry::{
195 DirectoryEntry, EntryInfo, GetEntryInfo, OpenRequest, serve_directory,
196 };
197 use vfs::execution_scope::ExecutionScope;
198 use vfs::path::Path;
199 use vfs::remote::RemoteLike;
200 use vfs::{ObjectRequestRef, pseudo_directory};
201
202 static CAP_KEY: LazyLock<Key> = LazyLock::new(|| "Cap".parse().unwrap());
203
204 #[derive(Debug, Clone, Copy)]
205 enum TestType {
206 Small,
208 Big,
210 }
211
212 #[fuchsia::test]
213 async fn create() {
214 let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
215 let _server = fasync::Task::spawn(async move {
216 let receiver_scope = fasync::Scope::new();
217 serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
218 });
219 let id_gen = sandbox::CapabilityIdGenerator::new();
220
221 let dict_id = id_gen.next();
222 assert_matches!(store.dictionary_create(dict_id).await.unwrap(), Ok(()));
223 assert_matches!(
224 store.dictionary_create(dict_id).await.unwrap(),
225 Err(fsandbox::CapabilityStoreError::IdAlreadyExists)
226 );
227
228 let value = 10;
229 store.import(value, Data::Int64(1).into()).await.unwrap().unwrap();
230 store
231 .dictionary_insert(dict_id, &fsandbox::DictionaryItem { key: "k".into(), value })
232 .await
233 .unwrap()
234 .unwrap();
235
236 let (iterator, server_end) = create_proxy();
238 store.dictionary_keys(dict_id, server_end).await.unwrap().unwrap();
239 let keys = iterator.get_next().await.unwrap();
240 assert!(iterator.get_next().await.unwrap().is_empty());
241 assert_eq!(keys, ["k"]);
242 }
243
244 #[fuchsia::test]
245 async fn create_error() {
246 let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
247 let _server = fasync::Task::spawn(async move {
248 let receiver_scope = fasync::Scope::new();
249 serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
250 });
251
252 let cap = Capability::Data(Data::Int64(42));
253 assert_matches!(
254 store
255 .import(1, cap.into_fsandbox_capability(WeakInstanceToken::new_invalid()))
256 .await
257 .unwrap(),
258 Ok(())
259 );
260 assert_matches!(
261 store.dictionary_create(1).await.unwrap(),
262 Err(fsandbox::CapabilityStoreError::IdAlreadyExists)
263 );
264 }
265
266 #[fuchsia::test]
267 async fn legacy_import() {
268 let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
269 let _server = fasync::Task::spawn(async move {
270 let receiver_scope = fasync::Scope::new();
271 serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
272 });
273
274 let dict_id = 1;
275 assert_matches!(store.dictionary_create(dict_id).await.unwrap(), Ok(()));
276 assert_matches!(
277 store.dictionary_create(dict_id).await.unwrap(),
278 Err(fsandbox::CapabilityStoreError::IdAlreadyExists)
279 );
280
281 let value = 10;
282 store.import(value, Data::Int64(1).into()).await.unwrap().unwrap();
283 store
284 .dictionary_insert(dict_id, &fsandbox::DictionaryItem { key: "k".into(), value })
285 .await
286 .unwrap()
287 .unwrap();
288
289 let (client, server) = fidl::Channel::create();
291 store.dictionary_legacy_export(dict_id, server).await.unwrap().unwrap();
292 let dict_id = 2;
293 store.dictionary_legacy_import(dict_id, client).await.unwrap().unwrap();
294
295 let (iterator, server_end) = create_proxy();
297 store.dictionary_keys(dict_id, server_end).await.unwrap().unwrap();
298 let keys = iterator.get_next().await.unwrap();
299 assert!(iterator.get_next().await.unwrap().is_empty());
300 assert_eq!(keys, ["k"]);
301 }
302
303 #[fuchsia::test]
304 async fn legacy_import_error() {
305 let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
306 let _server = fasync::Task::spawn(async move {
307 let receiver_scope = fasync::Scope::new();
308 serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
309 });
310
311 store.dictionary_create(10).await.unwrap().unwrap();
312 let (dict_ch, server) = fidl::Channel::create();
313 store.dictionary_legacy_export(10, server).await.unwrap().unwrap();
314
315 let cap1 = Capability::Data(Data::Int64(42));
316 store
317 .import(1, cap1.into_fsandbox_capability(WeakInstanceToken::new_invalid()))
318 .await
319 .unwrap()
320 .unwrap();
321 assert_matches!(
322 store.dictionary_legacy_import(1, dict_ch).await.unwrap(),
323 Err(fsandbox::CapabilityStoreError::IdAlreadyExists)
324 );
325
326 let (ch, _) = fidl::Channel::create();
327 assert_matches!(
328 store.dictionary_legacy_import(2, ch.into()).await.unwrap(),
329 Err(fsandbox::CapabilityStoreError::BadCapability)
330 );
331 }
332
333 #[fuchsia::test]
334 async fn legacy_export_error() {
335 let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
336 let _server = fasync::Task::spawn(async move {
337 let receiver_scope = fasync::Scope::new();
338 serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
339 });
340
341 let (_dict_ch, server) = fidl::Channel::create();
342 assert_matches!(
343 store.dictionary_legacy_export(1, server).await.unwrap(),
344 Err(fsandbox::CapabilityStoreError::IdNotFound)
345 );
346 }
347
348 #[test_case(TestType::Small)]
349 #[test_case(TestType::Big)]
350 #[fuchsia::test]
351 async fn insert(test_type: TestType) {
352 let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
353 let _server = fasync::Task::spawn(async move {
354 let receiver_scope = fasync::Scope::new();
355 serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
356 });
357
358 let dict = new_dict(test_type);
359 let dict_ref = Capability::Dictionary(dict.clone())
360 .into_fsandbox_capability(WeakInstanceToken::new_invalid());
361 let dict_id = 1;
362 store.import(dict_id, dict_ref).await.unwrap().unwrap();
363
364 let data = Data::Int64(1).into();
365 let value = 2;
366 store.import(value, data).await.unwrap().unwrap();
367 store
368 .dictionary_insert(
369 dict_id,
370 &fsandbox::DictionaryItem { key: CAP_KEY.to_string(), value },
371 )
372 .await
373 .unwrap()
374 .unwrap();
375
376 assert_eq!(adjusted_len(&dict, test_type), 1);
378
379 let cap = dict.remove(&*CAP_KEY).expect("not in entries after insert");
381 let Capability::Data(data) = cap else { panic!("Bad capability type: {:#?}", cap) };
382 assert_eq!(data, Data::Int64(1));
383 }
384
385 #[fuchsia::test]
386 async fn insert_error() {
387 let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
388 let _server = fasync::Task::spawn(async move {
389 let receiver_scope = fasync::Scope::new();
390 serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
391 });
392
393 let data = Data::Int64(1).into();
394 let value = 2;
395 store.import(value, data).await.unwrap().unwrap();
396
397 assert_matches!(
398 store
399 .dictionary_insert(1, &fsandbox::DictionaryItem { key: "k".into(), value })
400 .await
401 .unwrap(),
402 Err(fsandbox::CapabilityStoreError::IdNotFound)
403 );
404 assert_matches!(
405 store
406 .dictionary_insert(2, &fsandbox::DictionaryItem { key: "k".into(), value })
407 .await
408 .unwrap(),
409 Err(fsandbox::CapabilityStoreError::WrongType)
410 );
411
412 store.dictionary_create(1).await.unwrap().unwrap();
413 assert_matches!(
414 store
415 .dictionary_insert(1, &fsandbox::DictionaryItem { key: "^bad".into(), value })
416 .await
417 .unwrap(),
418 Err(fsandbox::CapabilityStoreError::InvalidKey)
419 );
420
421 assert_matches!(
422 store
423 .dictionary_insert(1, &fsandbox::DictionaryItem { key: "k".into(), value })
424 .await
425 .unwrap(),
426 Ok(())
427 );
428
429 let data = Data::Int64(1).into();
430 let value = 3;
431 store.import(value, data).await.unwrap().unwrap();
432 assert_matches!(
433 store
434 .dictionary_insert(1, &fsandbox::DictionaryItem { key: "k".into(), value })
435 .await
436 .unwrap(),
437 Err(fsandbox::CapabilityStoreError::ItemAlreadyExists)
438 );
439 }
440
441 #[test_case(TestType::Small)]
442 #[test_case(TestType::Big)]
443 #[fuchsia::test]
444 async fn remove(test_type: TestType) {
445 let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
446 let _server = fasync::Task::spawn(async move {
447 let receiver_scope = fasync::Scope::new();
448 serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
449 });
450
451 let dict = new_dict(test_type);
452
453 assert!(dict.insert(CAP_KEY.clone(), Capability::Data(Data::Int64(1))).is_none());
455 assert_eq!(adjusted_len(&dict, test_type), 1);
456
457 let dict_ref = Capability::Dictionary(dict.clone())
458 .into_fsandbox_capability(WeakInstanceToken::new_invalid());
459 let dict_id = 1;
460 store.import(dict_id, dict_ref).await.unwrap().unwrap();
461
462 let dest_id = 2;
463 store
464 .dictionary_remove(
465 dict_id,
466 &CAP_KEY.to_string(),
467 Some(&fsandbox::WrappedNewCapabilityId { id: dest_id }),
468 )
469 .await
470 .unwrap()
471 .unwrap();
472 let cap = store.export(dest_id).await.unwrap().unwrap();
473 assert_eq!(cap, Data::Int64(1).into());
475
476 assert_eq!(adjusted_len(&dict, test_type), 0);
478 }
479
480 #[fuchsia::test]
481 async fn remove_error() {
482 let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
483 let _server = fasync::Task::spawn(async move {
484 let receiver_scope = fasync::Scope::new();
485 serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
486 });
487
488 assert_matches!(
489 store.dictionary_remove(1, "k".into(), None).await.unwrap(),
490 Err(fsandbox::CapabilityStoreError::IdNotFound)
491 );
492
493 store.dictionary_create(1).await.unwrap().unwrap();
494
495 let data = Data::Int64(1).into();
496 store.import(2, data).await.unwrap().unwrap();
497
498 assert_matches!(
499 store.dictionary_remove(2, "k".into(), None).await.unwrap(),
500 Err(fsandbox::CapabilityStoreError::WrongType)
501 );
502 store
503 .dictionary_insert(1, &fsandbox::DictionaryItem { key: "k".into(), value: 2 })
504 .await
505 .unwrap()
506 .unwrap();
507 assert_matches!(
508 store
509 .dictionary_remove(1, "k".into(), Some(&fsandbox::WrappedNewCapabilityId { id: 1 }))
510 .await
511 .unwrap(),
512 Err(fsandbox::CapabilityStoreError::IdAlreadyExists)
513 );
514 assert_matches!(
515 store.dictionary_remove(1, "^bad".into(), None).await.unwrap(),
516 Err(fsandbox::CapabilityStoreError::InvalidKey)
517 );
518 assert_matches!(
519 store.dictionary_remove(1, "not_found".into(), None).await.unwrap(),
520 Err(fsandbox::CapabilityStoreError::ItemNotFound)
521 );
522 }
523
524 #[test_case(TestType::Small)]
525 #[test_case(TestType::Big)]
526 #[fuchsia::test]
527 async fn get(test_type: TestType) {
528 let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
529 let _server = fasync::Task::spawn(async move {
530 let receiver_scope = fasync::Scope::new();
531 serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
532 });
533
534 let dict = new_dict(test_type);
535
536 assert!(dict.insert(CAP_KEY.clone(), Capability::Data(Data::Int64(1))).is_none());
537 assert_eq!(adjusted_len(&dict, test_type), 1);
538 let (ch, _) = fidl::Channel::create();
539 let handle = Handle::new(ch.into_handle());
540 assert!(dict.insert("h".parse().unwrap(), Capability::Handle(handle)).is_none());
541
542 let dict_ref = Capability::Dictionary(dict.clone())
543 .into_fsandbox_capability(WeakInstanceToken::new_invalid());
544 let dict_id = 1;
545 store.import(dict_id, dict_ref).await.unwrap().unwrap();
546
547 let dest_id = 2;
548 store.dictionary_get(dict_id, CAP_KEY.as_str(), dest_id).await.unwrap().unwrap();
549 let cap = store.export(dest_id).await.unwrap().unwrap();
550 assert_eq!(cap, Data::Int64(1).into());
551 }
552
553 #[fuchsia::test]
554 async fn get_error() {
555 let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
556 let _server = fasync::Task::spawn(async move {
557 let receiver_scope = fasync::Scope::new();
558 serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
559 });
560
561 assert_matches!(
562 store.dictionary_get(1, "k".into(), 2).await.unwrap(),
563 Err(fsandbox::CapabilityStoreError::IdNotFound)
564 );
565
566 store.dictionary_create(1).await.unwrap().unwrap();
567
568 store.import(2, Data::Int64(1).into()).await.unwrap().unwrap();
569
570 assert_matches!(
571 store.dictionary_get(2, "k".into(), 3).await.unwrap(),
572 Err(fsandbox::CapabilityStoreError::WrongType)
573 );
574 store
575 .dictionary_insert(1, &fsandbox::DictionaryItem { key: "k".into(), value: 2 })
576 .await
577 .unwrap()
578 .unwrap();
579
580 store.import(2, Data::Int64(1).into()).await.unwrap().unwrap();
581 assert_matches!(
582 store.dictionary_get(1, "k".into(), 2).await.unwrap(),
583 Err(fsandbox::CapabilityStoreError::IdAlreadyExists)
584 );
585 assert_matches!(
586 store.dictionary_get(1, "^bad".into(), 3).await.unwrap(),
587 Err(fsandbox::CapabilityStoreError::InvalidKey)
588 );
589 assert_matches!(
590 store.dictionary_get(1, "not_found".into(), 3).await.unwrap(),
591 Err(fsandbox::CapabilityStoreError::ItemNotFound)
592 );
593 }
594
595 #[test_case(TestType::Small)]
596 #[test_case(TestType::Big)]
597 #[fuchsia::test]
598 async fn copy(test_type: TestType) {
599 let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
600 let _server = fasync::Task::spawn(async move {
601 let receiver_scope = fasync::Scope::new();
602 serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
603 });
604
605 let dict = new_dict(test_type);
607 assert!(dict.insert("data1".parse().unwrap(), Capability::Data(Data::Int64(1))).is_none());
608 store
609 .import(
610 1,
611 dict.clone().into_fsandbox_capability(WeakInstanceToken::new_invalid()).into(),
612 )
613 .await
614 .unwrap()
615 .unwrap();
616 store.dictionary_copy(1, 2).await.unwrap().unwrap();
617
618 store.import(3, Data::Int64(1).into()).await.unwrap().unwrap();
620 store
621 .dictionary_insert(2, &fsandbox::DictionaryItem { key: "k".into(), value: 3 })
622 .await
623 .unwrap()
624 .unwrap();
625
626 let copy = store.export(2).await.unwrap().unwrap();
628 let copy = Capability::try_from(copy).unwrap();
629 let Capability::Dictionary(copy) = copy else { panic!() };
630 {
631 assert_eq!(adjusted_len(©, test_type), 2);
632 let copy = copy.lock();
633 assert!(copy.entries.iter().all(|(_, value)| matches!(value, Capability::Data(_))));
634 }
635
636 {
638 assert_eq!(adjusted_len(&dict, test_type), 1);
639 let dict = dict.lock();
640 assert!(dict.entries.iter().all(|(_, value)| matches!(value, Capability::Data(_))));
641 }
642 }
643
644 #[test_case(TestType::Small)]
645 #[test_case(TestType::Big)]
646 #[fuchsia::test]
647 async fn duplicate(test_type: TestType) {
648 let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
649 let _server = fasync::Task::spawn(async move {
650 let receiver_scope = fasync::Scope::new();
651 serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
652 });
653
654 let dict = new_dict(test_type);
655 store
656 .import(1, dict.clone().into_fsandbox_capability(WeakInstanceToken::new_invalid()))
657 .await
658 .unwrap()
659 .unwrap();
660 store.duplicate(1, 2).await.unwrap().unwrap();
661
662 store.import(3, Data::Int64(1).into()).await.unwrap().unwrap();
664 store
665 .dictionary_insert(2, &fsandbox::DictionaryItem { key: "k".into(), value: 3 })
666 .await
667 .unwrap()
668 .unwrap();
669 let dict_dup = store.export(2).await.unwrap().unwrap();
670 let dict_dup = Capability::try_from(dict_dup).unwrap();
671 let Capability::Dictionary(dict_dup) = dict_dup else { panic!() };
672 assert_eq!(adjusted_len(&dict_dup, test_type), 1);
673
674 assert_eq!(adjusted_len(&dict_dup, test_type), 1);
676 }
677
678 #[test_case(TestType::Small)]
680 #[test_case(TestType::Big)]
681 #[fuchsia::test]
682 async fn read(test_type: TestType) {
683 let dict = new_dict(test_type);
684 let id_gen = sandbox::CapabilityIdGenerator::new();
685
686 let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
687 let _server = fasync::Task::spawn(async move {
688 let receiver_scope = fasync::Scope::new();
689 serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
690 });
691 let dict_ref =
692 Capability::Dictionary(dict).into_fsandbox_capability(WeakInstanceToken::new_invalid());
693 let dict_id = id_gen.next();
694 store.import(dict_id, dict_ref).await.unwrap().unwrap();
695
696 let mut data_caps = vec![];
698 for i in 1..3 {
699 let id = id_gen.next();
700 store.import(id, Data::Int64(i.try_into().unwrap()).into()).await.unwrap().unwrap();
701 data_caps.push(id);
702 }
703
704 store
706 .dictionary_insert(
707 dict_id,
708 &fsandbox::DictionaryItem { key: "Cap1".into(), value: data_caps.remove(0) },
709 )
710 .await
711 .unwrap()
712 .unwrap();
713 store
714 .dictionary_insert(
715 dict_id,
716 &fsandbox::DictionaryItem { key: "Cap2".into(), value: data_caps.remove(0) },
717 )
718 .await
719 .unwrap()
720 .unwrap();
721 let (ch, _) = fidl::Channel::create();
722 let handle = ch.into_handle();
723 let id = id_gen.next();
724 store.import(id, fsandbox::Capability::Handle(handle)).await.unwrap().unwrap();
725 store
726 .dictionary_insert(dict_id, &fsandbox::DictionaryItem { key: "Cap3".into(), value: id })
727 .await
728 .unwrap()
729 .unwrap();
730
731 {
733 let (iterator, server_end) = create_proxy();
734 store.dictionary_keys(dict_id, server_end).await.unwrap().unwrap();
735 let keys = iterator.get_next().await.unwrap();
736 assert!(iterator.get_next().await.unwrap().is_empty());
737 match test_type {
738 TestType::Small => assert_eq!(keys, ["Cap1", "Cap2", "Cap3"]),
739 TestType::Big => {
740 assert_eq!(keys[0..3], ["Cap1", "Cap2", "Cap3"]);
741 assert_eq!(keys.len(), 3 + HYBRID_SWITCH_INSERTION_LEN);
742 }
743 }
744 }
745 {
747 let (iterator, server_end) = create_proxy();
748 store.dictionary_enumerate(dict_id, server_end).await.unwrap().unwrap();
749 let start_id = 100;
750 let ofs: u32 = match test_type {
751 TestType::Small => 0,
752 TestType::Big => HYBRID_SWITCH_INSERTION_LEN as u32,
753 };
754 let limit = 4 + ofs;
755 let (mut items, end_id) = iterator.get_next(start_id, limit).await.unwrap().unwrap();
756 assert_eq!(end_id, 103 + ofs as u64);
757 let (last, end_id) = iterator.get_next(end_id, limit).await.unwrap().unwrap();
758 assert!(last.is_empty());
759 assert_eq!(end_id, 103 + ofs as u64);
760
761 assert_matches!(
762 items.remove(0),
763 fsandbox::DictionaryOptionalItem {
764 key,
765 value: Some(value)
766 }
767 if key == "Cap1" && value.id == 100
768 );
769 assert_matches!(
770 store.export(100).await.unwrap().unwrap(),
771 fsandbox::Capability::Data(fsandbox::Data::Int64(1))
772 );
773 assert_matches!(
774 items.remove(0),
775 fsandbox::DictionaryOptionalItem {
776 key,
777 value: Some(value)
778 }
779 if key == "Cap2" && value.id == 101
780 );
781 assert_matches!(
782 store.export(101).await.unwrap().unwrap(),
783 fsandbox::Capability::Data(fsandbox::Data::Int64(2))
784 );
785 assert_matches!(
786 items.remove(0),
787 fsandbox::DictionaryOptionalItem {
788 key,
789 value: Some(value)
790 }
791 if key == "Cap3" && value.id == 102
792 );
793 match test_type {
794 TestType::Small => {}
795 TestType::Big => {
796 assert_eq!(items.len(), HYBRID_SWITCH_INSERTION_LEN);
797 }
798 }
799 }
800 }
801
802 #[test_case(TestType::Small)]
803 #[test_case(TestType::Big)]
804 #[fuchsia::test]
805 async fn drain(test_type: TestType) {
806 let dict = new_dict(test_type);
807
808 let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
809 let _server = fasync::Task::spawn(async move {
810 let receiver_scope = fasync::Scope::new();
811 serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
812 });
813 let dict_ref = Capability::Dictionary(dict.clone())
814 .into_fsandbox_capability(WeakInstanceToken::new_invalid());
815 let dict_id = 1;
816 store.import(dict_id, dict_ref).await.unwrap().unwrap();
817
818 let mut data_caps = vec![];
820 for i in 1..3 {
821 let value = 10 + i;
822 store.import(value, Data::Int64(i.try_into().unwrap()).into()).await.unwrap().unwrap();
823 data_caps.push(value);
824 }
825
826 store
828 .dictionary_insert(
829 dict_id,
830 &fsandbox::DictionaryItem { key: "Cap1".into(), value: data_caps.remove(0) },
831 )
832 .await
833 .unwrap()
834 .unwrap();
835 store
836 .dictionary_insert(
837 dict_id,
838 &fsandbox::DictionaryItem { key: "Cap2".into(), value: data_caps.remove(0) },
839 )
840 .await
841 .unwrap()
842 .unwrap();
843 let (ch, _) = fidl::Channel::create();
844 let handle = ch.into_handle();
845 let handle_koid = handle.koid().unwrap();
846 let value = 20;
847 store.import(value, fsandbox::Capability::Handle(handle)).await.unwrap().unwrap();
848 store
849 .dictionary_insert(dict_id, &fsandbox::DictionaryItem { key: "Cap3".into(), value })
850 .await
851 .unwrap()
852 .unwrap();
853
854 let (iterator, server_end) = create_proxy();
855 store.dictionary_drain(dict_id, Some(server_end)).await.unwrap().unwrap();
856 let ofs: u32 = match test_type {
857 TestType::Small => 0,
858 TestType::Big => HYBRID_SWITCH_INSERTION_LEN as u32,
859 };
860 let start_id = 100;
861 let limit = 4 + ofs;
862 let (mut items, end_id) = iterator.get_next(start_id, limit).await.unwrap().unwrap();
863 assert_eq!(end_id, 103 + ofs as u64);
864 let (last, end_id) = iterator.get_next(end_id, limit).await.unwrap().unwrap();
865 assert!(last.is_empty());
866 assert_eq!(end_id, 103 + ofs as u64);
867
868 assert_matches!(
869 items.remove(0),
870 fsandbox::DictionaryItem {
871 key,
872 value: 100
873 }
874 if key == "Cap1"
875 );
876 assert_matches!(
877 store.export(100).await.unwrap().unwrap(),
878 fsandbox::Capability::Data(fsandbox::Data::Int64(1))
879 );
880 assert_matches!(
881 items.remove(0),
882 fsandbox::DictionaryItem {
883 key,
884 value: 101
885 }
886 if key == "Cap2"
887 );
888 assert_matches!(
889 store.export(101).await.unwrap().unwrap(),
890 fsandbox::Capability::Data(fsandbox::Data::Int64(2))
891 );
892 assert_matches!(
893 items.remove(0),
894 fsandbox::DictionaryItem {
895 key,
896 value: 102
897 }
898 if key == "Cap3"
899 );
900 assert_matches!(
901 store.export(102).await.unwrap().unwrap(),
902 fsandbox::Capability::Handle(handle)
903 if handle.koid().unwrap() == handle_koid
904 );
905
906 assert!(dict.is_empty());
908 }
909
910 #[fuchsia::test]
912 async fn read_batches() {
913 const NUM_ENTRIES: u32 = fsandbox::MAX_DICTIONARY_ITERATOR_CHUNK * 2 + 1;
917
918 const EXPECTED_CHUNK_LENGTHS: &[u32] =
920 &[fsandbox::MAX_DICTIONARY_ITERATOR_CHUNK, fsandbox::MAX_DICTIONARY_ITERATOR_CHUNK, 1];
921
922 let id_gen = sandbox::CapabilityIdGenerator::new();
923
924 let dict = Dictionary::new();
926 for i in 0..NUM_ENTRIES {
927 assert!(
928 dict.insert(format!("{}", i).parse().unwrap(), Capability::Data(Data::Int64(1)))
929 .is_none()
930 );
931 }
932
933 let (store, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
934 let _server = fasync::Task::spawn(async move {
935 let receiver_scope = fasync::Scope::new();
936 serve_capability_store(stream, &receiver_scope, WeakInstanceToken::new_invalid()).await
937 });
938 let dict_ref = Capability::Dictionary(dict.clone())
939 .into_fsandbox_capability(WeakInstanceToken::new_invalid());
940 let dict_id = id_gen.next();
941 store.import(dict_id, dict_ref).await.unwrap().unwrap();
942
943 let (key_iterator, server_end) = create_proxy();
944 store.dictionary_keys(dict_id, server_end).await.unwrap().unwrap();
945 let (item_iterator, server_end) = create_proxy();
946 store.dictionary_enumerate(dict_id, server_end).await.unwrap().unwrap();
947
948 let mut num_got_items: u32 = 0;
950 let mut start_id = 100;
951 let limit = fsandbox::MAX_DICTIONARY_ITERATOR_CHUNK;
952 for expected_len in EXPECTED_CHUNK_LENGTHS {
953 let keys = key_iterator.get_next().await.unwrap();
954 let (items, end_id) = item_iterator.get_next(start_id, limit).await.unwrap().unwrap();
955 if keys.is_empty() && items.is_empty() {
956 break;
957 }
958 assert_eq!(*expected_len, keys.len() as u32);
959 assert_eq!(*expected_len, items.len() as u32);
960 assert_eq!(u64::from(*expected_len), end_id - start_id);
961 start_id = end_id;
962 num_got_items += *expected_len;
963 }
964
965 let (items, _) = item_iterator.get_next(start_id, limit).await.unwrap().unwrap();
967 assert!(items.is_empty());
968 assert!(key_iterator.get_next().await.unwrap().is_empty());
969
970 assert_eq!(num_got_items, NUM_ENTRIES);
971 }
972
973 #[fuchsia::test]
974 async fn drain_batches() {
975 const NUM_ENTRIES: u32 = fsandbox::MAX_DICTIONARY_ITERATOR_CHUNK * 2 + 1;
979
980 const EXPECTED_CHUNK_LENGTHS: &[u32] =
982 &[fsandbox::MAX_DICTIONARY_ITERATOR_CHUNK, fsandbox::MAX_DICTIONARY_ITERATOR_CHUNK, 1];
983
984 let dict = Dictionary::new();
986 for i in 0..NUM_ENTRIES {
987 assert!(
988 dict.insert(format!("{}", i).parse().unwrap(), Capability::Data(Data::Int64(1)))
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 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 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 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 {
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(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 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 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 {
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 {
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 {
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 {
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 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 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 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 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 *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 store.dictionary_remove(dict_id, key_for(i).as_str(), None).await.unwrap().unwrap();
1452
1453 let updates: Vec<_> = iter::from_fn(move || match update_rx.try_recv() {
1455 Ok(e) => Some(e),
1456 Err(mpsc::TryRecvError::Disconnected) => None,
1457 Err(mpsc::TryRecvError::Empty) => unreachable!(),
1459 })
1460 .collect();
1461 let expected_updates = [
1462 Update::Idle,
1463 Update::Add(key_for(1)),
1465 Update::Add(key_for(2)),
1466 Update::Remove(key_for(2)),
1467 Update::Add(key_for(2)),
1468 Update::Remove(key_for(1)),
1470 Update::Add(key_for(3)),
1472 Update::Remove(key_for(2)),
1473 Update::Remove(key_for(3)),
1474 Update::Add(key_for(4)),
1476 ];
1477 match test_type {
1478 TestType::Small => {
1479 assert_eq!(updates, expected_updates);
1480 }
1481 TestType::Big => {
1482 let updates = &updates[HYBRID_SWITCH_INSERTION_LEN..];
1484 let nexpected = expected_updates.len() - 1;
1485 assert_eq!(updates[..nexpected], expected_updates[..nexpected]);
1486
1487 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_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 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 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 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 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 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 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 assert!(dict.is_empty());
1699 assert!(inner_dict.is_empty());
1700
1701 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 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) -> 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 assert!(
1731 dict.insert(
1732 format!("_{i}").parse().unwrap(),
1733 Capability::Data(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}