use std::time::Duration; use std::time::UNIX_EPOCH; use openpgp_cert_d as cert_d; use cert_d::CertD; use cert_d::Tag; fn main() -> std::result::Result<(), Box> { let args = std::env::args().collect::>(); let certd = if args.len() == 1 { CertD::new()? } else if args.len() == 2 { CertD::with_base_dir(&args[1])? } else { eprintln!("Usage: {} [CERTD]", args[0]); return Err("Invalid arguments".into()); }; let mut size_one_count = [0; 64]; let mut secs_one_count = [0; 64]; let mut nanos_one_count = [0; 64]; let mut tag_one_count = [0; 64]; let mut count = 0; let count_ones = |one_count: &mut [usize; 64], mut value: u64| { for i in 0..64 { if value % 2 == 1 { one_count[i] += 1; } value >>= 1; } }; for item in certd.iter_files() { let Ok((fpr, file)) = item else { continue }; let m = match file.metadata() { Ok(m) => m, Err(err) => { eprintln!("Failed to stat {}: {}", fpr, err); continue; } }; let tag = match Tag::try_from(&m) { Ok(m) => m, Err(err) => { eprintln!("Failed to compute tag for {}: {}", fpr, err); continue; } }; count_ones(&mut size_one_count, m.len()); let mtime = m.modified()? .duration_since(UNIX_EPOCH) .unwrap_or_else(|_| Duration::new(0, 0)); count_ones(&mut secs_one_count, mtime.as_secs()); count_ones(&mut nanos_one_count, mtime.subsec_nanos() as u64); count_ones(&mut tag_one_count, tag.0); count += 1; } // Estimate the entropy using a Maximum Likelihood estimator // (i.e., shannon entropy). // // https://en.wikipedia.org/wiki/Entropy_estimation // https://strimmerlab.github.io/publications/lecture-notes/MATH20802/from-entropy-to-maximum-likelihood.html let mut size_entropy = 0f64; let mut secs_entropy = 0f64; let mut nanos_entropy = 0f64; let mut tag_entropy = 0f64; let mut max_entropy = 0f64; // Entropy of the probability (`p` = 0..1) of a binary event. let entropy = |p: f64| -> f64 { assert!(0. <= p); assert!(p <= 1.); // log(0) is NaN, so is 0 * log(0). But we need 0 * log(0) to // be 0. if p < 0.0001 || p > 0.9999 { 0. } else { -p * p.log2() - (1. - p) * (1. - p).log2() } }; let p = |one_count: usize| -> f64 { f64::from(one_count as u32) / f64::from(count) }; for i in 0..64 { let size_prob = p(size_one_count[i]); let secs_prob = p(secs_one_count[i]); let nanos_prob = p(nanos_one_count[i]); let tag_prob = p(tag_one_count[i]); size_entropy += entropy(size_prob); secs_entropy += entropy(secs_prob); nanos_entropy += entropy(nanos_prob); tag_entropy += entropy(tag_prob); // Sanity check. max_entropy += entropy(0.5); eprintln!("{:2}: size: {:3.0}% ({:5}); secs: {:3.0}% ({:5}); \ nanos: {:3.0}% ({:5}); tag: {:3.0}% ({:5})", i, size_prob * 100., size_one_count[i], secs_prob * 100., secs_one_count[i], nanos_prob * 100., nanos_one_count[i], tag_prob * 100., tag_one_count[i]); } eprintln!("Maximum-likelihood estimate of entropy (max: {} bits):", max_entropy); eprintln!(" size: {:.2} bits", size_entropy); eprintln!(" secs: {:.2} bits", secs_entropy); eprintln!(" nanos: {:.2} bits", nanos_entropy); eprintln!(" max empirical entropy: {:.2} bits", size_entropy + secs_entropy + nanos_entropy); eprintln!(" tag: {:.2} bits", tag_entropy); Ok(()) }