1#![deny(missing_docs)]
8
9use anyhow::{Context as _, Result, anyhow};
10use async_trait::async_trait;
11use block_matcher::{BlockDeviceMatcher, Guid, create_random_guid, find_block_device, into_guid};
12use fidl_fuchsia_blackout_test::{ControllerRequest, ControllerRequestStream};
13use fidl_fuchsia_device::ControllerMarker;
14use fidl_fuchsia_io as fio;
15use fidl_fuchsia_storage_partitions as fpartitions;
16use fs_management::filesystem::BlockConnector;
17use fuchsia_async as fasync;
18use fuchsia_component::client::{Service, connect_to_protocol, connect_to_protocol_at_path};
19use fuchsia_component::server::{ServiceFs, ServiceObj};
20use fuchsia_fs::directory::readdir;
21use futures::{FutureExt, StreamExt, TryFutureExt, TryStreamExt, future};
22use rand::distr::StandardUniform;
23use rand::rngs::StdRng;
24use rand::{Rng, SeedableRng};
25use std::pin::pin;
26use std::sync::Arc;
27
28pub mod random_op;
29pub mod static_tree;
30
31#[async_trait]
33pub trait Test {
34 async fn setup(
36 self: Arc<Self>,
37 device_label: String,
38 device_path: Option<String>,
39 seed: u64,
40 ) -> Result<()>;
41 async fn test(
43 self: Arc<Self>,
44 device_label: String,
45 device_path: Option<String>,
46 seed: u64,
47 ) -> Result<()>;
48 async fn verify(
50 self: Arc<Self>,
51 device_label: String,
52 device_path: Option<String>,
53 seed: u64,
54 ) -> Result<()>;
55}
56
57struct BlackoutController(ControllerRequestStream);
58
59pub struct TestServer<'a, T> {
61 fs: ServiceFs<ServiceObj<'a, BlackoutController>>,
62 test: Arc<T>,
63}
64
65impl<'a, T> TestServer<'a, T>
66where
67 T: Test + 'static,
68{
69 pub fn new(test: T) -> Result<TestServer<'a, T>> {
71 let mut fs = ServiceFs::new();
72 fs.dir("svc").add_fidl_service(BlackoutController);
73 fs.take_and_serve_directory_handle()?;
74
75 Ok(TestServer { fs, test: Arc::new(test) })
76 }
77
78 pub async fn serve(self) {
80 const MAX_CONCURRENT: usize = 10_000;
81 let test = self.test;
82 self.fs
83 .for_each_concurrent(MAX_CONCURRENT, move |stream| {
84 handle_request(test.clone(), stream).unwrap_or_else(|e| log::error!("{}", e))
85 })
86 .await;
87 }
88}
89
90async fn handle_request<T: Test + 'static>(
91 test: Arc<T>,
92 BlackoutController(mut stream): BlackoutController,
93) -> Result<()> {
94 while let Some(request) = stream.try_next().await? {
95 handle_controller(test.clone(), request).await?;
96 }
97
98 Ok(())
99}
100
101async fn handle_controller<T: Test + 'static>(
102 test: Arc<T>,
103 request: ControllerRequest,
104) -> Result<()> {
105 match request {
106 ControllerRequest::Setup { responder, device_label, device_path, seed } => {
107 let res = test.setup(device_label, device_path, seed).await.map_err(|err| {
108 log::error!(err:?; "Setup failed");
109 zx::Status::INTERNAL.into_raw()
110 });
111 responder.send(res)?;
112 }
113 ControllerRequest::Test { responder, device_label, device_path, seed, duration } => {
114 let test_fut = test.test(device_label, device_path, seed).map_err(|err| {
115 log::error!(err:?; "Test failed");
116 zx::Status::INTERNAL.into_raw()
117 });
118 if duration != 0 {
119 log::info!("starting test and replying in {} seconds...", duration);
122 let timer = pin!(fasync::Timer::new(std::time::Duration::from_secs(duration)));
123 let res = match future::select(test_fut, timer).await {
124 future::Either::Left((res, _)) => res,
125 future::Either::Right((_, test_fut)) => {
126 fasync::Task::spawn(test_fut.map(|_| ())).detach();
127 Ok(())
128 }
129 };
130 responder.send(res)?;
131 } else {
132 log::info!("starting test...");
134 responder.send(test_fut.await)?;
135 }
136 }
137 ControllerRequest::Verify { responder, device_label, device_path, seed } => {
138 let res = test.verify(device_label, device_path, seed).await.map_err(|e| {
139 log::warn!("{:?}", e);
141 zx::Status::BAD_STATE.into_raw()
142 });
143 responder.send(res)?;
144 }
145 }
146
147 Ok(())
148}
149
150pub fn generate_content(seed: u64) -> Vec<u8> {
152 let mut rng = StdRng::seed_from_u64(seed);
153
154 let size = rng.random_range(1..1 << 16);
155 rng.sample_iter(&StandardUniform).take(size).collect()
156}
157
158pub async fn find_dev(dev: &str) -> Result<String> {
161 let dev_class_block =
162 fuchsia_fs::directory::open_in_namespace("/dev/class/block", fio::PERM_READABLE)?;
163 for entry in readdir(&dev_class_block).await? {
164 let path = format!("/dev/class/block/{}", entry.name);
165 let proxy = connect_to_protocol_at_path::<ControllerMarker>(&path)?;
166 let topo_path = proxy.get_topological_path().await?.map_err(|s| zx::Status::from_raw(s))?;
167 log::info!("{} => {}", path, topo_path);
168 if dev == topo_path {
169 return Ok(path);
170 }
171 }
172 Err(anyhow::anyhow!("Couldn't find {} in /dev/class/block", dev))
173}
174
175pub fn dev() -> fio::DirectoryProxy {
177 fuchsia_fs::directory::open_in_namespace("/dev", fio::PERM_READABLE)
178 .expect("failed to open /dev")
179}
180
181const BLACKOUT_TYPE_GUID: &Guid = &[
184 0x68, 0x45, 0x23, 0x01, 0xab, 0x89, 0xef, 0xcd, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
185];
186
187const GPT_PARTITION_SIZE: u64 = 60 * 1024 * 1024;
188
189pub async fn set_up_partition(device_label: String) -> Result<Box<dyn BlockConnector>> {
193 let partitions = Service::open(fpartitions::PartitionServiceMarker).unwrap();
194 let manager = connect_to_protocol::<fpartitions::PartitionsManagerMarker>().unwrap();
195
196 let service_instances =
197 partitions.clone().enumerate().await.expect("Failed to enumerate partitions");
198 if let Some(connector) =
199 find_block_device(&[BlockDeviceMatcher::Name(&device_label)], service_instances.into_iter())
200 .await
201 .context("Failed to find block device")?
202 {
203 log::info!(device_label:%; "found existing partition");
204 Ok(Box::new(connector))
205 } else {
206 log::info!(device_label:%; "adding new partition to the system gpt");
207 let info =
208 manager.get_block_info().await.expect("FIDL error").expect("get_block_info failed");
209 let transaction = manager
210 .create_transaction()
211 .await
212 .expect("FIDL error")
213 .map_err(zx::Status::from_raw)
214 .expect("create_transaction failed");
215 let request = fpartitions::PartitionsManagerAddPartitionRequest {
216 transaction: Some(transaction.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap()),
217 name: Some(device_label.clone()),
218 type_guid: Some(into_guid(BLACKOUT_TYPE_GUID.clone())),
219 instance_guid: Some(into_guid(create_random_guid())),
220 num_blocks: Some(GPT_PARTITION_SIZE / info.1 as u64),
221 ..Default::default()
222 };
223 manager
224 .add_partition(request)
225 .await
226 .expect("FIDL error")
227 .map_err(zx::Status::from_raw)
228 .expect("add_partition failed");
229 manager
230 .commit_transaction(transaction)
231 .await
232 .expect("FIDL error")
233 .map_err(zx::Status::from_raw)
234 .expect("add_partition failed");
235 let service_instances =
236 partitions.enumerate().await.expect("Failed to enumerate partitions");
237 let connector = find_block_device(
238 &[BlockDeviceMatcher::Name(&device_label)],
239 service_instances.into_iter(),
240 )
241 .await
242 .context("Failed to find block device")?
243 .unwrap();
244 Ok(Box::new(connector))
245 }
246}
247
248pub async fn find_partition(device_label: String) -> Result<Box<dyn BlockConnector>> {
250 let partitions = Service::open(fpartitions::PartitionServiceMarker).unwrap();
251 let service_instances = partitions.enumerate().await.expect("Failed to enumerate partitions");
252 let connector = find_block_device(
253 &[BlockDeviceMatcher::Name(&device_label)],
254 service_instances.into_iter(),
255 )
256 .await
257 .context("Failed to find block device")?
258 .ok_or_else(|| anyhow!("Block device not found"))?;
259 log::info!(device_label:%; "found existing partition");
260 Ok(Box::new(connector))
261}