- add sources.
[platform/framework/web/crosswalk.git] / src / content / browser / geolocation / wifi_data_provider_linux.cc
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 // Provides wifi scan API binding for suitable for typical linux distributions.
6 // Currently, only the NetworkManager API is used, accessed via D-Bus (in turn
7 // accessed via the GLib wrapper).
8
9 #include "content/browser/geolocation/wifi_data_provider_linux.h"
10
11 #include "base/memory/scoped_ptr.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "dbus/bus.h"
15 #include "dbus/message.h"
16 #include "dbus/object_path.h"
17 #include "dbus/object_proxy.h"
18
19 namespace content {
20 namespace {
21 // The time periods between successive polls of the wifi data.
22 const int kDefaultPollingIntervalMilliseconds = 10 * 1000;  // 10s
23 const int kNoChangePollingIntervalMilliseconds = 2 * 60 * 1000;  // 2 mins
24 const int kTwoNoChangePollingIntervalMilliseconds = 10 * 60 * 1000;  // 10 mins
25 const int kNoWifiPollingIntervalMilliseconds = 20 * 1000; // 20s
26
27 const char kNetworkManagerServiceName[] = "org.freedesktop.NetworkManager";
28 const char kNetworkManagerPath[] = "/org/freedesktop/NetworkManager";
29 const char kNetworkManagerInterface[] = "org.freedesktop.NetworkManager";
30
31 // From http://projects.gnome.org/NetworkManager/developers/spec.html
32 enum { NM_DEVICE_TYPE_WIFI = 2 };
33
34 // Wifi API binding to NetworkManager, to allow reuse of the polling behavior
35 // defined in WifiDataProviderCommon.
36 // TODO(joth): NetworkManager also allows for notification based handling,
37 // however this will require reworking of the threading code to run a GLib
38 // event loop (GMainLoop).
39 class NetworkManagerWlanApi : public WifiDataProviderCommon::WlanApiInterface {
40  public:
41   NetworkManagerWlanApi();
42   virtual ~NetworkManagerWlanApi();
43
44   // Must be called before any other interface method. Will return false if the
45   // NetworkManager session cannot be created (e.g. not present on this distro),
46   // in which case no other method may be called.
47   bool Init();
48
49   // Similar to Init() but can inject the bus object. Used for testing.
50   bool InitWithBus(dbus::Bus* bus);
51
52   // WifiDataProviderCommon::WlanApiInterface
53   //
54   // This function makes blocking D-Bus calls, but it's totally fine as
55   // the code runs in "Geolocation" thread, not the browser's UI thread.
56   virtual bool GetAccessPointData(WifiData::AccessPointDataSet* data) OVERRIDE;
57
58  private:
59   // Enumerates the list of available network adapter devices known to
60   // NetworkManager. Return true on success.
61   bool GetAdapterDeviceList(std::vector<dbus::ObjectPath>* device_paths);
62
63   // Given the NetworkManager path to a wireless adapater, dumps the wifi scan
64   // results and appends them to |data|. Returns false if a fatal error is
65   // encountered such that the data set could not be populated.
66   bool GetAccessPointsForAdapter(const dbus::ObjectPath& adapter_path,
67                                  WifiData::AccessPointDataSet* data);
68
69   // Internal method used by |GetAccessPointsForAdapter|, given a wifi access
70   // point proxy retrieves the named property and returns it. Returns NULL in
71   // a scoped_ptr if the property could not be read.
72   scoped_ptr<dbus::Response> GetAccessPointProperty(
73       dbus::ObjectProxy* proxy,
74       const std::string& property_name);
75
76   scoped_refptr<dbus::Bus> system_bus_;
77   dbus::ObjectProxy* network_manager_proxy_;
78
79   DISALLOW_COPY_AND_ASSIGN(NetworkManagerWlanApi);
80 };
81
82 // Convert a wifi frequency to the corresponding channel. Adapted from
83 // geolocaiton/wifilib.cc in googleclient (internal to google).
84 int frquency_in_khz_to_channel(int frequency_khz) {
85   if (frequency_khz >= 2412000 && frequency_khz <= 2472000)  // Channels 1-13.
86     return (frequency_khz - 2407000) / 5000;
87   if (frequency_khz == 2484000)
88     return 14;
89   if (frequency_khz > 5000000 && frequency_khz < 6000000)  // .11a bands.
90     return (frequency_khz - 5000000) / 5000;
91   // Ignore everything else.
92   return AccessPointData().channel;  // invalid channel
93 }
94
95 NetworkManagerWlanApi::NetworkManagerWlanApi()
96     : network_manager_proxy_(NULL) {
97 }
98
99 NetworkManagerWlanApi::~NetworkManagerWlanApi() {
100   // Close the connection.
101   system_bus_->ShutdownAndBlock();
102 }
103
104 bool NetworkManagerWlanApi::Init() {
105   dbus::Bus::Options options;
106   options.bus_type = dbus::Bus::SYSTEM;
107   options.connection_type = dbus::Bus::PRIVATE;
108   return InitWithBus(new dbus::Bus(options));
109 }
110
111 bool NetworkManagerWlanApi::InitWithBus(dbus::Bus* bus) {
112   system_bus_ = bus;
113   // system_bus_ will own all object proxies created from the bus.
114   network_manager_proxy_ =
115       system_bus_->GetObjectProxy(kNetworkManagerServiceName,
116                                   dbus::ObjectPath(kNetworkManagerPath));
117   // Validate the proxy object by checking we can enumerate devices.
118   std::vector<dbus::ObjectPath> adapter_paths;
119   const bool success = GetAdapterDeviceList(&adapter_paths);
120   VLOG(1) << "Init() result:  " << success;
121   return success;
122 }
123
124 bool NetworkManagerWlanApi::GetAccessPointData(
125     WifiData::AccessPointDataSet* data) {
126   std::vector<dbus::ObjectPath> device_paths;
127   if (!GetAdapterDeviceList(&device_paths)) {
128     LOG(WARNING) << "Could not enumerate access points";
129     return false;
130   }
131   int success_count = 0;
132   int fail_count = 0;
133
134   // Iterate the devices, getting APs for each wireless adapter found
135   for (size_t i = 0; i < device_paths.size(); ++i) {
136     const dbus::ObjectPath& device_path = device_paths[i];
137     VLOG(1) << "Checking device: " << device_path.value();
138
139     dbus::ObjectProxy* device_proxy =
140         system_bus_->GetObjectProxy(kNetworkManagerServiceName,
141                                     device_path);
142
143     dbus::MethodCall method_call(DBUS_INTERFACE_PROPERTIES, "Get");
144     dbus::MessageWriter builder(&method_call);
145     builder.AppendString("org.freedesktop.NetworkManager.Device");
146     builder.AppendString("DeviceType");
147     scoped_ptr<dbus::Response> response(
148         device_proxy->CallMethodAndBlock(
149             &method_call,
150             dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
151     if (!response) {
152       LOG(WARNING) << "Failed to get the device type for "
153                    << device_path.value();
154       continue;  // Check the next device.
155     }
156     dbus::MessageReader reader(response.get());
157     uint32 device_type = 0;
158     if (!reader.PopVariantOfUint32(&device_type)) {
159       LOG(WARNING) << "Unexpected response for " << device_type << ": "
160                    << response->ToString();
161       continue;  // Check the next device.
162     }
163     VLOG(1) << "Device type: " << device_type;
164
165     if (device_type == NM_DEVICE_TYPE_WIFI) {  // Found a wlan adapter
166       if (GetAccessPointsForAdapter(device_path, data))
167         ++success_count;
168       else
169         ++fail_count;
170     }
171   }
172   // At least one successfull scan overrides any other adapter reporting error.
173   return success_count || fail_count == 0;
174 }
175
176 bool NetworkManagerWlanApi::GetAdapterDeviceList(
177     std::vector<dbus::ObjectPath>* device_paths) {
178   dbus::MethodCall method_call(kNetworkManagerInterface, "GetDevices");
179   scoped_ptr<dbus::Response> response(
180       network_manager_proxy_->CallMethodAndBlock(
181           &method_call,
182           dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
183   if (!response) {
184     LOG(WARNING) << "Failed to get the device list";
185     return false;
186   }
187
188   dbus::MessageReader reader(response.get());
189   if (!reader.PopArrayOfObjectPaths(device_paths)) {
190     LOG(WARNING) << "Unexpected response: " << response->ToString();
191     return false;
192   }
193   return true;
194 }
195
196
197 bool NetworkManagerWlanApi::GetAccessPointsForAdapter(
198     const dbus::ObjectPath& adapter_path, WifiData::AccessPointDataSet* data) {
199   // Create a proxy object for this wifi adapter, and ask it to do a scan
200   // (or at least, dump its scan results).
201   dbus::ObjectProxy* device_proxy =
202       system_bus_->GetObjectProxy(kNetworkManagerServiceName,
203                                   adapter_path);
204   dbus::MethodCall method_call(
205       "org.freedesktop.NetworkManager.Device.Wireless",
206       "GetAccessPoints");
207   scoped_ptr<dbus::Response> response(
208       device_proxy->CallMethodAndBlock(
209           &method_call,
210           dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
211   if (!response) {
212     LOG(WARNING) << "Failed to get access points data for "
213                  << adapter_path.value();
214     return false;
215   }
216   dbus::MessageReader reader(response.get());
217   std::vector<dbus::ObjectPath> access_point_paths;
218   if (!reader.PopArrayOfObjectPaths(&access_point_paths)) {
219     LOG(WARNING) << "Unexpected response for " << adapter_path.value() << ": "
220                  << response->ToString();
221     return false;
222   }
223
224   VLOG(1) << "Wireless adapter " << adapter_path.value() << " found "
225           << access_point_paths.size() << " access points.";
226
227   for (size_t i = 0; i < access_point_paths.size(); ++i) {
228     const dbus::ObjectPath& access_point_path = access_point_paths[i];
229     VLOG(1) << "Checking access point: " << access_point_path.value();
230
231     dbus::ObjectProxy* access_point_proxy =
232         system_bus_->GetObjectProxy(kNetworkManagerServiceName,
233                                     access_point_path);
234
235     AccessPointData access_point_data;
236     {
237       scoped_ptr<dbus::Response> response(
238           GetAccessPointProperty(access_point_proxy, "Ssid"));
239       if (!response)
240         continue;
241       // The response should contain a variant that contains an array of bytes.
242       dbus::MessageReader reader(response.get());
243       dbus::MessageReader variant_reader(response.get());
244       if (!reader.PopVariant(&variant_reader)) {
245         LOG(WARNING) << "Unexpected response for " << access_point_path.value()
246                      << ": " << response->ToString();
247         continue;
248       }
249       uint8* ssid_bytes = NULL;
250       size_t ssid_length = 0;
251       if (!variant_reader.PopArrayOfBytes(&ssid_bytes, &ssid_length)) {
252         LOG(WARNING) << "Unexpected response for " << access_point_path.value()
253                      << ": " << response->ToString();
254         continue;
255       }
256       std::string ssid(ssid_bytes, ssid_bytes + ssid_length);
257       access_point_data.ssid = UTF8ToUTF16(ssid);
258     }
259
260     { // Read the mac address
261       scoped_ptr<dbus::Response> response(
262           GetAccessPointProperty(access_point_proxy, "HwAddress"));
263       if (!response)
264         continue;
265       dbus::MessageReader reader(response.get());
266       std::string mac;
267       if (!reader.PopVariantOfString(&mac)) {
268         LOG(WARNING) << "Unexpected response for " << access_point_path.value()
269                      << ": " << response->ToString();
270         continue;
271       }
272
273       ReplaceSubstringsAfterOffset(&mac, 0U, ":", std::string());
274       std::vector<uint8> mac_bytes;
275       if (!base::HexStringToBytes(mac, &mac_bytes) || mac_bytes.size() != 6) {
276         LOG(WARNING) << "Can't parse mac address (found " << mac_bytes.size()
277                      << " bytes) so using raw string: " << mac;
278         access_point_data.mac_address = UTF8ToUTF16(mac);
279       } else {
280         access_point_data.mac_address = MacAddressAsString16(&mac_bytes[0]);
281       }
282     }
283
284     {  // Read signal strength.
285       scoped_ptr<dbus::Response> response(
286           GetAccessPointProperty(access_point_proxy, "Strength"));
287       if (!response)
288         continue;
289       dbus::MessageReader reader(response.get());
290       uint8 strength = 0;
291       if (!reader.PopVariantOfByte(&strength)) {
292         LOG(WARNING) << "Unexpected response for " << access_point_path.value()
293                      << ": " << response->ToString();
294         continue;
295       }
296       // Convert strength as a percentage into dBs.
297       access_point_data.radio_signal_strength = -100 + strength / 2;
298     }
299
300     { // Read the channel
301       scoped_ptr<dbus::Response> response(
302           GetAccessPointProperty(access_point_proxy, "Frequency"));
303       if (!response)
304         continue;
305       dbus::MessageReader reader(response.get());
306       uint32 frequency = 0;
307       if (!reader.PopVariantOfUint32(&frequency)) {
308         LOG(WARNING) << "Unexpected response for " << access_point_path.value()
309                      << ": " << response->ToString();
310         continue;
311       }
312
313       // NetworkManager returns frequency in MHz.
314       access_point_data.channel =
315           frquency_in_khz_to_channel(frequency * 1000);
316     }
317     VLOG(1) << "Access point data of " << access_point_path.value() << ": "
318             << "SSID: " << access_point_data.ssid << ", "
319             << "MAC: " << access_point_data.mac_address << ", "
320             << "Strength: " << access_point_data.radio_signal_strength << ", "
321             << "Channel: " << access_point_data.channel;
322
323     data->insert(access_point_data);
324   }
325   return true;
326 }
327
328 scoped_ptr<dbus::Response> NetworkManagerWlanApi::GetAccessPointProperty(
329     dbus::ObjectProxy* access_point_proxy,
330     const std::string& property_name) {
331   dbus::MethodCall method_call(DBUS_INTERFACE_PROPERTIES, "Get");
332   dbus::MessageWriter builder(&method_call);
333   builder.AppendString("org.freedesktop.NetworkManager.AccessPoint");
334   builder.AppendString(property_name);
335   scoped_ptr<dbus::Response> response = access_point_proxy->CallMethodAndBlock(
336       &method_call,
337       dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
338   if (!response) {
339     LOG(WARNING) << "Failed to get property for " << property_name;
340   }
341   return response.Pass();
342 }
343
344 }  // namespace
345
346 // static
347 WifiDataProviderImplBase* WifiDataProvider::DefaultFactoryFunction() {
348   return new WifiDataProviderLinux();
349 }
350
351 WifiDataProviderLinux::WifiDataProviderLinux() {
352 }
353
354 WifiDataProviderLinux::~WifiDataProviderLinux() {
355 }
356
357 WifiDataProviderCommon::WlanApiInterface*
358 WifiDataProviderLinux::NewWlanApi() {
359   scoped_ptr<NetworkManagerWlanApi> wlan_api(new NetworkManagerWlanApi);
360   if (wlan_api->Init())
361     return wlan_api.release();
362   return NULL;
363 }
364
365 WifiPollingPolicy* WifiDataProviderLinux::NewPollingPolicy() {
366   return new GenericWifiPollingPolicy<kDefaultPollingIntervalMilliseconds,
367                                       kNoChangePollingIntervalMilliseconds,
368                                       kTwoNoChangePollingIntervalMilliseconds,
369                                       kNoWifiPollingIntervalMilliseconds>;
370 }
371
372 WifiDataProviderCommon::WlanApiInterface*
373 WifiDataProviderLinux::NewWlanApiForTesting(dbus::Bus* bus) {
374   scoped_ptr<NetworkManagerWlanApi> wlan_api(new NetworkManagerWlanApi);
375   if (wlan_api->InitWithBus(bus))
376     return wlan_api.release();
377   return NULL;
378 }
379
380 }  // namespace content