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         ::CreateEventW(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 !=
104       (*mWlanLibrary
105             ->GetWlanRegisterNotificationPtr())(mWlanLibrary->GetWLANHandle(),
106                                                 WLAN_NOTIFICATION_SOURCE_ACM,
107                                                 TRUE,
108                                                 (WLAN_NOTIFICATION_CALLBACK)
109                                                     OnScanComplete,
110                                                 &cbData, NULL,
111                                                 &wlanNotifySource)) {
112     return NS_ERROR_FAILURE;
113   }
114 
115   // Go through the list of interfaces and call `WlanScan` on each
116   for (unsigned int i = 0; i < interface_list->dwNumberOfItems; ++i) {
117     if (ERROR_SUCCESS !=
118         (*mWlanLibrary->GetWlanScanPtr())(mWlanLibrary->GetWLANHandle(),
119                                           &interface_list->InterfaceInfo[i]
120                                                .InterfaceGuid,
121                                           NULL, NULL, NULL)) {
122       cbData.OnInterfaceScanComplete();
123     }
124   }
125 
126   // From the MSDN documentation:
127   //   "Wireless network drivers that meet Windows logo requirements are
128   // required to complete a WlanScan function request in 4 seconds"
129   cbData.WaitForAllInterfacesToFinishScanning(5000);
130 
131   // Unregister for the notifications. The documentation mentions that,
132   // if a callback is currently running, this will wait for the callback
133   // to complete.
134   (*mWlanLibrary->GetWlanRegisterNotificationPtr())(
135       mWlanLibrary->GetWLANHandle(), WLAN_NOTIFICATION_SOURCE_NONE, TRUE, NULL,
136       NULL, NULL, &wlanNotifySource);
137 
138   // Go through the list of interfaces and get the data for each.
139   for (uint32_t i = 0; i < interface_list->dwNumberOfItems; ++i) {
140     WLAN_BSS_LIST* bss_list;
141     if (ERROR_SUCCESS !=
142         (*mWlanLibrary
143               ->GetWlanGetNetworkBssListPtr())(mWlanLibrary->GetWLANHandle(),
144                                                &interface_list->InterfaceInfo[i]
145                                                     .InterfaceGuid,
146                                                nullptr,  // Use all SSIDs.
147                                                DOT11_BSS_TYPE_UNUSED,
148                                                false,    // bSecurityEnabled -
149                                                          // unused
150                                                nullptr,  // reserved
151                                                &bss_list)) {
152       continue;
153     }
154 
155     // This ensures we call WlanFreeMemory on bss_list
156     ScopedWLANObject scopedBssList(*mWlanLibrary, bss_list);
157 
158     // Store each discovered access point in our outparam
159     for (int j = 0; j < static_cast<int>(bss_list->dwNumberOfItems); ++j) {
160       nsWifiAccessPoint* ap = new nsWifiAccessPoint();
161       if (!ap) {
162         continue;
163       }
164 
165       const WLAN_BSS_ENTRY bss_entry = bss_list->wlanBssEntries[j];
166       ap->setMac(bss_entry.dot11Bssid);
167       ap->setSignal(bss_entry.lRssi);
168       ap->setSSID(reinterpret_cast<char const*>(bss_entry.dot11Ssid.ucSSID),
169                   bss_entry.dot11Ssid.uSSIDLength);
170 
171       accessPoints.AppendObject(ap);
172     }
173   }
174 
175   return NS_OK;
176 }
177