use std::fs; use std::path::{Path, PathBuf}; use std::process::{Command, Output, Stdio}; use std::sync::atomic::{AtomicU64, Ordering}; const USE_CIRCUIT_CODE: &str = concat!( "400000000000ffff000344332211", "000102030405060708090a0b0c0d0e0f", "101112131415161718191a1b1c1d1e1f" ); const UNKNOWN_PACKET: &str = "400000000000fe"; const EXIT_USAGE: i32 = 2; const EXIT_INPUT: i32 = 3; const EXIT_OUTPUT: i32 = 5; static TEMP_ID: AtomicU64 = AtomicU64::new(0); struct TestDir(PathBuf); impl TestDir { fn new(test_name: &str) -> Self { let id = TEMP_ID.fetch_add(1, Ordering::Relaxed); let path = std::env::temp_dir().join(format!( "metacrate-packet-dump-{test_name}-{}-{id}", std::process::id() )); fs::create_dir(&path).expect("create isolated test directory"); Self(path) } fn path(&self, name: &str) -> PathBuf { self.0.join(name) } } impl Drop for TestDir { fn drop(&mut self) { let _ = fs::remove_dir_all(&self.0); } } fn run(args: &[&str]) -> Output { Command::new(env!("CARGO_BIN_EXE_packet-dump")) .args(args) .stdin(Stdio::null()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .output() .expect("run packet-dump") } fn text(bytes: &[u8]) -> &str { std::str::from_utf8(bytes).expect("command output is UTF-8") } fn path_text(path: &Path) -> &str { path.to_str().expect("test path is UTF-8") } fn assert_exit(output: &Output, expected: i32) { assert_eq!( output.status.code(), Some(expected), "stdout:\n{}\nstderr:\n{}", String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr) ); } fn script(directory: &TestDir) -> PathBuf { let path = directory.path("packets.tsv"); fs::write( &path, format!( "# direction, simulator, and wire bytes\n\ incoming\tAlpha Region\t{USE_CIRCUIT_CODE}\n\ outgoing\tBeta Region\t{USE_CIRCUIT_CODE}\n\ incoming\tAlpha Region\t{UNKNOWN_PACKET}\n" ), ) .expect("write fake packet script"); path } #[test] fn help_preserves_upstream_arguments_and_documents_safe_capture_controls() { let output = run(&["--help"]); assert!(output.status.success()); let help = text(&output.stdout); for marker in [ "[FIRSTNAME]", "[LASTNAME]", "[PASSWORD]", "[SECONDS]", "--fake-script", "--direction", "--packet-type", "--raw", "--output", "--max-output-bytes", "--max-packets", "incomingsimulatorhex-bytes", ] { assert!(help.contains(marker), "help omitted {marker}:\n{help}"); } let output = run(&[]); assert_exit(&output, EXIT_USAGE); assert!(text(&output.stderr).contains("Usage: packet-dump")); } #[test] fn fake_capture_decodes_directions_types_headers_raw_bytes_and_malformed_data() { let directory = TestDir::new("capture"); let script = script(&directory); let output = run(&["--fake-script", path_text(&script), "--raw"]); assert!(output.status.success(), "{}", text(&output.stderr)); assert!(output.stderr.is_empty()); let output = text(&output.stdout); assert!(output.contains( "IN type=UseCircuitCode simulator=Alpha Region bytes=46 sequence=0 frequency=Low id=3 reliable=true resent=false zerocoded=false appended_acks=false" )); assert!(output.contains(&format!("raw={USE_CIRCUIT_CODE}"))); assert!(output.contains("OUT type=UseCircuitCode simulator=Beta Region bytes=46")); assert!(output.contains("IN malformed-or-unknown simulator=Alpha Region bytes=7")); assert!(output.contains("Capture complete; packets=3 dropped=0 active_tasks=0 open_sockets=0")); let output = run(&[ "--fake-script", path_text(&script), "--direction", "incoming", "--packet-type", "UseCircuitCode", ]); assert!(output.status.success()); let output = text(&output.stdout); assert!(output.contains("IN type=UseCircuitCode")); assert!(!output.contains("OUT type=")); assert!(!output.contains("malformed-or-unknown")); assert!(output.contains("Capture complete; packets=1")); } #[test] fn output_file_packet_and_byte_limits_are_enforced_without_unbounded_writes() { let directory = TestDir::new("limits"); let script = script(&directory); let capture = directory.path("capture.log"); let output = run(&[ "--fake-script", path_text(&script), "--max-packets", "1", "--output", path_text(&capture), ]); assert!(output.status.success(), "{}", text(&output.stderr)); assert!(output.stdout.is_empty()); let saved = fs::read_to_string(capture).expect("read capture output"); assert!(saved.contains("Packet limit reached")); assert!(saved.contains("Capture complete; packets=1")); let limited = directory.path("limited.log"); let output = run(&[ "--fake-script", path_text(&script), "--max-output-bytes", "10", "--output", path_text(&limited), ]); assert_exit(&output, EXIT_OUTPUT); assert!(text(&output.stderr).contains("output byte limit")); assert_eq!( fs::metadata(limited).expect("limited file metadata").len(), 0 ); let output = run(&["--fake-script", path_text(&script), "--max-packets", "0"]); assert_exit(&output, EXIT_USAGE); } #[test] fn invalid_scripts_filters_and_credentials_fail_without_leaking_secrets() { let directory = TestDir::new("invalid"); let invalid = directory.path("invalid.tsv"); fs::write(&invalid, "incoming\tRegion\tzz\n").expect("write invalid script"); let output = run(&["--fake-script", path_text(&invalid)]); assert_exit(&output, EXIT_INPUT); assert!(output.stdout.is_empty()); assert!(text(&output.stderr).contains("non-hex digit")); let valid = script(&directory); let output = run(&[ "--fake-script", path_text(&valid), "--packet-type", "NotAPacket", ]); assert_exit(&output, EXIT_USAGE); assert!(text(&output.stderr).contains("unknown packet type")); let password = "do-not-print-this-password"; let output = run(&[ "First", "Last", password, "10", "--fake-script", path_text(&valid), ]); assert_exit(&output, EXIT_USAGE); assert!(!text(&output.stdout).contains(password)); assert!(!text(&output.stderr).contains(password)); }