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}