shill: Add support to set physical mode.

BUG=chromium-os:24776
TEST=Unit tests, network_WiFiManager

Change-Id: Ic46404e71dbb54a06a7d668992a96df9f8db2598
Reviewed-on: https://gerrit.chromium.org/gerrit/15765
Commit-Ready: Thieu Le <thieule@chromium.org>
Reviewed-by: Thieu Le <thieule@chromium.org>
Tested-by: Thieu Le <thieule@chromium.org>
diff --git a/wifi_endpoint.cc b/wifi_endpoint.cc
index 01d5a42..a36ad45 100644
--- a/wifi_endpoint.cc
+++ b/wifi_endpoint.cc
@@ -11,6 +11,7 @@
 #include <base/string_util.h>
 #include <chromeos/dbus/service_constants.h>
 
+#include "shill/ieee80211.h"
 #include "shill/wifi.h"
 #include "shill/wpa_supplicant.h"
 
@@ -38,6 +39,7 @@
       properties.find(wpa_supplicant::kBSSPropertyFrequency);
   if (it != properties.end())
     frequency_ = it->second.reader().get_uint16();
+  physical_mode_ = DeterminePhyMode(properties, frequency_);
   network_mode_ = ParseMode(
       properties.find(wpa_supplicant::kBSSPropertyMode)->second);
   security_mode_ = ParseSecurity(properties);
@@ -97,6 +99,10 @@
   return frequency_;
 }
 
+uint16 WiFiEndpoint::physical_mode() const {
+  return physical_mode_;
+}
+
 const string &WiFiEndpoint::network_mode() const {
   return network_mode_;
 }
@@ -211,4 +217,64 @@
   }
 }
 
+// static
+Metrics::WiFiNetworkPhyMode WiFiEndpoint::DeterminePhyMode(
+    const map<string, ::DBus::Variant> &properties, uint16 frequency) {
+  uint32_t max_rate = 0;
+  map<string, ::DBus::Variant>::const_iterator it =
+      properties.find(wpa_supplicant::kBSSPropertyRates);
+  if (it != properties.end()) {
+    vector<uint32_t> rates = it->second.operator vector<uint32_t>();
+    if (rates.size() > 0)
+      max_rate = rates[0];  // Rates are sorted in descending order
+  }
+
+  Metrics::WiFiNetworkPhyMode phy_mode = Metrics::kWiFiNetworkPhyModeUndef;
+  it = properties.find(wpa_supplicant::kBSSPropertyIEs);
+  if (it != properties.end()) {
+    phy_mode = ParseIEsForPhyMode(it->second.operator vector<uint8_t>());
+    if (phy_mode != Metrics::kWiFiNetworkPhyModeUndef)
+      return phy_mode;
+  }
+
+  if (frequency < 3000) {
+    // 2.4GHz legacy, check for tx rate for 11b-only
+    // (note 22M is valid)
+    if (max_rate < 24000000)
+      phy_mode = Metrics::kWiFiNetworkPhyMode11b;
+    else
+      phy_mode = Metrics::kWiFiNetworkPhyMode11g;
+  } else {
+    phy_mode = Metrics::kWiFiNetworkPhyMode11a;
+  }
+
+  return phy_mode;
+}
+
+// static
+Metrics::WiFiNetworkPhyMode WiFiEndpoint::ParseIEsForPhyMode(
+    const vector<uint8_t> &ies) {
+  // Format of an information element:
+  //    1       1           3           1 - 252
+  // +------+--------+------------+----------------+
+  // | Type | Length | OUI        | Data           |
+  // +------+--------+------------+----------------+
+  Metrics::WiFiNetworkPhyMode phy_mode = Metrics::kWiFiNetworkPhyModeUndef;
+  vector<uint8_t>::const_iterator it;
+  for (it = ies.begin();
+       it + 1 < ies.end();  // +1 to ensure Length field is in valid memory
+       it += 2 + *(it + 1)) {
+    if (*it == IEEE_80211::kElemIdErp) {
+      phy_mode = Metrics::kWiFiNetworkPhyMode11g;
+      continue;  // NB: Continue to check for HT
+    }
+    if (*it == IEEE_80211::kElemIdHTCap || *it == IEEE_80211::kElemIdHTInfo) {
+      phy_mode = Metrics::kWiFiNetworkPhyMode11n;
+      break;
+    }
+  }
+
+  return phy_mode;
+}
+
 }  // namespace shill