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