shill: Add method for querying geolocation objects on a device.

Currently, only WiFi is implemented.

BUG=chromium-os:34844
TEST=unit test

Change-Id: I1ef485162052af3165da71fb170940f617e4f071
Reviewed-on: https://gerrit.chromium.org/gerrit/35975
Reviewed-by: Paul Stewart <pstew@chromium.org>
Commit-Ready: Gaurav Shah <gauravsh@chromium.org>
Tested-by: Gaurav Shah <gauravsh@chromium.org>
diff --git a/Makefile b/Makefile
index 5a4d579..ecabbd2 100644
--- a/Makefile
+++ b/Makefile
@@ -167,6 +167,7 @@
 	ethernet.o \
 	ethernet_service.o \
 	event_dispatcher.o \
+	geolocation_info.o \
 	glib.o \
 	glib_io_ready_handler.o \
 	glib_io_input_handler.o \
diff --git a/device.cc b/device.cc
index bfc5b49..9ad0b32 100644
--- a/device.cc
+++ b/device.cc
@@ -25,6 +25,7 @@
 #include "shill/dhcp_provider.h"
 #include "shill/error.h"
 #include "shill/event_dispatcher.h"
+#include "shill/geolocation_info.h"
 #include "shill/http_proxy.h"
 #include "shill/link_monitor.h"
 #include "shill/logging.h"
@@ -271,6 +272,10 @@
   return id;
 }
 
+vector<GeolocationInfo> Device::GetGeolocationObjects() const {
+  return vector<GeolocationInfo>();
+};
+
 string Device::GetTechnologyString(Error */*error*/) {
   return Technology::NameFromIdentifier(technology());
 }
diff --git a/device.h b/device.h
index 9cd823e..306f234 100644
--- a/device.h
+++ b/device.h
@@ -33,6 +33,7 @@
 class Endpoint;
 class Error;
 class EventDispatcher;
+class GeolocationInfo;
 class LinkMonitor;
 class Manager;
 class Metrics;
@@ -124,6 +125,11 @@
   std::string GetRpcIdentifier();
   std::string GetStorageIdentifier();
 
+  // Returns a list of Geolocation objects. Each object is multiple
+  // key-value pairs representing one entity that can be used for
+  // Geolocation.
+  virtual std::vector<GeolocationInfo> GetGeolocationObjects() const;
+
   const std::string &address() const { return hardware_address_; }
   const std::string &link_name() const { return link_name_; }
   int interface_index() const { return interface_index_; }
diff --git a/geolocation_info.cc b/geolocation_info.cc
new file mode 100644
index 0000000..c975162
--- /dev/null
+++ b/geolocation_info.cc
@@ -0,0 +1,34 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "shill/geolocation_info.h"
+
+#include <map>
+#include <string>
+
+namespace shill {
+
+using std::string;
+
+GeolocationInfo::GeolocationInfo() {
+}
+
+GeolocationInfo::~GeolocationInfo() {
+}
+
+void GeolocationInfo::AddField(const std::string &key,
+                               const std::string &value) {
+  properties_[key] = value;
+}
+
+const std::string &GeolocationInfo::GetFieldValue(
+    const std::string &key) const {
+  return properties_.find(key)->second;
+}
+
+bool GeolocationInfo::Equals(const GeolocationInfo &info) const{
+  return properties_ == info.properties_;
+}
+
+}  // namespace shill
diff --git a/geolocation_info.h b/geolocation_info.h
new file mode 100644
index 0000000..45307e0
--- /dev/null
+++ b/geolocation_info.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SHILL_GEOLOCATION_INFO_
+#define SHILL_GEOLOCATION_INFO_
+
+#include <map>
+#include <string>
+
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+namespace shill {
+
+class WiFiMainTest;
+
+// This class stores properties (key-value pairs) for a single entity
+// (e.g. a WiFi access point) that may be used for geolocation.
+class GeolocationInfo {
+ public:
+  GeolocationInfo();
+  ~GeolocationInfo();
+
+  void AddField(const std::string &key, const std::string &value);
+  const std::string &GetFieldValue(const std::string &key) const;
+ private:
+  FRIEND_TEST(WiFiMainTest, GetGeolocationObjects);
+
+  // An equality testing helper for unit tests.
+  bool Equals(const GeolocationInfo &info) const;
+
+  std::map<std::string, std::string> properties_;
+};
+
+}  // namespace shill
+
+#endif  // SHILL_GEOLOCATION_INFO_
diff --git a/metrics.h b/metrics.h
index a6c8cf8..ec40c2e 100644
--- a/metrics.h
+++ b/metrics.h
@@ -19,6 +19,7 @@
 
 namespace shill {
 
+class WiFiMainTest;
 class WiFiService;
 
 class Metrics {
@@ -321,6 +322,7 @@
   FRIEND_TEST(MetricsTest, TimeToPortal);
   FRIEND_TEST(MetricsTest, WiFiServiceChannel);
   FRIEND_TEST(MetricsTest, WiFiServicePostReady);
+  FRIEND_TEST(WiFiMainTest, GetGeolocationObjects);
 
   typedef ScopedVector<chromeos_metrics::TimerReporter> TimerReporters;
   typedef std::list<chromeos_metrics::TimerReporter *> TimerReportersList;
diff --git a/wifi.cc b/wifi.cc
index 574baa2..40a705c 100644
--- a/wifi.cc
+++ b/wifi.cc
@@ -28,6 +28,7 @@
 #include "shill/device.h"
 #include "shill/error.h"
 #include "shill/event_dispatcher.h"
+#include "shill/geolocation_info.h"
 #include "shill/key_value_store.h"
 #include "shill/ieee80211.h"
 #include "shill/link_monitor.h"
@@ -1327,6 +1328,26 @@
   }
 }
 
+vector<GeolocationInfo> WiFi::GetGeolocationObjects() const {
+  vector<GeolocationInfo> objects;
+  for (EndpointMap::const_iterator it = endpoint_by_rpcid_.begin();
+      it != endpoint_by_rpcid_.end();
+      ++it) {
+    GeolocationInfo geoinfo;
+    WiFiEndpointRefPtr endpoint = it->second;
+    geoinfo.AddField(kGeoMacAddressProperty, endpoint->bssid_string());
+    geoinfo.AddField(kGeoSignalStrengthProperty,
+                StringPrintf("%d", endpoint->signal_strength()));
+    geoinfo.AddField(
+        kGeoChannelProperty,
+        StringPrintf("%d",
+                     Metrics::WiFiFrequencyToChannel(endpoint->frequency())));
+    // TODO(gauravsh): Include age field. crosbug.com/35445
+    objects.push_back(geoinfo);
+  }
+  return objects;
+}
+
 void WiFi::HelpRegisterDerivedInt32(
     PropertyStore *store,
     const string &name,
diff --git a/wifi.h b/wifi.h
index ce22485..b883ad8 100644
--- a/wifi.h
+++ b/wifi.h
@@ -101,6 +101,7 @@
 namespace shill {
 
 class Error;
+class GeolocationInfo;
 class KeyValueStore;
 class ProxyFactory;
 class SupplicantInterfaceProxyInterface;
@@ -160,6 +161,9 @@
   // Called by Linkmonitor (overriden from Device superclass).
   virtual void OnLinkMonitorFailure();
 
+  // Overriden from Device superclass
+  virtual std::vector<GeolocationInfo> GetGeolocationObjects() const;
+
  private:
   friend class WiFiObjectTest;  // access to supplicant_*_proxy_, link_up_
   friend class WiFiTimerTest;  // kNumFastScanAttempts, kFastScanIntervalSeconds
diff --git a/wifi_unittest.cc b/wifi_unittest.cc
index 0f5d6fe..2857c1d 100644
--- a/wifi_unittest.cc
+++ b/wifi_unittest.cc
@@ -28,6 +28,7 @@
 
 #include "shill/dbus_adaptor.h"
 #include "shill/event_dispatcher.h"
+#include "shill/geolocation_info.h"
 #include "shill/ieee80211.h"
 #include "shill/key_value_store.h"
 #include "shill/logging.h"
@@ -2465,4 +2466,43 @@
   EXPECT_TRUE(file_util::PathExists(FilePath(SYSROOT).Append(path)));
 }
 
+struct BSS {
+  string bsspath;
+  string ssid;
+  string bssid;
+  int16_t signal_strength;
+  uint16 frequency;
+  const char* mode;
+};
+
+TEST_F(WiFiMainTest, GetGeolocationObjects) {
+  BSS bsses[] = {
+    {"bssid1", "ssid1", "00:00:00:00:00:00", 5, Metrics::kWiFiFrequency2412,
+     kNetworkModeInfrastructure},
+    {"bssid2", "ssid2", "01:00:00:00:00:00", 30, Metrics::kWiFiFrequency5170,
+     kNetworkModeInfrastructure},
+    // Same SSID but different BSSID is an additional geolocation object.
+    {"bssid3", "ssid1", "02:00:00:00:00:00", 100, 0,
+     kNetworkModeInfrastructure}
+  };
+  StartWiFi();
+  vector<GeolocationInfo> objects;
+  EXPECT_EQ(objects.size(), 0);
+
+  for (size_t i = 0; i < arraysize(bsses); ++i) {
+    ReportBSS(bsses[i].bsspath, bsses[i].ssid, bsses[i].bssid,
+              bsses[i].signal_strength, bsses[i].frequency, bsses[i].mode);
+    objects = wifi()->GetGeolocationObjects();
+    EXPECT_EQ(objects.size(), i + 1);
+
+    GeolocationInfo expected_info;
+    expected_info.AddField(kGeoMacAddressProperty, bsses[i].bssid);
+    expected_info.AddField(kGeoSignalStrengthProperty,
+                           StringPrintf("%d", bsses[i].signal_strength));
+    expected_info.AddField(kGeoChannelProperty, StringPrintf(
+        "%d", Metrics::WiFiFrequencyToChannel(bsses[i].frequency)));
+    EXPECT_TRUE(objects[i].Equals(expected_info));
+  };
+};
+
 }  // namespace shill