feat: add calibrated health score and survival depth
This commit is contained in:
parent
bd56c1ec33
commit
6e177ce868
9 changed files with 1645 additions and 45 deletions
|
|
@ -30,7 +30,8 @@ use std::fs::File;
|
|||
use std::io::{self, Read};
|
||||
use std::path::Path;
|
||||
|
||||
use onod3000::Onod;
|
||||
use onod3000::calibration::MIN_BYTES;
|
||||
use onod3000::{Onod, TestResult, Verdict, TESTS};
|
||||
|
||||
fn main() -> io::Result<()> {
|
||||
let mut args = std::env::args();
|
||||
|
|
@ -38,22 +39,30 @@ fn main() -> io::Result<()> {
|
|||
|
||||
let mut input_data = Vec::new();
|
||||
let mut source = String::from("stdin");
|
||||
let mut file_path = None;
|
||||
let mut depth = true;
|
||||
|
||||
if let Some(arg) = args.next() {
|
||||
while let Some(arg) = args.next() {
|
||||
if arg == "-f" {
|
||||
if let Some(file_path) = args.next() {
|
||||
source = file_path.clone();
|
||||
let path = Path::new(&file_path);
|
||||
let mut file = File::open(path)?;
|
||||
file.read_to_end(&mut input_data)?;
|
||||
if let Some(path) = args.next() {
|
||||
file_path = Some(path);
|
||||
} else {
|
||||
eprintln!("Error: No file path provided after -f.");
|
||||
std::process::exit(1);
|
||||
}
|
||||
} else if arg == "--depth-off" {
|
||||
depth = false;
|
||||
} else {
|
||||
eprintln!("Usage: {} [-f <file_path>]", std::env::args().next().unwrap());
|
||||
eprintln!("Usage: {} [-f <file_path>] [--depth-off]", std::env::args().next().unwrap());
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(file_path) = file_path {
|
||||
source = file_path.clone();
|
||||
let path = Path::new(&file_path);
|
||||
let mut file = File::open(path)?;
|
||||
file.read_to_end(&mut input_data)?;
|
||||
} else {
|
||||
io::stdin().read_to_end(&mut input_data)?;
|
||||
}
|
||||
|
|
@ -64,60 +73,68 @@ fn main() -> io::Result<()> {
|
|||
}
|
||||
|
||||
println!("\nTesting {} bytes from {}.", input_data.len(), source);
|
||||
println!("--------------------------------------------------------");
|
||||
println!("Randomness Test Value Z-Score P-Value Pass");
|
||||
println!("--------------------------------------------------------");
|
||||
println!("----------------------------------------------------------------");
|
||||
println!("Randomness Test Value Z-Score P-Value Cal-P Pass");
|
||||
println!("----------------------------------------------------------------");
|
||||
|
||||
let mut passed_tests = 0;
|
||||
let alpha = 0.01;
|
||||
|
||||
// Create vectors to store results
|
||||
let (mut observations, mut z_scores, mut p_values) = (Vec::new(), Vec::new(), Vec::new());
|
||||
let score = Onod::score(&input_data, depth);
|
||||
|
||||
// Run each test with the appropriate handling
|
||||
let tests = [
|
||||
("Shannon", Onod::shannon(&input_data)),
|
||||
("Monobit", Onod::monobit(&input_data)),
|
||||
("ChiBit", Onod::chi_bit(&input_data)),
|
||||
("ChiByte", Onod::chi_byte(&input_data)),
|
||||
("MeanByte", Onod::mean_byte(&input_data)),
|
||||
("Compression", Onod::compression(&input_data)),
|
||||
("Kolm.-Smirnov", Onod::ks(&input_data)),
|
||||
("Pi", Onod::pi(&input_data)),
|
||||
("Shells", Onod::shells(&input_data)),
|
||||
("Gaps", Onod::gaps(&input_data)),
|
||||
("Avalanche", Onod::avalanche(&input_data)),
|
||||
("Runs", Onod::runs(&input_data)),
|
||||
("RunUps", Onod::run_ups(&input_data)),
|
||||
("Prediction", Onod::prediction(&input_data)),
|
||||
("UnCorrelation", Onod::uncorrelation(&input_data)),
|
||||
];
|
||||
// Below the calibrated size the score has no test results, so run them raw.
|
||||
let tests: Vec<TestResult> = if score.tests.is_empty() {
|
||||
TESTS
|
||||
.iter()
|
||||
.map(|&(name, key)| {
|
||||
let (stat, z, p_raw) = Onod::run(key, &input_data);
|
||||
TestResult { name, stat, z, p_raw, p_cal: None }
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
score.tests.clone()
|
||||
};
|
||||
|
||||
for (test_name, (observation, z_score, p_value)) in &tests {
|
||||
let result = if *p_value >= alpha && *observation != -1.0 {
|
||||
for test in &tests {
|
||||
// Pass on the calibrated p-value where there is one.
|
||||
let p_value = test.p_cal.unwrap_or(test.p_raw);
|
||||
let result = if p_value >= alpha && test.stat != -1.0 {
|
||||
passed_tests += 1;
|
||||
"✅"
|
||||
} else {
|
||||
if *observation == -1.0 {
|
||||
if test.stat == -1.0 {
|
||||
"SKIP"
|
||||
} else {
|
||||
"❌"
|
||||
}
|
||||
}
|
||||
};
|
||||
let p_cal = test.p_cal.map_or(String::from("-"), |p| format!("{:.4}", p));
|
||||
|
||||
println!(
|
||||
"{:<15} {:>15.3} {:>8.4} {:>7.4} {:>2}",
|
||||
test_name, observation, z_score, p_value, result
|
||||
"{:<15} {:>15.3} {:>8.4} {:>7.4} {:>7} {:>2}",
|
||||
test.name, test.stat, test.z, test.p_raw, p_cal, result
|
||||
);
|
||||
|
||||
observations.push(*observation);
|
||||
z_scores.push(*z_score);
|
||||
p_values.push(*p_value);
|
||||
}
|
||||
|
||||
println!("--------------------------------------------------------");
|
||||
println!("----------------------------------------------------------------");
|
||||
println!("{}/{} tests passed.", passed_tests, tests.len());
|
||||
println!("--------------------------------------------------------");
|
||||
println!("----------------------------------------------------------------");
|
||||
if score.verdict == Verdict::Insufficient && score.tests.is_empty() {
|
||||
println!("Score needs at least {} bytes.", MIN_BYTES);
|
||||
} else if score.verdict == Verdict::Insufficient {
|
||||
println!("Score unavailable: no calibration table compiled in.");
|
||||
} else {
|
||||
let weakest = score.weakest.map_or(String::new(), |i| {
|
||||
format!(" · weakest: {} p={:.3}", tests[i].name, tests[i].p_cal.unwrap_or(f64::NAN))
|
||||
});
|
||||
let depth = if depth {
|
||||
format!(" · Depth {:.1}{} bits", score.depth_bits, if score.depth_complete { "+" } else { "" })
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
println!("Score {:.2} ({}){}{}", score.score, score.verdict.as_str(), weakest, depth);
|
||||
}
|
||||
println!("----------------------------------------------------------------");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue