realm_client/
lib.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 fdio::Namespace;
6use fidl::endpoints::Proxy;
7use fidl_fuchsia_component as fcomponent;
8use fuchsia_component::client::connect_to_protocol;
9use std::fmt::Debug;
10use uuid::Uuid;
11
12#[cfg(fuchsia_api_level_at_least = "HEAD")]
13use fidl_fuchsia_component_sandbox as _;
14#[cfg(fuchsia_api_level_less_than = "HEAD")]
15use {fidl::endpoints::ClientEnd, fidl_fuchsia_component_sandbox as fsandbox};
16
17mod error;
18pub use error::Error;
19
20/// A thin wrapper that represents the namespace created by [extend_namespace].
21///
22/// Users can obtain the path to the namespace from [InstalledNamespace::prefix] and pass that
23/// to capability connection APIs such as [fuchsia-component]'s [client::connect_to_protocol_at]
24/// to access capabilities in the namespace.
25///
26/// Furthermore, the [InstalledNamespace] acts as an RAII container for the capabilities. When
27/// the [InstalledNamespace] is dropped, the test realm factory server may free state associated
28/// with serving those capabilities. Therefore, the test should only drop this once it no longer
29/// needs to connect to the capabilities or needs activity performed on their behalf.
30pub struct InstalledNamespace {
31    prefix: String,
32
33    /// This is not used, but it keeps the RealmFactory connection alive.
34    ///
35    /// The RealmFactory server may use this connection to pin the lifetime of the realm created
36    /// for the test.
37    _realm_factory: fidl::AsyncChannel,
38}
39
40impl InstalledNamespace {
41    pub fn prefix(&self) -> &str {
42        &self.prefix
43    }
44}
45
46impl Drop for InstalledNamespace {
47    fn drop(&mut self) {
48        let Ok(namespace) = Namespace::installed() else {
49            return;
50        };
51        let _ = namespace.unbind(&self.prefix);
52    }
53}
54
55impl AsRef<str> for &InstalledNamespace {
56    fn as_ref(&self) -> &str {
57        self.prefix()
58    }
59}
60
61/// Converts the given dictionary to a namespace and adds it this component's namespace,
62/// thinly wrapped by the returned [InstalledNamespace].
63///
64/// Users can obtain the path to the namespace from [InstalledNamespace::prefix] and pass that
65/// to capability connection APIs such as [fuchsia-component]'s [client::connect_to_protocol_at]
66/// to access capabilities in the namespace.
67///
68/// Furthermore, the [InstalledNamespace] acts as an RAII container for the capabilities. When
69/// the [InstalledNamespace] is dropped, the test realm factory server may free state associated
70/// with serving those capabilities. Therefore, the test should only drop this once it no longer
71/// needs to connect to the capabilities or needs activity performed on their behalf.
72pub async fn extend_namespace<T>(
73    realm_factory: T,
74    #[cfg(fuchsia_api_level_at_least = "HEAD")] capability: zx::EventPair,
75    #[cfg(fuchsia_api_level_less_than = "HEAD")] dictionary: ClientEnd<fsandbox::DictionaryMarker>,
76) -> Result<InstalledNamespace, error::Error>
77where
78    T: Proxy + Debug,
79{
80    let namespace_proxy = connect_to_protocol::<fcomponent::NamespaceMarker>()
81        .map_err(|e| error::Error::ConnectionFailed(format!("{:?}", e)))?;
82    // TODO(https://fxbug.dev/336392298): What should we use for
83    // the namespace's unique id? Could also consider an atomic counter,
84    // or the name of the test.
85    let prefix = format!("/dict-{}", Uuid::new_v4());
86    #[cfg(fuchsia_api_level_at_least = "HEAD")]
87    let mut namespace_entries = {
88        let dicts =
89            vec![fcomponent::NamespaceInputEntry2 { path: prefix.clone().into(), capability }];
90        namespace_proxy.create2(dicts).await?.map_err(error::Error::NamespaceCreation)?
91    };
92    #[cfg(fuchsia_api_level_less_than = "HEAD")]
93    let mut namespace_entries = {
94        let dicts =
95            vec![fcomponent::NamespaceInputEntry { path: prefix.clone().into(), dictionary }];
96        namespace_proxy.create(dicts).await?.map_err(error::Error::NamespaceCreation)?
97    };
98    let namespace = Namespace::installed().map_err(error::Error::NamespaceNotInstalled)?;
99    let count = namespace_entries.len();
100    if count != 1 {
101        return Err(error::Error::InvalidNamespaceEntryCount { prefix, count });
102    }
103    let entry = namespace_entries.remove(0);
104    if entry.path.is_none() || entry.directory.is_none() {
105        return Err(error::Error::EntryIncomplete { prefix, message: format!("{:?}", entry) });
106    }
107    if entry.path.as_ref().unwrap() != &prefix {
108        return Err(error::Error::PrefixDoesNotMatchPath {
109            prefix,
110            path: format!("{:?}", entry.path),
111        });
112    }
113    namespace.bind(&prefix, entry.directory.unwrap()).map_err(error::Error::NamespaceBind)?;
114    Ok(InstalledNamespace { prefix, _realm_factory: realm_factory.into_channel().unwrap() })
115}