netscanner 0.6.43

Network Scanner
use color_eyre::eyre::Result;
use pnet::datalink::{self, NetworkInterface};
use ratatui::{prelude::*, widgets::*};
use std::collections::HashMap;
use std::process::{Command, Output};
use std::time::Instant;
use tokio::sync::mpsc::UnboundedSender;

use super::Component;
use crate::{
    action::Action,
    layout::{get_horizontal_layout, get_vertical_layout},
    mode::Mode,
    tui::Frame,
};

#[derive(Debug, PartialEq)]
struct WifiConn {
    interface: String,
    ifindex: u8,
    mac: String,
    ssid: String,
    channel: String,
    txpower: String,
}

struct CommandError {
    desc: String,
}

pub struct WifiInterface {
    action_tx: Option<UnboundedSender<Action>>,
    last_update: Instant,
    wifi_info: Option<WifiConn>,
}

impl Default for WifiInterface {
    fn default() -> Self {
        Self::new()
    }
}

impl WifiInterface {
    pub fn new() -> Self {
        Self {
            action_tx: None,
            last_update: Instant::now(),
            wifi_info: None,
        }
    }

    fn app_tick(&mut self) -> Result<()> {
        let now = Instant::now();
        let elapsed = (now - self.last_update).as_secs_f64();

        if self.wifi_info.is_none() || elapsed > 5.0 {
            self.last_update = now;
            self.get_connected_wifi_info();
        }
        Ok(())
    }

    fn iw_command(&mut self, intf_name: String) -> Result<Output, CommandError> {
        let iw_output = Command::new("iw")
            .arg("dev")
            .arg(intf_name)
            .arg("info")
            .output()
            .map_err(|e| CommandError {
                desc: format!("command failed: {}", e),
            })?;
        if iw_output.status.success() {
            Ok(iw_output)
        } else {
            Err(CommandError {
                desc: "command failed".to_string(),
            })
        }
    }

    fn parse_iw_command(&mut self, output: String) -> WifiConn {
        let lines = output.lines();
        let mut hash = HashMap::new();
        for l in lines {
            let split = l.trim().split(" ").collect::<Vec<&str>>();
            if split.len() > 1 {
                hash.insert(split[0], split[1].trim());
            }
        }
        WifiConn {
            interface: hash
                .get("Interface")
                .unwrap_or(&"")
                .parse::<String>()
                .unwrap_or(String::from("")),
            ssid: hash
                .get("ssid")
                .unwrap_or(&"")
                .parse::<String>()
                .unwrap_or(String::from("")),
            ifindex: hash
                .get("ifindex")
                .unwrap_or(&"")
                .parse::<u8>()
                .unwrap_or(0),
            mac: hash
                .get("addr")
                .unwrap_or(&"")
                .parse::<String>()
                .unwrap_or(String::from("")),
            channel: hash
                .get("channel")
                .unwrap_or(&"")
                .parse::<String>()
                .unwrap_or(String::from("")),
            txpower: hash
                .get("txpower")
                .unwrap_or(&"")
                .parse::<String>()
                .unwrap_or(String::from("")),
        }
    }

    fn get_connected_wifi_info(&mut self) {
        let interfaces = datalink::interfaces();
        for i in interfaces {
            if let Ok(output) = self.iw_command(i.name) {
                let o = String::from_utf8(output.stdout).unwrap_or(String::from(""));
                self.wifi_info = Some(self.parse_iw_command(o));
            }
        }
    }

    fn make_list(&mut self) -> List<'_> {
        if let Some(wifi_info) = &self.wifi_info {
            let interface = &wifi_info.interface;
            let interface_label = "Interface:";
            let ssid = &wifi_info.ssid;
            let ssid_label = "SSID:";
            let ifindex = &wifi_info.ifindex;
            let ifindex_label = "Intf index:";
            let channel = &wifi_info.channel;
            let channel_label = "Channel:";
            let txpower = &wifi_info.txpower;
            let txpower_label = "TxPower:";
            let mac = &wifi_info.mac;
            let mac_label = "Mac addr:";

            let mut items: Vec<ListItem> = Vec::new();

            items.push(ListItem::new(vec![
                Line::from(vec![
                    Span::styled(
                        format!("{ssid_label:<12}"),
                        Style::default().fg(Color::White),
                    ),
                    Span::styled(format!("{ssid:<12}"), Style::default().fg(Color::Green)),
                ]),
                Line::from(vec![
                    Span::styled(
                        format!("{channel_label:<12}"),
                        Style::default().fg(Color::White),
                    ),
                    Span::styled(format!("{channel:<12}"), Style::default().fg(Color::Green)),
                ]),
            ]));

            List::new(items).block(
                Block::default()
                    .borders(Borders::TOP)
                    .title("|WiFi Interface|")
                    .border_style(Style::default().fg(Color::Rgb(100, 100, 100)))
                    .title_style(Style::default().fg(Color::Yellow))
                    .padding(Padding::new(2, 0, 0, 0))
                    .title_alignment(Alignment::Right),
            )
        } else {
            let items: Vec<ListItem> = Vec::new();
            List::new(items)
        }
    }
}

impl Component for WifiInterface {
    fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> {
        self.action_tx = Some(tx);
        Ok(())
    }

    fn as_any(&self) -> &dyn std::any::Any {
        self
    }

    fn update(&mut self, action: Action) -> Result<Option<Action>> {
        if let Action::Tick = action {
            self.app_tick()?
        }
        Ok(None)
    }

    fn draw(&mut self, f: &mut Frame<'_>, area: Rect) -> Result<()> {
        let v_layout = get_vertical_layout(area);
        let h_layout = get_horizontal_layout(area);

        let rect = Rect::new(
            h_layout.right.x + 1,
            (v_layout.top.y + v_layout.top.height) - 3,
            h_layout.right.width - 2,
            4,
        );

        let block = self.make_list();
        f.render_widget(block, rect);

        Ok(())
    }
}