1// Copyright 2022 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.
45use crate::input::Input;
6use crate::util::digest_path;
7use anyhow::{bail, Context as _, Result};
8use fidl_fuchsia_fuzzer::{Artifact as FidlArtifact, Result_ as FuzzResult};
9use std::fs;
10use std::path::{Path, PathBuf};
11use zx_status as zx;
1213/// Combines the results of a long-running fuzzer workflow.
14pub struct Artifact {
15/// If `status` is OK, indicates the outcome of the workflow; otherwise undefined.
16pub result: FuzzResult,
1718/// The path to which the fuzzer input, if any, has been saved.
19pub path: Option<PathBuf>,
20}
2122impl Artifact {
23/// Returns an artifact for a workflow that completed without producing results or data.
24pub fn ok() -> Self {
25Self { result: FuzzResult::NoErrors, path: None }
26 }
2728/// Returns an artifact for a workflow that produced a result without data.
29pub fn from_result(result: FuzzResult) -> Self {
30Self { result, path: None }
31 }
3233/// Returns `artifact.path` as a String, or an empty string if it is `None`.
34pub fn pathname(&self) -> String {
35self.path.as_ref().map(|p| p.to_string_lossy().to_string()).unwrap_or(String::default())
36 }
37}
3839/// Reads fuzzer input data from a `FidlArtifact` and saves it locally.
40///
41/// Returns:
42/// Returns an `Artifact` on success. Returns an error if the artifact indicates an error, if
43/// it fails to read the data from the `input`, or if it fails to write the data to the file.
44///
45/// See also `utils::digest_path`.
46///
47pub async fn save_artifact<P: AsRef<Path>>(
48 fidl_artifact: FidlArtifact,
49 out_dir: P,
50) -> Result<Option<Artifact>> {
51if let Some(e) = fidl_artifact.error {
52if e == zx::Status::PEER_CLOSED.into_raw() {
53return Ok(None);
54 }
55bail!("workflow returned an error: ZX_ERR_{}", e);
56 }
57let result = fidl_artifact.result.context("invalid FIDL artifact: missing result")?;
58let mut artifact = Artifact::from_result(result);
59if let Some(fidl_input) = fidl_artifact.input {
60let input =
61 Input::try_receive(fidl_input).await.context("failed to receive fuzzer input data")?;
62if artifact.result != FuzzResult::NoErrors {
63let path = digest_path(out_dir, Some(artifact.result), &input.data);
64 fs::write(&path, input.data).with_context(|| {
65format!("failed to write fuzzer input to '{}'", path.to_string_lossy())
66 })?;
67 artifact.path = Some(path);
68 }
69 };
70Ok(Some(artifact))
71}
7273#[cfg(test)]
74mod tests {
75use super::save_artifact;
76use crate::input::InputPair;
77use crate::util::digest_path;
78use anyhow::Result;
79use fidl_fuchsia_fuzzer::{Artifact as FidlArtifact, Result_ as FuzzResult};
80use fuchsia_fuzzctl_test::{verify_saved, Test};
81use futures::join;
8283#[fuchsia::test]
84async fn test_save_artifact() -> Result<()> {
85let test = Test::try_new()?;
86let saved_dir = test.create_dir("saved")?;
8788let input_pair = InputPair::try_from_data(b"data".to_vec())?;
89let (fidl_input, input) = input_pair.as_tuple();
90let send_fut = input.send();
91let fidl_artifact = FidlArtifact {
92 result: Some(FuzzResult::Crash),
93 input: Some(fidl_input),
94 ..Default::default()
95 };
96let save_fut = save_artifact(fidl_artifact, &saved_dir);
97let results = join!(send_fut, save_fut);
98assert!(results.0.is_ok());
99assert!(results.1.is_ok());
100let saved = digest_path(&saved_dir, Some(FuzzResult::Crash), b"data");
101 verify_saved(&saved, b"data")?;
102Ok(())
103 }
104}