sandbox/fidl/
store.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::dict::Key;
6use crate::fidl::registry;
7use crate::{Capability, Connector, Dict, DirConnector, Message};
8use cm_types::RelativePath;
9use fidl::handle::Signals;
10use fidl::AsHandleRef;
11use futures::{FutureExt, TryStreamExt};
12use log::warn;
13use std::collections::hash_map::Entry;
14use std::collections::HashMap;
15use std::sync::{self, Arc, Weak};
16use {fidl_fuchsia_component_sandbox as fsandbox, fuchsia_async as fasync};
17
18type Store = sync::Mutex<HashMap<u64, Capability>>;
19
20pub async fn serve_capability_store(
21    mut stream: fsandbox::CapabilityStoreRequestStream,
22    // Receiver tasks launched on behalf of a *Connector may need to outlive this
23    // `CapabilityStore`. For example, if a client creates a Connector, Export()s it right away,
24    // then drops the `CapabilityStore`, dropping the task at that point deliver a `PEER_CLOSED`
25    // to the client and make the Connector they just created unusable.
26    //
27    // We could simply detach() the Task instead, but fuchsia_async considers that holding the
28    // handle is considered better practice.
29    receiver_scope: &fasync::Scope,
30) -> Result<(), fidl::Error> {
31    let outer_store: Arc<Store> = Arc::new(Store::new(Default::default()));
32    while let Some(request) = stream.try_next().await? {
33        let mut store = outer_store.lock().unwrap();
34        match request {
35            fsandbox::CapabilityStoreRequest::Duplicate { id, dest_id, responder } => {
36                let result = (|| {
37                    let cap = store.get(&id).ok_or(fsandbox::CapabilityStoreError::IdNotFound)?;
38                    let cap = cap
39                        .try_clone()
40                        .map_err(|_| fsandbox::CapabilityStoreError::NotDuplicatable)?;
41                    insert_capability(&mut store, dest_id, cap)
42                })();
43                responder.send(result)?;
44            }
45            fsandbox::CapabilityStoreRequest::Drop { id, responder } => {
46                let result =
47                    store.remove(&id).map(|_| ()).ok_or(fsandbox::CapabilityStoreError::IdNotFound);
48                responder.send(result)?;
49            }
50            fsandbox::CapabilityStoreRequest::Export { id, responder } => {
51                let result = (|| {
52                    let cap =
53                        store.remove(&id).ok_or(fsandbox::CapabilityStoreError::IdNotFound)?;
54                    Ok(cap.into())
55                })();
56                responder.send(result)?;
57            }
58            fsandbox::CapabilityStoreRequest::Import { id, capability, responder } => {
59                let result = (|| {
60                    let capability = capability
61                        .try_into()
62                        .map_err(|_| fsandbox::CapabilityStoreError::BadCapability)?;
63                    insert_capability(&mut store, id, capability)
64                })();
65                responder.send(result)?;
66            }
67            fsandbox::CapabilityStoreRequest::ConnectorCreate { id, receiver, responder } => {
68                let result = (|| {
69                    let connector = Connector::new_with_fidl_receiver(receiver, receiver_scope);
70                    insert_capability(&mut store, id, Capability::Connector(connector))
71                })();
72                responder.send(result)?;
73            }
74            fsandbox::CapabilityStoreRequest::ConnectorOpen { id, server_end, responder } => {
75                let result = (|| {
76                    let this = get_connector(&store, id)?;
77                    let _ = this.send(Message { channel: server_end });
78                    Ok(())
79                })();
80                responder.send(result)?;
81            }
82            fsandbox::CapabilityStoreRequest::DirConnectorCreate { id, receiver, responder } => {
83                let result = (|| {
84                    let connector = DirConnector::new_with_fidl_receiver(receiver, receiver_scope);
85                    insert_capability(&mut store, id, Capability::DirConnector(connector))
86                })();
87                responder.send(result)?;
88            }
89            fsandbox::CapabilityStoreRequest::DirConnectorOpen { id, server_end, responder } => {
90                let result = (|| {
91                    let this = get_dir_connector(&store, id)?;
92                    let _ = this.send(server_end, RelativePath::dot(), None);
93                    Ok(())
94                })();
95                responder.send(result)?;
96            }
97            fsandbox::CapabilityStoreRequest::DictionaryCreate { id, responder } => {
98                let result = insert_capability(&mut store, id, Capability::Dictionary(Dict::new()));
99                responder.send(result)?;
100            }
101            fsandbox::CapabilityStoreRequest::DictionaryLegacyImport {
102                id,
103                client_end,
104                responder,
105            } => {
106                let result = (|| {
107                    let capability = Dict::try_from(client_end)
108                        .map_err(|_| fsandbox::CapabilityStoreError::BadCapability)?
109                        .into();
110                    insert_capability(&mut store, id, capability)
111                })();
112                responder.send(result)?;
113            }
114            fsandbox::CapabilityStoreRequest::DictionaryLegacyExport {
115                id,
116                server_end,
117                responder,
118            } => {
119                let result = (|| {
120                    let cap =
121                        store.remove(&id).ok_or(fsandbox::CapabilityStoreError::IdNotFound)?;
122                    let Capability::Dictionary(_) = &cap else {
123                        return Err(fsandbox::CapabilityStoreError::WrongType);
124                    };
125                    let koid = server_end.basic_info().unwrap().related_koid;
126                    registry::insert(
127                        cap,
128                        koid,
129                        fasync::OnSignals::new(server_end, Signals::OBJECT_PEER_CLOSED).map(|_| ()),
130                    );
131                    Ok(())
132                })();
133                responder.send(result)?
134            }
135            fsandbox::CapabilityStoreRequest::DictionaryInsert { id, item, responder } => {
136                let result = (|| {
137                    let this = get_dictionary(&store, id)?;
138                    let this = this.clone();
139                    let key =
140                        item.key.parse().map_err(|_| fsandbox::CapabilityStoreError::InvalidKey)?;
141                    let value = store
142                        .remove(&item.value)
143                        .ok_or(fsandbox::CapabilityStoreError::IdNotFound)?;
144                    this.insert(key, value)
145                })();
146                responder.send(result)?;
147            }
148            fsandbox::CapabilityStoreRequest::DictionaryGet { id, key, dest_id, responder } => {
149                let result = (|| {
150                    let this = get_dictionary(&store, id)?;
151                    let key =
152                        Key::new(key).map_err(|_| fsandbox::CapabilityStoreError::InvalidKey)?;
153                    let cap = match this.get(&key) {
154                        Ok(Some(cap)) => Ok(cap),
155                        Ok(None) => {
156                            this.not_found(key.as_str());
157                            Err(fsandbox::CapabilityStoreError::ItemNotFound)
158                        }
159                        Err(()) => Err(fsandbox::CapabilityStoreError::NotDuplicatable),
160                    }?;
161                    insert_capability(&mut store, dest_id, cap)
162                })();
163                responder.send(result)?;
164            }
165            fsandbox::CapabilityStoreRequest::DictionaryRemove { id, key, dest_id, responder } => {
166                let result = (|| {
167                    let this = get_dictionary(&store, id)?;
168                    let key =
169                        Key::new(key).map_err(|_| fsandbox::CapabilityStoreError::InvalidKey)?;
170                    // Check this before removing from the dictionary.
171                    if let Some(dest_id) = dest_id.as_ref() {
172                        if store.contains_key(&dest_id.id) {
173                            return Err(fsandbox::CapabilityStoreError::IdAlreadyExists);
174                        }
175                    }
176                    let cap = match this.remove(&key) {
177                        Some(cap) => Ok(cap.into()),
178                        None => {
179                            this.not_found(key.as_str());
180                            Err(fsandbox::CapabilityStoreError::ItemNotFound)
181                        }
182                    }?;
183                    if let Some(dest_id) = dest_id.as_ref() {
184                        store.insert(dest_id.id, cap);
185                    }
186                    Ok(())
187                })();
188                responder.send(result)?;
189            }
190            fsandbox::CapabilityStoreRequest::DictionaryCopy { id, dest_id, responder } => {
191                let result = (|| {
192                    let this = get_dictionary(&store, id)?;
193                    let dict = this
194                        .shallow_copy()
195                        .map_err(|_| fsandbox::CapabilityStoreError::NotDuplicatable)?;
196                    insert_capability(&mut store, dest_id, Capability::Dictionary(dict))
197                })();
198                responder.send(result)?
199            }
200            fsandbox::CapabilityStoreRequest::DictionaryKeys {
201                id,
202                iterator: server_end,
203                responder,
204            } => {
205                let result = (|| {
206                    let this = get_dictionary(&store, id)?;
207                    let keys = this.keys();
208                    let stream = server_end.into_stream();
209                    let mut this = this.lock();
210                    this.tasks().spawn(serve_dictionary_keys_iterator(keys, stream));
211                    Ok(())
212                })();
213                responder.send(result)?
214            }
215            fsandbox::CapabilityStoreRequest::DictionaryEnumerate {
216                id,
217                iterator: server_end,
218                responder,
219            } => {
220                let result = (|| {
221                    let this = get_dictionary(&store, id)?;
222                    let items = this.enumerate();
223                    let stream = server_end.into_stream();
224                    let mut this = this.lock();
225                    this.tasks().spawn(serve_dictionary_enumerate_iterator(
226                        Arc::downgrade(&outer_store),
227                        items,
228                        stream,
229                    ));
230                    Ok(())
231                })();
232                responder.send(result)?
233            }
234            fsandbox::CapabilityStoreRequest::DictionaryDrain {
235                id,
236                iterator: server_end,
237                responder,
238            } => {
239                let result = (|| {
240                    let this = get_dictionary(&store, id)?;
241                    // Take out entries, replacing with an empty BTreeMap.
242                    // They are dropped if the caller does not request an iterator.
243                    let items = this.drain();
244                    if let Some(server_end) = server_end {
245                        let stream = server_end.into_stream();
246                        let mut this = this.lock();
247                        this.tasks().spawn(serve_dictionary_drain_iterator(
248                            Arc::downgrade(&outer_store),
249                            items,
250                            stream,
251                        ));
252                    }
253                    Ok(())
254                })();
255                responder.send(result)?
256            }
257            fsandbox::CapabilityStoreRequest::_UnknownMethod { ordinal, .. } => {
258                warn!("Received unknown CapabilityStore request with ordinal {ordinal}");
259            }
260        }
261    }
262    Ok(())
263}
264
265async fn serve_dictionary_keys_iterator(
266    mut keys: impl Iterator<Item = Key>,
267    mut stream: fsandbox::DictionaryKeysIteratorRequestStream,
268) {
269    while let Ok(Some(request)) = stream.try_next().await {
270        match request {
271            fsandbox::DictionaryKeysIteratorRequest::GetNext { responder } => {
272                let mut chunk = vec![];
273                for _ in 0..fsandbox::MAX_DICTIONARY_ITERATOR_CHUNK {
274                    match keys.next() {
275                        Some(key) => {
276                            chunk.push(key.into());
277                        }
278                        None => break,
279                    }
280                }
281                let _ = responder.send(&chunk);
282            }
283            fsandbox::DictionaryKeysIteratorRequest::_UnknownMethod { ordinal, .. } => {
284                warn!(ordinal:%; "Unknown DictionaryKeysIterator request");
285            }
286        }
287    }
288}
289
290async fn serve_dictionary_enumerate_iterator(
291    store: Weak<Store>,
292    mut items: impl Iterator<Item = (Key, Result<Capability, ()>)>,
293    mut stream: fsandbox::DictionaryEnumerateIteratorRequestStream,
294) {
295    while let Ok(Some(request)) = stream.try_next().await {
296        let Some(store) = store.upgrade() else {
297            return;
298        };
299        let mut store = store.lock().unwrap();
300        match request {
301            fsandbox::DictionaryEnumerateIteratorRequest::GetNext {
302                start_id,
303                limit,
304                responder,
305            } => {
306                let result = (|| {
307                    let mut next_id = start_id;
308                    let chunk = get_next_chunk(&*store, &mut items, &mut next_id, limit)?;
309                    let end_id = next_id;
310
311                    let chunk: Vec<_> = chunk
312                        .into_iter()
313                        .map(|(key, value)| {
314                            if let Some((capability, id)) = value {
315                                store.insert(id, capability);
316                                fsandbox::DictionaryOptionalItem {
317                                    key: key.into(),
318                                    value: Some(Box::new(fsandbox::WrappedCapabilityId { id })),
319                                }
320                            } else {
321                                fsandbox::DictionaryOptionalItem { key: key.into(), value: None }
322                            }
323                        })
324                        .collect();
325                    Ok((chunk, end_id))
326                })();
327                let err = result.is_err();
328                let _ = responder.send(result);
329                if err {
330                    return;
331                }
332            }
333            fsandbox::DictionaryEnumerateIteratorRequest::_UnknownMethod { ordinal, .. } => {
334                warn!(ordinal:%; "Unknown DictionaryEnumerateIterator request");
335            }
336        }
337    }
338}
339
340async fn serve_dictionary_drain_iterator(
341    store: Weak<Store>,
342    items: impl Iterator<Item = (Key, Capability)>,
343    mut stream: fsandbox::DictionaryDrainIteratorRequestStream,
344) {
345    // Transform iterator to be compatible with get_next_chunk()
346    let mut items = items.map(|(key, capability)| (key, Ok(capability)));
347    while let Ok(Some(request)) = stream.try_next().await {
348        let Some(store) = store.upgrade() else {
349            return;
350        };
351        let mut store = store.lock().unwrap();
352        match request {
353            fsandbox::DictionaryDrainIteratorRequest::GetNext { start_id, limit, responder } => {
354                let result = (|| {
355                    let mut next_id = start_id;
356                    let chunk = get_next_chunk(&*store, &mut items, &mut next_id, limit)?;
357                    let end_id = next_id;
358
359                    let chunk: Vec<_> = chunk
360                        .into_iter()
361                        .map(|(key, value)| {
362                            let value = value.expect("unreachable: all values are present");
363                            let (capability, id) = value;
364                            store.insert(id, capability);
365                            fsandbox::DictionaryItem { key: key.into(), value: id }
366                        })
367                        .collect();
368                    Ok((chunk, end_id))
369                })();
370                match result {
371                    Ok((chunk, id)) => {
372                        let _ = responder.send(Ok((&chunk[..], id)));
373                    }
374                    Err(e) => {
375                        let _ = responder.send(Err(e));
376                        return;
377                    }
378                }
379            }
380            fsandbox::DictionaryDrainIteratorRequest::_UnknownMethod { ordinal, .. } => {
381                warn!(ordinal:%; "Unknown DictionaryDrainIterator request");
382            }
383        }
384    }
385}
386
387fn get_next_chunk(
388    store: &HashMap<u64, Capability>,
389    items: &mut impl Iterator<Item = (Key, Result<Capability, ()>)>,
390    next_id: &mut u64,
391    limit: u32,
392) -> Result<Vec<(Key, Option<(Capability, fsandbox::CapabilityId)>)>, fsandbox::CapabilityStoreError>
393{
394    if limit == 0 || limit > fsandbox::MAX_DICTIONARY_ITERATOR_CHUNK {
395        return Err(fsandbox::CapabilityStoreError::InvalidArgs);
396    }
397
398    let mut chunk = vec![];
399    for _ in 0..limit {
400        match items.next() {
401            Some((key, value)) => {
402                let value = match value {
403                    Ok(value) => {
404                        let id = *next_id;
405                        // Pre-flight check: if an id is unavailable, return early
406                        // and don't make any changes to the store.
407                        if store.contains_key(&id) {
408                            return Err(fsandbox::CapabilityStoreError::IdAlreadyExists);
409                        }
410                        *next_id += 1;
411                        Some((value, id))
412                    }
413                    Err(_) => None,
414                };
415                chunk.push((key, value));
416            }
417            None => break,
418        }
419    }
420    Ok(chunk)
421}
422
423fn get_connector(
424    store: &HashMap<u64, Capability>,
425    id: u64,
426) -> Result<&Connector, fsandbox::CapabilityStoreError> {
427    let conn = store.get(&id).ok_or(fsandbox::CapabilityStoreError::IdNotFound)?;
428    if let Capability::Connector(conn) = conn {
429        Ok(conn)
430    } else {
431        Err(fsandbox::CapabilityStoreError::WrongType)
432    }
433}
434
435fn get_dir_connector(
436    store: &HashMap<u64, Capability>,
437    id: u64,
438) -> Result<&DirConnector, fsandbox::CapabilityStoreError> {
439    let conn = store.get(&id).ok_or(fsandbox::CapabilityStoreError::IdNotFound)?;
440    if let Capability::DirConnector(conn) = conn {
441        Ok(conn)
442    } else {
443        Err(fsandbox::CapabilityStoreError::WrongType)
444    }
445}
446
447fn get_dictionary(
448    store: &HashMap<u64, Capability>,
449    id: u64,
450) -> Result<&Dict, fsandbox::CapabilityStoreError> {
451    let dict = store.get(&id).ok_or(fsandbox::CapabilityStoreError::IdNotFound)?;
452    if let Capability::Dictionary(dict) = dict {
453        Ok(dict)
454    } else {
455        Err(fsandbox::CapabilityStoreError::WrongType)
456    }
457}
458
459fn insert_capability(
460    store: &mut HashMap<u64, Capability>,
461    id: u64,
462    cap: Capability,
463) -> Result<(), fsandbox::CapabilityStoreError> {
464    match store.entry(id) {
465        Entry::Occupied(_) => Err(fsandbox::CapabilityStoreError::IdAlreadyExists),
466        Entry::Vacant(entry) => {
467            entry.insert(cap);
468            Ok(())
469        }
470    }
471}
472
473#[cfg(test)]
474mod tests {
475    use super::*;
476    use crate::Data;
477    use assert_matches::assert_matches;
478    use fidl::{endpoints, HandleBased};
479
480    #[fuchsia::test]
481    async fn import_export() {
482        let (store, stream) =
483            endpoints::create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
484        let _server = fasync::Task::spawn(async move {
485            let receiver_scope = fasync::Scope::new();
486            serve_capability_store(stream, &receiver_scope).await
487        });
488
489        let (ch, _) = fidl::Channel::create();
490        let handle = ch.into_handle();
491        let handle_koid = handle.get_koid().unwrap();
492        let cap1 = Capability::Handle(handle.into());
493        let cap2 = Capability::Data(Data::Int64(42));
494        store.import(1, cap1.into()).await.unwrap().unwrap();
495        store.import(2, cap2.into()).await.unwrap().unwrap();
496
497        let cap1 = store.export(1).await.unwrap().unwrap();
498        let cap2 = store.export(2).await.unwrap().unwrap();
499        assert_matches!(
500            cap1,
501            fsandbox::Capability::Handle(h) if h.get_koid().unwrap() == handle_koid
502        );
503        assert_matches!(
504            cap2,
505            fsandbox::Capability::Data(fsandbox::Data::Int64(i)) if i == 42
506        );
507    }
508
509    #[fuchsia::test]
510    async fn import_error() {
511        let (store, stream) =
512            endpoints::create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
513        let _server = fasync::Task::spawn(async move {
514            let receiver_scope = fasync::Scope::new();
515            serve_capability_store(stream, &receiver_scope).await
516        });
517
518        let cap1 = Capability::Data(Data::Int64(42));
519        store.import(1, cap1.try_clone().unwrap().into()).await.unwrap().unwrap();
520        assert_matches!(
521            store.import(1, cap1.into()).await.unwrap(),
522            Err(fsandbox::CapabilityStoreError::IdAlreadyExists)
523        );
524
525        let (token, _) = fidl::EventPair::create();
526        let bad_connector = fsandbox::Capability::Connector(fsandbox::Connector { token });
527        assert_matches!(
528            store.import(2, bad_connector).await.unwrap(),
529            Err(fsandbox::CapabilityStoreError::BadCapability)
530        );
531    }
532
533    #[fuchsia::test]
534    async fn export_error() {
535        let (store, stream) =
536            endpoints::create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
537        let _server = fasync::Task::spawn(async move {
538            let receiver_scope = fasync::Scope::new();
539            serve_capability_store(stream, &receiver_scope).await
540        });
541
542        let cap1 = Capability::Data(Data::Int64(42));
543        store.import(1, cap1.try_clone().unwrap().into()).await.unwrap().unwrap();
544
545        assert_matches!(
546            store.export(2).await.unwrap(),
547            Err(fsandbox::CapabilityStoreError::IdNotFound)
548        );
549    }
550
551    #[fuchsia::test]
552    async fn drop() {
553        let (store, stream) =
554            endpoints::create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
555        let _server = fasync::Task::spawn(async move {
556            let receiver_scope = fasync::Scope::new();
557            serve_capability_store(stream, &receiver_scope).await
558        });
559
560        let (ch, _) = fidl::Channel::create();
561        let handle = ch.into_handle();
562        let handle_koid = handle.get_koid().unwrap();
563        let cap1 = Capability::Handle(handle.into());
564        let cap2 = Capability::Data(Data::Int64(42));
565        store.import(1, cap1.into()).await.unwrap().unwrap();
566        store.import(2, cap2.into()).await.unwrap().unwrap();
567
568        // Drop capability 2. It's no longer in the store.
569        store.drop(2).await.unwrap().unwrap();
570        assert_matches!(
571            store.export(1).await.unwrap(),
572            Ok(fsandbox::Capability::Handle(h)) if h.get_koid().unwrap() == handle_koid
573        );
574        assert_matches!(
575            store.export(2).await.unwrap(),
576            Err(fsandbox::CapabilityStoreError::IdNotFound)
577        );
578
579        // Id 2 can be reused.
580        let cap2 = Capability::Data(Data::Int64(84));
581        store.import(2, cap2.into()).await.unwrap().unwrap();
582        assert_matches!(
583            store.export(2).await.unwrap(),
584            Ok(fsandbox::Capability::Data(fsandbox::Data::Int64(i))) if i == 84
585        );
586    }
587
588    #[fuchsia::test]
589    async fn drop_error() {
590        let (store, stream) =
591            endpoints::create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
592        let _server = fasync::Task::spawn(async move {
593            let receiver_scope = fasync::Scope::new();
594            serve_capability_store(stream, &receiver_scope).await
595        });
596
597        let cap1 = Capability::Data(Data::Int64(42));
598        store.import(1, cap1.into()).await.unwrap().unwrap();
599
600        assert_matches!(
601            store.drop(2).await.unwrap(),
602            Err(fsandbox::CapabilityStoreError::IdNotFound)
603        );
604    }
605
606    #[fuchsia::test]
607    async fn duplicate() {
608        let (store, stream) =
609            endpoints::create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
610        let _server = fasync::Task::spawn(async move {
611            let receiver_scope = fasync::Scope::new();
612            serve_capability_store(stream, &receiver_scope).await
613        });
614
615        let (event, _) = fidl::EventPair::create();
616        let handle = event.into_handle();
617        let handle_koid = handle.get_koid().unwrap();
618        let cap1 = Capability::Handle(handle.into());
619        store.import(1, cap1.into()).await.unwrap().unwrap();
620        store.duplicate(1, 2).await.unwrap().unwrap();
621        store.drop(1).await.unwrap().unwrap();
622
623        let cap1 = store.export(2).await.unwrap().unwrap();
624        assert_matches!(
625            cap1,
626            fsandbox::Capability::Handle(h) if h.get_koid().unwrap() == handle_koid
627        );
628    }
629
630    #[fuchsia::test]
631    async fn duplicate_error() {
632        let (store, stream) =
633            endpoints::create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
634        let _server = fasync::Task::spawn(async move {
635            let receiver_scope = fasync::Scope::new();
636            serve_capability_store(stream, &receiver_scope).await
637        });
638
639        assert_matches!(
640            store.duplicate(1, 2).await.unwrap(),
641            Err(fsandbox::CapabilityStoreError::IdNotFound)
642        );
643
644        let cap1 = Capability::Data(Data::Int64(42));
645        let cap2 = Capability::Data(Data::Int64(84));
646        store.import(1, cap1.into()).await.unwrap().unwrap();
647        store.import(2, cap2.into()).await.unwrap().unwrap();
648        assert_matches!(
649            store.duplicate(1, 2).await.unwrap(),
650            Err(fsandbox::CapabilityStoreError::IdAlreadyExists)
651        );
652
653        // Channels do not support duplication.
654        let (ch, _) = fidl::Channel::create();
655        let handle = ch.into_handle();
656        let cap1 = Capability::Handle(handle.into());
657        store.import(3, cap1.into()).await.unwrap().unwrap();
658        assert_matches!(
659            store.duplicate(3, 4).await.unwrap(),
660            Err(fsandbox::CapabilityStoreError::NotDuplicatable)
661        );
662    }
663}