wifi-manager 0.1.1

A cross-platform Wi-Fi management library for Rust, supporting Linux and Windows.
Documentation
use std::num::ParseIntError;

use crate::{WiFiError, WiFiResult, utils::ParseNum};

#[derive(Default, Debug)]
pub struct Info {
    pub wiphy: usize,
    pub ifindex: usize,
}

impl Info {
    pub fn parse(s: &str) -> WiFiResult<Self> {
        let mut lines = s.trim().lines();

        let interface = lines.next().ok_or(WiFiError::new_system("no Interface"))?;

        if !interface.contains("Interface") {
            Err(WiFiError::new_system("no Interface"))?;
        }

        let mut out = Info::default();

        for line in lines {
            let parts = line.split_whitespace().collect::<Vec<_>>();
            match parts[0] {
                "wiphy" => {
                    out.wiphy = parts[1].parse_num()?;
                }
                "ifindex" => {
                    out.ifindex = parts[1].parse_num()?;
                }
                _ => {}
            }
        }

        Ok(out)
    }
}

impl From<ParseIntError> for WiFiError {
    fn from(value: ParseIntError) -> Self {
        WiFiError::new_system(format!("ParseIntError: {}", value))
    }
}

#[derive(Default, Debug, Clone)]
pub struct PhyInfo {
    pub bands: Vec<BandInfo>,
}

impl PhyInfo {
    pub fn parse(raw: &str) -> WiFiResult<Self> {
        let (_title, raws) = tab_children(raw)?;
        let mut bands = vec![];
        for raw in &raws {
            if raw.starts_with("Band ") {
                bands.push(BandInfo::parse(raw)?);
            }
        }
        Ok(Self { bands })
    }
}

#[derive(Default, Debug, Clone)]
pub struct BandInfo {
    pub capabilities: BandCapabilities,
    pub vht_capabilities: BandVHTCapabilities,
    pub frequencies: Vec<i32>,
}

impl BandInfo {
    pub fn parse(raw: &str) -> WiFiResult<Self> {
        let (_title, raws) = tab_children(raw)?;
        let mut out = Self::default();

        for raw in &raws {
            if raw.starts_with("Capabilities") {
                out.capabilities = BandCapabilities::parse(raw)?;
            }
            if raw.starts_with("VHT Capabilities") {
                out.vht_capabilities = BandVHTCapabilities::parse(raw)?;
            }
            if raw.starts_with("Frequencies") {
                let (_title, raws) = tab_children(raw)?;
                for raw in &raws {
                    let elems = raw.trim().split_ascii_whitespace().collect::<Vec<_>>();

                    out.frequencies.push(
                        elems
                            .get(1)
                            .ok_or(WiFiError::new_system("fmt err"))?
                            .parse()?,
                    );
                }
            }
        }

        Ok(out)
    }
}

#[derive(Default, Debug, Clone)]
pub struct BandCapabilities {
    pub rx_ht20_sgi: bool,
    pub rx_ht40_sgi: bool,
}

impl BandCapabilities {
    pub fn parse(raw: &str) -> WiFiResult<Self> {
        let (_title, raws) = tab_children(raw)?;
        let mut out = Self::default();

        for raw in &raws {
            if raw.contains("RX HT20 SGI") {
                out.rx_ht20_sgi = true;
            }
            if raw.contains("RX HT40 SGI") {
                out.rx_ht40_sgi = true;
            }
        }

        Ok(out)
    }
}

#[derive(Default, Debug, Clone)]
pub struct BandVHTCapabilities {
    pub mhz80: bool,
    pub mhz160: bool,
}

impl BandVHTCapabilities {
    pub fn parse(raw: &str) -> WiFiResult<Self> {
        let (_title, raws) = tab_children(raw)?;
        let mut out = Self::default();

        for raw in &raws {
            if raw.contains("80 MHz") {
                out.mhz80 = true;
            }
            if raw.contains("160/80+80 MHz") {
                out.mhz160 = true;
            }
        }

        Ok(out)
    }
}

fn tab_children(raw: &str) -> WiFiResult<(String, Vec<String>)> {
    let mut lines = raw.trim().lines();
    let mut children = vec![];

    let title = lines
        .next()
        .ok_or(WiFiError::new_system("no title"))?
        .to_string();
    let mut content = String::new();
    for l in lines {
        if l.as_bytes().first() != Some(&b'\t') {
            Err(WiFiError::new_system("child not start with tab"))?;
        }

        let l = String::from_utf8_lossy(&l.as_bytes()[1..]).to_string();

        if l.as_bytes().first() != Some(&b'\t') && !content.is_empty() {
            children.push(content.trim().to_string());
            content = String::new();
        }
        content += format!("{l}\r\n").as_str();
    }
    if !content.is_empty() {
        children.push(content);
    }

    Ok((title, children))
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_info() {
        let s = include_str!("data/info.txt");

        println!("{s}");

        let info = Info::parse(s).unwrap();

        assert_eq!(info.wiphy, 1);
        assert_eq!(info.ifindex, 5);
    }

    #[test]
    fn test_phy_info() {
        let s = include_str!("data/phy_info.txt");

        let i = PhyInfo::parse(s).unwrap();

        println!("{i:?}")
    }
}