shill: WiFi: Track signal strength of connected AP

While connected to an AP, perform periodic captures of the
"station" information element in the kernel for our connection
to the current endpoint.  When we receive a response, update
the endpoint with the signal strength in the station information.
This will in turn update the service's "Strength" property.

BUG=chromium:271492
TEST=Unit tests; Manual: Walk around while connected and ensure
bars move up and down to compensate.  ff_debug +wifi; ff_debug --level -2
shows messages about endpoint signal strength changing.

Change-Id: I246baafb01cc6ae94bb08b2b950eaa09f11b8cc2
Reviewed-on: https://gerrit.chromium.org/gerrit/65713
Reviewed-by: mukesh agrawal <quiche@chromium.org>
Commit-Queue: Paul Stewart <pstew@chromium.org>
Tested-by: Paul Stewart <pstew@chromium.org>
diff --git a/nl80211_message.cc b/nl80211_message.cc
index b87acc0..440af20 100644
--- a/nl80211_message.cc
+++ b/nl80211_message.cc
@@ -473,6 +473,17 @@
 const uint8_t GetRegMessage::kCommand = NL80211_CMD_GET_REG;
 const char GetRegMessage::kCommandString[] = "NL80211_CMD_GET_REG";
 
+const uint8_t GetStationMessage::kCommand = NL80211_CMD_GET_STATION;
+const char GetStationMessage::kCommandString[] = "NL80211_CMD_GET_STATION";
+
+GetStationMessage::GetStationMessage()
+    : Nl80211Message(kCommand, kCommandString) {
+  attributes()->CreateAttribute(
+      NL80211_ATTR_IFINDEX, Bind(&NetlinkAttribute::NewNl80211AttributeFromId));
+  attributes()->CreateAttribute(
+      NL80211_ATTR_MAC, Bind(&NetlinkAttribute::NewNl80211AttributeFromId));
+}
+
 const uint8_t GetWiphyMessage::kCommand = NL80211_CMD_GET_WIPHY;
 const char GetWiphyMessage::kCommandString[] = "NL80211_CMD_GET_WIPHY";
 
@@ -599,6 +610,8 @@
       return new GetInterfaceMessage();
     case GetRegMessage::kCommand:
       return new GetRegMessage();
+    case GetStationMessage::kCommand:
+      return new GetStationMessage();
     case GetWiphyMessage::kCommand:
       return new GetWiphyMessage();
     case JoinIbssMessage::kCommand:
diff --git a/nl80211_message.h b/nl80211_message.h
index 62c8405..2ba050b 100644
--- a/nl80211_message.h
+++ b/nl80211_message.h
@@ -215,6 +215,17 @@
   DISALLOW_COPY_AND_ASSIGN(GetRegMessage);
 };
 
+class GetStationMessage : public Nl80211Message {
+ public:
+  static const uint8_t kCommand;
+  static const char kCommandString[];
+
+  GetStationMessage();
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(GetStationMessage);
+};
+
 class GetWiphyMessage : public Nl80211Message {
  public:
   static const uint8_t kCommand;
diff --git a/wifi.cc b/wifi.cc
index 4ea90ec..a668272 100644
--- a/wifi.cc
+++ b/wifi.cc
@@ -79,6 +79,7 @@
 const int WiFi::kFastScanIntervalSeconds = 10;
 const int WiFi::kPendingTimeoutSeconds = 15;
 const int WiFi::kReconnectTimeoutSeconds = 10;
+const int WiFi::kRequestStationInfoPeriodSeconds = 20;
 const size_t WiFi::kMinumumFrequenciesToScan = 4;  // Arbitrary but > 0.
 const float WiFi::kDefaultFractionPerScan = 0.34;
 const char WiFi::kProgressiveScanFlagFile[] = "/home/chronos/.progressive_scan";
@@ -300,6 +301,7 @@
   SetScanState(kScanIdle, kScanMethodNone, __func__);
   StopPendingTimer();
   StopReconnectTimer();
+  StopRequestingStationInfo();
 
   OnEnabledStateChanged(EnabledStateChangedCallback(), Error());
   if (error)
@@ -511,6 +513,7 @@
 
   SetPendingService(NULL);
   StopReconnectTimer();
+  StopRequestingStationInfo();
 
   if (!supplicant_present_) {
     LOG(ERROR) << "In " << __func__ << "(): "
@@ -699,6 +702,7 @@
   // connectivity.  We no longer need to track any previously pending
   // reconnect.
   StopReconnectTimer();
+  StopRequestingStationInfo();
 
   if (new_bss == WPASupplicant::kCurrentBSSNull) {
     HandleDisconnect();
@@ -1543,6 +1547,7 @@
     // this earlier when the association process succeeded.
     current_service_->ResetSuspectedCredentialFailures();
   }
+  RequestStationInfo();
 }
 
 void WiFi::OnIPConfigFailure() {
@@ -2145,4 +2150,99 @@
   }
 }
 
+void WiFi::RequestStationInfo() {
+  if (!current_service_ || !current_service_->IsConnected()) {
+    LOG(ERROR) << "Not collecting station info because we are not connected.";
+    return;
+  }
+
+  EndpointMap::iterator endpoint_it = endpoint_by_rpcid_.find(supplicant_bss_);
+  if (endpoint_it == endpoint_by_rpcid_.end()) {
+    LOG(ERROR) << "Can't get endpoint for current supplicant BSS "
+               << supplicant_bss_;
+    return;
+  }
+
+  GetStationMessage get_station;
+  if (!get_station.attributes()->SetU32AttributeValue(NL80211_ATTR_IFINDEX,
+                                                      interface_index())) {
+    LOG(ERROR) << "Could not add IFINDEX attribute for GetStation message.";
+    return;
+  }
+
+  const WiFiEndpointConstRefPtr endpoint(endpoint_it->second);
+  if (!get_station.attributes()->SetRawAttributeValue(
+          NL80211_ATTR_MAC,
+          ByteString::CreateFromHexString(endpoint->bssid_hex()))) {
+    LOG(ERROR) << "Could not add MAC attribute for GetStation message.";
+    return;
+  }
+
+  netlink_manager_->SendNl80211Message(
+      &get_station,
+      Bind(&WiFi::OnReceivedStationInfo, weak_ptr_factory_.GetWeakPtr()),
+      Bind(&NetlinkManager::OnNetlinkMessageError));
+
+  request_station_info_callback_.Reset(
+      Bind(&WiFi::RequestStationInfo, weak_ptr_factory_.GetWeakPtr()));
+  dispatcher()->PostDelayedTask(request_station_info_callback_.callback(),
+                                kRequestStationInfoPeriodSeconds * 1000);
+}
+
+void WiFi::OnReceivedStationInfo(const Nl80211Message &nl80211_message) {
+  // Verify NL80211_CMD_NEW_STATION
+  if (nl80211_message.command() != NewStationMessage::kCommand) {
+    LOG(ERROR) << "Received unexpected command:"
+               << nl80211_message.command();
+    return;
+  }
+
+  if (!current_service_ || !current_service_->IsConnected()) {
+    LOG(ERROR) << "Not accepting station info because we are not connected.";
+    return;
+  }
+
+  EndpointMap::iterator endpoint_it = endpoint_by_rpcid_.find(supplicant_bss_);
+  if (endpoint_it == endpoint_by_rpcid_.end()) {
+    LOG(ERROR) << "Can't get endpoint for current supplicant BSS."
+               << supplicant_bss_;
+    return;
+  }
+
+  ByteString station_bssid;
+  if (!nl80211_message.const_attributes()->GetRawAttributeValue(
+          NL80211_ATTR_MAC, &station_bssid)) {
+    LOG(ERROR) << "Unable to get MAC attribute from received station info.";
+    return;
+  }
+
+  WiFiEndpointRefPtr endpoint(endpoint_it->second);
+
+  if (!station_bssid.Equals(
+          ByteString::CreateFromHexString(endpoint->bssid_hex()))) {
+    LOG(ERROR) << "Received station info for a non-current BSS.";
+    return;
+  }
+
+  AttributeListConstRefPtr station_info;
+  if (!nl80211_message.const_attributes()->ConstGetNestedAttributeList(
+      NL80211_ATTR_STA_INFO, &station_info)) {
+    LOG(ERROR) << "Received station info had no NL80211_ATTR_STA_INFO.";
+    return;
+  }
+
+  uint8_t signal;
+  if (!station_info->GetU8AttributeValue(NL80211_STA_INFO_SIGNAL, &signal)) {
+    LOG(ERROR) << "Received station info had no NL80211_STA_INFO_SIGNAL.";
+    return;
+  }
+
+  endpoint->UpdateSignalStrength(static_cast<signed char>(signal));
+}
+
+void WiFi::StopRequestingStationInfo() {
+  SLOG(WiFi, 2) << "WiFi Device " << link_name() << ": " << __func__;
+  request_station_info_callback_.Cancel();
+}
+
 }  // namespace shill
diff --git a/wifi.h b/wifi.h
index c19f3af..e8bfeaf 100644
--- a/wifi.h
+++ b/wifi.h
@@ -245,6 +245,7 @@
   FRIEND_TEST(WiFiMainTest, VerifyPaths);
   FRIEND_TEST(WiFiPropertyTest, BgscanMethodProperty);  // bgscan_method_
   FRIEND_TEST(WiFiTimerTest, FastRescan);  // kFastScanIntervalSeconds
+  FRIEND_TEST(WiFiTimerTest, RequestStationInfo);  // kRequestStationInfoPeriod
 
   typedef std::map<const std::string, WiFiEndpointRefPtr> EndpointMap;
   typedef std::map<const WiFiService *, std::string> ReverseServiceMap;
@@ -263,6 +264,7 @@
   static const int kFastScanIntervalSeconds;
   static const int kPendingTimeoutSeconds;
   static const int kReconnectTimeoutSeconds;
+  static const int kRequestStationInfoPeriodSeconds;
   static const size_t kMinumumFrequenciesToScan;
   static const float kDefaultFractionPerScan;
   static const char kProgressiveScanFlagFile[];
@@ -411,6 +413,11 @@
   // on the initial association and every reassociation afterward.
   void EnableHighBitrates();
 
+  // Request and retrieve information about the currently connected station.
+  void RequestStationInfo();
+  void OnReceivedStationInfo(const Nl80211Message &nl80211_message);
+  void StopRequestingStationInfo();
+
   void ConnectToSupplicant();
 
   void Restart();
@@ -471,6 +478,9 @@
   // Executes when a reconnecting service timer expires. Calls
   // ReconnectTimeoutHandler.
   base::CancelableClosure reconnect_timeout_callback_;
+  // Executes periodically while a service is connected, to update the
+  // signal strength from the currently connected AP.
+  base::CancelableClosure request_station_info_callback_;
   // Number of remaining fast scans to be done during startup and disconnect.
   int fast_scans_remaining_;
   // Indicates that the current BSS has reached the completed state according
diff --git a/wifi_endpoint.cc b/wifi_endpoint.cc
index f97a65b..1874861 100644
--- a/wifi_endpoint.cc
+++ b/wifi_endpoint.cc
@@ -124,6 +124,16 @@
   }
 }
 
+void WiFiEndpoint::UpdateSignalStrength(int16 strength) {
+  if (signal_strength_ == strength ) {
+    return;
+  }
+
+  SLOG(WiFi, 2) << __func__ << ": signal strength "
+                << signal_strength_ << " -> " << strength;
+  signal_strength_ = strength;
+  device_->NotifyEndpointChanged(this);
+}
 
 map<string, string> WiFiEndpoint::GetVendorInformation() const {
   map<string, string> vendor_information;
diff --git a/wifi_endpoint.h b/wifi_endpoint.h
index e182fe7..1bf54ca 100644
--- a/wifi_endpoint.h
+++ b/wifi_endpoint.h
@@ -62,6 +62,9 @@
   void PropertiesChanged(
       const std::map<std::string, ::DBus::Variant> &properties);
 
+  // Called by WiFi when it polls for signal strength from the kernel.
+  void UpdateSignalStrength(int16 strength);
+
   // Maps mode strings from flimflam's nomenclature, as defined
   // in chromeos/dbus/service_constants.h, to uints used by supplicant
   static uint32_t ModeStringToUint(const std::string &mode_string);
diff --git a/wifi_unittest.cc b/wifi_unittest.cc
index 30c6c21..eedb543 100644
--- a/wifi_unittest.cc
+++ b/wifi_unittest.cc
@@ -610,6 +610,12 @@
   const base::CancelableClosure &GetReconnectTimeoutCallback() {
     return wifi_->reconnect_timeout_callback_;
   }
+  const string &GetSupplicantBSS() {
+    return wifi_->supplicant_bss_;
+  }
+  void SetSupplicantBSS(const string &bss) {
+    wifi_->supplicant_bss_ = bss;
+  }
   int GetReconnectTimeoutSeconds() {
     return WiFi::kReconnectTimeoutSeconds;
   }
@@ -687,6 +693,12 @@
   void ReportWiFiDebugScopeChanged(bool enabled) {
     wifi_->OnWiFiDebugScopeChanged(enabled);
   }
+  void RequestStationInfo() {
+    wifi_->RequestStationInfo();
+  }
+  void ReportReceivedStationInfo(const Nl80211Message &nl80211_message) {
+    wifi_->OnReceivedStationInfo(nl80211_message);
+  }
   void SetPendingService(const WiFiServiceRefPtr &service) {
     wifi_->SetPendingService(service);
   }
@@ -2548,6 +2560,76 @@
   StartReconnectTimer();
 }
 
+TEST_F(WiFiTimerTest, RequestStationInfo) {
+  EXPECT_CALL(mock_dispatcher_, PostTask(_)).Times(AnyNumber());
+  EXPECT_CALL(mock_dispatcher_, PostDelayedTask(_, _)).Times(AnyNumber());
+
+  // Setup a connected service here while we have the expectations above set.
+  StartWiFi();
+  MockWiFiServiceRefPtr service =
+      SetupConnectedService(DBus::Path(), NULL, NULL);
+  string connected_bss = GetSupplicantBSS();
+  Mock::VerifyAndClearExpectations(&mock_dispatcher_);
+
+  EXPECT_CALL(netlink_manager_, SendNl80211Message(_, _, _)).Times(0);
+  EXPECT_CALL(mock_dispatcher_, PostDelayedTask(_, _)).Times(0);
+  NiceScopedMockLog log;
+
+  // There is no current_service_.
+  EXPECT_CALL(log, Log(_, _, HasSubstr("we are not connected")));
+  SetCurrentService(NULL);
+  RequestStationInfo();
+
+  // current_service_ is not connnected.
+  EXPECT_CALL(*service, IsConnected()).WillOnce(Return(false));
+  SetCurrentService(service);
+  EXPECT_CALL(log, Log(_, _, HasSubstr("we are not connected")));
+  RequestStationInfo();
+
+  // Endpoint does not exist in endpoint_by_rpcid_.
+  EXPECT_CALL(*service, IsConnected()).WillRepeatedly(Return(true));
+  SetSupplicantBSS("/some/path/that/does/not/exist/in/endpoint_by_rpcid");
+  EXPECT_CALL(log, Log(_, _, HasSubstr(
+      "Can't get endpoint for current supplicant BSS")));
+  RequestStationInfo();
+  Mock::VerifyAndClearExpectations(&netlink_manager_);
+  Mock::VerifyAndClearExpectations(&mock_dispatcher_);
+
+  // We successfully trigger a request to get the station and start a timer
+  // for the next call.
+  EXPECT_CALL(netlink_manager_, SendNl80211Message(
+      IsNl80211Command(kNl80211FamilyId, NL80211_CMD_GET_STATION), _, _));
+  EXPECT_CALL(mock_dispatcher_, PostDelayedTask(
+      _, WiFi::kRequestStationInfoPeriodSeconds * 1000));
+  SetSupplicantBSS(connected_bss);
+  RequestStationInfo();
+
+  // Now test that a properly formatted New Station message updates strength.
+  NewStationMessage new_station;
+  new_station.attributes()->CreateRawAttribute(NL80211_ATTR_MAC, "BSSID");
+
+  // Use a reference to the endpoint instance in the WiFi device instead of
+  // the copy returned by SetupConnectedService().
+  WiFiEndpointRefPtr endpoint = GetEndpointMap().begin()->second;
+  new_station.attributes()->SetRawAttributeValue(
+      NL80211_ATTR_MAC, ByteString::CreateFromHexString(endpoint->bssid_hex()));
+  new_station.attributes()->CreateNestedAttribute(
+      NL80211_ATTR_STA_INFO, "Station Info");
+  AttributeListRefPtr station_info;
+  new_station.attributes()->GetNestedAttributeList(
+      NL80211_ATTR_STA_INFO, &station_info);
+  station_info->CreateU8Attribute(NL80211_STA_INFO_SIGNAL, "Signal");
+  const int16_t kSignalValue = -20;
+  station_info->SetU8AttributeValue(NL80211_STA_INFO_SIGNAL, kSignalValue);
+  new_station.attributes()->SetNestedAttributeHasAValue(NL80211_ATTR_STA_INFO);
+
+  EXPECT_NE(kSignalValue, endpoint->signal_strength());
+  EXPECT_CALL(*wifi_provider(), OnEndpointUpdated(EndpointMatch(endpoint)));
+  AttributeListConstRefPtr station_info_prime;
+  ReportReceivedStationInfo(new_station);
+  EXPECT_EQ(kSignalValue, endpoint->signal_strength());
+}
+
 TEST_F(WiFiMainTest, EAPCertification) {
   MockWiFiServiceRefPtr service = MakeMockService(flimflam::kSecurity8021x);
   EXPECT_CALL(*service, AddEAPCertification(_, _)).Times(0);