fshost_test_fixture/
mocks.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 ffeedback::FileReportResults;
7use fidl::prelude::*;
8use fuchsia_component_test::LocalComponentHandles;
9use futures::channel::mpsc::{self};
10use futures::future::BoxFuture;
11use futures::{FutureExt as _, SinkExt as _, StreamExt as _};
12use std::sync::Arc;
13use vfs::directory::entry_container::Directory;
14use vfs::execution_scope::ExecutionScope;
15use vfs::path::Path;
16use vfs::service;
17use {fidl_fuchsia_boot as fboot, fidl_fuchsia_feedback as ffeedback, fidl_fuchsia_io as fio};
18
19/// Identifier for ramdisk storage. Defined in sdk/lib/zbi-format/include/lib/zbi-format/zbi.h.
20const ZBI_TYPE_STORAGE_RAMDISK: u32 = 0x4b534452;
21
22pub async fn new_mocks(
23    netboot: bool,
24    vmo: Option<zx::Vmo>,
25    crash_reports_sink: mpsc::Sender<ffeedback::CrashReport>,
26) -> impl Fn(LocalComponentHandles) -> BoxFuture<'static, Result<(), Error>> + Sync + Send + 'static
27{
28    let vmo = vmo.map(Arc::new);
29    let mock = move |handles: LocalComponentHandles| {
30        let vmo_clone = vmo.clone();
31        run_mocks(handles, netboot, vmo_clone, crash_reports_sink.clone()).boxed()
32    };
33
34    mock
35}
36
37async fn run_mocks(
38    handles: LocalComponentHandles,
39    netboot: bool,
40    vmo: Option<Arc<zx::Vmo>>,
41    crash_reports_sink: mpsc::Sender<ffeedback::CrashReport>,
42) -> Result<(), Error> {
43    let export = vfs::pseudo_directory! {
44        "svc" => vfs::pseudo_directory! {
45            fboot::ArgumentsMarker::PROTOCOL_NAME => service::host(move |stream| {
46                run_boot_args(stream, netboot)
47            }),
48            fboot::ItemsMarker::PROTOCOL_NAME => service::host(move |stream| {
49                let vmo_clone = vmo.clone();
50                run_boot_items(stream, vmo_clone)
51            }),
52            ffeedback::CrashReporterMarker::PROTOCOL_NAME => service::host(move |stream| {
53                run_crash_reporter(stream, crash_reports_sink.clone())
54            }),
55        },
56    };
57
58    let scope = ExecutionScope::new();
59    export.open(
60        scope.clone(),
61        fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DIRECTORY,
62        Path::dot(),
63        fidl::endpoints::ServerEnd::from(handles.outgoing_dir.into_channel()),
64    );
65    scope.wait().await;
66
67    Ok(())
68}
69
70/// fshost uses exactly one boot item - it checks to see if there is an item of type
71/// ZBI_TYPE_STORAGE_RAMDISK. If it's there, it's a vmo that represents a ramdisk version of the
72/// fvm, and fshost creates a ramdisk from the vmo so it can go through the normal device matching.
73async fn run_boot_items(mut stream: fboot::ItemsRequestStream, vmo: Option<Arc<zx::Vmo>>) {
74    while let Some(request) = stream.next().await {
75        match request.unwrap() {
76            fboot::ItemsRequest::Get { type_, extra, responder } => {
77                assert_eq!(type_, ZBI_TYPE_STORAGE_RAMDISK);
78                assert_eq!(extra, 0);
79                let response_vmo = vmo.as_ref().map(|vmo| {
80                    vmo.create_child(zx::VmoChildOptions::SLICE, 0, vmo.get_size().unwrap())
81                        .unwrap()
82                });
83                responder.send(response_vmo, 0).unwrap();
84            }
85            fboot::ItemsRequest::Get2 { type_, extra, responder } => {
86                assert_eq!(type_, ZBI_TYPE_STORAGE_RAMDISK);
87                assert_eq!((*extra.unwrap()).n, 0);
88                responder.send(Ok(Vec::new())).unwrap();
89            }
90            fboot::ItemsRequest::GetBootloaderFile { .. } => {
91                panic!(
92                    "unexpectedly called GetBootloaderFile on {}",
93                    fboot::ItemsMarker::PROTOCOL_NAME
94                );
95            }
96        }
97    }
98}
99
100/// fshost expects a set of string and bool arguments to be available. This is a list of all the
101/// arguments it looks for. NOTE: For what we are currently testing for, none of these are required,
102/// so for now we either return None or the provided default depending on the context.
103///
104/// String args -
105///   factory_verity_seal - only used when writing to the factory partition
106/// Bool args -
107///   netsvc.netboot (optional; default false)
108async fn run_boot_args(mut stream: fboot::ArgumentsRequestStream, netboot: bool) {
109    while let Some(request) = stream.next().await {
110        match request.unwrap() {
111            fboot::ArgumentsRequest::GetString { key: _, responder } => {
112                responder.send(None).unwrap();
113            }
114            fboot::ArgumentsRequest::GetStrings { keys, responder } => {
115                responder.send(&vec![None; keys.len()]).unwrap();
116            }
117            fboot::ArgumentsRequest::GetBool { key: _, defaultval, responder } => {
118                responder.send(defaultval).unwrap();
119            }
120            fboot::ArgumentsRequest::GetBools { keys, responder } => {
121                let vec: Vec<_> = keys
122                    .iter()
123                    .map(|bool_pair| {
124                        if bool_pair.key == "netsvc.netboot".to_string() && netboot {
125                            true
126                        } else {
127                            bool_pair.defaultval
128                        }
129                    })
130                    .collect();
131                responder.send(&vec).unwrap();
132            }
133            fboot::ArgumentsRequest::Collect { .. } => {
134                // This seems to be deprecated. Either way, fshost doesn't use it.
135                panic!("unexpectedly called Collect on {}", fboot::ArgumentsMarker::PROTOCOL_NAME);
136            }
137        }
138    }
139}
140
141async fn run_crash_reporter(
142    mut stream: ffeedback::CrashReporterRequestStream,
143    mut crash_reports_sink: mpsc::Sender<ffeedback::CrashReport>,
144) {
145    while let Some(request) = stream.next().await {
146        match request.unwrap() {
147            ffeedback::CrashReporterRequest::FileReport { report, responder } => {
148                crash_reports_sink.send(report).await.unwrap();
149                responder.send(Ok(&FileReportResults::default())).unwrap();
150            }
151        }
152    }
153}