blob: f8e0a2c6de7941fea5402605e3a1fc545ad4cc57 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! library for target side of filesystem integrity host-target interaction tests
#![deny(missing_docs)]
use {
anyhow::Result,
async_trait::async_trait,
fidl_fuchsia_blackout_test::{ControllerRequest, ControllerRequestStream},
fidl_fuchsia_device::ControllerMarker,
fuchsia_component::client::connect_to_protocol_at_path,
fuchsia_component::server::{ServiceFs, ServiceObj},
fuchsia_fs,
fuchsia_fs::directory::readdir,
fuchsia_zircon as zx,
futures::{StreamExt, TryFutureExt, TryStreamExt},
rand::{distributions, rngs::StdRng, Rng, SeedableRng},
};
pub mod static_tree;
/// The three steps the target-side of a blackout test needs to implement.
#[async_trait]
pub trait Test {
/// Setup the test run on the given block_device.
async fn setup(&self, block_device: String, seed: u64) -> Result<()>;
/// Run the test body on the given block_device.
async fn test(&self, block_device: String, seed: u64) -> Result<()>;
/// Verify the consistency of the filesystem on the block_device.
async fn verify(&self, block_device: String, seed: u64) -> Result<()>;
}
struct BlackoutController(ControllerRequestStream);
/// A test server, which serves the fuchsia.blackout.test.Controller protocol.
pub struct TestServer<'a, T> {
fs: ServiceFs<ServiceObj<'a, BlackoutController>>,
test: T,
}
impl<'a, T> TestServer<'a, T>
where
T: Test + Copy,
{
/// Create a new test server for this test.
pub fn new(test: T) -> Result<TestServer<'a, T>> {
let mut fs = ServiceFs::new();
fs.dir("svc").add_fidl_service(BlackoutController);
fs.take_and_serve_directory_handle()?;
Ok(TestServer { fs, test })
}
/// Start serving the outgoing directory. Blocks until all connections are closed.
pub async fn serve(self) {
const MAX_CONCURRENT: usize = 10_000;
let test = self.test;
self.fs
.for_each_concurrent(MAX_CONCURRENT, move |stream| {
handle_request(test, stream).unwrap_or_else(|e| log::error!("{}", e))
})
.await;
}
}
async fn handle_request<T: Test + Copy>(
test: T,
BlackoutController(mut stream): BlackoutController,
) -> Result<()> {
while let Some(request) = stream.try_next().await? {
handle_controller(test, request).await?;
}
Ok(())
}
async fn handle_controller<T: Test + Copy>(test: T, request: ControllerRequest) -> Result<()> {
match request {
ControllerRequest::Setup { responder, device_path, seed } => {
let mut res = test.setup(device_path, seed).await.map_err(|e| {
log::error!("{}", e);
zx::Status::INTERNAL.into_raw()
});
responder.send(&mut res)?;
}
ControllerRequest::Test { device_path, seed, .. } => test.test(device_path, seed).await?,
ControllerRequest::Verify { responder, device_path, seed } => {
let mut res = test.verify(device_path, seed).await.map_err(|e| {
// The test tries failing on purpose, so only print errors as warnings.
log::warn!("{}", e);
zx::Status::BAD_STATE.into_raw()
});
responder.send(&mut res)?;
}
}
Ok(())
}
/// Generate a Vec<u8> of random bytes from a seed using a standard distribution.
pub fn generate_content(seed: u64) -> Vec<u8> {
let mut rng = StdRng::seed_from_u64(seed);
let size = rng.gen_range(1..1 << 16);
rng.sample_iter(&distributions::Standard).take(size).collect()
}
/// Find the device in /dev/class/block that represents a given topological path. Returns the full
/// path of the device in /dev/class/block.
pub async fn find_dev(dev: &str) -> Result<String> {
let dev_class_block = fuchsia_fs::open_directory_in_namespace(
"/dev/class/block",
fuchsia_fs::OpenFlags::RIGHT_READABLE | fuchsia_fs::OpenFlags::RIGHT_WRITABLE,
)?;
for entry in readdir(&dev_class_block).await? {
let path = format!("/dev/class/block/{}", entry.name);
let proxy = connect_to_protocol_at_path::<ControllerMarker>(&path)?;
let topo_path = proxy.get_topological_path().await?.map_err(|s| zx::Status::from_raw(s))?;
println!("{} => {}", path, topo_path);
if dev == topo_path {
return Ok(path);
}
}
Err(anyhow::anyhow!("Couldn't find {} in /dev/class/block", dev))
}