realm_proxy/service.rs
1// Copyright 2023 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 anyhow::{Error, Result};
6use fidl::endpoints;
7use fidl_fuchsia_io as fio;
8use fidl_fuchsia_testing_harness::{OperationError, RealmProxy_Request, RealmProxy_RequestStream};
9use fuchsia_async as fasync;
10use fuchsia_component::runtime::ConnectorReceiver;
11use fuchsia_component_test::RealmInstance;
12use futures::{Future, StreamExt};
13use log::{debug, error, warn};
14
15// RealmProxy mediates a test suite's access to the services in a test realm.
16pub trait RealmProxy {
17 // Connects to the named service in this proxy's realm.
18 //
19 // If the connection fails, the resulting [OperationError] is determined
20 // by the [RealmProxy] implementation.
21 fn connect_to_named_protocol(
22 &mut self,
23 protocol: &str,
24 server_end: zx::Channel,
25 ) -> Result<(), OperationError>;
26
27 // Opens the service directory in this proxy's realm.
28 //
29 // If the connection fails, the resulting [OperationError] is determined
30 // by the [RealmProxy] implementation.
31 fn open_service(&self, service: &str, server_end: zx::Channel) -> Result<(), OperationError>;
32
33 // Connects to the service instance in this proxy's realm.
34 //
35 // If the connection fails, the resulting [OperationError] is determined
36 // by the [RealmProxy] implementation.
37 fn connect_to_service_instance(
38 &self,
39 service: &str,
40 instance: &str,
41 server_end: zx::Channel,
42 ) -> Result<(), OperationError>;
43}
44
45// A default [RealmProxy] implementation that mediates access to a specific [RealmInstance].
46struct RealmInstanceProxy(RealmInstance);
47
48impl RealmProxy for RealmInstanceProxy {
49 fn connect_to_named_protocol(
50 &mut self,
51 protocol: &str,
52 server_end: zx::Channel,
53 ) -> Result<(), OperationError> {
54 let res =
55 self.0.root.connect_request_to_named_protocol_at_exposed_dir(protocol, server_end);
56
57 if let Some(err) = res.err() {
58 error!("{err:?}");
59 return Err(OperationError::Failed);
60 }
61
62 Ok(())
63 }
64
65 fn open_service(&self, service: &str, server_end: zx::Channel) -> Result<(), OperationError> {
66 self.0
67 .root
68 .get_exposed_dir()
69 .open(
70 service,
71 fio::PERM_READABLE | fio::Flags::PROTOCOL_DIRECTORY,
72 &Default::default(),
73 server_end,
74 )
75 .map_err(|e| {
76 warn!("Failed to open service directory for {service}: {e:?}");
77 OperationError::Failed
78 })
79 }
80
81 fn connect_to_service_instance(
82 &self,
83 service: &str,
84 instance: &str,
85 server_end: zx::Channel,
86 ) -> Result<(), OperationError> {
87 self.0
88 .root
89 .get_exposed_dir()
90 .open(
91 format!("{service}/{instance}").as_str(),
92 fio::PERM_READABLE | fio::Flags::PROTOCOL_DIRECTORY,
93 &Default::default(),
94 server_end,
95 )
96 .map_err(|e| {
97 warn!("Failed to open service instance directory for {service}/{instance}: {e:?}");
98 OperationError::Failed
99 })
100 }
101}
102
103// serve_with_proxy uses [proxy] to handle all requests from [stream].
104//
105// This function is useful when implementing a custom test harness that serves
106// other protocols in addition to the RealmProxy protocol.
107//
108// # Example Usage
109//
110// ```
111// #[fuchsia::main(logging = true)]
112// async fn main() -> Result<(), Error> {
113// let mut fs = ServiceFs::new();
114//
115// fs.dir("svc").add_fidl_service(|stream| {
116// fasync::Task::spawn(async move {
117// let realm = build_realm().await.unwrap();
118// let realm_proxy = MyCustomRealmProxy(realm);
119// realm_proxy::service::serve_with_proxy(realm_proxy, stream).await.unwrap();
120// }).detach();
121// });
122//
123// fs.take_and_serve_directory_handle()?;
124// fs.collect::<()>().await;
125// Ok(())
126// }
127// ```
128pub async fn serve_with_proxy<P: RealmProxy>(
129 mut proxy: P,
130 mut stream: RealmProxy_RequestStream,
131) -> Result<(), crate::Error> {
132 while let Some(option) = stream.next().await {
133 match option {
134 Ok(request) => match request {
135 RealmProxy_Request::ConnectToNamedProtocol {
136 protocol,
137 server_end,
138 responder,
139 ..
140 } => {
141 let res = proxy.connect_to_named_protocol(protocol.as_str(), server_end);
142 responder.send(res)?;
143 }
144 RealmProxy_Request::OpenService { service, server_end, responder } => {
145 let res = proxy.open_service(service.as_str(), server_end);
146 responder.send(res)?;
147 }
148 RealmProxy_Request::ConnectToServiceInstance {
149 service,
150 instance,
151 server_end,
152 responder,
153 } => {
154 let res = proxy.connect_to_service_instance(
155 service.as_str(),
156 instance.as_str(),
157 server_end,
158 );
159 responder.send(res)?;
160 }
161 },
162 // Tell the user if we failed to read from the channel. These errors occur during
163 // testing and ignoring them can make it difficult to root cause test failures.
164 Err(e) => return Err(crate::error::Error::Fidl(e)),
165 }
166 }
167
168 // Tell the user we're disconnecting in case this is a premature shutdown.
169 debug!("done serving the RealmProxy connection");
170 Ok(())
171}
172
173// serve proxies all requests in [stream] to [realm].
174//
175// # Example Usage
176//
177// ```
178// #[fuchsia::main(logging = true)]
179// async fn main() -> Result<(), Error> {
180// let mut fs = ServiceFs::new();
181//
182// fs.dir("svc").add_fidl_service(|stream| {
183// fasync::Task::spawn(async move {
184// let realm = build_realm().await.unwrap();
185// realm_proxy::service::serve(realm, stream).await.unwrap();
186// }).detach();
187// });
188//
189// fs.take_and_serve_directory_handle()?;
190// fs.collect::<()>().await;
191// Ok(())
192// }
193// ```
194pub async fn serve(realm: RealmInstance, stream: RealmProxy_RequestStream) -> Result<(), Error> {
195 let proxy = RealmInstanceProxy(realm);
196 serve_with_proxy(proxy, stream).await?;
197 Ok(())
198}
199
200/// Dispatches incoming connections on `receiver to `request_stream_handler`.
201/// `receiver` is a component framework runtime receiver channel.
202///
203/// Example:
204///
205/// async fn handle_echo_request_stream(mut stream: fecho::EchoRequestStream) {
206/// while let Ok(Some(_request)) = stream.try_next().await {
207/// // ... handle request ...
208/// }
209/// }
210/// ...
211/// task_group.spawn(async move {
212/// let _ = realm_proxy::service::handle_receiver::<fecho::EchoMarker, _, _>(
213/// echo_receiver,
214/// handle_echo_request_stream,
215/// )
216/// .await
217/// .map_err(|e| {
218/// error!("Failed to serve echo stream: {}", e);
219/// });
220/// });
221pub async fn handle_receiver<T, Fut, F>(
222 mut receiver: ConnectorReceiver,
223 request_stream_handler: F,
224) -> Result<(), Error>
225where
226 T: endpoints::ProtocolMarker,
227 Fut: Future<Output = ()> + Send,
228 F: Fn(T::RequestStream) -> Fut + Send + Sync + Copy + 'static,
229{
230 let scope = fasync::Scope::new();
231 while let Some(channel) = receiver.next().await {
232 scope.spawn(async move {
233 let server_end = endpoints::ServerEnd::<T>::new(channel.into());
234 let stream: T::RequestStream = server_end.into_stream();
235 request_stream_handler(stream).await;
236 });
237 }
238 scope.join().await;
239 Ok(())
240}