1use getopts::Options;
2use std::path::PathBuf;
3
4#[derive(Debug, Clone, Copy)]
5pub enum FuzzerCommand {
6 MinifyInput,
7 Fuzz,
8 Read,
9 MinifyCorpus,
10}
11
12pub const TIMEOUT_FLAG: &str = "timeout";
13pub const MAX_NBR_RUNS_FLAG: &str = "max-iter";
14pub const MAX_INPUT_CPLX_FLAG: &str = "max-cplx";
15pub const INPUT_FILE_FLAG: &str = "input-file";
16pub const IN_CORPUS_FLAG: &str = "in-corpus";
17pub const NO_IN_CORPUS_FLAG: &str = "no-in-corpus";
18pub const OUT_CORPUS_FLAG: &str = "out-corpus";
19pub const NO_OUT_CORPUS_FLAG: &str = "no-out-corpus";
20pub const ARTIFACTS_FLAG: &str = "artifacts";
21pub const NO_ARTIFACTS_FLAG: &str = "no-artifacts";
22pub const CORPUS_SIZE_FLAG: &str = "corpus-size";
23
24pub const COMMAND_FUZZ: &str = "fuzz";
25pub const COMMAND_MINIFY_INPUT: &str = "tmin";
26pub const COMMAND_MINIFY_CORPUS: &str = "cmin";
27pub const COMMAND_READ: &str = "read";
28
29#[derive(Clone)]
30pub struct DefaultArguments<'a> {
31 pub command: FuzzerCommand,
32 pub in_corpus: &'a str,
33 pub out_corpus: &'a str,
34 pub artifacts: &'a str,
35 pub max_nbr_of_runs: usize,
36 pub max_input_cplx: usize,
37 pub timeout: usize,
38 pub corpus_size: usize,
39}
40
41pub const DEFAULT_ARGUMENTS: DefaultArguments<'static> = DefaultArguments {
42 command: FuzzerCommand::Fuzz,
43 in_corpus: "corpus",
44 out_corpus: "corpus",
45 artifacts: "artifacts",
46 max_nbr_of_runs: core::usize::MAX,
47 max_input_cplx: 4096,
48 timeout: 0,
49 corpus_size: 100,
50};
51
52#[derive(Debug, Clone)]
53pub struct ResolvedCommandLineArguments {
54 pub command: FuzzerCommand,
55 pub max_nbr_of_runs: usize,
56 pub max_input_cplx: f64,
57 pub timeout: usize,
58 pub corpus_size: usize,
59 pub input_file: Option<PathBuf>,
60 pub corpus_in: Option<PathBuf>,
61 pub corpus_out: Option<PathBuf>,
62 pub artifacts_folder: Option<PathBuf>,
63}
64
65#[derive(Debug, Clone)]
66pub struct CommandLineArguments {
67 pub command: FuzzerCommand,
68 pub max_nbr_of_runs: Option<usize>,
69 pub max_input_cplx: Option<f64>,
70 pub timeout: Option<usize>,
71 pub corpus_size: Option<usize>,
72 pub input_file: Option<PathBuf>,
73 pub corpus_in: Option<PathBuf>,
74 pub corpus_out: Option<PathBuf>,
75 pub artifacts_folder: Option<PathBuf>,
76
77 pub no_in_corpus: Option<()>,
78 pub no_out_corpus: Option<()>,
79 pub no_artifacts: Option<()>,
80}
81
82#[must_use]
83pub fn options_parser() -> Options {
84 let mut options = Options::new();
85
86 options.long_only(true);
87 options.optopt("", IN_CORPUS_FLAG, "folder for the input corpus", "PATH");
88 options.optflag(
89 "",
90 NO_IN_CORPUS_FLAG,
91 format!(
92 "do not use an input corpus, overrides --{in_corpus}",
93 in_corpus = IN_CORPUS_FLAG
94 )
95 .as_str(),
96 );
97 options.optopt("", OUT_CORPUS_FLAG, "folder for the output corpus", "PATH");
98 options.optflag(
99 "",
100 NO_OUT_CORPUS_FLAG,
101 format!(
102 "do not use an output corpus, overrides --{out_corpus}",
103 out_corpus = OUT_CORPUS_FLAG
104 )
105 .as_str(),
106 );
107 options.optopt("", ARTIFACTS_FLAG, "folder where the artifacts will be written", "PATH");
108 options.optflag(
109 "",
110 NO_ARTIFACTS_FLAG,
111 format!(
112 "do not save artifacts, overrides --{artifacts}",
113 artifacts = ARTIFACTS_FLAG
114 )
115 .as_str(),
116 );
117 options.optopt("", INPUT_FILE_FLAG, "file containing a JSON-encoded input", "PATH");
118 options.optopt(
119 "",
120 CORPUS_SIZE_FLAG,
121 format!(
122 "target size of the corpus (default: {default})",
123 default = DEFAULT_ARGUMENTS.corpus_size
124 )
125 .as_str(),
126 "N",
127 );
128 options.optopt(
129 "",
130 MAX_INPUT_CPLX_FLAG,
131 format!(
132 "maximum allowed complexity of inputs (default: {default})",
133 default = DEFAULT_ARGUMENTS.max_input_cplx
134 )
135 .as_str(),
136 "N",
137 );
138 options.optopt("", MAX_NBR_RUNS_FLAG, "maximum number of iterations", "N");
139 options.optopt(
140 "",
141 TIMEOUT_FLAG,
142 format!(
143 "maximum allowed time in milliseconds for a single run to finish, or 0 for no limit (default: {default})",
144 default = DEFAULT_ARGUMENTS.timeout
145 )
146 .as_str(),
147 "N",
148 );
149 options.optflag("", "help", "print this help menu");
150
151 options
152}
153
154impl CommandLineArguments {
155 pub fn from_parser(options: &Options, args: &[String]) -> Result<Self, String> {
156 let matches = options.parse(args).map_err(|e| e.to_string())?;
157
158 if matches.opt_present("help") || args.is_empty() {
160 return Err("".to_owned());
161 }
162
163 let command: FuzzerCommand = match args[0].as_str() {
164 COMMAND_FUZZ => FuzzerCommand::Fuzz,
165 COMMAND_READ => FuzzerCommand::Read,
166 COMMAND_MINIFY_INPUT => FuzzerCommand::MinifyInput,
167 COMMAND_MINIFY_CORPUS => FuzzerCommand::MinifyCorpus,
168 _ => Err(format!(
169 r#"The command {c} is not supported. It can either be ‘{fuzz}’, ‘{tmin}’, or ‘{cmin}’."#,
170 c = args[0],
171 fuzz = COMMAND_FUZZ,
172 tmin = COMMAND_MINIFY_INPUT,
173 cmin = COMMAND_MINIFY_CORPUS
174 ))?,
175 };
176
177 let max_input_cplx: Option<f64> = matches
178 .opt_str(MAX_INPUT_CPLX_FLAG)
179 .and_then(|x| x.parse::<usize>().ok())
180 .map(|x| x as f64);
181
182 let input_file: Option<PathBuf> = matches.opt_str(INPUT_FILE_FLAG).and_then(|x| x.parse::<PathBuf>().ok());
183
184 let corpus_size: Option<usize> = matches.opt_str(CORPUS_SIZE_FLAG).and_then(|x| x.parse::<usize>().ok());
185
186 let corpus_in: Option<PathBuf> = matches.opt_str(IN_CORPUS_FLAG).and_then(|x| x.parse::<PathBuf>().ok());
187
188 let no_in_corpus = if matches.opt_present(NO_IN_CORPUS_FLAG) {
189 Some(())
190 } else {
191 None
192 };
193
194 let corpus_out: Option<PathBuf> = matches.opt_str(OUT_CORPUS_FLAG).and_then(|x| x.parse::<PathBuf>().ok());
195
196 let no_out_corpus = if matches.opt_present(NO_OUT_CORPUS_FLAG) {
197 Some(())
198 } else {
199 None
200 };
201
202 let artifacts_folder: Option<PathBuf> = matches.opt_str(ARTIFACTS_FLAG).and_then(|x| x.parse::<PathBuf>().ok());
203
204 let no_artifacts = if matches.opt_present(NO_ARTIFACTS_FLAG) {
205 Some(())
206 } else {
207 None
208 };
209
210 let max_nbr_of_runs: Option<usize> = matches.opt_str(MAX_NBR_RUNS_FLAG).and_then(|x| x.parse::<usize>().ok());
211
212 let timeout: Option<usize> = matches.opt_str(TIMEOUT_FLAG).and_then(|x| x.parse::<usize>().ok());
213
214 Ok(Self {
215 command,
216 max_nbr_of_runs,
217 max_input_cplx,
218 timeout,
219 corpus_size,
220 input_file,
221 corpus_in,
222 corpus_out,
223 artifacts_folder,
224 no_in_corpus,
225 no_out_corpus,
226 no_artifacts,
227 })
228 }
229
230 pub fn resolved(&self, defaults: DefaultArguments) -> ResolvedCommandLineArguments {
231 let command = self.command;
232
233 let max_input_cplx: f64 = self.max_input_cplx.unwrap_or(defaults.max_input_cplx as f64);
234
235 let input_file: Option<PathBuf> = self.input_file.clone();
236
237 let corpus_size: usize = self.corpus_size.unwrap_or(defaults.corpus_size);
238
239 let corpus_in: Option<PathBuf> = if self.no_in_corpus.is_some() {
240 None
241 } else {
242 self.corpus_in.clone()
243 };
244
245 match (command, &input_file, &corpus_in) {
246 (FuzzerCommand::MinifyInput, &None, _) => {
247 panic!("An input file must be given when minifying a test case".to_owned())
248 }
250 (FuzzerCommand::MinifyCorpus, _, &None) => {
251 panic!("An input corpus must be given when minifying a corpus".to_owned())
252 }
253 _ => (),
254 }
255
256 let corpus_out: Option<PathBuf> = if self.no_out_corpus.is_some() {
257 None
258 } else {
259 self.corpus_out.clone()
260 };
261
262 let artifacts_folder: Option<PathBuf> = if self.no_artifacts.is_some() {
263 None
264 } else {
265 self.artifacts_folder.clone()
266 };
267
268 let max_nbr_of_runs: usize = self.max_nbr_of_runs.unwrap_or(core::usize::MAX);
269
270 let timeout: usize = self.timeout.unwrap_or(defaults.timeout);
271
272 ResolvedCommandLineArguments {
273 command,
274 max_nbr_of_runs,
275 max_input_cplx,
276 timeout,
277 corpus_size,
278 input_file,
279 corpus_in,
280 corpus_out,
281 artifacts_folder,
282 }
283 }
284}
285
286impl ResolvedCommandLineArguments {
287 pub fn from_parser(options: &Options, args: &[String]) -> Result<Self, String> {
291 Ok(CommandLineArguments::from_parser(options, args)?.resolved(DEFAULT_ARGUMENTS))
292 }
293}