wifi-manager 0.1.4

A cross-platform Wi-Fi management library for Rust, supporting Linux and Windows.
Documentation
use std::{
    collections::HashMap,
    fmt::{Debug, Display},
    io::BufRead,
};

use log::debug;
use tokio::process::Command;

use crate::{
    BandWidth, BandWidthArg, CommandExt, Impl, WiFiResult, check_command,
    os::iw_res::{Info, PhyInfo},
};

mod iw_res;

pub struct OS;

impl Impl for OS {
    async fn check_environment() -> crate::WiFiResult {
        check_command("iw").await?;
        check_command("ifconfig").await?;
        Ok(())
    }

    async fn interface_list() -> Result<Vec<crate::Interface>, crate::WiFiError> {
        let output = Command::new("sh")
            .arg("-c")
            .arg("iw dev | grep Interface")
            .output()
            .await
            .map_err(|e| crate::WiFiError::System(format!("Failed to execute command: {}", e)))?;

        let lines = output.stdout.lines();

        // 过滤和解析出网卡名称,这里假设网卡名称位于 "Interface <name>" 后面
        let mut interfaces = Vec::new();
        for line in lines.map_while(|i| i.ok()) {
            if let Some(interface_name) = line.split_whitespace().nth(1) {
                debug!("found interface: {}", interface_name);
                interfaces.push(crate::Interface {
                    id: interface_name.into(),
                    support_mode: vec![],
                });
            }
        }
        Ok(interfaces)
    }

    async fn set_mode(id: &ID, mode: crate::Mode) -> crate::WiFiResult {
        OS::ifdown(id).await?;
        Command::new("iw")
            .arg("dev")
            .arg(id.as_ref())
            .arg("set")
            .arg("type")
            .arg(mode.cmd())
            .execute(format!("[{id}] set mode `{}`", mode.cmd()))
            .await?;

        OS::ifup(id).await?;
        Ok(())
    }

    async fn get_mode(id: &ID) -> crate::WiFiResult<crate::Mode> {
        let info = info(id).await?;
        let mode_str = info
            .mode
            .ok_or_else(|| crate::WiFiError::System(format!("无法获取接口 {} 的模式", id)))?;

        mode_str
            .as_str()
            .try_into()
            .map_err(|_| crate::WiFiError::System(format!("未知的接口模式: {}", mode_str)))
    }

    async fn set_channel(
        id: &ID,
        freq_mhz: usize,
        band_width: Option<BandWidthArg>,
    ) -> crate::WiFiResult {
        let mut cmd = Command::new("iw");
        let c = cmd
            .arg("dev")
            .arg(id.as_ref())
            .arg("set")
            .arg("channel")
            .arg(format!("{}", freq_mhz));

        if let Some(band_width) = band_width {
            c.arg(band_width.to_string());
        }

        cmd.execute(format!("set channel for interface {id}")).await
    }

    async fn ifup(id: &ID) -> crate::WiFiResult {
        Command::new("ifconfig")
            .arg(id.as_ref())
            .arg("up")
            .execute("bring up interface")
            .await
    }

    async fn ifdown(id: &ID) -> crate::WiFiResult {
        Command::new("ifconfig")
            .arg(id.as_ref())
            .arg("down")
            .execute("bring down interface")
            .await
    }

    async fn is_ifup(id: &ID) -> crate::WiFiResult<bool> {
        let output = Command::new("ifconfig")
            .arg(id.as_ref())
            .output()
            .await
            .map_err(|e| crate::WiFiError::System(format!("执行 ifconfig 失败: {}", e)))?;

        let stdout = String::from_utf8_lossy(&output.stdout);
        let stderr = String::from_utf8_lossy(&output.stderr);

        // ifconfig 不存在接口时会返回错误
        if !output.status.success() {
            // 检查是否是接口不存在错误
            if stderr.contains("does not exist") || stderr.contains("Device not found") {
                return Ok(false);
            }
            return Err(crate::WiFiError::System(format!(
                "检查接口状态失败: {}",
                stderr
            )));
        }

        // 检查输出中是否包含 "UP" 或 "RUNNING" 标志
        // 典型的 ifconfig 输出第一行包含状态标志,如: wlan0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        let first_line = stdout.lines().next().unwrap_or("");
        let is_up = first_line.contains("UP") || first_line.contains("RUNNING");

        Ok(is_up)
    }

    async fn freq_max_bandwidth(id: &ID) -> WiFiResult<HashMap<usize, BandWidth>> {
        let mut out = HashMap::new();
        let info = info(id).await?;
        let output = Command::new("iw")
            .arg("phy")
            .arg(format!("phy{}", info.wiphy))
            .arg("info")
            .output()
            .await?;

        let result = String::from_utf8_lossy(&output.stdout);
        let info = PhyInfo::parse(&result)?;
        for band in info.bands {
            let mut max_width = BandWidth::HT20;
            if band.capabilities.rx_ht40_sgi {
                max_width = BandWidth::HT40;
            }
            if band.vht_capabilities.mhz80 {
                max_width = BandWidth::MHz80;
            }
            if band.vht_capabilities.mhz160 {
                max_width = BandWidth::MHz160;
            }

            for freq in band.frequencies {
                out.insert(freq as usize, max_width);
            }
        }

        Ok(out)
    }
}

async fn info(interface: &ID) -> WiFiResult<Info> {
    let output = Command::new("iw")
        .args(["dev", interface.as_ref(), "info"])
        .output()
        .await?;

    let result = String::from_utf8_lossy(&output.stdout);

    Info::parse(&result)
}

#[derive(Clone, Hash, Eq, PartialEq)]
pub struct ID {
    ifname: String,
}
impl Display for ID {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.ifname)
    }
}

impl Debug for ID {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "ID({})", self.ifname)
    }
}

impl From<String> for ID {
    fn from(ifname: String) -> Self {
        Self { ifname }
    }
}
impl From<&str> for ID {
    fn from(value: &str) -> Self {
        Self {
            ifname: value.to_string(),
        }
    }
}
impl AsRef<str> for ID {
    fn as_ref(&self) -> &str {
        self.ifname.as_str()
    }
}