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