1use crate::util::digest_path;
6use crate::writer::{OutputSink, Writer};
7use anyhow::{Context as _, Result, bail};
8use flex_fuchsia_fuzzer::Input as FidlInput;
9use futures::AsyncReadExt;
10use futures::io::AsyncWriteExt as _;
11use std::fs;
12use std::path::{Path, PathBuf};
13
14#[derive(Debug)]
16pub struct InputPair {
17 pub fidl_input: FidlInput,
19
20 pub input: Input,
22}
23
24impl From<(FidlInput, Input)> for InputPair {
25 fn from(tuple: (FidlInput, Input)) -> Self {
26 InputPair { fidl_input: tuple.0, input: tuple.1 }
27 }
28}
29
30impl InputPair {
31 pub fn try_from_str<S, O>(
41 client: &flex_client::ClientArg,
42 input: S,
43 writer: &Writer<O>,
44 ) -> Result<Self>
45 where
46 S: AsRef<str>,
47 O: OutputSink,
48 {
49 let input = input.as_ref();
50 let hex_result = hex::decode(input);
51 let fs_result = fs::read(input);
52 let input_data = match (hex_result, fs_result) {
53 (Ok(input_data), Err(_)) => input_data,
54 (Err(_), Ok(input_data)) => input_data,
55 (Ok(input_data), Ok(_)) => {
56 writer.print("WARNING: ");
57 writer.print(input);
58 writer.println("can be interpreted as either a hex string or a file.");
59 writer.println("The input will be treated as a hex string.");
60 writer.println("To force treatment as a file, include more of the path, e.g.");
61 writer.print(" ./");
62 writer.println(input);
63 input_data
64 }
65 (Err(_), Err(e)) => bail!("failed to read fuzzer input: {}", e),
66 };
67 InputPair::try_from_data(client, input_data)
68 }
69
70 pub fn try_from_path<P: AsRef<Path>>(client: &flex_client::ClientArg, path: P) -> Result<Self> {
75 let path = path.as_ref();
76 let input_data = fs::read(path)
77 .with_context(|| format!("failed to read '{}'", path.to_string_lossy()))?;
78 InputPair::try_from_data(client, input_data)
79 }
80
81 pub fn try_from_data(client: &flex_client::ClientArg, input_data: Vec<u8>) -> Result<Self> {
83 let (reader, writer) = client.create_stream_socket();
84 let fidl_input = FidlInput { socket: reader, size: input_data.len() as u64 };
85 let input = Input { socket: Some(flex_client::socket_to_async(writer)), data: input_data };
86 Ok(InputPair::from((fidl_input, input)))
87 }
88
89 pub fn as_tuple(self) -> (FidlInput, Input) {
91 (self.fidl_input, self.input)
92 }
93
94 pub fn len(&self) -> usize {
96 self.input.data.len()
97 }
98}
99
100#[derive(Debug)]
108pub struct Input {
109 socket: Option<flex_client::AsyncSocket>,
110
111 pub data: Vec<u8>,
113}
114
115impl Input {
116 pub async fn send(mut self) -> Result<()> {
120 let mut writer = self.socket.take().context("input already sent")?;
121 writer.write_all(&self.data).await.context("failed to write fuzz input")?;
122 Ok(())
123 }
124
125 pub async fn try_receive(fidl_input: FidlInput) -> Result<Self> {
129 let mut data = Vec::new();
130 let reader = flex_client::socket_to_async(fidl_input.socket);
131 reader
132 .take(fidl_input.size)
133 .read_to_end(&mut data)
134 .await
135 .context("failed to read fuzz input from socket")?;
136 Ok(Input { socket: None, data })
137 }
138}
139
140pub async fn save_input<P: AsRef<Path>>(fidl_input: FidlInput, out_dir: P) -> Result<PathBuf> {
148 let input =
149 Input::try_receive(fidl_input).await.context("failed to receive fuzzer input data")?;
150 let path = digest_path(out_dir, None, &input.data);
151 fs::write(&path, input.data)
152 .with_context(|| format!("failed to write fuzzer input to '{}'", path.to_string_lossy()))?;
153 Ok(path)
154}
155
156#[cfg(test)]
157mod tests {
158 use super::{Input, save_input};
159 use crate::util::digest_path;
160 use anyhow::Result;
161 use flex_fuchsia_fuzzer::Input as FidlInput;
162 use fuchsia_fuzzctl::InputPair;
163 use fuchsia_fuzzctl_test::{Test, verify_saved};
164 use futures::{AsyncReadExt, join};
165 use std::fs::File;
166 use std::io::Write;
167
168 #[fuchsia::test]
169 async fn test_from_str() -> Result<()> {
170 let test = Test::try_new()?;
171 let writer = test.writer();
172 let input_dir = test.create_dir("inputs")?;
173
174 let input1 = input_dir.join("input1");
176 let actual = format!(
177 "{:?}",
178 InputPair::try_from_str(&test.domain(), input1.to_string_lossy(), writer)
179 );
180 assert!(actual.contains("failed to read fuzzer input"));
181
182 let mut file = File::create(&input1)?;
184 let input_pair = InputPair::try_from_str(&test.domain(), input1.to_string_lossy(), writer)?;
185 let (fidl_input, input) = input_pair.as_tuple();
186 assert_eq!(fidl_input.size, 0);
187 assert!(input.data.is_empty());
188
189 file.write_all(b"data")?;
191 let input_pair = InputPair::try_from_str(&test.domain(), input1.to_string_lossy(), writer)?;
192 let (fidl_input, input) = input_pair.as_tuple();
193 assert_eq!(fidl_input.size, 4);
194 assert_eq!(input.data, b"data");
195
196 let input_pair = InputPair::try_from_str(&test.domain(), "64617461", writer)?;
198 let (fidl_input, input) = input_pair.as_tuple();
199 assert_eq!(fidl_input.size, 4);
200 assert_eq!(input.data, b"data");
201 Ok(())
202 }
203
204 #[fuchsia::test]
205 async fn test_from_path() -> Result<()> {
206 let test = Test::try_new()?;
207 let mut path = test.create_dir("inputs")?;
208 path.push("input");
209 assert!(InputPair::try_from_path(&test.domain(), &path).is_err());
210
211 let mut file = File::create(&path)?;
212 let input_pair = InputPair::try_from_path(&test.domain(), &path)?;
213 let (fidl_input, input) = input_pair.as_tuple();
214 assert_eq!(fidl_input.size, 0);
215 assert!(input.data.is_empty());
216
217 file.write_all(b"data")?;
218 let input_pair = InputPair::try_from_path(&test.domain(), &path)?;
219 let (fidl_input, input) = input_pair.as_tuple();
220 assert_eq!(fidl_input.size, 4);
221 assert_eq!(input.data, b"data");
222 Ok(())
223 }
224
225 #[fuchsia::test]
226 async fn test_send() -> Result<()> {
227 async fn recv(fidl_input: FidlInput, expected: &[u8]) {
228 let mut reader = flex_client::socket_to_async(fidl_input.socket);
229 let mut buf = Vec::new();
230 let num_read = reader.read_to_end(&mut buf).await.expect("read_to_end failed");
231 assert_eq!(num_read as u64, fidl_input.size);
232 assert_eq!(buf, expected);
233 }
234 let test = Test::try_new()?;
235 let input_pair = InputPair::try_from_data(&test.domain(), b"".to_vec())?;
236 let (fidl_input, input) = input_pair.as_tuple();
237 assert!(join!(input.send(), recv(fidl_input, b"")).0.is_ok());
238 let input_pair = InputPair::try_from_data(&test.domain(), b"data".to_vec())?;
239 let (fidl_input, input) = input_pair.as_tuple();
240 assert!(join!(input.send(), recv(fidl_input, b"data")).0.is_ok());
241 Ok(())
242 }
243
244 #[fuchsia::test]
245 async fn test_save_input() -> Result<()> {
246 let test = Test::try_new()?;
247 let saved_dir = test.create_dir("saved")?;
248
249 let (reader, writer) = test.domain().create_stream_socket();
250 let writer = flex_client::socket_to_async(writer);
251 let fidl_input = FidlInput { socket: reader, size: 0 };
252 let input = Input { socket: Some(writer), data: Vec::new() };
253 let send_fut = input.send();
254 let save_fut = save_input(fidl_input, &saved_dir);
255 let results = join!(send_fut, save_fut);
256 assert!(results.0.is_ok());
257 assert!(results.1.is_ok());
258 let saved = digest_path(&saved_dir, None, b"");
259 verify_saved(&saved, b"")?;
260 Ok(())
261 }
262}