use clap::Parser; use std::net::IpAddr; use tokio::sync::mpsc; use tokio::sync::mpsc::error::TryRecvError; use tokio::time::sleep; mod args; mod ip; mod pinger; mod print; mod state; mod stats; use args::Args; use ip::parse_ip; use pinger::{spawn_ping, PingResult}; use print::{print_legende_line, print_ping_line, print_stats}; use state::State; use stats::compute_stats; #[tokio::main] async fn main() { let args = Args::parse(); let target: IpAddr = if let Some(target_str) = args.target.clone() { parse_ip(&target_str).unwrap_or_else(|e| { eprintln!("Error: {}", e); std::process::exit(1); }) } else { eprintln!("Error: target address is required"); std::process::exit(1); }; let (tx, mut rx) = mpsc::channel::(10); let mut states: Vec = Vec::new(); let mut packet_id: u32 = 0; print_legende_line(); // Save the current cursor position if !std::process::Command::new("tput").arg("sc").status().map(|s| s.success()).unwrap_or(false) { println!("Process failed"); std::process::exit(1); } // Should be quit with Ctrl + C loop { let current_time = State::current_time_ms(); for state in states.iter_mut() { if let State::NotYetReceived { time_sent } = state { // TODO: 2000ms Should be a configurable variable if current_time - *time_sent > 2000 { // 2s = 2000ms *state = State::Lost; } } } // Send the ping states.push(State::NotYetReceived { time_sent: current_time }); spawn_ping(target, tx.clone(), packet_id); packet_id += 1; // The update state loop loop { match rx.try_recv() { // Update the state of the received packet Ok(result) => { match result { PingResult::Ping { rtt, ttl, id } => { if (id as usize) < states.len() { states[id as usize] = State::Received { rtt: rtt, ttl }; } } PingResult::NoPingErr { id } => { states[id as usize] = State::SendError; } } } Err(TryRecvError::Disconnected) => { eprintln!("Critical error, pinger thread killed"); std::process::exit(2); } Err(TryRecvError::Empty) => { // No more ping to read break; } } } // Put the cursor back up if !std::process::Command::new("tput").arg("rc").status().map(|s| s.success()).unwrap_or(false) { println!("Process failed too"); std::process::exit(1); } // Prints lines based on the state of each ping print_ping_line(&states); // Print stats if args.stats { if let Some(stats) = compute_stats(&states) { print_stats(&stats); } // Print stats of the last 30 pings let first_index = if (states.len() as i32) - 30 > 0 { states.len() - 30 } else { 0 }; if let Some(stats) = compute_stats(&states[first_index..]) { print_stats(&stats); } } // Sleep for 1 second before the next ping // FEAT: Should be configurable via CLI sleep(tokio::time::Duration::from_millis(1000)).await; } }