use hashbrown::HashMap;
use crate::{
BirthdayConfig, BirthdayError, BirthdayResult, BirthdaySuite, ChiSqConfig, ChiSqError,
ChiSqResult, ChiSqSuite, KsConfig, KsError, KsResult, KsSuite, McPiConfig, McPiError,
McPiResult, McPiSuite, NistConfig, NistError, NistResult, NistSuite, RunsConfig, RunsError,
RunsResult, RunsSuite, SerialConfig, SerialError, SerialResult, SerialSuite,
};
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SuiteResult {
pub chisq: ChiSqResult,
pub mcpi: McPiResult,
pub serial: SerialResult,
pub runs: RunsResult,
pub ks: KsResult,
pub birthday: BirthdayResult,
pub nist: NistResult,
}
pub type SuiteResults = HashMap<String, SuiteResult>;
#[derive(Debug, thiserror::Error)]
pub enum SuiteError {
#[error("chisq: {0}")]
ChiSq(#[from] ChiSqError),
#[error("mcpi: {0}")]
McPi(#[from] McPiError),
#[error("serial: {0}")]
Serial(#[from] SerialError),
#[error("runs: {0}")]
Runs(#[from] RunsError),
#[error("ks: {0}")]
Ks(#[from] KsError),
#[error("birthday: {0}")]
Birthday(#[from] BirthdayError),
#[error("nist: {0}")]
Nist(#[from] NistError),
#[error("{0}")]
Other(&'static str),
}
#[derive(Default)]
pub struct Suite<'a> {
chisq: ChiSqSuite<'a>,
mcpi: McPiSuite<'a>,
serial: SerialSuite<'a>,
runs: RunsSuite<'a>,
ks: KsSuite<'a>,
birthday: BirthdaySuite<'a>,
nist: NistSuite<'a>,
}
macro_rules! config {
($suite:ty, $seed:expr, $conf:expr) => {
if let Some(config) = $conf {
<$suite>::with_config($seed, config)?
} else {
<$suite>::new($seed)
}
};
}
impl<'a> Suite<'a> {
pub fn new(mut seed: u64) -> Self {
seed |= 1;
Self {
chisq: ChiSqSuite::new(seed),
mcpi: McPiSuite::new(seed),
serial: SerialSuite::new(seed),
runs: RunsSuite::new(seed),
ks: KsSuite::new(seed),
birthday: BirthdaySuite::new(seed),
nist: NistSuite::new(seed),
}
}
#[allow(clippy::too_many_arguments)]
pub fn with_config(
seed: u64,
chisq: Option<ChiSqConfig>,
mcpi: Option<McPiConfig>,
serial: Option<SerialConfig>,
runs: Option<RunsConfig>,
ks: Option<KsConfig>,
birthday: Option<BirthdayConfig>,
nist: Option<NistConfig>,
) -> Result<Self, SuiteError> {
Ok(Self {
chisq: config!(ChiSqSuite, seed, chisq),
mcpi: config!(McPiSuite, seed, mcpi),
serial: config!(SerialSuite, seed, serial),
runs: config!(RunsSuite, seed, runs),
ks: config!(KsSuite, seed, ks),
birthday: config!(BirthdaySuite, seed, birthday),
nist: config!(NistSuite, seed, nist),
})
}
#[cfg(feature = "urng")]
pub fn from_urng32<R>(mut self) -> Result<Self, SuiteError>
where
R: crate::source::UrngSeed32 + 'a,
{
self.chisq = self.chisq.from_urng32::<R>()?;
self.mcpi = self.mcpi.from_urng32::<R>()?;
self.serial = self.serial.from_urng32::<R>()?;
self.runs = self.runs.from_urng32::<R>()?;
self.ks = self.ks.from_urng32::<R>()?;
self.birthday = self.birthday.from_urng32::<R>()?;
self.nist = self.nist.from_urng32::<R>()?;
Ok(self)
}
#[cfg(feature = "urng")]
pub fn from_urng64<R>(mut self) -> Result<Self, SuiteError>
where
R: crate::source::UrngSeed64 + 'a,
{
self.chisq = self.chisq.from_urng64::<R>()?;
self.mcpi = self.mcpi.from_urng64::<R>()?;
self.serial = self.serial.from_urng64::<R>()?;
self.runs = self.runs.from_urng64::<R>()?;
self.ks = self.ks.from_urng64::<R>()?;
self.birthday = self.birthday.from_urng64::<R>()?;
self.nist = self.nist.from_urng64::<R>()?;
Ok(self)
}
#[cfg(feature = "rand")]
pub fn from_rand<R>(mut self) -> Result<Self, SuiteError>
where
R: crate::source::RandSource + rand_core::SeedableRng + 'a,
{
self.chisq = self.chisq.from_rand::<R>()?;
self.mcpi = self.mcpi.from_rand::<R>()?;
self.serial = self.serial.from_rand::<R>()?;
self.runs = self.runs.from_rand::<R>()?;
self.ks = self.ks.from_rand::<R>()?;
self.birthday = self.birthday.from_rand::<R>()?;
self.nist = self.nist.from_rand::<R>()?;
Ok(self)
}
pub fn from_custom<S>(mut self, source: S) -> Result<Self, SuiteError>
where
S: Clone + Into<crate::WordSource<'a>>,
{
self.chisq = self.chisq.from_custom(source.clone())?;
self.mcpi = self.mcpi.from_custom(source.clone())?;
self.serial = self.serial.from_custom(source.clone())?;
self.runs = self.runs.from_custom(source.clone())?;
self.ks = self.ks.from_custom(source.clone())?;
self.birthday = self.birthday.from_custom(source.clone())?;
self.nist = self.nist.from_custom(source)?;
Ok(self)
}
pub fn run(&mut self) -> Result<SuiteResults, SuiteError> {
let mut chisq = self.chisq.run()?;
let mut mcpi = self.mcpi.run()?;
let mut serial = self.serial.run()?;
let mut runs = self.runs.run()?;
let mut ks = self.ks.run()?;
let mut birthday = self.birthday.run()?;
let mut nist = self.nist.run()?;
let names: Vec<_> = chisq.keys().cloned().collect();
let mut out = HashMap::with_capacity(names.len());
for name in names {
let result = SuiteResult {
chisq: chisq
.remove(&name)
.ok_or(SuiteError::Other("case registered on every engine"))?,
mcpi: mcpi
.remove(&name)
.ok_or(SuiteError::Other("case registered on every engine"))?,
serial: serial
.remove(&name)
.ok_or(SuiteError::Other("case registered on every engine"))?,
runs: runs
.remove(&name)
.ok_or(SuiteError::Other("case registered on every engine"))?,
ks: ks.remove(&name).expect("case registered on every engine"),
birthday: birthday
.remove(&name)
.ok_or(SuiteError::Other("case registered on every engine"))?,
nist: nist
.remove(&name)
.ok_or(SuiteError::Other("case registered on every engine"))?,
};
out.insert(name, result);
}
Ok(out)
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "urng")]
#[test]
fn suite_runs_every_engine_over_the_same_generator() {
use crate::Suite;
use urng::Sfc32;
let results = Suite::new(1).from_urng32::<Sfc32>().unwrap().run().unwrap();
let result = results.get("Sfc32").unwrap();
assert_eq!(result.chisq.verdict, crate::ChiSqVerdict::Pass);
assert_eq!(result.nist.verdict, crate::NistVerdict::Pass);
}
#[test]
fn suite_from_custom_registers_on_every_engine() {
use crate::{Suite, WordSource};
#[derive(Clone)]
struct MyRng(u64);
impl<'a> From<MyRng> for WordSource<'a> {
fn from(mut rng: MyRng) -> Self {
WordSource::new("MyRng", 64, move || {
rng.0 = rng.0.wrapping_mul(6364136223846793005).wrapping_add(1);
rng.0
})
}
}
let results = Suite::new(1).from_custom(MyRng(1)).unwrap().run().unwrap();
assert!(results.contains_key("MyRng"));
}
#[cfg(all(feature = "serde", feature = "urng"))]
#[test]
fn suite_results_roundtrip_through_json() {
use crate::Suite;
use urng::Sfc32;
let results = Suite::new(1).from_urng32::<Sfc32>().unwrap().run().unwrap();
let json = serde_json::to_string(&results).unwrap();
let round_tripped: super::SuiteResults = serde_json::from_str(&json).unwrap();
assert_eq!(
results["Sfc32"].chisq.verdict,
round_tripped["Sfc32"].chisq.verdict
);
}
}