Skip to main content

realmbuilder_mock_helpers/
lib.rs

1// Copyright 2021 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;
6use fidl::endpoints::{DiscoverableProtocolMarker, ProtocolMarker, Proxy};
7use fidl_fuchsia_device::{NameProviderMarker, NameProviderRequestStream};
8use fidl_fuchsia_stash::SecureStoreMarker;
9use fuchsia_async as fasync;
10use fuchsia_component::server::{ServiceFs, ServiceObj};
11use fuchsia_component_test::LocalComponentHandles;
12use futures::channel::mpsc;
13use futures::{SinkExt, StreamExt, TryStream, TryStreamExt};
14use log::info;
15use std::sync::Arc;
16use vfs::directory::entry_container::Directory;
17
18// #! Library for common utilities (mocks, definitions) for the manifest integration tests.
19
20/// Process requests received in the `stream` and relay them to the provided `sender`.
21/// Logs incoming requests prefixed with the `tag`.
22pub async fn process_request_stream<S, Event>(
23    mut stream: S::RequestStream,
24    mut sender: mpsc::Sender<Event>,
25) where
26    S: DiscoverableProtocolMarker,
27    Event: std::convert::From<<S::RequestStream as TryStream>::Ok>,
28    <S::RequestStream as TryStream>::Ok: std::fmt::Debug,
29{
30    while let Some(request) = stream.try_next().await.expect("serving request stream failed") {
31        info!("Received {} service request: {:?}", S::PROTOCOL_NAME, request);
32        sender.send(request.into()).await.expect("should send");
33    }
34}
35
36/// Adds a handler for the FIDL service `S` which relays the ServerEnd of the service
37/// connection request to the provided `sender`.
38/// Note: This method does not process requests from the service connection. It only relays
39/// the stream to the `sender.
40pub fn add_fidl_service_handler<S, Event: 'static>(
41    fs: &mut ServiceFs<ServiceObj<'_, ()>>,
42    sender: mpsc::Sender<Event>,
43) where
44    S: DiscoverableProtocolMarker,
45    Event: std::convert::From<S::RequestStream> + std::marker::Send,
46{
47    let _ = fs.dir("svc").add_fidl_service(move |req_stream: S::RequestStream| {
48        let mut s = sender.clone();
49        fasync::Task::local(async move {
50            info!("Received connection for {}", S::PROTOCOL_NAME);
51            s.send(req_stream.into()).await.expect("should send");
52        })
53        .detach()
54    });
55}
56
57/// A mock component that provides the generic service `S`. The request stream
58/// of the service is processed and any requests relayed to the provided `sender`.
59pub async fn mock_component<S, Event: 'static>(
60    sender: mpsc::Sender<Event>,
61    handles: LocalComponentHandles,
62) -> Result<(), Error>
63where
64    S: DiscoverableProtocolMarker,
65    Event: std::convert::From<<<S as ProtocolMarker>::RequestStream as TryStream>::Ok>
66        + std::marker::Send,
67    <<S as ProtocolMarker>::RequestStream as TryStream>::Ok: std::fmt::Debug,
68{
69    let mut fs = ServiceFs::new();
70    let _ = fs.dir("svc").add_fidl_service(move |req_stream: S::RequestStream| {
71        let sender_clone = sender.clone();
72        info!("Received connection for {}", S::PROTOCOL_NAME);
73        fasync::Task::local(process_request_stream::<S, _>(req_stream, sender_clone)).detach();
74    });
75
76    let _ = fs.serve_connection(handles.outgoing_dir)?;
77    fs.collect::<()>().await;
78    Ok(())
79}
80
81/// Sets up a mock dev/ directory with the provided `dev_directory` topology.
82pub async fn mock_dev(
83    handles: LocalComponentHandles,
84    dev_directory: Arc<dyn Directory>,
85) -> Result<(), Error> {
86    let mut fs = ServiceFs::new();
87    let _ = fs.add_remote(
88        "dev",
89        vfs::directory::serve_read_only(dev_directory, vfs::execution_scope::ExecutionScope::new()),
90    );
91    let _ = fs.serve_connection(handles.outgoing_dir)?;
92    fs.collect::<()>().await;
93    Ok(())
94}
95
96/// A mock component serving a protocol `S` on `handles`. Specifically, this services S by calling
97/// `responder` for every request of every client connection to S.
98pub async fn stateless_mock_responder<S, F>(
99    handles: LocalComponentHandles,
100    responder: F,
101) -> Result<(), anyhow::Error>
102where
103    S: DiscoverableProtocolMarker,
104    <<S as ProtocolMarker>::RequestStream as TryStream>::Ok: std::fmt::Debug,
105    F: Fn(<<S as ProtocolMarker>::RequestStream as TryStream>::Ok) -> Result<(), Error>
106        + Copy
107        + Send
108        + 'static,
109{
110    let mut fs = ServiceFs::new();
111    // The FIDL service's task is generated for every client connection, hence the `F: Copy` bound
112    // in order to use `responder` inside the task. F's bound could be changed to `Clone` in the
113    // future, but we chose not to do so for now to avoid unexpected implicit `clone`s.
114    let _ = fs.dir("svc").add_fidl_service(
115        move |mut req_stream: <S as ProtocolMarker>::RequestStream| {
116            fasync::Task::local(async move {
117                let failure_msg = format!("serving {} request stream failed", S::DEBUG_NAME);
118                while let Some(req) = req_stream.try_next().await.expect(&failure_msg) {
119                    let failed_to_respond = format!("failed to respond to req {:?}", req);
120                    responder(req).expect(&failed_to_respond);
121                }
122            })
123            .detach()
124        },
125    );
126    let _ = fs.serve_connection(handles.outgoing_dir)?;
127    fs.collect::<()>().await;
128    Ok(())
129}
130
131/// Exposes implementations of the the services used by bt-gap in the provided `ServiceFs`.
132pub fn provide_bt_gap_uses<Event>(
133    fs: &mut ServiceFs<ServiceObj<'_, ()>>,
134    sender: &mpsc::Sender<Event>,
135    handles: &LocalComponentHandles,
136) -> Result<(), Error>
137where
138    Event: From<SecureStoreMarker> + From<NameProviderRequestStream> + Send + 'static,
139{
140    let svc_dir = handles.clone_from_namespace("svc")?;
141    let sender_clone = Some(sender.clone());
142    let _ = fs.dir("svc").add_service_at(SecureStoreMarker::PROTOCOL_NAME, move |chan| {
143        let mut s = sender_clone.clone();
144        let svc_dir = Clone::clone(&svc_dir);
145        fasync::Task::local(async move {
146            info!(
147                "Proxying {} connection to real implementation",
148                SecureStoreMarker::PROTOCOL_NAME
149            );
150            fdio::service_connect_at(
151                svc_dir.as_channel().as_ref(),
152                SecureStoreMarker::PROTOCOL_NAME,
153                chan,
154            )
155            .expect("unable to forward secure store");
156            // We only care that the Secure Store is routed correctly, so if a client connects
157            // to it more than once, we only want to report it the first time.
158            if let Some(mut sender) = s.take() {
159                sender.send(Event::from(SecureStoreMarker)).await.expect("should send");
160            }
161        })
162        .detach();
163        None
164    });
165    add_fidl_service_handler::<NameProviderMarker, _>(fs, sender.clone());
166    Ok(())
167}