//! `randtest` is an application for statistically testing pseudo-random number generators à la //! [`dieharder`](http://webhome.phy.duke.edu/~rgb/General/dieharder.php), //! [`TestU01`](http://simul.iro.umontreal.ca/testu01/tu01.html), and the [`NIST //! sts`](http://csrc.nist.gov/groups/ST/toolkit/rng/index.html). //! //! *Warning*: This is alpha software still in the pre-`0.1` release stage. //! //! This application is built on top of the [`librandtest`](https://docs.rs/librandtest) crate. //! //! The `randtest` application should still be usable by those developing a custom generator, but //! who do not know any Rust. //! //! You can ask questions about this project or talk to me directly in the `#randtest` IRC channel //! on the `irc.mozilla.org` network. //! //! # Installation //! //! Install `randtest` using the [`cargo`](http://doc.crates.io/) package manager for Rust. //! //! It is worth considering using `RUSTFLAGS="-C target-cpu=native" cargo install randtest`, //! because `rustc` will only enable certain performant native cpu extensions if explicitly told to //! do so. This especially holds true if you do not use the `blas` feature, since this resorts to //! native Rust substitutes for blas routines and the CPU extensions availability really helps. //! //! # Usage //! //! The expected usage of `randtest` is primarily through the terminal application. We'll example a //! few common use cases below, but use `randtest -h` to see a help message after installation. //! //! ## Builtin Generators //! //! Run all the tests against all the generators built into `randtest`: //! //! ```bash //! randtest --all-gen --all-tests //! ``` //! //! Run only the 'frequency' test (a short name for the Monobit Frequency Deviation test) against //! only the Isaac 32 and 64 bit generators using the `-t` and `-g` selectors: //! //! ```bash //! randtest -t frequency -g isaac -g isaac64 //! ``` //! //! ## Custom Generators //! //! You can write custom random number generators in any programming language and pass the output //! to `randtest` for testing as long as you can produce a sufficiently large file to read enough //! bytes from (not ideal) or can write to the stdout standard stream in base64 encoding //! (preferred). //! //! ### Files //! //! Run all the tests against the system non-blocking generator file `/dev/urandom` (on some Unix //! systems) using the `-f` or `--file` selectors: //! //! ```bash //! randtest --all-tests -f /dev/urandom //! ``` //! //! Try that on the *blocking* system generator file `/dev/random` on systems that support it and //! watch it immediately stall out. //! //! Your best bet to complete a run with the blocking system generator is to run only one of the //! tests and pass very small values to the `-p`/`--psamples` (e.g. 1 or 2) and `-m`/`--multiplier` //! (e.g. 0.001 or 0.005) options which modulate the sample sizes of the two-level and one-level //! internal hypothesis testing procedures respectively. Then wave your mouse about wildly to //! generate some hardware entropy!! //! //! Note that this reads the raw bytes of the file and does not expect any form of other encoding. //! //! ### Stdin //! //! Below, we use the `--stdin` flag to take in base64 encoded data from the system generator over //! the standard streams. We read from the same non-blocking system generator source file as in the //! file example above and use the common `base64` program from [GNU //! coreutils](https://www.gnu.org/software/coreutils/coreutils.html) for encoding. //! //! ```bash //! cat /dev/urandom | base64 -w 0 | randtest --stdin --all-tests //! ``` //! //! On macOS and other systems you may not have coreutils `base64` installed, you can also use //! `openssl` for encoding or any other program with a base64 encoding function: //! //! ```bash //! cat /dev/urandom | openssl base64 -A | randtest --stdin --all-tests //! ``` //! //! In both encoding examples, the flags `-w 0` and `-A` are used to prevent newlines being placed //! into our encoded data stream. We require one, long data stream without any whitespace. //! //! However, suppose you write an executable program `gen.py` in Python that writes random bits to //! stdout in base64 encoding directly. Then you can provide it as a custom generator directly with //! no intermediate encoding program: //! //! ```bash //! ./gen.py | randtest --stdin --all-tests //! ``` //! //! [`Suite`]: suite/struct.Suite.html //! [`Suite::run()`]: suite/struct.Suite.html#method.run //! [`Statistics`]: stats/enum.Statistics.html //! [`Generators`]: gen/enum.Generators.html extern crate randtest; extern crate clap; extern crate byteorder; use std::path::Path; use std::str::FromStr; use std::string::String; use std::io::{Read, Cursor}; use byteorder::{BigEndian, ReadBytesExt}; use clap::{Arg, ArgMatches, App}; use randtest::gen::rand::*; use randtest::{ Generator, Generators, Unif01Statistics, Statistics, Suite, create_report, }; const VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION"); const DESCRIPTION: Option<&'static str> = option_env!("CARGO_PKG_DESCRIPTION"); /// Pass this to Clap to validate that a cli argument is of the required type. macro_rules! type_validator { ( $type:ty, $from_str_path:path ) => ( |attempt| -> Result<(), String> { if let Err(_) = $from_str_path(&attempt) { return Err(format!(concat!("could not convert {:?} to type ", stringify!($type)), attempt)) } Ok(()) } ); } /// This function defines the policy for generating a default superseed u64 value to use /// when the user does not provide one on the command-line. fn superseed() -> u64 { let mut bits: Vec = vec![0u8; 8]; Os::new().read(&mut bits[..]).unwrap(); let mut rdr = Cursor::new(bits); rdr.read_u64::().unwrap() } fn cli() -> App<'static, 'static> { let gennames = &Generators::short_names(); let statnames = &Statistics::short_names(); let unif01statnames = &Unif01Statistics::short_names(); let mut allnames = gennames.clone(); allnames.append(&mut statnames.clone()); allnames.append(&mut unif01statnames.clone()); App::new("randtest") .version(VERSION.unwrap_or("unknown")) .about(DESCRIPTION.unwrap_or("Statistical testing for random number generators")) .after_help("See more documentation at https://docs.rs/randtest.") .arg(Arg::with_name("all-tests") .long("all-tests") .conflicts_with("tests") .help("Run all of the available tests")) .arg(Arg::with_name("all-gens") .long("all-gens") .conflicts_with("generators") .help("Use all of the available generators")) .arg(Arg::with_name("describe") .short("d") .long("describe") .takes_value(true) .possible_values(&allnames) .help("Describe a bit test, a unif01 test, or a generator by name. Will cancel any other actions.")) .arg(Arg::with_name("tests") .short("t") .long("test") .takes_value(true) .multiple(true) .help("Run selected generators against tests by name") .possible_values(&statnames)) .arg(Arg::with_name("generators") .short("g") .long("gen") .takes_value(true) .multiple(true) .conflicts_with("files") .help("Run selected tests against all selected generators by name") .possible_values(&gennames)) .arg(Arg::with_name("psamples") .short("p") .long("psamples") .takes_value(true) .default_value("1000") .help("The number of p-value samples collected per test") .validator(type_validator!(u64, u64::from_str))) .arg(Arg::with_name("multiplier") .short("m") .takes_value(true) .default_value("10.0") .help("Multiple of the minimum recommended sample size used for each test") .validator(type_validator!(f64, f64::from_str))) .arg(Arg::with_name("superseed") .short("s") .long("superseed") .takes_value(true) .help("Value converted to u64 used to superseed deterministic generation of individual test seeds. The default will seed generators with the OS provided system generator.") .validator(type_validator!(u64, u64::from_str))) .arg(Arg::with_name("stdin") .long("stdin") .multiple(false) .help("Use binary data from stdin as a generator")) .arg(Arg::with_name("significance") .short("sig") .long("significance") .takes_value(true) .default_value("0.005") .help("The pass/fail significance level for the two-level hypothesis test")) .arg(Arg::with_name("unif01") .short("u") .long("unif01") .takes_value(true) .default_value("ks") .possible_values(&unif01statnames) .help("The statistic used to test for standard uniformity of the p-values")) .arg(Arg::with_name("files") .short("f") .long("files") .multiple(true) .takes_value(true) .help("Name binary files to use as a recycling generator") .validator(|file: String| -> Result<(), String> { if !Path::new(&file).exists() { return Err(format!("data file '{}' does not exist", &file)) } Ok(()) })) } fn gather_generators(config: &ArgMatches) -> Vec { let mut generators = Vec::new(); // if stdin was selected add it as a generator if config.is_present("stdin") { generators.push(Generators::Stdin); } // if files were given, add each as a separate generator if let Some(values) = config.values_of("files") { for file in values { generators.push(Generators::from(&file)); } } // either all generators if config.is_present("all-gens") { for gen in Generators::nonspecial_generators().into_iter() { generators.push(gen.clone()); } // or the named ones } else if let Some(values) = config.values_of("generators") { for gen_name in values { generators.push(Generators::from(&gen_name)); } } generators } fn gather_statistics(config: &ArgMatches) -> Vec { let mut statistics = Vec::new(); // either all tests if config.is_present("all-tests") { for stat in Statistics::iterator() { statistics.push(stat.clone()); } // or the named ones } else if let Some(values) = config.values_of("tests") { for stat_name in values { statistics.push(Statistics::from(&stat_name)); } } statistics } fn main() { let config = cli().get_matches(); let generators = gather_generators(&config); let statistics = gather_statistics(&config); let suite = Suite::new(statistics, generators) .psamples(u64::from_str(config.value_of("psamples").unwrap()).unwrap()) .multiplier(f64::from_str(config.value_of("multiplier").unwrap()).unwrap()) .unif01(Unif01Statistics::from(config.value_of("unif01").unwrap())) .superseed({ if config.is_present("superseed") { u64::from_str(config.value_of("superseed").unwrap()).unwrap() } else { superseed() } }); println!("Suite: {:?}", suite); println!("{}", create_report(&suite.run()[..])); }