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