rustwindows-rs

How can I get the network list with WlanGetAvailableNetworkList using windows-rs in Rust


I am trying to get the list of all available networks using WlanGetAvailableNetworkList. When I loop over the array of networks based on dwNumberOfItems it only shows me the first network and anything beyond that gives me index out of bounds: the len is 1 but the index is 1. Here's my code:

let mut w_handle: HANDLE = HANDLE::default();
let mut current_version: u32 = 0;

if WIN32_ERROR(WlanOpenHandle(2, None, &mut current_version, &mut w_handle)) != ERROR_SUCCESS {
  return;
}

let mut wlan_interface_info_list = std::ptr::null_mut();
if WIN32_ERROR(WlanEnumInterfaces(w_handle, None, &mut wlan_interface_info_list)) != ERROR_SUCCESS {
  return;
}

for i in 0..wlan_interface_info_list.as_ref().unwrap().dwNumberOfItems {
  let interface_info = wlan_interface_info_list.as_ref().unwrap().InterfaceInfo[i as usize];
  let interface_name = interface_info.strInterfaceDescription;

  let mut available_network_list = std::mem::zeroed();
  if WIN32_ERROR(WlanGetAvailableNetworkList(w_handle, &interface_info.InterfaceGuid, 0, None, &mut available_network_list)) != ERROR_SUCCESS {
    continue;
  }

  let list = available_network_list.as_ref().unwrap();
  println!("Number of available networks on {}: {}", String::from_utf16_lossy(&interface_name), list.dwNumberOfItems);

  for j in 0..list.dwNumberOfItems {
    let network_name = (*list).Network[j as usize].strProfileName; // <- Panicked
    println!("Network name: {}", String::from_utf16_lossy(&network_name));
  }
}

WlanCloseHandle(w_handle, None);

Solution

  • The type behind available_network_list says that it only ever has one Network (docs):

    pub struct WLAN_AVAILABLE_NETWORK_LIST {
        pub dwNumberOfItems: u32,
        pub dwIndex: u32,
        pub Network: [WLAN_AVAILABLE_NETWORK; 1],
    }
    

    However, we know from the official documentation that it is actually intended to be a DST-like type: where dwNumberOfItems and dwIndex are followed by dwNumberOfItems number of Networks. It is designed like this because Rust can't exactly express this type properly (DSTs are possible but not when the value itself contains its size) and a one-length array member is a C++ -ism to convey the same thing (see flexible array member in C but C++ can't have zero-length arrays).

    So when you do .Network[j as usize] it is using the index operator for the array which "knows" it only has one element. To circumvent this you should make your own slice (via pointers to avoid potential UB):

    let networks_len = (*available_network_list).dwNumberOfItems;
    let networks_ptr = std::ptr::addr_of!((*available_network_list).Network);
    let networks = std::slice::from_raw_parts(
        networks_ptr.cast::<WLAN_AVAILABLE_NETWORK>(),
        networks_len as usize,
    );
    
    for network in networks {
        let network_name = str::from_utf8(&network.dot11Ssid.ucSSID).unwrap();
        println!("Network name: {network_name}");
    }
    

    I would also show the .dot11Ssid.ucSSID instead of the .strProfileName.

    You will encounter a similar error with wlan_interface_info_list if you were to have more than one WLAN interface. The same principle applies.