use rpki::bundle::{decode_ccr_compare_views, write_vap_csv, write_vrp_csv}; use rpki::ccr::decode_content_info; #[derive(Debug, Default, PartialEq, Eq)] struct Args { ccr_path: Option, vrps_out_path: Option, vaps_out_path: Option, trust_anchor: String, } fn usage() -> &'static str { "Usage: ccr_to_compare_views --ccr --vrps-out --vaps-out [--trust-anchor ]" } fn parse_args(argv: &[String]) -> Result { let mut args = Args { trust_anchor: "unknown".to_string(), ..Args::default() }; let mut i = 1usize; while i < argv.len() { match argv[i].as_str() { "--ccr" => { i += 1; let v = argv.get(i).ok_or("--ccr requires a value")?; args.ccr_path = Some(v.into()); } "--vrps-out" => { i += 1; let v = argv.get(i).ok_or("--vrps-out requires a value")?; args.vrps_out_path = Some(v.into()); } "--vaps-out" => { i += 1; let v = argv.get(i).ok_or("--vaps-out requires a value")?; args.vaps_out_path = Some(v.into()); } "--trust-anchor" => { i += 1; let v = argv.get(i).ok_or("--trust-anchor requires a value")?; args.trust_anchor = v.clone(); } "-h" | "--help" => return Err(usage().to_string()), other => return Err(format!("unknown argument: {other}\n{}", usage())), } i += 1; } if args.ccr_path.is_none() { return Err(format!("--ccr is required\n{}", usage())); } if args.vrps_out_path.is_none() { return Err(format!("--vrps-out is required\n{}", usage())); } if args.vaps_out_path.is_none() { return Err(format!("--vaps-out is required\n{}", usage())); } Ok(args) } fn main() -> Result<(), String> { let args = parse_args(&std::env::args().collect::>())?; let ccr_path = args.ccr_path.as_ref().unwrap(); let bytes = std::fs::read(ccr_path) .map_err(|e| format!("read ccr failed: {}: {e}", ccr_path.display()))?; let content_info = decode_content_info(&bytes).map_err(|e| e.to_string())?; let (vrps, vaps) = decode_ccr_compare_views(&content_info, &args.trust_anchor).map_err(|e| e.to_string())?; write_vrp_csv(args.vrps_out_path.as_ref().unwrap(), &vrps)?; write_vap_csv(args.vaps_out_path.as_ref().unwrap(), &vaps)?; println!( "{}\n{}", args.vrps_out_path.as_ref().unwrap().display(), args.vaps_out_path.as_ref().unwrap().display() ); Ok(()) } #[cfg(test)] mod tests { use super::*; #[test] fn parse_args_accepts_required_flags() { let argv = vec![ "ccr_to_compare_views".to_string(), "--ccr".to_string(), "a.ccr".to_string(), "--vrps-out".to_string(), "vrps.csv".to_string(), "--vaps-out".to_string(), "vaps.csv".to_string(), "--trust-anchor".to_string(), "apnic".to_string(), ]; let args = parse_args(&argv).expect("parse args"); assert_eq!( args.ccr_path.as_deref(), Some(std::path::Path::new("a.ccr")) ); assert_eq!( args.vrps_out_path.as_deref(), Some(std::path::Path::new("vrps.csv")) ); assert_eq!( args.vaps_out_path.as_deref(), Some(std::path::Path::new("vaps.csv")) ); assert_eq!(args.trust_anchor, "apnic"); } #[test] fn parse_args_rejects_missing_required_flags() { let argv = vec![ "ccr_to_compare_views".to_string(), "--ccr".to_string(), "a.ccr".to_string(), "--vrps-out".to_string(), "vrps.csv".to_string(), ]; let err = parse_args(&argv).unwrap_err(); assert!(err.contains("--vaps-out is required"), "{err}"); } }