1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 #include "nsWifiAccessPoint.h"
6 #include "win_wifiScanner.h"
7 
8 // Moz headers (alphabetical)
9 #include "win_wlanLibrary.h"
10 
11 #define DOT11_BSS_TYPE_UNUSED static_cast<DOT11_BSS_TYPE>(0)
12 
13 class InterfaceScanCallbackData {
14  public:
InterfaceScanCallbackData(uint32_t numInterfaces)15   explicit InterfaceScanCallbackData(uint32_t numInterfaces)
16       : mCurrentlyScanningInterfaces(numInterfaces) {
17     mAllInterfacesDoneScanningEvent =
18         ::CreateEvent(nullptr,   // null security
19                       TRUE,      // manual reset event
20                       FALSE,     // initially nonsignaled
21                       nullptr);  // not named
22     MOZ_ASSERT(NULL != mAllInterfacesDoneScanningEvent);
23   }
24 
~InterfaceScanCallbackData()25   ~InterfaceScanCallbackData() {
26     ::CloseHandle(mAllInterfacesDoneScanningEvent);
27   }
28 
OnInterfaceScanComplete()29   void OnInterfaceScanComplete() {
30     uint32_t val = ::InterlockedDecrement(&mCurrentlyScanningInterfaces);
31     if (!val) {
32       ::SetEvent(mAllInterfacesDoneScanningEvent);
33     }
34   }
35 
WaitForAllInterfacesToFinishScanning(uint32_t msToWait)36   void WaitForAllInterfacesToFinishScanning(uint32_t msToWait) {
37     ::WaitForSingleObject(mAllInterfacesDoneScanningEvent, msToWait);
38   }
39 
40  private:
41   volatile uint32_t mCurrentlyScanningInterfaces;
42   HANDLE mAllInterfacesDoneScanningEvent;
43 };
44 
OnScanComplete(PWLAN_NOTIFICATION_DATA data,PVOID context)45 static void WINAPI OnScanComplete(PWLAN_NOTIFICATION_DATA data, PVOID context) {
46   if (WLAN_NOTIFICATION_SOURCE_ACM != data->NotificationSource) {
47     return;
48   }
49 
50   if (wlan_notification_acm_scan_complete != data->NotificationCode &&
51       wlan_notification_acm_scan_fail != data->NotificationCode) {
52     return;
53   }
54 
55   InterfaceScanCallbackData* cbData =
56       reinterpret_cast<InterfaceScanCallbackData*>(context);
57   cbData->OnInterfaceScanComplete();
58 }
59 
WinWifiScanner()60 WinWifiScanner::WinWifiScanner() {
61   // NOTE: We assume that, if we were unable to load the WLAN library when
62   // we initially tried, we will not be able to load it in the future.
63   // Technically, on Windows XP SP2, a user could install the redistributable
64   // and make our assumption incorrect. We opt to avoid making a bunch of
65   // spurious LoadLibrary calls in the common case rather than load the
66   // WLAN API in the edge case.
67   mWlanLibrary.reset(WinWLANLibrary::Load());
68   if (!mWlanLibrary) {
69     NS_WARNING("Could not initialize Windows Wi-Fi scanner");
70   }
71 }
72 
~WinWifiScanner()73 WinWifiScanner::~WinWifiScanner() {}
74 
GetAccessPointsFromWLAN(nsCOMArray<nsWifiAccessPoint> & accessPoints)75 nsresult WinWifiScanner::GetAccessPointsFromWLAN(
76     nsCOMArray<nsWifiAccessPoint>& accessPoints) {
77   accessPoints.Clear();
78 
79   // NOTE: We do not try to load the WLAN library if we previously failed
80   // to load it. See the note in WinWifiScanner constructor
81   if (!mWlanLibrary) {
82     return NS_ERROR_NOT_AVAILABLE;
83   }
84 
85   // Get the list of interfaces. WlanEnumInterfaces allocates interface_list.
86   WLAN_INTERFACE_INFO_LIST* interface_list = nullptr;
87   if (ERROR_SUCCESS !=
88       (*mWlanLibrary->GetWlanEnumInterfacesPtr())(mWlanLibrary->GetWLANHandle(),
89                                                   nullptr, &interface_list)) {
90     return NS_ERROR_FAILURE;
91   }
92 
93   // This ensures we call WlanFreeMemory on interface_list
94   ScopedWLANObject scopedInterfaceList(*mWlanLibrary, interface_list);
95 
96   if (!interface_list->dwNumberOfItems) {
97     return NS_OK;
98   }
99 
100   InterfaceScanCallbackData cbData(interface_list->dwNumberOfItems);
101 
102   DWORD wlanNotifySource;
103   if (ERROR_SUCCESS != (*mWlanLibrary->GetWlanRegisterNotificationPtr())(
104                            mWlanLibrary->GetWLANHandle(),
105                            WLAN_NOTIFICATION_SOURCE_ACM, TRUE,
106                            (WLAN_NOTIFICATION_CALLBACK)OnScanComplete, &cbData,
107                            NULL, &wlanNotifySource)) {
108     return NS_ERROR_FAILURE;
109   }
110 
111   // Go through the list of interfaces and call `WlanScan` on each
112   for (unsigned int i = 0; i < interface_list->dwNumberOfItems; ++i) {
113     if (ERROR_SUCCESS != (*mWlanLibrary->GetWlanScanPtr())(
114                              mWlanLibrary->GetWLANHandle(),
115                              &interface_list->InterfaceInfo[i].InterfaceGuid,
116                              NULL, NULL, NULL)) {
117       cbData.OnInterfaceScanComplete();
118     }
119   }
120 
121   // From the MSDN documentation:
122   //   "Wireless network drivers that meet Windows logo requirements are
123   // required to complete a WlanScan function request in 4 seconds"
124   cbData.WaitForAllInterfacesToFinishScanning(5000);
125 
126   // Unregister for the notifications. The documentation mentions that,
127   // if a callback is currently running, this will wait for the callback
128   // to complete.
129   (*mWlanLibrary->GetWlanRegisterNotificationPtr())(
130       mWlanLibrary->GetWLANHandle(), WLAN_NOTIFICATION_SOURCE_NONE, TRUE, NULL,
131       NULL, NULL, &wlanNotifySource);
132 
133   // Go through the list of interfaces and get the data for each.
134   for (uint32_t i = 0; i < interface_list->dwNumberOfItems; ++i) {
135     WLAN_BSS_LIST* bss_list;
136     if (ERROR_SUCCESS != (*mWlanLibrary->GetWlanGetNetworkBssListPtr())(
137                              mWlanLibrary->GetWLANHandle(),
138                              &interface_list->InterfaceInfo[i].InterfaceGuid,
139                              nullptr,  // Use all SSIDs.
140                              DOT11_BSS_TYPE_UNUSED,
141                              false,    // bSecurityEnabled - unused
142                              nullptr,  // reserved
143                              &bss_list)) {
144       continue;
145     }
146 
147     // This ensures we call WlanFreeMemory on bss_list
148     ScopedWLANObject scopedBssList(*mWlanLibrary, bss_list);
149 
150     // Store each discovered access point in our outparam
151     for (int j = 0; j < static_cast<int>(bss_list->dwNumberOfItems); ++j) {
152       nsWifiAccessPoint* ap = new nsWifiAccessPoint();
153       if (!ap) {
154         continue;
155       }
156 
157       const WLAN_BSS_ENTRY bss_entry = bss_list->wlanBssEntries[j];
158       ap->setMac(bss_entry.dot11Bssid);
159       ap->setSignal(bss_entry.lRssi);
160       ap->setSSID(reinterpret_cast<char const*>(bss_entry.dot11Ssid.ucSSID),
161                   bss_entry.dot11Ssid.uSSIDLength);
162 
163       accessPoints.AppendObject(ap);
164     }
165   }
166 
167   return NS_OK;
168 }
169