Skip to main content

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}