use std::collections::HashSet;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use super::{set_states_corpus_index, CorpusIndex, ProductScheduler, Scheduler};
use crate::error::FeroxFuzzError;
use crate::state::SharedState;
use crate::std_ext::ops::Len;
use crate::std_ext::tuple::Named;
use tracing::{error, instrument, trace};
#[allow(clippy::doc_link_with_quotes)]
#[derive(Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct UniqueProductScheduler {
current: usize,
indices: Vec<CorpusIndex>,
scheduled: HashSet<Vec<usize>>,
#[cfg_attr(feature = "serde", serde(skip))]
state: SharedState,
}
impl std::fmt::Debug for UniqueProductScheduler {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("UniqueProductScheduler")
.field("current", &self.current)
.field("indices", &self.indices)
.finish_non_exhaustive() }
}
impl Scheduler for UniqueProductScheduler {
#[instrument(skip(self), fields(%self.current, ?self.indices), level = "trace")]
fn next(&mut self) -> Result<(), FeroxFuzzError> {
let num_indices = self.indices.len();
let mut indices_copy: Vec<_> = self.indices.iter().map(CorpusIndex::current).collect();
let outermost = self.indices.last().ok_or_else(|| {
error!("scheduler has no associated CorpusIndex");
FeroxFuzzError::EmptyCorpusIndices
})?;
if self.current > 0 && outermost.should_reset(self.current) {
trace!("scheduler has run to completion");
return Err(FeroxFuzzError::IterationStopped);
}
let innermost = &mut self.indices[0];
innermost.next()?; indices_copy[0] = innermost.current();
if self.scheduled.contains(&indices_copy) {
trace!("skipped scheduled item: {:?}", indices_copy);
return Err(FeroxFuzzError::SkipScheduledItem);
}
if num_indices == 1 {
set_states_corpus_index(&self.state, innermost.name(), innermost.current())?;
let newly_inserted = self.scheduled.insert(indices_copy.clone());
if newly_inserted {
self.current += 1;
return Ok(());
}
trace!("skipped scheduled item: {:?}", indices_copy);
return Err(FeroxFuzzError::SkipScheduledItem);
}
if innermost.current() != innermost.length && self.current != 0 {
set_states_corpus_index(&self.state, innermost.name(), innermost.current())?;
let newly_inserted = self.scheduled.insert(indices_copy.clone());
if newly_inserted {
self.current += 1;
return Ok(());
}
trace!("skipped scheduled item: {:?}", indices_copy);
return Err(FeroxFuzzError::SkipScheduledItem);
}
innermost.reset();
indices_copy[0] = innermost.current();
set_states_corpus_index(&self.state, innermost.name(), innermost.current())?;
for (i, index) in self.indices.iter_mut().enumerate().skip(1) {
index.next()?;
indices_copy[i] = index.current();
if index.current() != index.length && self.current != 0 {
break;
}
index.reset();
indices_copy[i] = index.current();
}
let newly_inserted = self.scheduled.insert(indices_copy.clone());
self.indices.iter_mut().for_each(|index| {
set_states_corpus_index(&self.state, index.name(), index.current()).unwrap();
});
if newly_inserted {
self.current += 1;
Ok(())
} else {
trace!("skipped scheduled item: {:?}", indices_copy);
Err(FeroxFuzzError::SkipScheduledItem)
}
}
#[instrument(skip(self), level = "trace")]
fn reset(&mut self) {
self.current = 0;
self.scheduled.clear();
let mut total_iterations = 1;
self.indices.iter_mut().for_each(|index| {
let corpus = self.state.corpus_by_name(index.name()).unwrap();
let len = corpus.len();
total_iterations *= len;
index.update_length(len);
index.update_iterations(total_iterations);
index.reset();
set_states_corpus_index(&self.state, index.name(), 0).unwrap();
});
trace!("scheduler has been reset");
}
fn update_length(&mut self) {
let current = self.current;
let scheduled = self.scheduled.clone();
self.reset();
self.scheduled = scheduled;
self.current = current;
trace!("updated corpora length in scheduler: {:#?}", self);
}
}
impl UniqueProductScheduler {
#[inline]
#[instrument(skip(state), level = "trace")]
pub fn new<'a, I>(corpus_order: I, state: SharedState) -> Result<Self, FeroxFuzzError>
where
I: IntoIterator<Item = &'a str> + std::fmt::Debug,
{
let product_scheduler = ProductScheduler::new(corpus_order, state)?;
Ok(product_scheduler.into())
}
}
#[allow(clippy::copy_iterator)]
impl Iterator for UniqueProductScheduler {
type Item = ();
fn next(&mut self) -> Option<Self::Item> {
Scheduler::next(self).ok()
}
}
impl Named for UniqueProductScheduler {
#[allow(clippy::unnecessary_literal_bound)]
fn name(&self) -> &str {
"UniqueProductScheduler"
}
}
impl From<ProductScheduler> for UniqueProductScheduler {
fn from(scheduler: ProductScheduler) -> Self {
let indices = scheduler.indices;
let state = scheduler.state;
let current = scheduler.current;
Self {
current,
indices,
scheduled: HashSet::new(),
state,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::corpora::{RangeCorpus, Wordlist};
use crate::requests::{Request, ShouldFuzz};
#[test]
fn test_product_iterator() {
let users = Wordlist::new()
.words(["user", "admin"])
.name("users")
.build();
let ids = RangeCorpus::with_stop(5).name("ids").build().unwrap();
let state = SharedState::with_corpora([ids, users]);
let order = ["users", "ids"];
let mut scheduler = UniqueProductScheduler::new(order, state).unwrap();
let mut counter = 0;
while Scheduler::next(&mut scheduler).is_ok() {
counter += 1;
}
assert_eq!(counter, 10);
}
#[test]
fn test_product_iterator_with_single_corpus() {
let users = Wordlist::new()
.words(["user", "admin"])
.name("users")
.build();
let state = SharedState::with_corpus(users);
let order = ["users"];
let mut scheduler = UniqueProductScheduler::new(order, state).unwrap();
let mut counter = 0;
while Iterator::next(&mut scheduler).is_some() {
counter += 1;
}
assert_eq!(counter, 2);
scheduler.reset();
}
#[test]
fn test_product_iterator_with_add_to_corpus() {
let users = Wordlist::new()
.words(["user", "admin"])
.name("users")
.build();
let ids = RangeCorpus::with_stop(5).name("ids").build().unwrap();
let state = SharedState::with_corpora([ids, users]);
let order = ["users", "ids"];
let mut scheduler = UniqueProductScheduler::new(order, state.clone()).unwrap();
let mut counter = 0;
while Scheduler::next(&mut scheduler).is_ok() {
counter += 1;
}
assert_eq!(counter, 10);
let request = Request::from_url(
"http://localhost",
Some(&[ShouldFuzz::RequestBody(b"administrator")]),
)
.unwrap();
state
.add_request_fields_to_corpus("users", &request)
.unwrap();
let users_corpus = state.corpus_by_name("users").unwrap();
assert_eq!(users_corpus.len(), 3);
scheduler.reset();
counter = 0;
while Scheduler::next(&mut scheduler).is_ok() {
counter += 1;
}
assert_eq!(counter, 15);
state.add_request_fields_to_corpus("ids", &request).unwrap();
state
.add_request_fields_to_corpus("users", &request)
.unwrap();
let ids_corpus = state.corpus_by_name("ids").unwrap();
assert_eq!(users_corpus.len(), 4);
assert_eq!(ids_corpus.len(), 6);
scheduler.reset();
counter = 0;
while Scheduler::next(&mut scheduler).is_ok() {
counter += 1;
}
assert_eq!(counter, 24);
}
#[test]
fn test_product_iterator_with_add_to_corpus_complex() {
let outer = RangeCorpus::with_stop(2).name("outer").build().unwrap();
let first_middle = RangeCorpus::with_stop(1)
.name("first_middle")
.build()
.unwrap();
let second_middle = RangeCorpus::with_stop(1)
.name("second_middle")
.build()
.unwrap();
let inner = RangeCorpus::with_stop(1).name("inner").build().unwrap();
assert_eq!(outer.len(), 2);
assert_eq!(first_middle.len(), 1);
assert_eq!(second_middle.len(), 1);
assert_eq!(inner.len(), 1);
let state = SharedState::with_corpora([outer, first_middle, second_middle, inner]);
let order = ["outer", "first_middle", "second_middle", "inner"];
let mut scheduler = UniqueProductScheduler::new(order, state.clone()).unwrap();
let mut counter = 0;
while Scheduler::next(&mut scheduler).is_ok() {
counter += 1;
}
assert_eq!(counter, 2);
let request = Request::from_url(
"http://localhost/admin",
Some(&[
ShouldFuzz::RequestBody(b"administrator"),
ShouldFuzz::URLPath,
]),
)
.unwrap();
for name in order {
state.add_request_fields_to_corpus(name, &request).unwrap();
}
assert_eq!(state.corpus_by_name("outer").unwrap().len(), 4);
assert_eq!(state.corpus_by_name("first_middle").unwrap().len(), 3);
assert_eq!(state.corpus_by_name("second_middle").unwrap().len(), 3);
assert_eq!(state.corpus_by_name("inner").unwrap().len(), 3);
scheduler.reset();
counter = 0;
while Scheduler::next(&mut scheduler).is_ok() {
counter += 1;
}
assert_eq!(counter, 108);
}
}