// MIT License // Copyright (c) 2025 Tobias Raayoni Last (@randogoth) // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // --- // This project is a Rust port of the original Java implementation by Paul Uszak. // Original work is licensed under the terms specified in the respective file header. use std::fs::File; use std::io::{self, Read}; use std::path::Path; use onod3000::calibration::MIN_BYTES; use onod3000::{Onod, TestResult, Verdict, TESTS}; fn main() -> io::Result<()> { let mut args = std::env::args(); args.next(); // Skip program name let mut input_data = Vec::new(); let mut source = String::from("stdin"); let mut file_path = None; let mut depth = true; while let Some(arg) = args.next() { if arg == "-f" { 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 ] [--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)?; } if input_data.is_empty() { eprintln!("Error: No input data provided."); std::process::exit(1); } println!("\nTesting {} bytes from {}.", input_data.len(), source); println!("----------------------------------------------------------------"); println!("Randomness Test Value Z-Score P-Value Cal-P Pass"); println!("----------------------------------------------------------------"); let mut passed_tests = 0; let alpha = 0.01; let score = Onod::score(&input_data, depth); // Below the calibrated size the score has no test results, so run them raw. let tests: Vec = 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 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 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} {:>7} {:>2}", test.name, test.stat, test.z, test.p_raw, p_cal, result ); } println!("----------------------------------------------------------------"); println!("{}/{} tests passed.", passed_tests, tests.len()); 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(()) }