iwconf-win 0.1.2

A colorful, iwconfig-style WLAN CLI for Windows, backed by the native WLAN API (no netsh required).
//! The WLAN API only ever hands back a GUID and a driver description
//! ("Intel(R) Dual Band Wireless-AC 8265") — never the friendly name
//! ("Wi-Fi", "Wi-Fi 2") that Windows shows in Settings and that
//! `netsh wlan show interface` prints as `Name`. That mapping lives in
//! IP Helper's adapter list, keyed by the same GUID formatted as
//! `{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}`. This module is the only
//! bridge between the two APIs.

use std::collections::HashMap;

use windows::core::GUID;
use windows::Win32::NetworkManagement::IpHelper::{
    GetAdaptersAddresses, GET_ADAPTERS_ADDRESSES_FLAGS, IP_ADAPTER_ADDRESSES_LH,
};

const AF_UNSPEC: u32 = 0;

fn normalize_guid(s: &str) -> String {
    s.trim_matches(|c| c == '{' || c == '}').to_ascii_uppercase()
}

/// Builds a GUID-string -> friendly-name map by walking the adapter list
/// once. Best-effort: any failure just yields an empty map, and callers
/// fall back to showing the driver description instead of a name.
pub fn friendly_names() -> HashMap<String, String> {
    let mut map = HashMap::new();

    let mut size: u32 = 0;
    // First call with a null buffer to learn the required size.
    unsafe {
        let _ = GetAdaptersAddresses(
            AF_UNSPEC,
            GET_ADAPTERS_ADDRESSES_FLAGS(0),
            None,
            None,
            &mut size,
        );
    }
    if size == 0 {
        return map;
    }

    let mut buf = vec![0u8; size as usize];
    let status = unsafe {
        GetAdaptersAddresses(
            AF_UNSPEC,
            GET_ADAPTERS_ADDRESSES_FLAGS(0),
            None,
            Some(buf.as_mut_ptr() as *mut IP_ADAPTER_ADDRESSES_LH),
            &mut size,
        )
    };
    if status != 0 {
        return map;
    }

    let mut cursor = buf.as_ptr() as *const IP_ADAPTER_ADDRESSES_LH;
    while !cursor.is_null() {
        let adapter = unsafe { &*cursor };

        let name = unsafe {
            if adapter.AdapterName.0.is_null() {
                String::new()
            } else {
                adapter.AdapterName.to_string().unwrap_or_default()
            }
        };
        let friendly = unsafe {
            if adapter.FriendlyName.0.is_null() {
                String::new()
            } else {
                adapter.FriendlyName.to_string().unwrap_or_default()
            }
        };

        if !name.is_empty() && !friendly.is_empty() {
            map.insert(normalize_guid(&name), friendly);
        }
        cursor = adapter.Next;
    }

    map
}

pub fn lookup(map: &HashMap<String, String>, guid: &GUID) -> Option<String> {
    let key = normalize_guid(&format!("{guid:?}"));
    map.get(&key).cloned()
}