Services network reporting.

Added a network ID to each services object, to identify the network
(gateway) that the service is connected to. Network ID is generated by
hashing a random salt value, together with gateway's IP and MAC address.
At the time when the device is connected to a network, report the number
of services that are connected to the same network.

BUG=chromium:359302
CQ-DEPEND=CL:194942
TEST=unittest, manual
Manual Test
1. Configure airport extreme router with two BSSes bss1 "cros airport extreme wpa2"
   and bss2 "cros aiport extreme wpa2 5GHz".
2. Connect a peppy device to bss1, then connect it to bss2.
3. Verify the ip address stays the same when connected to bss2.
4. Verify the ConnectionId for both BSSes is the same in
   "/var/cache/shill/default_profile".
5. Verify histogram for Network.Shill.ServicesOnSameNetwork
   exist in "chrome://histogram", and there is 1 entry for value 2.

Change-Id: I01c446b58be96cb0613a7d5336248838a6e865a9
Reviewed-on: https://chromium-review.googlesource.com/194857
Reviewed-by: Paul Stewart <pstew@chromium.org>
Commit-Queue: Peter Qiu <zqiu@chromium.org>
Tested-by: Peter Qiu <zqiu@chromium.org>
diff --git a/default_profile.cc b/default_profile.cc
index 16816f0..94d1c10 100644
--- a/default_profile.cc
+++ b/default_profile.cc
@@ -50,6 +50,8 @@
 // static
 const char DefaultProfile::kStoragePortalCheckInterval[] =
     "PortalCheckInterval";
+// static
+const char DefaultProfile::kStorageConnectionIdSalt[] = "ConnectionIdSalt";
 
 DefaultProfile::DefaultProfile(ControlInterface *control,
                                Metrics *metrics,
@@ -113,6 +115,11 @@
     manager_props->portal_check_interval_seconds =
         PortalDetector::kDefaultCheckIntervalSeconds;
   }
+  if (!storage()->GetInt(kStorageId, kStorageConnectionIdSalt,
+                         &manager_props->connection_id_salt)) {
+    srand(time(NULL));
+    manager_props->connection_id_salt = rand();
+  }
 }
 
 bool DefaultProfile::ConfigureService(const ServiceRefPtr &service) {
@@ -150,6 +157,8 @@
   storage()->SetString(kStorageId,
                        kStoragePortalCheckInterval,
                        base::IntToString(props_.portal_check_interval_seconds));
+  storage()->SetInt(kStorageId, kStorageConnectionIdSalt,
+                    props_.connection_id_salt);
   return Profile::Save();
 }
 
diff --git a/default_profile.h b/default_profile.h
index c7e7b79..60125fa 100644
--- a/default_profile.h
+++ b/default_profile.h
@@ -79,6 +79,7 @@
   static const char kStorageOfflineMode[];
   static const char kStoragePortalCheckInterval[];
   static const char kStoragePortalURL[];
+  static const char kStorageConnectionIdSalt[];
 
   const base::FilePath storage_path_;
   const std::string profile_id_;
diff --git a/device.cc b/device.cc
index 198a31f..f8afd4b 100644
--- a/device.cc
+++ b/device.cc
@@ -834,6 +834,17 @@
              << ": Link Monitor indicates failure.";
 }
 
+void Device::OnLinkMonitorGatewayChange() {
+  string gateway_mac = link_monitor()->gateway_mac_address().HexEncode();
+  int connection_id = manager_->CalcConnectionId(
+      ipconfig_->properties().gateway, gateway_mac);
+
+  CHECK(selected_service_);
+  selected_service_->set_connection_id(connection_id);
+
+  manager_->ReportServicesOnSameNetwork(connection_id);
+}
+
 void Device::SetServiceConnectedState(Service::ConnectState state) {
   DCHECK(selected_service_.get());
 
diff --git a/device.h b/device.h
index 1ec0d09..58509bf 100644
--- a/device.h
+++ b/device.h
@@ -425,9 +425,8 @@
   // Respond to a LinkMonitor failure in a Device-specific manner.
   virtual void OnLinkMonitorFailure();
 
-  // Respond to a LinkMonitor gateway's MAC address found/change in a
-  // Device-specific manner.
-  virtual void OnLinkMonitorGatewayChange() {}
+  // Respond to a LinkMonitor gateway's MAC address found/change event.
+  virtual void OnLinkMonitorGatewayChange();
 
   // Set the state of the selected service, with checks to make sure
   // the service is already in a connected state before doing so.
diff --git a/manager.cc b/manager.cc
index caf6112..73ef057 100644
--- a/manager.cc
+++ b/manager.cc
@@ -1252,6 +1252,22 @@
   error->Populate(Error::kNotImplemented, "Not implemented");
 }
 
+int Manager::CalcConnectionId(std::string gateway_ip,
+                              std::string gateway_mac) {
+  return  (int)(std::hash<std::string>()(gateway_ip + gateway_mac +
+      std::to_string(props_.connection_id_salt)));
+}
+
+void Manager::ReportServicesOnSameNetwork(int connection_id) {
+  int num_services = 0;
+  for (const auto &service : services_) {
+    if (service->connection_id() == connection_id) {
+      num_services++;
+    }
+  }
+  metrics_->NotifyServicesOnSameNetwork(num_services);
+}
+
 void Manager::NotifyDefaultServiceChanged(const ServiceRefPtr &service) {
   for (map<int, ServiceCallback>::const_iterator it =
            default_service_callbacks_.begin();
diff --git a/manager.h b/manager.h
index 2bc287c..2f969b1 100644
--- a/manager.h
+++ b/manager.h
@@ -57,7 +57,8 @@
     Properties()
         : offline_mode(false),
           portal_check_interval_seconds(0),
-          arp_gateway(true) {}
+          arp_gateway(true),
+          connection_id_salt(0) {}
     bool offline_mode;
     std::string check_portal_list;
     std::string country;
@@ -72,6 +73,8 @@
     std::string link_monitor_technologies;
     // Comma-separated list of DNS search paths to be ignored.
     std::string ignored_dns_search_paths;
+    // Salt value use for calculating network connection ID.
+    int connection_id_salt;
   };
 
   Manager(ControlInterface *control_interface,
@@ -381,6 +384,14 @@
                                    const ResultStringCallback &cb,
                                    Error *error);
 
+  // Calculate connection identifier, which is hash of salt value, gateway IP
+  // address, and gateway MAC address.
+  int CalcConnectionId(std::string gateway_ip, std::string gateway_mac);
+
+  // Report the number of services associated with given connection
+  // |connection_id|.
+  void ReportServicesOnSameNetwork(int connection_id);
+
  private:
   friend class CellularTest;
   friend class DeviceInfoTest;
diff --git a/manager_unittest.cc b/manager_unittest.cc
index 0561c5f..fb44c26 100644
--- a/manager_unittest.cc
+++ b/manager_unittest.cc
@@ -2627,6 +2627,33 @@
   EXPECT_TRUE(manager()->default_service_callbacks_.empty());
 }
 
+TEST_F(ManagerTest, ReportServicesOnSameNetwork) {
+  int connection_id1 = 100;
+  int connection_id2 = 200;
+  scoped_refptr<MockService> mock_service1 =
+      new NiceMock<MockService>(control_interface(), dispatcher(),
+                                metrics(), manager());
+  mock_service1->set_connection_id(connection_id1);
+  scoped_refptr<MockService> mock_service2 =
+      new NiceMock<MockService>(control_interface(), dispatcher(),
+                                metrics(), manager());
+  mock_service2->set_connection_id(connection_id1);
+  scoped_refptr<MockService> mock_service3 =
+      new NiceMock<MockService>(control_interface(), dispatcher(),
+                                metrics(), manager());
+  mock_service3->set_connection_id(connection_id2);
+
+  manager()->RegisterService(mock_service1);
+  manager()->RegisterService(mock_service2);
+  manager()->RegisterService(mock_service3);
+
+  EXPECT_CALL(*metrics(), NotifyServicesOnSameNetwork(2));
+  manager()->ReportServicesOnSameNetwork(connection_id1);
+
+  EXPECT_CALL(*metrics(), NotifyServicesOnSameNetwork(1));
+  manager()->ReportServicesOnSameNetwork(connection_id2);
+}
+
 TEST_F(ManagerTest, AvailableTechnologies) {
   mock_devices_.push_back(new NiceMock<MockDevice>(control_interface(),
                                                    dispatcher(),
diff --git a/mock_metrics.h b/mock_metrics.h
index 55995ce..6355e57 100644
--- a/mock_metrics.h
+++ b/mock_metrics.h
@@ -44,7 +44,7 @@
                                int max, int num_buckets));
   MOCK_METHOD1(NotifyWifiAutoConnectableServices, void(int num_service));
   MOCK_METHOD1(NotifyWifiAvailableBSSes, void(int num_bss));
-  MOCK_METHOD1(NotifyWifiServicesOnSameNetwork, void(int num_service));
+  MOCK_METHOD1(NotifyServicesOnSameNetwork, void(int num_service));
 
  private:
   DISALLOW_COPY_AND_ASSIGN(MockMetrics);
diff --git a/service.cc b/service.cc
index 87584a2..499d275 100644
--- a/service.cc
+++ b/service.cc
@@ -84,6 +84,7 @@
 const char Service::kStorageSaveCredentials[] = "SaveCredentials";
 const char Service::kStorageType[] = "Type";
 const char Service::kStorageUIData[] = "UIData";
+const char Service::kStorageConnectionId[] = "ConnectionId";
 
 const uint8 Service::kStrengthMax = 100;
 const uint8 Service::kStrengthMin = 0;
@@ -146,7 +147,8 @@
       time_(Time::GetInstance()),
       diagnostics_reporter_(DiagnosticsReporter::GetInstance()),
       consecutive_dhcp_failures_(0),
-      dhcp_option_failure_state_(kDHCPOptionFailureNotDetected) {
+      dhcp_option_failure_state_(kDHCPOptionFailureNotDetected),
+      connection_id_(0) {
   HelpRegisterDerivedBool(kAutoConnectProperty,
                           &Service::GetAutoConnect,
                           &Service::SetAutoConnectFull,
@@ -225,6 +227,8 @@
                                   &Service::GetDisconnectsProperty);
   HelpRegisterConstDerivedStrings(kDiagnosticsMisconnectsProperty,
                                   &Service::GetMisconnectsProperty);
+  store_.RegisterConstInt32(kConnectionIdProperty, &connection_id_);
+
   metrics_->RegisterService(*this);
 
   static_ip_parameters_.PlumbPropertyStore(&store_);
@@ -454,6 +458,7 @@
     last_dhcp_option_failure_ = Timestamp();
     dhcp_option_failure_state_ = kDHCPOptionFailureNotDetected;
   }
+  storage->GetInt(id, kStorageConnectionId, &connection_id_);
 
   static_ip_parameters_.Load(storage, id);
 
@@ -540,6 +545,7 @@
   } else {
     storage->DeleteKey(id, kStorageLastDHCPOptionFailure);
   }
+  storage->SetInt(id, kStorageConnectionId, connection_id_);
 
   static_ip_parameters_.Save(storage, id);
   if (eap()) {
diff --git a/service.h b/service.h
index 86c564e..df38cc7 100644
--- a/service.h
+++ b/service.h
@@ -79,6 +79,7 @@
   static const char kStorageSaveCredentials[];
   static const char kStorageType[];
   static const char kStorageUIData[];
+  static const char kStorageConnectionId[];
 
   static const uint8 kStrengthMax;
   static const uint8 kStrengthMin;
@@ -464,6 +465,9 @@
   // this service.
   std::map<std::string, std::string> GetLoadableProfileEntries();
 
+  void set_connection_id(int connection_id) { connection_id_ = connection_id; }
+  int connection_id() const { return connection_id_; }
+
  protected:
   friend class base::RefCounted<Service>;
 
@@ -810,6 +814,10 @@
   // The |serial_number_| for the next Service.
   static unsigned int next_serial_number_;
 
+  // Network identifier indicating the network (gateway) the service is
+  // connected to.
+  int connection_id_;
+
   DISALLOW_COPY_AND_ASSIGN(Service);
 };