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