132 lines
No EOL
4.6 KiB
Rust
132 lines
No EOL
4.6 KiB
Rust
// Copyright 2025 Tobias Raayoni Last (@randogoth)
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// ---
|
|
|
|
// 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 <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)?;
|
|
}
|
|
|
|
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<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 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(())
|
|
} |