sandbox/
lib.rs

1// Copyright 2025 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
5//! A library for interacting with the `fuchsia.component.sandbox` FIDL APIs.
6//!
7//! This library provides type-safe wrappers around the raw FIDL types for
8//! capabilities and the capability store, simplifying their use.
9
10use fuchsia_component::client;
11use std::sync::Arc;
12use std::sync::atomic::{AtomicU64, Ordering};
13use thiserror::Error;
14use {fidl_fuchsia_component_sandbox as fsandbox, fidl_fuchsia_io as fio};
15
16/// A trait for types that can be imported into a [`CapabilityStore`].
17///
18/// Meant to be implemented by newtypes of [`CapabilityHandle`].
19pub trait Importable<'a>: Sized + Into<Capability> + TryFrom<Capability, Error = Error> {
20    /// The type of handle that will be returned on import.
21    type Handle: CapabilityHandle<'a>;
22}
23
24/// An error that can occur when interacting with the sandbox APIs.
25#[derive(Error, Clone, Debug)]
26pub enum Error {
27    /// Failed to connect to `fuchsia.component.sandbox.CapabilityStore`.
28    #[error("failed to connect to fuchsia.component.sandbox/CapabilityStore: {0}")]
29    FailedConnect(zx::Status),
30    /// An operation expected a capability of a certain type, but received a different one.
31    #[error("wrong Capability type; got \"{got}\", want \"{want}\"")]
32    WrongType { got: &'static str, want: &'static str },
33    /// A FIDL transport error occurred.
34    #[error("FIDL error {0}")]
35    Fidl(#[from] fidl::Error),
36    /// An error returned from the `fuchsia.component.sandbox.CapabilityStore` protocol.
37    #[error("CapabilityStoreError {0:?}")]
38    CapabilityStore(fsandbox::CapabilityStoreError),
39}
40
41impl From<fsandbox::CapabilityStoreError> for Error {
42    fn from(value: fsandbox::CapabilityStoreError) -> Self {
43        Self::CapabilityStore(value)
44    }
45}
46
47/// A helper struct to generate unused capability IDs.
48///
49/// This is clonable and thread-safe. There should generally be one of these
50/// for each [`CapabilityStore`] connection.
51#[derive(Clone, Default, Debug)]
52pub struct CapabilityIdGenerator {
53    next_id: Arc<AtomicU64>,
54}
55
56impl CapabilityIdGenerator {
57    /// Creates a new ID generator.
58    pub fn new() -> Self {
59        Self::default()
60    }
61
62    /// Get the next free id.
63    pub fn next(&self) -> u64 {
64        self.range(1)
65    }
66
67    /// Get a range of free ids of size `size`.
68    /// This returns the first id in the range.
69    pub fn range(&self, size: u64) -> u64 {
70        self.next_id.fetch_add(size, Ordering::Relaxed)
71    }
72}
73
74/// A wrapper over [`fsandbox::CapabilityStoreProxy`] providing self-contained types.
75#[derive(Clone, Debug)]
76pub struct CapabilityStore {
77    proxy: fsandbox::CapabilityStoreProxy,
78    id_gen: CapabilityIdGenerator,
79}
80
81impl<'a> CapabilityStore {
82    /// Connects to the `fuchsia.component.sandbox/CapabilityStore` protocol.
83    pub fn connect() -> Result<Self, Error> {
84        let proxy = client::connect_to_protocol::<fsandbox::CapabilityStoreMarker>().map_err(
85            |e| match e.downcast_ref() {
86                Some(status) => Error::FailedConnect(*status),
87                None => Error::FailedConnect(zx::Status::INTERNAL),
88            },
89        )?;
90        Ok(Self { proxy, id_gen: CapabilityIdGenerator::default() })
91    }
92
93    /// Imports a capability into the store.
94    pub async fn import<T>(&'a self, value: T) -> Result<T::Handle, Error>
95    where
96        T: Importable<'a>,
97    {
98        let id = self.id_gen.next();
99        self.proxy.import(id, value.into().0).await??;
100        Ok(T::Handle::from_store(self, id))
101    }
102
103    /// Creates a new, empty dictionary in the capability store.
104    pub async fn create_dictionary(&'a self) -> Result<Dictionary<'a>, Error> {
105        let id = self.id_gen.next();
106        self.proxy.dictionary_create(id).await??;
107        Ok(Dictionary { store: self, id })
108    }
109
110    /// Creates a new `Connector` capability from a `Receiver` client end.
111    pub async fn create_connector(
112        &'a self,
113        client_end: fidl::endpoints::ClientEnd<fsandbox::ReceiverMarker>,
114    ) -> Result<Connector<'a>, Error> {
115        let id = self.id_gen.next();
116        self.proxy.connector_create(id, client_end).await??;
117        Ok(Connector { store: self, id })
118    }
119}
120
121/// A handle to a [`fsandbox::Capability`] stored in a repository held by the
122/// component framework.
123///
124/// This handle represents a capability of an unconfirmed type. Specify type
125/// parameters on `export()` methods to derive the type.
126#[allow(async_fn_in_trait)]
127pub trait CapabilityHandle<'a>: Sized {
128    fn from_store(store: &'a CapabilityStore, id: fsandbox::CapabilityId) -> Self;
129    fn store(&self) -> &'a CapabilityStore;
130    fn id(&self) -> fsandbox::CapabilityId;
131
132    /// Duplicates the capability referenced by this handle, returning a new
133    /// handle to the duplicated capability.
134    async fn duplicate(&'a self) -> Result<Self, Error> {
135        let dup_id = self.store().id_gen.next();
136        self.store().proxy.duplicate(self.id(), dup_id).await??;
137        Ok(Self::from_store(self.store(), dup_id))
138    }
139
140    /// Drop the referenced Capability held by the component framework runtime.
141    async fn drop(self) -> Result<(), Error> {
142        Ok(self.store().proxy.drop(self.id()).await??)
143    }
144
145    /// Extract the value of the referenced Capability and remove it from the
146    /// component framework runtime.
147    async fn export<T>(self) -> Result<T, Error>
148    where
149        T: Importable<'a, Handle = Self>,
150    {
151        let cap = self.store().proxy.export(self.id()).await??;
152        T::try_from(cap.into())
153    }
154}
155
156/// A type-safe wrapper for a [`fsandbox::Capability`].
157///
158/// This enum is used to represent different kinds of capabilities that can be
159/// stored in the [`CapabilityStore`]. See the [`From`] implementations for how to
160/// create a [`Capability`] from a specific type like [`Dictionary`] or [`Data`].
161#[derive(Debug)]
162pub struct Capability(fsandbox::Capability);
163
164impl From<Capability> for fsandbox::Capability {
165    fn from(value: Capability) -> Self {
166        value.0
167    }
168}
169
170impl From<fsandbox::Capability> for Capability {
171    fn from(value: fsandbox::Capability) -> Self {
172        Self(value)
173    }
174}
175
176/// Returns the type name of a capability as a static string.
177fn capability_type_name(cap: fsandbox::Capability) -> &'static str {
178    match cap {
179        fsandbox::Capability::Unit(_) => "Unit",
180        fsandbox::Capability::Handle(_) => "Handle",
181        fsandbox::Capability::Dictionary(_) => "Dictionary",
182        fsandbox::Capability::Connector(_) => "Connector",
183        fsandbox::Capability::Directory(_) => "Directory",
184        fsandbox::Capability::DirEntry(_) => "DirEntry",
185        fsandbox::Capability::ConnectorRouter(_) => "ConnectorRouter",
186        fsandbox::Capability::DictionaryRouter(_) => "DictionaryRouter",
187        fsandbox::Capability::DirEntryRouter(_) => "DirEntryRouter",
188        fsandbox::Capability::DataRouter(_) => "DataRouter",
189        fsandbox::Capability::DirConnectorRouter(_) => "DirConnectorRouter",
190        fsandbox::Capability::Data(data) => match data {
191            fsandbox::Data::Bytes(_) => "Data::Bytes",
192            fsandbox::Data::String(_) => "Data::String",
193            fsandbox::Data::Int64(_) => "Data::Int64",
194            fsandbox::Data::Uint64(_) => "Data::Uint64",
195            _ => "Data::Unknown",
196        },
197        _ => "Unknown",
198    }
199}
200
201/// Implement TryFrom on simple [`fsandbox::Capability`] variants.
202macro_rules! impl_try_from_capability {
203    ($t:ty, $variant:path, $variant_type_name:expr) => {
204        impl TryFrom<Capability> for $t {
205            type Error = Error;
206            fn try_from(value: Capability) -> Result<Self, Self::Error> {
207                match value.0 {
208                    $variant(inner) => Ok(inner),
209                    capability => Err(Error::WrongType {
210                        got: capability_type_name(capability),
211                        want: $variant_type_name,
212                    }),
213                }
214            }
215        }
216    };
217}
218
219impl_try_from_capability!(fsandbox::Unit, fsandbox::Capability::Unit, "Unit");
220impl_try_from_capability!(fidl::Handle, fsandbox::Capability::Handle, "Handle");
221impl_try_from_capability!(fsandbox::DictionaryRef, fsandbox::Capability::Dictionary, "Dictionary");
222impl_try_from_capability!(fsandbox::Connector, fsandbox::Capability::Connector, "Connector");
223impl_try_from_capability!(
224    fidl::endpoints::ClientEnd<fio::DirectoryMarker>,
225    fsandbox::Capability::Directory,
226    "Directory"
227);
228impl_try_from_capability!(fsandbox::DirEntry, fsandbox::Capability::DirEntry, "DirEntry");
229impl_try_from_capability!(
230    fidl::endpoints::ClientEnd<fsandbox::ConnectorRouterMarker>,
231    fsandbox::Capability::ConnectorRouter,
232    "ConnectorRouter"
233);
234impl_try_from_capability!(
235    fidl::endpoints::ClientEnd<fsandbox::DictionaryRouterMarker>,
236    fsandbox::Capability::DictionaryRouter,
237    "DictionaryRouter"
238);
239impl_try_from_capability!(
240    fidl::endpoints::ClientEnd<fsandbox::DirEntryRouterMarker>,
241    fsandbox::Capability::DirEntryRouter,
242    "DirEntryRouter"
243);
244impl_try_from_capability!(
245    fidl::endpoints::ClientEnd<fsandbox::DataRouterMarker>,
246    fsandbox::Capability::DataRouter,
247    "DataRouter"
248);
249impl_try_from_capability!(
250    fidl::endpoints::ClientEnd<fsandbox::DirConnectorRouterMarker>,
251    fsandbox::Capability::DirConnectorRouter,
252    "DirConnectorRouter"
253);
254
255/// Implement TryFrom on [`fsandbox::Capability::Data`] variants.
256macro_rules! impl_data_capability {
257    ($t:ty, $variant:path, $variant_type_name:expr) => {
258        impl<'a> Importable<'a> for $t {
259            type Handle = Data<'a>;
260        }
261
262        impl From<$t> for Capability {
263            fn from(value: $t) -> Self {
264                Self(fsandbox::Capability::Data($variant(value)))
265            }
266        }
267
268        impl TryFrom<Capability> for $t {
269            type Error = Error;
270            fn try_from(value: Capability) -> Result<Self, Self::Error> {
271                match value.0 {
272                    fsandbox::Capability::Data($variant(inner)) => Ok(inner),
273                    capability => Err(Error::WrongType {
274                        got: capability_type_name(capability),
275                        want: $variant_type_name,
276                    }),
277                }
278            }
279        }
280    };
281}
282
283impl_data_capability!(Vec<u8>, fsandbox::Data::Bytes, "Data::Bytes");
284impl_data_capability!(String, fsandbox::Data::String, "Data::String");
285impl_data_capability!(i64, fsandbox::Data::Int64, "Data:Int64");
286impl_data_capability!(u64, fsandbox::Data::Uint64, "Data:Uint64");
287
288impl<'a> Importable<'a> for fsandbox::DictionaryRef {
289    type Handle = Dictionary<'a>;
290}
291
292impl From<fsandbox::DictionaryRef> for Capability {
293    fn from(value: fsandbox::DictionaryRef) -> Self {
294        Capability(fsandbox::Capability::Dictionary(value))
295    }
296}
297
298/// A handle to a dictionary capability in the store.
299///
300/// Component framework dictionaries are mutable collections of other
301/// capabilities, keyed by strings.
302#[derive(Debug)]
303pub struct Dictionary<'a> {
304    store: &'a CapabilityStore,
305    id: fsandbox::CapabilityId,
306}
307
308impl<'a> CapabilityHandle<'a> for Dictionary<'a> {
309    fn from_store(store: &'a CapabilityStore, id: fsandbox::CapabilityId) -> Self {
310        Self { store, id }
311    }
312
313    fn store(&self) -> &'a CapabilityStore {
314        self.store
315    }
316
317    fn id(&self) -> fsandbox::CapabilityId {
318        self.id
319    }
320}
321
322impl<'a> Dictionary<'a> {
323    /// Inserts a capability into the dictionary with the given key.
324    pub async fn insert<V>(&self, key: impl Into<String>, value: V) -> Result<(), Error>
325    where
326        V: CapabilityHandle<'a>,
327    {
328        self.store
329            .proxy
330            .dictionary_insert(
331                self.id,
332                &fsandbox::DictionaryItem { key: key.into(), value: value.id() },
333            )
334            .await??;
335        Ok(())
336    }
337
338    /// Retrieves a capability from the dictionary by its key.
339    pub async fn get<T>(&self, key: impl AsRef<str>) -> Result<T, Error>
340    where
341        T: CapabilityHandle<'a>,
342    {
343        let id = self.store.id_gen.next();
344        self.store.proxy.dictionary_get(self.id, key.as_ref(), id).await??;
345        Ok(T::from_store(self.store, id))
346    }
347}
348
349/// A handle to a connector capability in the store.
350///
351/// A [`Connector`] can be used to receive a channel from a client component.
352#[derive(Debug)]
353pub struct Connector<'a> {
354    store: &'a CapabilityStore,
355    id: fsandbox::CapabilityId,
356}
357
358impl<'a> CapabilityHandle<'a> for Connector<'a> {
359    fn from_store(store: &'a CapabilityStore, id: fsandbox::CapabilityId) -> Self {
360        Self { store, id }
361    }
362
363    fn store(&self) -> &'a CapabilityStore {
364        self.store
365    }
366
367    fn id(&self) -> fsandbox::CapabilityId {
368        self.id
369    }
370}
371
372impl<'a> Connector<'a> {}
373
374/// A handle to a data capability in the store.
375///
376/// The underlying [`Data`] in the component framework runtime holds fundamental
377/// types like strings, integers, and byte vectors.
378#[derive(Debug)]
379pub struct Data<'a> {
380    store: &'a CapabilityStore,
381    id: fsandbox::CapabilityId,
382}
383
384impl<'a> CapabilityHandle<'a> for Data<'a> {
385    fn from_store(store: &'a CapabilityStore, id: fsandbox::CapabilityId) -> Self {
386        Self { store, id }
387    }
388
389    fn store(&self) -> &'a CapabilityStore {
390        self.store
391    }
392
393    fn id(&self) -> fsandbox::CapabilityId {
394        self.id
395    }
396}
397
398#[cfg(test)]
399mod tests {
400    use super::*;
401    use assert_matches::assert_matches;
402    use fidl::endpoints::create_proxy_and_stream;
403    use fuchsia_async as fasync;
404    use futures::FutureExt;
405    use futures::prelude::*;
406    use futures::task::Poll;
407    use std::pin::Pin;
408    use zx::AsHandleRef as _;
409
410    fn setup_store() -> (CapabilityStore, fsandbox::CapabilityStoreRequestStream) {
411        let (proxy, stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
412        let store = CapabilityStore { proxy, id_gen: CapabilityIdGenerator::default() };
413        (store, stream)
414    }
415
416    async fn wait_for_request<F, T>(f: &mut Pin<Box<F>>)
417    where
418        F: Future<Output = T> + ?Sized,
419        T: std::fmt::Debug,
420    {
421        assert_matches!(fasync::TestExecutor::poll_until_stalled(f.as_mut()).await, Poll::Pending);
422    }
423
424    #[test]
425    fn capability_id_generator() {
426        let id_gen = CapabilityIdGenerator::new();
427        assert_eq!(id_gen.next(), 0);
428        assert_eq!(id_gen.next(), 1);
429        assert_eq!(id_gen.range(5), 2);
430        assert_eq!(id_gen.next(), 7);
431    }
432
433    #[test]
434    fn data_capability_conversions() {
435        // String
436        let cap: Capability = "hello".to_string().into();
437        let str_again: String = cap.try_into().unwrap();
438        assert_eq!(str_again, "hello");
439
440        // u64
441        let cap: Capability = 123u64.into();
442        let num_again: u64 = cap.try_into().unwrap();
443        assert_eq!(num_again, 123);
444
445        // Wrong type
446        let cap: Capability = "hello".to_string().into();
447        let res: Result<u64, _> = cap.try_into();
448        assert_matches!(res, Err(Error::WrongType { got: "Data::String", want: _ }));
449    }
450
451    #[fuchsia::test(allow_stalls = false)]
452    async fn capability_store_import() {
453        let (store, mut stream) = setup_store();
454
455        let mut import_fut = store.import("hello".to_string()).boxed_local();
456        wait_for_request(&mut import_fut).await;
457
458        let (capability, responder) = assert_matches!(
459            stream.next().await.unwrap().unwrap(),
460            fsandbox::CapabilityStoreRequest::Import { id, capability, responder } if id == 0 => (capability, responder)
461        );
462
463        assert_matches!(capability, fsandbox::Capability::Data(fsandbox::Data::String(s)) if s == "hello");
464        responder.send(Ok(())).unwrap();
465        let handle = import_fut.await.unwrap();
466        assert_eq!(handle.id, 0);
467    }
468
469    #[fuchsia::test(allow_stalls = false)]
470    async fn capability_handle_duplicate() {
471        let (store, mut stream) = setup_store();
472
473        // Simulate an existing capability with id 100
474        let handle = Data { id: 100, store: &store };
475
476        let mut duplicate_fut = handle.duplicate().boxed_local();
477        wait_for_request(&mut duplicate_fut).await;
478
479        let responder = assert_matches!(
480            stream.next().await.unwrap().unwrap(),
481            fsandbox::CapabilityStoreRequest::Duplicate { id, dest_id, responder } if id == 100 && dest_id == 0 => responder
482        );
483
484        responder.send(Ok(())).unwrap();
485        let new_handle: Data<'_> = duplicate_fut.await.unwrap();
486        assert_eq!(new_handle.id, 0);
487        assert_eq!(store.id_gen.next(), 1); // next id is 1
488    }
489
490    #[fuchsia::test(allow_stalls = false)]
491    async fn capability_handle_drop() {
492        let (store, mut stream) = setup_store();
493        let handle = Data { id: 100, store: &store };
494
495        let mut drop_fut = handle.drop().boxed_local();
496        wait_for_request(&mut drop_fut).await;
497
498        let responder = assert_matches!(
499            stream.next().await.unwrap().unwrap(),
500            fsandbox::CapabilityStoreRequest::Drop { id, responder } if id == 100 => responder
501        );
502
503        responder.send(Ok(())).unwrap();
504        drop_fut.await.unwrap();
505    }
506
507    #[fuchsia::test(allow_stalls = false)]
508    async fn capability_handle_export() {
509        let (store, mut stream) = setup_store();
510        let handle = Data { id: 100, store: &store };
511
512        let mut export_fut = handle.export::<String>().boxed_local();
513        wait_for_request(&mut export_fut).await;
514
515        let responder = assert_matches!(
516            stream.next().await.unwrap().unwrap(),
517            fsandbox::CapabilityStoreRequest::Export { id, responder } if id == 100 => responder
518        );
519
520        let cap = fsandbox::Capability::Data(fsandbox::Data::String("exported".to_string()));
521        responder.send(Ok(cap)).unwrap();
522
523        let result = export_fut.await.unwrap();
524        assert_eq!(result, "exported");
525    }
526
527    #[fuchsia::test(allow_stalls = false)]
528    async fn dictionary_create() {
529        let (store, mut stream) = setup_store();
530
531        let mut create_fut = store.create_dictionary().boxed_local();
532        wait_for_request(&mut create_fut).await;
533
534        let responder = assert_matches!(
535            stream.next().await.unwrap().unwrap(),
536            fsandbox::CapabilityStoreRequest::DictionaryCreate { id, responder } if id == 0 => responder
537        );
538
539        responder.send(Ok(())).unwrap();
540        let dict = create_fut.await.unwrap();
541        assert_eq!(dict.id, 0);
542    }
543
544    #[fuchsia::test(allow_stalls = false)]
545    async fn dictionary_insert() {
546        let (store, mut stream) = setup_store();
547        let dict = Dictionary { id: 10, store: &store };
548        let data = Data { id: 20, store: &store };
549
550        let mut insert_fut = dict.insert("key", data).boxed_local();
551        wait_for_request(&mut insert_fut).await;
552
553        let responder = assert_matches!(
554            stream.next().await.unwrap().unwrap(),
555            fsandbox::CapabilityStoreRequest::DictionaryInsert { id, item, responder } if id == 10 && item.key == "key" && item.value == 20 => responder
556        );
557
558        responder.send(Ok(())).unwrap();
559        insert_fut.await.unwrap();
560    }
561
562    #[fuchsia::test(allow_stalls = false)]
563    async fn dictionary_get() {
564        let (store, mut stream) = setup_store();
565        let dict = Dictionary { id: 10, store: &store };
566
567        let mut get_fut = dict.get::<Data<'_>>("key").boxed_local();
568        wait_for_request(&mut get_fut).await;
569
570        let responder = assert_matches!(
571            stream.next().await.unwrap().unwrap(),
572            fsandbox::CapabilityStoreRequest::DictionaryGet { id, key, dest_id, responder } if id == 10 && key == "key" && dest_id == 0 => responder
573        );
574
575        responder.send(Ok(())).unwrap();
576        let handle = get_fut.await.unwrap();
577        assert_eq!(handle.id, 0);
578    }
579
580    #[fuchsia::test(allow_stalls = false)]
581    async fn dictionary_export() {
582        let (store, mut stream) = setup_store();
583        let dict = Dictionary { id: 10, store: &store };
584
585        let mut export_fut = dict.export().boxed_local();
586        wait_for_request(&mut export_fut).await;
587
588        let responder = assert_matches!(
589            stream.next().await.unwrap().unwrap(),
590            fsandbox::CapabilityStoreRequest::Export { id, responder } if id == 10 => responder
591        );
592
593        let (client, _server) = zx::EventPair::create();
594        let cap = fsandbox::Capability::Dictionary(fsandbox::DictionaryRef { token: client });
595        responder.send(Ok(cap)).unwrap();
596
597        let result: fsandbox::DictionaryRef = export_fut.await.unwrap();
598        assert_eq!(result.token.as_handle_ref().is_invalid(), false);
599    }
600
601    #[fuchsia::test(allow_stalls = false)]
602    async fn connector_create() {
603        let (store, mut stream) = setup_store();
604        let (client_end, _server_end) =
605            fidl::endpoints::create_endpoints::<fsandbox::ReceiverMarker>();
606
607        let mut create_fut = store.create_connector(client_end).boxed_local();
608        wait_for_request(&mut create_fut).await;
609
610        let responder = assert_matches!(
611            stream.next().await.unwrap().unwrap(),
612            fsandbox::CapabilityStoreRequest::ConnectorCreate { id, receiver, responder } if id == 0 && receiver.as_handle_ref().is_invalid() == false => responder
613        );
614
615        responder.send(Ok(())).unwrap();
616        let connector = create_fut.await.unwrap();
617        assert_eq!(connector.id, 0);
618    }
619
620    #[fuchsia::test(allow_stalls = false)]
621    async fn data_export() {
622        let (store, mut stream) = setup_store();
623        let data = Data { id: 10, store: &store };
624
625        let mut export_fut = data.export::<String>().boxed_local();
626        wait_for_request(&mut export_fut).await;
627
628        let responder = assert_matches!(
629            stream.next().await.unwrap().unwrap(),
630            fsandbox::CapabilityStoreRequest::Export { id, responder } if id == 10 => responder
631        );
632
633        let cap = fsandbox::Capability::Data(fsandbox::Data::String("exported data".to_string()));
634        responder.send(Ok(cap)).unwrap();
635
636        let result = export_fut.await.unwrap();
637        assert_eq!(result, "exported data");
638    }
639}