use chrono::{DateTime, Local};
use color_eyre::eyre::Result;
use ratatui::crossterm::event::KeyEvent;
use ratatui::prelude::Rect;
use serde::{Deserialize, Serialize};
use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
use crate::{
action::Action,
components::{
discovery::{self, Discovery, ScannedIp},
export::Export,
interfaces::Interfaces,
packetdump::PacketDump,
ports::{Ports, ScannedIpPorts},
tabs::Tabs,
title::Title,
wifi_chart::WifiChart,
wifi_interface::WifiInterface,
wifi_scan::WifiScan,
sniff::Sniffer,
Component,
},
config::Config,
enums::{ExportData, PacketTypeEnum, PacketsInfoTypesEnum},
mode::Mode,
tui,
};
pub struct App {
pub config: Config,
pub tick_rate: f64,
pub frame_rate: f64,
pub components: Vec<Box<dyn Component>>,
pub should_quit: bool,
pub should_suspend: bool,
pub mode: Mode,
pub last_tick_key_events: Vec<KeyEvent>,
pub action_tx: UnboundedSender<Action>,
pub action_rx: UnboundedReceiver<Action>,
pub post_exist_msg: Option<String>,
}
impl App {
pub fn new(tick_rate: f64, frame_rate: f64) -> Result<Self> {
let title = Title::new();
let interfaces = Interfaces::default();
let wifiscan = WifiScan::default();
let wifi_interface = WifiInterface::default();
let wifi_chart = WifiChart::default();
let tabs = Tabs::default();
let discovery = Discovery::default();
let packetdump = PacketDump::default();
let ports = Ports::default();
let sniff = Sniffer::default();
let export = Export::default();
let config = Config::new()?;
let mode = Mode::Normal;
let (action_tx, action_rx) = mpsc::unbounded_channel();
Ok(Self {
tick_rate: 1.0,
frame_rate: 10.0,
components: vec![
Box::new(title),
Box::new(interfaces),
Box::new(wifiscan),
Box::new(wifi_interface),
Box::new(wifi_chart),
Box::new(tabs),
Box::new(discovery),
Box::new(packetdump),
Box::new(ports),
Box::new(sniff),
Box::new(export),
],
should_quit: false,
should_suspend: false,
config,
mode,
last_tick_key_events: Vec::new(),
action_tx,
action_rx,
post_exist_msg: None,
})
}
pub async fn run(&mut self) -> Result<()> {
let action_tx = &self.action_tx;
let action_rx = &mut self.action_rx;
let mut tui = tui::Tui::new()?
.tick_rate(self.tick_rate)
.frame_rate(self.frame_rate);
tui.enter()?;
for component in self.components.iter_mut() {
component.register_action_handler(action_tx.clone())?;
}
for component in self.components.iter_mut() {
component.register_config_handler(self.config.clone())?;
}
for component in self.components.iter_mut() {
component.init(tui.size()?)?;
}
loop {
if let Some(e) = tui.next().await {
match e {
tui::Event::Quit => action_tx.send(Action::Quit)?,
tui::Event::Tick => action_tx.send(Action::Tick)?,
tui::Event::Render => action_tx.send(Action::Render)?,
tui::Event::Resize(x, y) => action_tx.send(Action::Resize(x, y))?,
tui::Event::Key(key) => {
if let Some(keymap) = self.config.keybindings.get(&self.mode) {
if let Some(action) = keymap.get(&vec![key]) {
log::info!("Got action: {action:?}");
action_tx.send(action.clone())?;
} else {
self.last_tick_key_events.push(key);
if let Some(action) = keymap.get(&self.last_tick_key_events) {
log::info!("Got action: {action:?}");
action_tx.send(action.clone())?;
}
}
};
}
_ => {}
}
for component in self.components.iter_mut() {
if let Some(action) = component.handle_events(Some(e.clone()))? {
action_tx.send(action)?;
}
}
}
while let Ok(action) = action_rx.try_recv() {
if action != Action::Tick && action != Action::Render {
log::debug!("{action:?}");
}
match action {
Action::AppModeChange(mode) => {
self.mode = mode;
}
Action::Error(ref err_msg) => {
self.post_exist_msg = Some(err_msg.to_string());
self.should_quit = true;
}
Action::Export => {
let mut scanned_ips: Vec<ScannedIp> = Vec::new();
let mut scanned_ports: Vec<ScannedIpPorts> = Vec::new();
let mut arp_packets: Vec<(DateTime<Local>, PacketsInfoTypesEnum)> = Vec::new();
let mut udp_packets = Vec::new();
let mut tcp_packets = Vec::new();
let mut icmp_packets = Vec::new();
let mut icmp6_packets = Vec::new();
for component in &self.components {
if let Some(d) = component.as_any().downcast_ref::<Discovery>() {
scanned_ips = d.get_scanned_ips().to_vec();
} else if let Some(pd) = component.as_any().downcast_ref::<PacketDump>() {
arp_packets = pd.clone_array_by_packet_type(PacketTypeEnum::Arp);
udp_packets = pd.clone_array_by_packet_type(PacketTypeEnum::Udp);
tcp_packets = pd.clone_array_by_packet_type(PacketTypeEnum::Tcp);
icmp_packets = pd.clone_array_by_packet_type(PacketTypeEnum::Icmp);
icmp6_packets = pd.clone_array_by_packet_type(PacketTypeEnum::Icmp6);
} else if let Some(p) = component.as_any().downcast_ref::<Ports>() {
scanned_ports = p.get_scanned_ports().to_vec();
}
}
action_tx
.send(Action::ExportData(ExportData {
scanned_ips,
scanned_ports,
arp_packets,
udp_packets,
tcp_packets,
icmp_packets,
icmp6_packets,
}))
.unwrap();
}
Action::Tick => {
self.last_tick_key_events.drain(..);
}
Action::Quit => self.should_quit = true,
Action::Suspend => self.should_suspend = true,
Action::Resume => self.should_suspend = false,
Action::Resize(w, h) => {
tui.resize(Rect::new(0, 0, w, h))?;
tui.draw(|f| {
for component in self.components.iter_mut() {
let r = component.draw(f, f.area());
if let Err(e) = r {
action_tx
.send(Action::Error(format!("Failed to draw: {:?}", e)))
.unwrap();
}
}
})?;
}
Action::Render => {
tui.draw(|f| {
for component in self.components.iter_mut() {
let r = component.draw(f, f.area());
if let Err(e) = r {
action_tx
.send(Action::Error(format!("Failed to draw: {:?}", e)))
.unwrap();
}
}
})?;
}
_ => {}
}
for component in self.components.iter_mut() {
if let Some(action) = component.update(action.clone())? {
action_tx.send(action)?
};
}
}
if self.should_suspend {
tui.suspend()?;
action_tx.send(Action::Resume)?;
tui = tui::Tui::new()?
.tick_rate(self.tick_rate)
.frame_rate(self.frame_rate);
tui.enter()?;
} else if self.should_quit {
tui.stop()?;
break;
}
}
tui.exit()?;
if let Some(ref s) = self.post_exist_msg {
println!("`netscanner` failed with Error:");
println!("{}", s);
}
Ok(())
}
}