netwerk/wifi/win_wifiScanner.cpp
author Emilio Cobos Álvarez <emilio@crisal.io>
Sat, 12 Jan 2019 16:49:39 +0100
changeset 453672 d884f9b54dc622c7e499633aec2180c236ea5ed5
parent 448947 6f3709b3878117466168c40affa7bca0b60cf75b
child 460101 b9c19a2b48548963aa0b469912f82bf156a829bc
permissions -rw-r--r--
Bug 1519639 - Update cbindgen config and generated FFI header. r=jrmuizel Differential Revision: https://phabricator.services.mozilla.com/D16391

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "nsWifiAccessPoint.h"
#include "win_wifiScanner.h"

// Moz headers (alphabetical)
#include "win_wlanLibrary.h"

#define DOT11_BSS_TYPE_UNUSED static_cast<DOT11_BSS_TYPE>(0)

class InterfaceScanCallbackData {
 public:
  explicit InterfaceScanCallbackData(uint32_t numInterfaces)
      : mCurrentlyScanningInterfaces(numInterfaces) {
    mAllInterfacesDoneScanningEvent =
        ::CreateEvent(nullptr,   // null security
                      TRUE,      // manual reset event
                      FALSE,     // initially nonsignaled
                      nullptr);  // not named
    MOZ_ASSERT(NULL != mAllInterfacesDoneScanningEvent);
  }

  ~InterfaceScanCallbackData() {
    ::CloseHandle(mAllInterfacesDoneScanningEvent);
  }

  void OnInterfaceScanComplete() {
    uint32_t val = ::InterlockedDecrement(&mCurrentlyScanningInterfaces);
    if (!val) {
      ::SetEvent(mAllInterfacesDoneScanningEvent);
    }
  }

  void WaitForAllInterfacesToFinishScanning(uint32_t msToWait) {
    ::WaitForSingleObject(mAllInterfacesDoneScanningEvent, msToWait);
  }

 private:
  volatile uint32_t mCurrentlyScanningInterfaces;
  HANDLE mAllInterfacesDoneScanningEvent;
};

static void OnScanComplete(PWLAN_NOTIFICATION_DATA data, PVOID context) {
  if (WLAN_NOTIFICATION_SOURCE_ACM != data->NotificationSource) {
    return;
  }

  if (wlan_notification_acm_scan_complete != data->NotificationCode &&
      wlan_notification_acm_scan_fail != data->NotificationCode) {
    return;
  }

  InterfaceScanCallbackData *cbData =
      reinterpret_cast<InterfaceScanCallbackData *>(context);
  cbData->OnInterfaceScanComplete();
}

WinWifiScanner::WinWifiScanner() {
  // NOTE: We assume that, if we were unable to load the WLAN library when
  // we initially tried, we will not be able to load it in the future.
  // Technically, on Windows XP SP2, a user could install the redistributable
  // and make our assumption incorrect. We opt to avoid making a bunch of
  // spurious LoadLibrary calls in the common case rather than load the
  // WLAN API in the edge case.
  mWlanLibrary = WinWLANLibrary::Load();
  if (!mWlanLibrary) {
    NS_WARNING("Could not initialize Windows Wi-Fi scanner");
  }
}

WinWifiScanner::~WinWifiScanner() {}

nsresult WinWifiScanner::GetAccessPointsFromWLAN(
    nsCOMArray<nsWifiAccessPoint> &accessPoints) {
  accessPoints.Clear();

  // NOTE: We do not try to load the WLAN library if we previously failed
  // to load it. See the note in WinWifiScanner constructor
  if (!mWlanLibrary) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  // Get the list of interfaces. WlanEnumInterfaces allocates interface_list.
  WLAN_INTERFACE_INFO_LIST *interface_list = nullptr;
  if (ERROR_SUCCESS !=
      (*mWlanLibrary->GetWlanEnumInterfacesPtr())(mWlanLibrary->GetWLANHandle(),
                                                  nullptr, &interface_list)) {
    return NS_ERROR_FAILURE;
  }

  // This ensures we call WlanFreeMemory on interface_list
  ScopedWLANObject scopedInterfaceList(mWlanLibrary, interface_list);

  if (!interface_list->dwNumberOfItems) {
    return NS_OK;
  }

  InterfaceScanCallbackData cbData(interface_list->dwNumberOfItems);

  DWORD wlanNotifySource;
  if (ERROR_SUCCESS != (*mWlanLibrary->GetWlanRegisterNotificationPtr())(
                           mWlanLibrary->GetWLANHandle(),
                           WLAN_NOTIFICATION_SOURCE_ACM, TRUE,
                           (WLAN_NOTIFICATION_CALLBACK)OnScanComplete, &cbData,
                           NULL, &wlanNotifySource)) {
    return NS_ERROR_FAILURE;
  }

  // Go through the list of interfaces and call `WlanScan` on each
  for (unsigned int i = 0; i < interface_list->dwNumberOfItems; ++i) {
    if (ERROR_SUCCESS != (*mWlanLibrary->GetWlanScanPtr())(
                             mWlanLibrary->GetWLANHandle(),
                             &interface_list->InterfaceInfo[i].InterfaceGuid,
                             NULL, NULL, NULL)) {
      cbData.OnInterfaceScanComplete();
    }
  }

  // From the MSDN documentation:
  //   "Wireless network drivers that meet Windows logo requirements are
  // required to complete a WlanScan function request in 4 seconds"
  cbData.WaitForAllInterfacesToFinishScanning(5000);

  // Unregister for the notifications. The documentation mentions that,
  // if a callback is currently running, this will wait for the callback
  // to complete.
  (*mWlanLibrary->GetWlanRegisterNotificationPtr())(
      mWlanLibrary->GetWLANHandle(), WLAN_NOTIFICATION_SOURCE_NONE, TRUE, NULL,
      NULL, NULL, &wlanNotifySource);

  // Go through the list of interfaces and get the data for each.
  for (uint32_t i = 0; i < interface_list->dwNumberOfItems; ++i) {
    WLAN_BSS_LIST *bss_list;
    if (ERROR_SUCCESS != (*mWlanLibrary->GetWlanGetNetworkBssListPtr())(
                             mWlanLibrary->GetWLANHandle(),
                             &interface_list->InterfaceInfo[i].InterfaceGuid,
                             nullptr,  // Use all SSIDs.
                             DOT11_BSS_TYPE_UNUSED,
                             false,    // bSecurityEnabled - unused
                             nullptr,  // reserved
                             &bss_list)) {
      continue;
    }

    // This ensures we call WlanFreeMemory on bss_list
    ScopedWLANObject scopedBssList(mWlanLibrary, bss_list);

    // Store each discovered access point in our outparam
    for (int j = 0; j < static_cast<int>(bss_list->dwNumberOfItems); ++j) {
      nsWifiAccessPoint *ap = new nsWifiAccessPoint();
      if (!ap) {
        continue;
      }

      const WLAN_BSS_ENTRY bss_entry = bss_list->wlanBssEntries[j];
      ap->setMac(bss_entry.dot11Bssid);
      ap->setSignal(bss_entry.lRssi);
      ap->setSSID(reinterpret_cast<char const *>(bss_entry.dot11Ssid.ucSSID),
                  bss_entry.dot11Ssid.uSSIDLength);

      accessPoints.AppendObject(ap);
    }
  }

  return NS_OK;
}