blob: 667baa4c1e017b47554cd860fe33a48a2cf4e468 [file] [log] [blame]
// Copyright 2021 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.
use crate::diagnostics::{ArtifactBridge, ArtifactHandler};
use crate::events::{handle_run_events, handle_suite_events};
use anyhow::{Context as _, Result};
use futures::channel::{mpsc, oneshot};
use futures::{pin_mut, select, FutureExt, SinkExt};
use std::cell::RefCell;
use std::rc::Rc;
use test_manager::{Artifact, LaunchError, RunControllerProxy, SuiteControllerProxy};
use tracing::warn;
use {
fidl_fuchsia_fuzzer as fuzz, fidl_fuchsia_test_manager as test_manager,
fuchsia_async as fasync, fuchsia_zircon as zx,
};
/// Represents the what stage of its lifecycle a fuzzer is currently in.
#[derive(Clone, Debug)]
pub enum FuzzerState {
Stopped,
Starting,
Running,
Failed(zx::Status),
}
/// Represents a fuzzer running on a target device.
#[derive(Debug)]
pub struct Fuzzer {
state: Rc<RefCell<FuzzerState>>,
stdout: Option<mpsc::UnboundedSender<zx::Socket>>,
stderr: Option<mpsc::UnboundedSender<zx::Socket>>,
syslog: Option<mpsc::UnboundedSender<zx::Socket>>,
kill: Option<oneshot::Sender<()>>,
task: Option<fasync::Task<()>>,
}
impl Default for Fuzzer {
fn default() -> Self {
Self {
state: Rc::new(RefCell::new(FuzzerState::Stopped)),
stdout: None,
stderr: None,
syslog: None,
kill: None,
task: None,
}
}
}
impl Fuzzer {
/// Returns the current fuzzer state.
pub fn get_state(&self) -> FuzzerState {
self.state.borrow().clone()
}
fn set_state(&self, desired: FuzzerState) {
let mut state = self.state.borrow_mut();
*state = desired;
}
/// Starts a fuzzer and sets up connections to forward its output.
///
/// Given `RunController` and `SuiteController` proxies to `test_manager`, this method will
/// create handlers for `RunEvent`s, `SuiteEvent`s, test `Artifact`s, `SocketBridge`s, and
/// `LogBridge`s and establish the connections between them to forward stdout, stderr, and
/// syslog from the fuzzer to the appropriate sockets provided by `ffx fuzz` via a call to
/// `Fuzzer::get_output`.
pub async fn start(
&mut self,
run_proxy: RunControllerProxy,
suite_proxy: SuiteControllerProxy,
) {
self.set_state(FuzzerState::Starting);
let (artifact_sender, artifact_receiver) = mpsc::unbounded::<Artifact>();
let (stdout_tx, stdout_rx) = mpsc::unbounded::<zx::Socket>();
let (stderr_tx, stderr_rx) = mpsc::unbounded::<zx::Socket>();
let (syslog_tx, syslog_rx) = mpsc::unbounded::<zx::Socket>();
let (start_sender, start_receiver) = oneshot::channel::<Result<(), LaunchError>>();
let (kill_sender, kill_receiver) = oneshot::channel::<()>();
let state = Rc::clone(&self.state);
let mut artifact_handler = ArtifactHandler::new(artifact_receiver);
let stdout = artifact_handler.create_stdout_bridge(stdout_rx);
let stderr = artifact_handler.create_stderr_bridge(stderr_rx);
let syslog = artifact_handler.create_syslog_bridge(syslog_rx);
let task = || async move {
let run_fut =
handle_run_events(run_proxy, artifact_sender.clone(), kill_receiver).fuse();
let suite_fut = handle_suite_events(suite_proxy, artifact_sender, start_sender).fuse();
let artifact_fut = artifact_handler.run().fuse();
let stdout_fut = stdout.forward().fuse();
let stderr_fut = stderr.forward().fuse();
let syslog_fut = syslog.forward().fuse();
pin_mut!(run_fut, suite_fut, artifact_fut, stdout_fut, stderr_fut, syslog_fut);
let mut event_futs = 2;
while event_futs > 0 {
let result = select! {
result = run_fut => {
event_futs -= 1;
result
}
result = suite_fut => {
event_futs -= 1;
result
}
result = artifact_fut => result,
_ = stdout_fut => Ok(()),
_ = stderr_fut => Ok(()),
_ = syslog_fut => Ok(()),
};
if let Err(e) = result {
warn!("{:?}", e);
break;
}
}
let mut state = state.borrow_mut();
*state = FuzzerState::Stopped;
};
self.stdout = Some(stdout_tx);
self.stderr = Some(stderr_tx);
self.syslog = Some(syslog_tx);
self.kill = Some(kill_sender);
self.task = Some(fasync::Task::local(task()));
// Wait for the task to indicate it has launched (or failed).
let launch_result = match start_receiver.await {
Ok(launch_result) => launch_result,
Err(_) => {
warn!("failed to start fuzzer: suite controller closed unexpectedly");
self.set_state(FuzzerState::Failed(zx::Status::INTERNAL));
return;
}
};
match launch_result {
Ok(()) => self.set_state(FuzzerState::Running),
Err(e) => {
warn!("failed to start fuzzer: {:?}", e);
let status = match e {
LaunchError::ResourceUnavailable => zx::Status::NO_RESOURCES,
LaunchError::InstanceCannotResolve => zx::Status::NOT_FOUND,
LaunchError::InvalidArgs => zx::Status::INVALID_ARGS,
LaunchError::FailedToConnectToTestSuite => zx::Status::NOT_SUPPORTED,
LaunchError::NoMatchingCases => zx::Status::NOT_FOUND,
_ => zx::Status::INTERNAL,
};
self.set_state(FuzzerState::Failed(status));
}
};
}
/// Installs a socket to receive fuzzer output of the given type.
pub async fn get_output(&mut self, output: fuzz::TestOutput, socket: zx::Socket) -> Result<()> {
match output {
fuzz::TestOutput::Stdout => {
if let Some(mut stdout) = self.stdout.as_ref() {
stdout.send(socket).await.context("failed to send stdout")?;
}
}
fuzz::TestOutput::Stderr => {
if let Some(mut stderr) = self.stderr.as_ref() {
stderr.send(socket).await.context("failed to send stderr")?;
}
}
fuzz::TestOutput::Syslog => {
if let Some(mut syslog) = self.syslog.as_ref() {
syslog.send(socket).await.context("failed to send syslog")?;
}
}
_ => unimplemented!(),
}
Ok(())
}
/// Waits for the fuzzer to stop.
///
/// If a `max_wait_time` is provided, and the fuzzer currently has an output forwarding task, it
/// will wait up to that much time for the task to complete. If it has a task but no
/// `max_wait_time` is provided, it will drop the task immediately.
///
/// Returns an error if the `max_wait_time` elapses without the output forwarding task
/// completing.
///
pub async fn stop(&mut self, max_wait_time: Option<zx::Duration>) -> Result<(), zx::Status> {
if let (Some(task), Some(max_wait_time)) = (self.task.take(), max_wait_time) {
// TODO(https://fxbug.dev/42063642): Extend the test fixtures and add tests for the timeout case.
let stop_fut = task.fuse();
let timer_fut = fasync::Timer::new(max_wait_time).fuse();
pin_mut!(stop_fut, timer_fut);
select! {
_ = stop_fut => Ok(()),
_ = timer_fut => Err(zx::Status::TIMED_OUT)
}?;
}
self.stdout = None;
self.stderr = None;
self.syslog = None;
self.set_state(FuzzerState::Stopped);
Ok(())
}
/// Stops the fuzzer immediately.
pub async fn kill(&mut self) -> Result<(), zx::Status> {
if let Some(kill) = self.kill.take() {
let _ = kill.send(());
}
self.stop(None).await
}
}