use std::fs::{read_dir, OpenOptions};
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use std::sync::{Arc, RwLock};
use std::time::Duration;
use rhexdump as rh;
use crate::coverage::*;
use crate::error::*;
use crate::utils::*;
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum LoadTestcaseAction {
New,
NewAndReset,
Keep,
KeepAndReset,
Invalid,
InvalidAndReset,
}
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
pub struct Testcase {
pub(crate) path: Option<PathBuf>,
pub(crate) seed: Option<u64>,
pub(crate) exec_time: Duration,
pub(crate) coverage: Coverage,
pub(crate) data: Vec<u8>,
}
impl Testcase {
pub fn new(seed: u64, data: &[u8]) -> Self {
Self {
path: None,
seed: Some(seed),
exec_time: Duration::new(0, 0),
coverage: Coverage::new(),
data: data.to_vec(),
}
}
pub fn from_file(filepath: impl AsRef<Path>) -> Result<Self> {
let mut testcase = OpenOptions::new().read(true).open(&filepath)?;
let mut data = vec![];
testcase.read_to_end(&mut data)?;
Ok(Self {
path: Some(filepath.as_ref().to_owned()),
seed: None,
exec_time: Duration::new(0, 0),
coverage: Coverage::new(),
data,
})
}
pub fn to_file(&self, dir: impl AsRef<Path>) -> Result<()> {
let filepath = self.filepath(dir);
let mut testcase = OpenOptions::new().create(true).write(true).open(filepath)?;
testcase.write_all(&self.data)?;
Ok(())
}
pub fn len(&self) -> usize {
self.data.len()
}
pub fn is_empty(&self) -> bool {
self.data.len() == 0
}
pub fn get_data(&self) -> &[u8] {
&self.data
}
pub fn get_data_mut(&mut self) -> &mut Vec<u8> {
&mut self.data
}
pub fn set_seed(&mut self, seed: u64) {
self.seed = Some(seed);
}
fn filepath(&self, dir: impl AsRef<Path>) -> PathBuf {
let fmt =
time::format_description::parse("[year][month][day]-[hour][minute][second]").unwrap();
let seed = self.seed.unwrap();
dir.as_ref().join(PathBuf::from(format!(
"testcase_{}_{:x}",
time::OffsetDateTime::now_utc().format(&fmt).unwrap(),
seed,
)))
}
}
impl std::default::Default for Testcase {
fn default() -> Self {
Testcase::new(0, &[])
}
}
impl std::fmt::Display for Testcase {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", rh::hexdump(&self.data))
}
}
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
pub struct CorpusInner {
pub(crate) path: PathBuf,
pub(crate) testcases: Vec<(usize, Testcase)>,
pub(crate) rand: Random,
}
impl CorpusInner {
fn new(rand: Random, path: PathBuf) -> Result<Self> {
Ok(Self {
path,
testcases: vec![],
rand,
})
}
}
#[derive(Clone, Debug)]
pub struct Corpus {
pub(crate) inner: Arc<RwLock<CorpusInner>>,
}
impl Corpus {
pub fn new(
rand: Random,
corpus_path: impl AsRef<Path>,
work_dir: impl AsRef<Path>,
load_corpus: bool,
) -> Result<Self> {
let mut inputs_path = work_dir.as_ref().to_owned();
inputs_path.push("inputs");
std::fs::create_dir_all(&inputs_path)?;
if load_corpus && corpus_path.as_ref().exists() {
for corpus_entry in read_dir(&corpus_path)? {
let corpus_entry = corpus_entry?;
let corpus_entry_path = corpus_entry.path();
if !corpus_entry_path.is_dir() {
let mut inputs_entry_path = inputs_path.clone();
inputs_entry_path.push(corpus_entry.file_name());
std::fs::copy(corpus_entry_path, inputs_entry_path)?;
}
}
}
Ok(Self {
inner: Arc::new(RwLock::new(CorpusInner::new(rand, inputs_path)?)),
})
}
pub fn load_from_dir(&mut self, max_size: usize) -> Result<()> {
let mut inner = self.inner.write().unwrap();
for entry in read_dir(&inner.path)? {
let entry = entry?;
let path = entry.path();
if !path.is_dir() {
let testcase = Testcase::from_file(path)?;
if testcase.len() > max_size {
continue;
}
inner.testcases.push((0, testcase));
}
}
Ok(())
}
pub fn add_testcase(&mut self, testcase: Testcase) -> Result<()> {
let mut inner = self.inner.write().unwrap();
testcase.to_file(&inner.path)?;
inner.testcases.push((0, testcase));
Ok(())
}
pub fn get_testcase(&mut self) -> Testcase {
let mut inner = self.inner.write().unwrap();
let corpus_len = inner.testcases.len() as u64;
if corpus_len == 0 {
return Testcase::default();
}
let idx = corpus_len - 1 - inner.rand.exp_range(0, corpus_len).unwrap();
let testcase = if let Some((count, testcase)) = inner.testcases.get_mut(idx as usize) {
*count += 1;
testcase.clone()
} else {
unreachable!();
};
inner.testcases.sort_unstable_by(|a, b| b.0.cmp(&a.0));
testcase
}
pub fn nb_entries(&self) -> usize {
self.inner.read().unwrap().testcases.len()
}
}