shill: manager: add age for geolocation objects

Adds some utility functions to serialize and deserialize TimeTicks
to the GeolocationInfo map so that we can report the age of the
geolocation objects. This allows us to pull the age in from
wpa_supplicant when we create wifi endpoints and report that as
part of the geolocation object, so that we can see if the geo
information is stale.

BUG=chromium:217554
TEST=unit tests, deploy and call D-Bus methods

Change-Id: I1a80c234fef52535664dded6d4fd8b5532630b2b
Reviewed-on: https://chromium-review.googlesource.com/846423
Commit-Ready: Eric Caruso <ejcaruso@chromium.org>
Tested-by: Eric Caruso <ejcaruso@chromium.org>
Reviewed-by: Ben Chan <benchan@chromium.org>
diff --git a/geolocation_info.cc b/geolocation_info.cc
new file mode 100644
index 0000000..66acb93
--- /dev/null
+++ b/geolocation_info.cc
@@ -0,0 +1,67 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "shill/geolocation_info.h"
+
+#include <inttypes.h>
+
+#include <base/strings/stringprintf.h>
+#include <base/strings/string_number_conversions.h>
+#include <chromeos/dbus/service_constants.h>
+
+namespace {
+
+// This key is special, because we will look for it and transform it into
+// an up-to-date age property when D-Bus calls are made asking for geolocation
+// objects. It should not be exported outside of shill.
+constexpr char kLastSeenKey[] = "lastSeen";
+
+}  // namespace
+
+namespace shill {
+
+void AddLastSeenTime(GeolocationInfo* info, const base::TimeTicks& time) {
+  if (time.is_null())
+    return;
+
+  DCHECK(info);
+  (*info)[kLastSeenKey] = base::StringPrintf(
+      "%" PRId64, (time - base::TimeTicks()).InSeconds());
+}
+
+GeolocationInfo PrepareGeolocationInfoForExport(const GeolocationInfo& info) {
+  const auto& it = info.find(kLastSeenKey);
+  if (it == info.end())
+    return info;
+
+  int64_t last_seen;
+  if (!base::StringToInt64(it->second, &last_seen)) {
+    DLOG(ERROR) << "Invalid last seen time: " << it->second;
+    return GeolocationInfo();
+  }
+
+  // Calculate the age based on the current time. We have to
+  // reconstitute last_seen into a TimeTicks so we can get a TimeDelta.
+  base::TimeDelta age = base::TimeTicks::Now() -
+      (base::TimeTicks() + base::TimeDelta::FromSeconds(last_seen));
+
+  GeolocationInfo new_info(info);
+  new_info.erase(kLastSeenKey);
+  new_info[kGeoAgeProperty] = base::StringPrintf("%" PRId64, age.InSeconds());
+  return new_info;
+}
+
+}  // namespace shill
diff --git a/geolocation_info.h b/geolocation_info.h
index 306fa16..8a88dcd 100644
--- a/geolocation_info.h
+++ b/geolocation_info.h
@@ -20,12 +20,19 @@
 #include <map>
 #include <string>
 
+#include <base/time/time.h>
+
 namespace shill {
 
 // Geolocation property field names are defined by those kGeo* constants in
 // <chromiumos>/src/platform/system_api/dbus/shill/dbus-constants.h.
 using GeolocationInfo = std::map<std::string, std::string>;
 
+// Helper functions to serialize and transform the last-seen time for a
+// geolocation object, so up-to-date age values can be returned over D-Bus.
+void AddLastSeenTime(GeolocationInfo* info, const base::TimeTicks& time);
+GeolocationInfo PrepareGeolocationInfoForExport(const GeolocationInfo& info);
+
 }  // namespace shill
 
 #endif  // SHILL_GEOLOCATION_INFO_H_
diff --git a/manager.cc b/manager.cc
index e2e6538..ef63062 100644
--- a/manager.cc
+++ b/manager.cc
@@ -20,6 +20,7 @@
 #include <time.h>
 
 #include <algorithm>
+#include <iterator>
 #include <set>
 #include <string>
 #include <vector>
@@ -2613,9 +2614,12 @@
       continue;
     }
 
+    // Insert new info objects, but ensure that the last seen field is
+    // replaced with an age field, if it exists.
     DCHECK(network_geolocation_info);
-    network_geolocation_info->insert(network_geolocation_info->end(),
-                                     device_info.begin(), device_info.end());
+    std::transform(device_info.begin(), device_info.end(),
+                   std::back_inserter(*network_geolocation_info),
+                   &PrepareGeolocationInfoForExport);
   }
 
   return geolocation_infos;
diff --git a/shill.gyp b/shill.gyp
index 69f660c..e90cfd9 100644
--- a/shill.gyp
+++ b/shill.gyp
@@ -487,6 +487,7 @@
         'external_task.cc',
         'file_io.cc',
         'file_reader.cc',
+        'geolocation_info.cc',
         'hook_table.cc',
         'http_request.cc',
         'http_url.cc',
diff --git a/supplicant/wpa_supplicant.cc b/supplicant/wpa_supplicant.cc
index 20359c3..4735779 100644
--- a/supplicant/wpa_supplicant.cc
+++ b/supplicant/wpa_supplicant.cc
@@ -25,6 +25,7 @@
 namespace shill {
 
 // static
+const char WPASupplicant::kBSSPropertyAge[] = "Age";
 const char WPASupplicant::kBSSPropertyBSSID[] = "BSSID";
 const char WPASupplicant::kBSSPropertyFrequency[] = "Frequency";
 const char WPASupplicant::kBSSPropertyIEs[] = "IEs";
diff --git a/supplicant/wpa_supplicant.h b/supplicant/wpa_supplicant.h
index cc86137..1ae7c97 100644
--- a/supplicant/wpa_supplicant.h
+++ b/supplicant/wpa_supplicant.h
@@ -25,6 +25,7 @@
 
 class WPASupplicant {
  public:
+  static const char kBSSPropertyAge[];
   static const char kBSSPropertyBSSID[];
   static const char kBSSPropertyFrequency[];
   static const char kBSSPropertyIEs[];
diff --git a/wifi/wifi.cc b/wifi/wifi.cc
index dc07c25..4929984 100644
--- a/wifi/wifi.cc
+++ b/wifi/wifi.cc
@@ -16,6 +16,7 @@
 
 #include "shill/wifi/wifi.h"
 
+#include <inttypes.h>
 #include <linux/if.h>  // Needs definitions from netinet/ether.h
 #include <netinet/ether.h>
 #include <stdio.h>
@@ -1785,7 +1786,7 @@
         StringPrintf("%d", endpoint->signal_strength());
     geoinfo[kGeoChannelProperty] = StringPrintf(
         "%d", Metrics::WiFiFrequencyToChannel(endpoint->frequency()));
-    // TODO(gauravsh): Include age field. crbug.com/217554
+    AddLastSeenTime(&geoinfo, endpoint->last_seen());
     objects.push_back(geoinfo);
   }
   return objects;
diff --git a/wifi/wifi_endpoint.cc b/wifi/wifi_endpoint.cc
index e342e87..b2b017d 100644
--- a/wifi/wifi_endpoint.cc
+++ b/wifi/wifi_endpoint.cc
@@ -62,6 +62,12 @@
   ssid_ = properties.GetUint8s(WPASupplicant::kBSSPropertySSID);
   bssid_ = properties.GetUint8s(WPASupplicant::kBSSPropertyBSSID);
   signal_strength_ = properties.GetInt16(WPASupplicant::kBSSPropertySignal);
+  if (properties.ContainsUint(WPASupplicant::kBSSPropertyAge)) {
+    last_seen_ = base::TimeTicks::Now() - base::TimeDelta::FromSeconds(
+        properties.GetUint(WPASupplicant::kBSSPropertyAge));
+  } else {
+    last_seen_ = base::TimeTicks();
+  }
   if (properties.ContainsUint16(WPASupplicant::kBSSPropertyFrequency)) {
     frequency_ = properties.GetUint16(WPASupplicant::kBSSPropertyFrequency);
   }
@@ -114,6 +120,12 @@
     should_notify = true;
   }
 
+  if (properties.ContainsUint(WPASupplicant::kBSSPropertyAge)) {
+    last_seen_ = base::TimeTicks::Now() - base::TimeDelta::FromSeconds(
+        properties.GetUint(WPASupplicant::kBSSPropertyAge));
+    should_notify = true;
+  }
+
   if (properties.ContainsString(WPASupplicant::kBSSPropertyMode)) {
     string new_mode =
         ParseMode(properties.GetString(WPASupplicant::kBSSPropertyMode));
@@ -240,6 +252,10 @@
   return signal_strength_;
 }
 
+base::TimeTicks WiFiEndpoint::last_seen() const {
+  return last_seen_;
+}
+
 uint16_t WiFiEndpoint::frequency() const {
   return frequency_;
 }
diff --git a/wifi/wifi_endpoint.h b/wifi/wifi_endpoint.h
index b89289f..fa3af63 100644
--- a/wifi/wifi_endpoint.h
+++ b/wifi/wifi_endpoint.h
@@ -24,6 +24,7 @@
 #include <vector>
 
 #include <base/memory/ref_counted.h>
+#include <base/time/time.h>
 #include <gtest/gtest_prod.h>  // for FRIEND_TEST
 
 #include "shill/event_dispatcher.h"
@@ -108,6 +109,7 @@
   const std::string& country_code() const;
   const WiFiRefPtr& device() const;
   int16_t signal_strength() const;
+  base::TimeTicks last_seen() const;
   uint16_t frequency() const;
   uint16_t physical_mode() const;
   const std::string& network_mode() const;
@@ -237,6 +239,7 @@
   std::string bssid_hex_;
   std::string country_code_;
   int16_t signal_strength_;
+  base::TimeTicks last_seen_;
   uint16_t frequency_;
   uint16_t physical_mode_;
   // network_mode_ and security_mode_ are represented as flimflam names