fuchsia_fuzzctl_test/
controller.rs

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.
4
5use crate::diagnostics::send_log_entry;
6use crate::options::add_defaults;
7use crate::test::Test;
8use anyhow::{anyhow, Context as _, Result};
9use fidl_fuchsia_fuzzer::{
10    self as fuzz, Artifact as FidlArtifact, Input as FidlInput, Result_ as FuzzResult,
11};
12use fuchsia_fuzzctl::InputPair;
13use futures::{join, AsyncReadExt, AsyncWriteExt, StreamExt};
14use std::cell::RefCell;
15use std::rc::Rc;
16use {fuchsia_async as fasync, zx_status as zx};
17
18/// Test fake that allows configuring how to respond to `fuchsia.fuzzer.Controller` methods.
19///
20/// These fields are Rc<RefCell<_>> in order to be cloned and shared with the `Task` serving the
21/// controller. Unit tests can use this object to query values passed in FIDL requests and set
22/// values returned by FIDL responses.
23#[derive(Debug)]
24pub struct FakeController {
25    corpus_type: Rc<RefCell<fuzz::Corpus>>,
26    input_to_send: Rc<RefCell<Option<Vec<u8>>>>,
27    options: Rc<RefCell<fuzz::Options>>,
28    received_input: Rc<RefCell<Vec<u8>>>,
29    result: Rc<RefCell<Result<FuzzResult, zx::Status>>>,
30    status: Rc<RefCell<fuzz::Status>>,
31    stdout: Rc<RefCell<Option<fasync::Socket>>>,
32    stderr: Rc<RefCell<Option<fasync::Socket>>>,
33    syslog: Rc<RefCell<Option<fasync::Socket>>>,
34    canceled: Rc<RefCell<bool>>,
35}
36
37impl FakeController {
38    /// Creates a fake fuzzer that can serve `fuchsia.fuzzer.Controller`.
39    pub fn new() -> Self {
40        let status = fuzz::Status { running: Some(false), ..Default::default() };
41        let mut options = fuzz::Options::default();
42        add_defaults(&mut options);
43        Self {
44            corpus_type: Rc::new(RefCell::new(fuzz::Corpus::Seed)),
45            input_to_send: Rc::new(RefCell::new(None)),
46            options: Rc::new(RefCell::new(options)),
47            received_input: Rc::new(RefCell::new(Vec::new())),
48            result: Rc::new(RefCell::new(Ok(FuzzResult::NoErrors))),
49            status: Rc::new(RefCell::new(status)),
50            stdout: Rc::new(RefCell::new(None)),
51            stderr: Rc::new(RefCell::new(None)),
52            syslog: Rc::new(RefCell::new(None)),
53            canceled: Rc::new(RefCell::new(false)),
54        }
55    }
56
57    /// Simulates a call to `fuchsia.fuzzer.Manager/GetOutput` without a `fuzz-manager`.
58    pub fn set_output(&self, output: fuzz::TestOutput, socket: fidl::Socket) -> zx::Status {
59        let socket = fasync::Socket::from_socket(socket);
60        match output {
61            fuzz::TestOutput::Stdout => {
62                let mut stdout_mut = self.stdout.borrow_mut();
63                *stdout_mut = Some(socket);
64            }
65            fuzz::TestOutput::Stderr => {
66                let mut stderr_mut = self.stderr.borrow_mut();
67                *stderr_mut = Some(socket);
68            }
69            fuzz::TestOutput::Syslog => {
70                let mut syslog_mut = self.syslog.borrow_mut();
71                *syslog_mut = Some(socket);
72            }
73            _ => todo!("not supported"),
74        }
75        zx::Status::OK
76    }
77
78    /// Returns the type of corpus received via FIDL requests.
79    pub fn get_corpus_type(&self) -> fuzz::Corpus {
80        self.corpus_type.borrow().clone()
81    }
82
83    /// Sets the type of corpus to return via FIDL responses.
84    pub fn set_corpus_type(&self, corpus_type: fuzz::Corpus) {
85        let mut corpus_type_mut = self.corpus_type.borrow_mut();
86        *corpus_type_mut = corpus_type;
87    }
88
89    /// Returns the test input to be sent via a FIDL response.
90    pub fn take_input_to_send(&self) -> Option<Vec<u8>> {
91        self.input_to_send.borrow_mut().take()
92    }
93
94    /// Sets the test input to be sent via a FIDL response.
95    pub fn set_input_to_send(&self, input_to_send: &[u8]) {
96        let mut input_to_send_mut = self.input_to_send.borrow_mut();
97        *input_to_send_mut = Some(input_to_send.to_vec());
98    }
99
100    /// Returns the options received via FIDL requests.
101    pub fn get_options(&self) -> fuzz::Options {
102        self.options.borrow().clone()
103    }
104
105    /// Sets the options to return via FIDL responses.
106    pub fn set_options(&self, mut options: fuzz::Options) {
107        add_defaults(&mut options);
108        let mut options_mut = self.options.borrow_mut();
109        *options_mut = options;
110    }
111
112    /// Returns the test input received via FIDL requests.
113    pub fn get_received_input(&self) -> Vec<u8> {
114        self.received_input.borrow().clone()
115    }
116
117    /// Reads test input data from a `fuchsia.fuzzer.Input` from a FIDL request.
118    async fn receive_input(&self, input: FidlInput) -> Result<()> {
119        let mut received_input = Vec::new();
120        let mut reader = fidl::AsyncSocket::from_socket(input.socket);
121        reader.read_to_end(&mut received_input).await?;
122        let mut received_input_mut = self.received_input.borrow_mut();
123        *received_input_mut = received_input;
124        Ok(())
125    }
126
127    /// Returns the fuzzing result to be sent via a FIDL response.
128    pub fn get_result(&self) -> Result<FuzzResult, zx::Status> {
129        self.result.borrow().clone()
130    }
131
132    /// Sets the fuzzing result to be sent via a FIDL response.
133    pub fn set_result(&self, result: Result<FuzzResult, zx::Status>) {
134        let mut result_mut = self.result.borrow_mut();
135        *result_mut = result;
136    }
137
138    /// Gets the fuzzer status to be sent via FIDL responses.
139    pub fn get_status(&self) -> fuzz::Status {
140        self.status.borrow().clone()
141    }
142
143    /// Sets the fuzzer status to be sent via FIDL responses.
144    pub fn set_status(&self, status: fuzz::Status) {
145        let mut status_mut = self.status.borrow_mut();
146        *status_mut = status;
147    }
148
149    // Simulates sending a `msg` to a fuzzer's standard output, standard error, and system log.
150    async fn send_output(&self, msg: &str) -> Result<()> {
151        let msg_str = format!("{}\n", msg);
152        {
153            let mut stdout_mut = self.stdout.borrow_mut();
154            if let Some(mut stdout) = stdout_mut.take() {
155                stdout.write_all(msg_str.as_bytes()).await?;
156                *stdout_mut = Some(stdout);
157            }
158        }
159        {
160            let mut stderr_mut = self.stderr.borrow_mut();
161            if let Some(mut stderr) = stderr_mut.take() {
162                stderr.write_all(msg_str.as_bytes()).await?;
163                *stderr_mut = Some(stderr);
164            }
165        }
166        {
167            let mut syslog_mut = self.syslog.borrow_mut();
168            if let Some(mut syslog) = syslog_mut.take() {
169                send_log_entry(&mut syslog, msg).await?;
170                *syslog_mut = Some(syslog);
171            }
172        }
173        Ok(())
174    }
175
176    /// Simulates a long-running workflow being canceled by `fuchsia.fuzzer.Manager/Stop`.
177    pub fn cancel(&self) {
178        let mut canceled_mut = self.canceled.borrow_mut();
179        *canceled_mut = true;
180    }
181
182    /// Get whether a simulated call to `fuchsia.fuzzer.Manager/Stop` has been made.
183    pub fn is_canceled(&self) -> bool {
184        *self.canceled.borrow()
185    }
186}
187
188impl Clone for FakeController {
189    fn clone(&self) -> Self {
190        Self {
191            corpus_type: Rc::clone(&self.corpus_type),
192            input_to_send: Rc::clone(&self.input_to_send),
193            options: Rc::clone(&self.options),
194            received_input: Rc::clone(&self.received_input),
195            result: Rc::clone(&self.result),
196            status: Rc::clone(&self.status),
197            stdout: Rc::clone(&self.stdout),
198            stderr: Rc::clone(&self.stderr),
199            syslog: Rc::clone(&self.syslog),
200            canceled: Rc::clone(&self.canceled),
201        }
202    }
203}
204
205/// Serves `fuchsia.fuzzer.Controller` using test fakes.
206pub async fn serve_controller(
207    mut stream: fuzz::ControllerRequestStream,
208    mut test: Test,
209) -> Result<()> {
210    let fake = test.controller();
211    let mut artifact = Some(FidlArtifact::default());
212    let mut watcher: Option<fuzz::ControllerWatchArtifactResponder> = None;
213    // Helper function to send an empty artifact when a workflow is starting.
214    fn reset_artifact(mut watcher: Option<fuzz::ControllerWatchArtifactResponder>) -> Result<()> {
215        if let Some(watcher) = watcher.take() {
216            watcher.send(FidlArtifact::default())?;
217        }
218        Ok(())
219    }
220    loop {
221        let request = stream.next().await;
222        if fake.is_canceled() {
223            break;
224        }
225        match request {
226            Some(Ok(fuzz::ControllerRequest::Configure { options, responder })) => {
227                test.record("fuchsia.fuzzer/Controller.Configure");
228                fake.set_options(options);
229                responder.send(Ok(()))?;
230            }
231            Some(Ok(fuzz::ControllerRequest::GetOptions { responder })) => {
232                test.record("fuchsia.fuzzer/Controller.GetOptions");
233                let options = fake.get_options();
234                responder.send(&options)?;
235            }
236            Some(Ok(fuzz::ControllerRequest::AddToCorpus { corpus, input, responder })) => {
237                test.record(format!("fuchsia.fuzzer/Controller.AddToCorpus({:?})", corpus));
238                fake.receive_input(input).await?;
239                fake.set_corpus_type(corpus);
240                responder.send(Ok(()))?;
241            }
242            Some(Ok(fuzz::ControllerRequest::ReadCorpus { corpus, corpus_reader, responder })) => {
243                test.record("fuchsia.fuzzer/Controller.ReadCorpus");
244                fake.set_corpus_type(corpus);
245                let corpus_reader = corpus_reader.into_proxy();
246                if let Some(input_to_send) = fake.take_input_to_send() {
247                    let input_pair = InputPair::try_from_data(input_to_send)?;
248                    let (fidl_input, input) = input_pair.as_tuple();
249                    let corpus_fut = corpus_reader.next(fidl_input);
250                    let input_fut = input.send();
251                    let results = join!(corpus_fut, input_fut);
252                    assert!(results.0.is_ok());
253                    assert!(results.1.is_ok());
254                }
255                responder.send()?;
256            }
257            Some(Ok(fuzz::ControllerRequest::GetStatus { responder })) => {
258                test.record("fuchsia.fuzzer/Controller.GetStatus");
259                let status = fake.get_status();
260                responder.send(&status)?;
261            }
262            Some(Ok(fuzz::ControllerRequest::Fuzz { responder })) => {
263                test.record("fuchsia.fuzzer/Controller.Fuzz");
264                reset_artifact(watcher.take())?;
265                // As special cases, fuzzing indefinitely without any errors or fuzzing with an
266                // explicit error of `SHOULD_WAIT` will imitate a FIDL call that does not
267                // complete. These can be interrupted by the shell or allowed to timeout.
268                let result = fake.get_result();
269                let options = fake.get_options();
270                match (options.runs, options.max_total_time, result) {
271                    (Some(0), Some(0), Ok(FuzzResult::NoErrors))
272                    | (_, _, Err(zx::Status::SHOULD_WAIT)) => {
273                        let mut status = fake.get_status();
274                        status.running = Some(true);
275                        fake.set_status(status);
276                        responder.send(Ok(()))?;
277                    }
278                    (_, _, Ok(fuzz_result)) => {
279                        let input_to_send = fake.take_input_to_send().unwrap_or(Vec::new());
280                        let input_pair = InputPair::try_from_data(input_to_send)?;
281                        let (fidl_input, input) = input_pair.as_tuple();
282                        artifact = Some(FidlArtifact {
283                            result: Some(fuzz_result),
284                            input: Some(fidl_input),
285                            ..Default::default()
286                        });
287                        responder.send(Ok(()))?;
288                        input.send().await?;
289                        fake.send_output(fuzz::DONE_MARKER).await?;
290                    }
291                    (_, _, Err(status)) => {
292                        responder.send(Err(status.into_raw()))?;
293                    }
294                };
295            }
296            Some(Ok(fuzz::ControllerRequest::TryOne { test_input, responder })) => {
297                test.record("fuchsia.fuzzer/Controller.TryOne");
298                reset_artifact(watcher.take())?;
299                fake.receive_input(test_input).await?;
300                match fake.get_result() {
301                    Ok(fuzz_result) => {
302                        artifact = Some(FidlArtifact {
303                            result: Some(fuzz_result),
304                            input: None,
305                            ..Default::default()
306                        });
307                        responder.send(Ok(()))?;
308                        fake.send_output(fuzz::DONE_MARKER).await?;
309                    }
310                    Err(status) => {
311                        responder.send(Err(status.into_raw()))?;
312                    }
313                }
314            }
315            Some(Ok(fuzz::ControllerRequest::Minimize { test_input, responder })) => {
316                test.record("fuchsia.fuzzer/Controller.Minimize");
317                reset_artifact(watcher.take())?;
318                fake.receive_input(test_input).await?;
319                let input_to_send = fake.take_input_to_send().context("input_to_send unset")?;
320                let input_pair = InputPair::try_from_data(input_to_send)?;
321                let (fidl_input, input) = input_pair.as_tuple();
322                artifact = Some(FidlArtifact {
323                    result: Some(FuzzResult::Minimized),
324                    input: Some(fidl_input),
325                    ..Default::default()
326                });
327                responder.send(Ok(()))?;
328                input.send().await?;
329                fake.send_output(fuzz::DONE_MARKER).await?;
330            }
331            Some(Ok(fuzz::ControllerRequest::Cleanse { test_input, responder })) => {
332                test.record("fuchsia.fuzzer/Controller.Cleanse");
333                reset_artifact(watcher.take())?;
334                fake.receive_input(test_input).await?;
335                let input_to_send = fake.take_input_to_send().context("input_to_send unset")?;
336                let input_pair = InputPair::try_from_data(input_to_send)?;
337                let (fidl_input, input) = input_pair.as_tuple();
338                artifact = Some(FidlArtifact {
339                    result: Some(FuzzResult::Cleansed),
340                    input: Some(fidl_input),
341                    ..Default::default()
342                });
343                responder.send(Ok(()))?;
344                input.send().await?;
345                fake.send_output(fuzz::DONE_MARKER).await?;
346            }
347            Some(Ok(fuzz::ControllerRequest::Merge { responder })) => {
348                test.record("fuchsia.fuzzer/Controller.Merge");
349                reset_artifact(watcher.take())?;
350                artifact = Some(FidlArtifact {
351                    result: Some(FuzzResult::Merged),
352                    input: None,
353                    ..Default::default()
354                });
355                responder.send(Ok(()))?;
356                fake.send_output(fuzz::DONE_MARKER).await?;
357            }
358            Some(Ok(fuzz::ControllerRequest::WatchArtifact { responder })) => {
359                match artifact.take() {
360                    Some(artifact) => {
361                        responder.send(artifact)?;
362                    }
363                    None => {
364                        watcher = Some(responder);
365                    }
366                };
367            }
368            Some(Err(e)) => return Err(anyhow!(e)),
369            None => break,
370            _ => todo!("not yet implemented"),
371        };
372    }
373    Ok(())
374}