1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
// Copyright 2022 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 {
    anyhow::{bail, Context as _, Result},
    fidl::endpoints::{create_proxy, ProtocolMarker},
    fidl_fuchsia_fuzzer as fuzz, fuchsia_zircon_status as zx,
    url::Url,
};

/// Represents the FIDL connection from the `ffx fuzz` plugin to the `fuzz-manager` component on a
/// target device.
pub struct Manager {
    proxy: fuzz::ManagerProxy,
}

impl Manager {
    /// Creates a new `Manager`.
    ///
    /// The created object maintains a FIDL `proxy` to the `fuzz-manager` component on a target
    /// device. Any output produced by this object will be written using the given `writer`.
    pub fn new(proxy: fuzz::ManagerProxy) -> Self {
        Self { proxy }
    }

    /// Requests that the `fuzz-manager` connect to a fuzzer instance.
    ///
    /// This will create and connect a `fuchsia.fuzzer.Controller` to the fuzzer on the target
    /// device given by the `url`. Any artifacts produced by the fuzzer will be saved to the
    /// `artifact_dir`, and outputs such as logs can optionally be saved to the `output_dir`.
    ///
    /// Returns an object representing the connected fuzzer, or an error.
    pub async fn connect(&self, url: &Url) -> Result<fuzz::ControllerProxy> {
        let (proxy, server_end) = create_proxy::<fuzz::ControllerMarker>()
            .context("failed to create fuchsia.fuzzer.Controller proxy")?;
        let result = self
            .proxy
            .connect(url.as_str(), server_end)
            .await
            .context("fuchsia.fuzzer/Manager.Connect")?;
        if let Err(e) = result {
            bail!("fuchsia.fuzzer/Manager.Connect returned ZX_ERR_{}", zx::Status::from_raw(e));
        }
        Ok(proxy)
    }

    /// Returns a socket that provides the given type of fuzzer output.
    pub async fn get_output(&self, url: &Url, output: fuzz::TestOutput) -> Result<fidl::Socket> {
        let (rx, tx) = fidl::Socket::create_stream();
        let result = self
            .proxy
            .get_output(url.as_str(), output, tx)
            .await
            .context("failed to get output")?;
        if let Err(e) = result {
            bail!("fuchsia.fuzzer/Manager.GetOutput returned ZX_ERR_{}", zx::Status::from_raw(e));
        }
        Ok(rx)
    }

    /// Requests that the `fuzz-manager` stop a running fuzzer instance.
    ///
    /// As a result of this call, the fuzzer component will cease an ongoing workflow and exit.
    ///
    /// Returns whether a fuzzer was stopped.
    pub async fn stop(&self, url: &Url) -> Result<bool> {
        let result = self.proxy.stop(url.as_str()).await.context(fidl_name("Stop"))?;
        match result {
            Ok(()) => Ok(true),
            Err(e) if e == zx::Status::NOT_FOUND.into_raw() => Ok(false),
            Err(e) => {
                bail!("fuchsia.fuzzer/Manager.Stop returned ZX_ERR_{}", zx::Status::from_raw(e))
            }
        }
    }
}

fn fidl_name(method: &str) -> String {
    format!("{}/{}", fuzz::ManagerMarker::DEBUG_NAME, method)
}

#[cfg(test)]
mod tests {
    use {
        super::Manager,
        anyhow::Result,
        fidl::endpoints::create_proxy,
        fidl_fuchsia_fuzzer as fuzz,
        fuchsia_fuzzctl_test::{create_task, serve_manager, Test, TEST_URL},
        url::Url,
    };

    #[fuchsia::test]
    async fn test_connect() -> Result<()> {
        let test = Test::try_new()?;
        let (proxy, server_end) = create_proxy::<fuzz::ManagerMarker>()?;
        let _task = create_task(serve_manager(server_end, test.clone()), test.writer());
        let manager = Manager::new(proxy);

        let url = Url::parse(TEST_URL)?;
        manager.connect(&url).await?;

        let actual = test.url().borrow().as_ref().map(|url| url.to_string());
        let expected = Some(url.to_string());
        assert_eq!(actual, expected);
        Ok(())
    }

    #[fuchsia::test]
    async fn test_stop() -> Result<()> {
        let test = Test::try_new()?;
        let (proxy, server_end) = create_proxy::<fuzz::ManagerMarker>()?;
        let _task = create_task(serve_manager(server_end, test.clone()), test.writer());
        let manager = Manager::new(proxy);

        let url = Url::parse(TEST_URL)?;
        manager.stop(&url).await?;

        let actual = test.url().borrow().as_ref().map(|url| url.to_string());
        let expected = Some(url.to_string());
        assert_eq!(actual, expected);
        Ok(())
    }
}