shill: WiFi: Track manufacturer information

Some information elements provided by APs give information
about the manufacturer and model name of the AP.  Report
this so it can be found by RPC callers.

BUG=chromium-os:31472
TEST=Unit tests, manual (list-services)

Change-Id: I9a1867d27095adb6b7fbb3d3a693ee9d9d6b558c
Reviewed-on: https://gerrit.chromium.org/gerrit/26681
Reviewed-by: Darin Petkov <petkov@chromium.org>
Commit-Ready: Paul Stewart <pstew@chromium.org>
Tested-by: Paul Stewart <pstew@chromium.org>
diff --git a/doc/service-api.txt b/doc/service-api.txt
index 1f0e0d6..0f08a6a 100644
--- a/doc/service-api.txt
+++ b/doc/service-api.txt
@@ -881,3 +881,22 @@
 		string WiFi.SSID [readonly]
 			(WiFi only) The service's SSID. Must have a non-zero
 			length less than or equal to 32.
+
+		dict WiFi.VendorInformation [readonly]
+			(WiFi only) Information about the vendor of the
+			AP, gleaned from WPS and vendor-specific information
+			elements in the beacon and probe respondss.
+
+			string Manufacturer [readonly]
+				Device manufacturer name as supplied by WPS IE.
+			string ModelName [readonly]
+				Device model name as supplied by WPS IE.
+			string ModelNumber [readonly]
+				Device model number as supplied by WPS IE.
+			string DeviceName [readonly]
+				Device name as supplied by WPS IE.
+			string OUIList [readonly]
+				Space separated list of OUI identifiers for
+				vendor-specific IEs that were neither the
+				Microsoft nor Epigram identifiers (the former
+				two are used for platform-neutral information).
diff --git a/ieee80211.h b/ieee80211.h
index 888d01a..fd0fe26 100644
--- a/ieee80211.h
+++ b/ieee80211.h
@@ -11,6 +11,7 @@
 const uint8_t kElemIdErp = 42;
 const uint8_t kElemIdHTCap = 45;
 const uint8_t kElemIdHTInfo = 61;
+const uint8_t kElemIdVendor = 221;
 
 const unsigned int kMaxSSIDLen = 32;
 
@@ -22,6 +23,15 @@
 const unsigned int kWPAAsciiMinLen = 8;
 const unsigned int kWPAAsciiMaxLen = 63;
 const unsigned int kWPAHexLen = 64;
+
+const uint32_t kOUIVendorEpigram = 0x00904c;
+const uint32_t kOUIVendorMicrosoft = 0x0050f2;
+
+const uint8_t kOUIMicrosoftWPS = 4;
+const uint16_t kWPSElementManufacturer = 0x1021;
+const uint16_t kWPSElementModelName = 0x1023;
+const uint16_t kWPSElementModelNumber = 0x1024;
+const uint16_t kWPSElementDeviceName = 0x1011;
 };
 
 }  // namespace shill
diff --git a/wifi_endpoint.cc b/wifi_endpoint.cc
index 5da0ec3..145a5c0 100644
--- a/wifi_endpoint.cc
+++ b/wifi_endpoint.cc
@@ -4,6 +4,8 @@
 
 #include "shill/wifi_endpoint.h"
 
+#include <algorithm>
+
 #include <base/logging.h>
 #include <base/stl_util.h>
 #include <base/stringprintf.h>
@@ -48,7 +50,13 @@
       properties.find(wpa_supplicant::kBSSPropertyFrequency);
   if (it != properties.end())
     frequency_ = it->second.reader().get_uint16();
-  physical_mode_ = DeterminePhyMode(properties, frequency_);
+
+  Metrics::WiFiNetworkPhyMode phy_mode = Metrics::kWiFiNetworkPhyModeUndef;
+  if (!ParseIEs(properties, &phy_mode, &vendor_information_)) {
+    phy_mode = DeterminePhyModeFromFrequency(properties, frequency_);
+  }
+  physical_mode_ = phy_mode;
+
   network_mode_ = ParseMode(
       properties.find(wpa_supplicant::kBSSPropertyMode)->second);
   security_mode_ = ParseSecurity(properties);
@@ -87,8 +95,43 @@
   }
 }
 
+
+map<string, string> WiFiEndpoint::GetVendorInformation() const {
+  map<string, string> vendor_information;
+  if (!vendor_information_.wps_manufacturer.empty()) {
+    vendor_information[kVendorWPSManufacturerProperty] =
+        vendor_information_.wps_manufacturer;
+  }
+  if (!vendor_information_.wps_model_name.empty()) {
+    vendor_information[kVendorWPSModelNameProperty] =
+        vendor_information_.wps_model_name;
+  }
+  if (!vendor_information_.wps_model_number.empty()) {
+    vendor_information[kVendorWPSModelNumberProperty] =
+        vendor_information_.wps_model_number;
+  }
+  if (!vendor_information_.wps_device_name.empty()) {
+    vendor_information[kVendorWPSDeviceNameProperty] =
+        vendor_information_.wps_device_name;
+  }
+  if (!vendor_information_.oui_list.empty()) {
+    vector<string> oui_list;
+    set<uint32_t>::const_iterator it;
+    for (it = vendor_information_.oui_list.begin();
+         it != vendor_information_.oui_list.end();
+         ++it) {
+      oui_list.push_back(
+          StringPrintf("%02x-%02x-%02x",
+              *it >> 16, (*it >> 8) & 0xff, *it & 0xff));
+    }
+    vendor_information[kVendorOUIListProperty] =
+        JoinString(oui_list, ' ');
+  }
+  return vendor_information;
+}
+
 // static
-uint32_t WiFiEndpoint::ModeStringToUint(const std::string &mode_string) {
+uint32_t WiFiEndpoint::ModeStringToUint(const string &mode_string) {
   if (mode_string == flimflam::kModeManaged)
     return wpa_supplicant::kNetworkModeInfrastructureInt;
   else if (mode_string == flimflam::kModeAdhoc)
@@ -252,7 +295,7 @@
 }
 
 // static
-Metrics::WiFiNetworkPhyMode WiFiEndpoint::DeterminePhyMode(
+Metrics::WiFiNetworkPhyMode WiFiEndpoint::DeterminePhyModeFromFrequency(
     const map<string, ::DBus::Variant> &properties, uint16 frequency) {
   uint32_t max_rate = 0;
   map<string, ::DBus::Variant>::const_iterator it =
@@ -264,13 +307,6 @@
   }
 
   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)
@@ -286,29 +322,116 @@
 }
 
 // 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;
-    }
+bool WiFiEndpoint::ParseIEs(
+    const map<string, ::DBus::Variant> &properties,
+    Metrics::WiFiNetworkPhyMode *phy_mode,
+    VendorInformation *vendor_information) {
+
+  map<string, ::DBus::Variant>::const_iterator ies_property =
+      properties.find(wpa_supplicant::kBSSPropertyIEs);
+  if (ies_property == properties.end()) {
+    SLOG(WiFi, 2) << __func__ << ": No IE property in BSS.";
+    return false;
   }
 
-  return phy_mode;
+  vector<uint8_t> ies = ies_property->second.operator vector<uint8_t>();
+
+
+  // Format of an information element:
+  //    1       1          1 - 252
+  // +------+--------+----------------+
+  // | Type | Length | Data           |
+  // +------+--------+----------------+
+  *phy_mode = Metrics::kWiFiNetworkPhyModeUndef;
+  bool found_ht = false;
+  bool found_erp = false;
+  int ie_len = 0;
+  vector<uint8_t>::iterator it;
+  for (it = ies.begin();
+       std::distance(it, ies.end()) > 1;  // Ensure Length field is within PDU.
+       it += ie_len) {
+    ie_len = 2 + *(it + 1);
+    if (std::distance(it, ies.end()) < ie_len) {
+      LOG(ERROR) << __func__ << ": IE extends past containing PDU.";
+      break;
+    }
+    switch (*it) {
+      case IEEE_80211::kElemIdErp:
+        if (!found_ht) {
+          *phy_mode = Metrics::kWiFiNetworkPhyMode11g;
+        }
+        found_erp = true;
+        break;
+      case IEEE_80211::kElemIdHTCap:
+      case IEEE_80211::kElemIdHTInfo:
+        *phy_mode = Metrics::kWiFiNetworkPhyMode11n;
+        found_ht = true;
+        break;
+      case IEEE_80211::kElemIdVendor:
+        ParseVendorIE(it + 2, it + ie_len, vendor_information);
+        break;
+    }
+  }
+  return found_ht || found_erp;
+}
+
+// static
+void WiFiEndpoint::ParseVendorIE(vector<uint8_t>::const_iterator ie,
+                                 vector<uint8_t>::const_iterator end,
+                                 VendorInformation *vendor_information) {
+  // Format of an vendor-specific information element (with type
+  // and length field for the IE removed by the caller):
+  //        3           1       1 - 248
+  // +------------+----------+----------------+
+  // | OUI        | OUI Type | Data           |
+  // +------------+----------+----------------+
+
+  if (std::distance(ie, end) < 4) {
+    LOG(ERROR) << __func__ << ": no room in IE for OUI and type field.";
+    return;
+  }
+  uint32_t oui = (*ie << 16) | (*(ie + 1) << 8) | *(ie + 2);
+  uint8_t oui_type = *(ie + 3);
+  ie += 4;
+
+  if (oui == IEEE_80211::kOUIVendorMicrosoft &&
+      oui_type == IEEE_80211::kOUIMicrosoftWPS) {
+    // Format of a WPS data element:
+    //    2       2
+    // +------+--------+----------------+
+    // | Type | Length | Data           |
+    // +------+--------+----------------+
+    while (std::distance(ie, end) >= 4) {
+      int element_type = (*ie << 8) | *(ie + 1);
+      int element_length = (*(ie + 2) << 8) | *(ie + 3);
+      ie += 4;
+      if (std::distance(ie, end) < element_length) {
+        LOG(ERROR) << __func__ << ": WPS element extends past containing PDU.";
+        break;
+      }
+      string s(ie, ie + element_length);
+      if (IsStringASCII(s)) {
+        switch (element_type) {
+          case IEEE_80211::kWPSElementManufacturer:
+            vendor_information->wps_manufacturer = s;
+            break;
+          case IEEE_80211::kWPSElementModelName:
+            vendor_information->wps_model_name = s;
+            break;
+          case IEEE_80211::kWPSElementModelNumber:
+            vendor_information->wps_model_number = s;
+            break;
+          case IEEE_80211::kWPSElementDeviceName:
+            vendor_information->wps_device_name = s;
+            break;
+        }
+      }
+      ie += element_length;
+    }
+  } else if (oui != IEEE_80211::kOUIVendorEpigram &&
+             oui != IEEE_80211::kOUIVendorMicrosoft) {
+    vendor_information->oui_list.insert(oui);
+  }
 }
 
 }  // namespace shill
diff --git a/wifi_endpoint.h b/wifi_endpoint.h
index 5332724..b4366d8 100644
--- a/wifi_endpoint.h
+++ b/wifi_endpoint.h
@@ -26,6 +26,13 @@
 
 class WiFiEndpoint : public Endpoint {
  public:
+  struct VendorInformation {
+    std::string wps_manufacturer;
+    std::string wps_model_name;
+    std::string wps_model_number;
+    std::string wps_device_name;
+    std::set<uint32_t> oui_list;
+  };
   WiFiEndpoint(ProxyFactory *proxy_factory,
                const WiFiRefPtr &device,
                const std::string &rpc_id,
@@ -46,6 +53,10 @@
   // in chromeos/dbus/service_constants.h, to uints used by supplicant
   static uint32_t ModeStringToUint(const std::string &mode_string);
 
+  // Returns a stringmap containing information gleaned about the
+  // vendor of this AP.
+  std::map<std::string, std::string> GetVendorInformation() const;
+
   const std::vector<uint8_t> &ssid() const;
   const std::string &ssid_string() const;
   const std::string &ssid_hex() const;
@@ -56,16 +67,19 @@
   uint16 physical_mode() const;
   const std::string &network_mode() const;
   const std::string &security_mode() const;
+  const VendorInformation &vendor_information() const;
 
  private:
   friend class WiFiEndpointTest;
   friend class WiFiObjectTest;  // for MakeOpenEndpoint
   friend class WiFiServiceTest;  // for MakeOpenEndpoint
   // these test cases need access to the KeyManagement enum
+  FRIEND_TEST(WiFiEndpointTest, DeterminePhyModeFromFrequency);
+  FRIEND_TEST(WiFiEndpointTest, ParseIEs);
   FRIEND_TEST(WiFiEndpointTest, ParseKeyManagementMethodsEAP);
   FRIEND_TEST(WiFiEndpointTest, ParseKeyManagementMethodsPSK);
   FRIEND_TEST(WiFiEndpointTest, ParseKeyManagementMethodsEAPAndPSK);
-  FRIEND_TEST(WiFiEndpointTest, DeterminePhyMode);
+  FRIEND_TEST(WiFiEndpointTest, ParseVendorIEs);
   FRIEND_TEST(WiFiServiceUpdateFromEndpointsTest, EndpointModified);
 
   enum KeyManagement {
@@ -95,12 +109,19 @@
   // Determine the negotiated operating mode for the channel by looking at
   // the information elements, frequency and data rates.  The information
   // elements and data rates live in |properties|.
-  static Metrics::WiFiNetworkPhyMode DeterminePhyMode(
+  static Metrics::WiFiNetworkPhyMode DeterminePhyModeFromFrequency(
       const std::map<std::string, ::DBus::Variant> &properties,
       uint16 frequency);
-  // Parse information elements to determine the physical mode.
-  static Metrics::WiFiNetworkPhyMode ParseIEsForPhyMode(
-      const std::vector<uint8_t> &ies);
+  // Parse information elements to determine the physical mode and vendor
+  // information associated with the AP.  Returns true if a physical mode
+  // was determined from the IE elements, false otherwise.
+  static bool ParseIEs(const std::map<std::string, ::DBus::Variant> &properties,
+                       Metrics::WiFiNetworkPhyMode *phy_mode,
+                       VendorInformation *vendor_information);
+  // Parse a single vendor information element.
+  static void ParseVendorIE(std::vector<uint8_t>::const_iterator ie,
+                            std::vector<uint8_t>::const_iterator end,
+                            VendorInformation *vendor_information);
 
   // TODO(quiche): make const?
   std::vector<uint8_t> ssid_;
@@ -116,6 +137,7 @@
   // (not necessarily the same as wpa_supplicant names)
   std::string network_mode_;
   std::string security_mode_;
+  VendorInformation vendor_information_;
 
   ProxyFactory *proxy_factory_;
   WiFiRefPtr device_;
diff --git a/wifi_endpoint_unittest.cc b/wifi_endpoint_unittest.cc
index f939e90..aab42ce 100644
--- a/wifi_endpoint_unittest.cc
+++ b/wifi_endpoint_unittest.cc
@@ -15,6 +15,7 @@
 #include <gtest/gtest.h>
 
 #include "shill/ieee80211.h"
+#include "shill/mock_log.h"
 #include "shill/mock_wifi.h"
 #include "shill/property_store_unittest.h"
 #include "shill/refptr_types.h"
@@ -25,6 +26,7 @@
 using std::string;
 using std::vector;
 using ::testing::_;
+using testing::HasSubstr;
 using ::testing::NiceMock;
 
 namespace shill {
@@ -84,11 +86,41 @@
 
   void AddIE(uint8_t type, vector<uint8_t> *ies) {
     ies->push_back(type);           // type
-    ies->push_back(4);              // length
-    ies->insert(ies->end(), 3, 0);  // OUI
+    ies->push_back(1);              // length
     ies->push_back(0);              // data
   }
 
+  void AddVendorIE(uint32_t oui, uint8_t vendor_type,
+                   const vector<uint8_t> &data,
+                   vector<uint8_t> *ies) {
+    ies->push_back(IEEE_80211::kElemIdVendor);  // type
+    ies->push_back(4 + data.size());            // length
+    ies->push_back((oui >> 16) & 0xff);         // OUI MSByte
+    ies->push_back((oui >> 8) & 0xff);          // OUI middle octet
+    ies->push_back(oui & 0xff);                 // OUI LSByte
+    ies->push_back(vendor_type);                // OUI Type
+    ies->insert(ies->end(), data.begin(), data.end());
+  }
+
+  void AddWPSElement(uint16_t type, const string &value,
+                     vector<uint8_t> *wps) {
+    wps->push_back(type >> 8);                   // type MSByte
+    wps->push_back(type);                        // type LSByte
+    CHECK(value.size() < kuint16max);
+    wps->push_back((value.size() >> 8) & 0xff);  // length MSByte
+    wps->push_back(value.size() & 0xff);         // length LSByte
+    wps->insert(wps->end(), value.begin(), value.end());
+  }
+
+  map<string, ::DBus::Variant> MakeBSSPropertiesWithIEs(
+      const vector<uint8_t> &ies) {
+    map<string, ::DBus::Variant> properties;
+    ::DBus::MessageIter writer =
+          properties[wpa_supplicant::kBSSPropertyIEs].writer();
+    writer << ies;
+    return properties;
+  }
+
   WiFiEndpoint *MakeOpenEndpoint(ProxyFactory *proxy_factory,
                                  const WiFiRefPtr &wifi,
                                  const std::string &ssid,
@@ -174,52 +206,11 @@
   EXPECT_EQ("?", endpoint->ssid_string());
 }
 
-TEST_F(WiFiEndpointTest, DeterminePhyMode) {
-  {
-    map<string, ::DBus::Variant> properties;
-    vector<uint8_t> ies;
-    AddIE(IEEE_80211::kElemIdErp, &ies);
-    ::DBus::MessageIter writer =
-        properties[wpa_supplicant::kBSSPropertyIEs].writer();
-    writer << ies;
-    EXPECT_EQ(Metrics::kWiFiNetworkPhyMode11g,
-              WiFiEndpoint::DeterminePhyMode(properties, 2400));
-  }
-  {
-    map<string, ::DBus::Variant> properties;
-    vector<uint8_t> ies;
-    AddIE(IEEE_80211::kElemIdHTCap, &ies);
-    ::DBus::MessageIter writer =
-        properties[wpa_supplicant::kBSSPropertyIEs].writer();
-    writer << ies;
-    EXPECT_EQ(Metrics::kWiFiNetworkPhyMode11n,
-              WiFiEndpoint::DeterminePhyMode(properties, 2400));
-  }
-  {
-    map<string, ::DBus::Variant> properties;
-    vector<uint8_t> ies;
-    AddIE(IEEE_80211::kElemIdHTInfo, &ies);
-    ::DBus::MessageIter writer =
-        properties[wpa_supplicant::kBSSPropertyIEs].writer();
-    writer << ies;
-    EXPECT_EQ(Metrics::kWiFiNetworkPhyMode11n,
-              WiFiEndpoint::DeterminePhyMode(properties, 2400));
-  }
-  {
-    map<string, ::DBus::Variant> properties;
-    vector<uint8_t> ies;
-    AddIE(IEEE_80211::kElemIdErp, &ies);
-    AddIE(IEEE_80211::kElemIdHTCap, &ies);
-    ::DBus::MessageIter writer =
-        properties[wpa_supplicant::kBSSPropertyIEs].writer();
-    writer << ies;
-    EXPECT_EQ(Metrics::kWiFiNetworkPhyMode11n,
-              WiFiEndpoint::DeterminePhyMode(properties, 2400));
-  }
+TEST_F(WiFiEndpointTest, DeterminePhyModeFromFrequency) {
   {
     map<string, ::DBus::Variant> properties;
     EXPECT_EQ(Metrics::kWiFiNetworkPhyMode11a,
-              WiFiEndpoint::DeterminePhyMode(properties, 3200));
+              WiFiEndpoint::DeterminePhyModeFromFrequency(properties, 3200));
   }
   {
     map<string, ::DBus::Variant> properties;
@@ -228,7 +219,7 @@
         properties[wpa_supplicant::kBSSPropertyRates].writer();
     writer << rates;
     EXPECT_EQ(Metrics::kWiFiNetworkPhyMode11b,
-              WiFiEndpoint::DeterminePhyMode(properties, 2400));
+              WiFiEndpoint::DeterminePhyModeFromFrequency(properties, 2400));
   }
   {
     map<string, ::DBus::Variant> properties;
@@ -237,7 +228,189 @@
         properties[wpa_supplicant::kBSSPropertyRates].writer();
     writer << rates;
     EXPECT_EQ(Metrics::kWiFiNetworkPhyMode11g,
-              WiFiEndpoint::DeterminePhyMode(properties, 2400));
+              WiFiEndpoint::DeterminePhyModeFromFrequency(properties, 2400));
+  }
+}
+
+TEST_F(WiFiEndpointTest, ParseIEs) {
+  {
+    vector<uint8_t> ies;
+    Metrics::WiFiNetworkPhyMode phy_mode = Metrics::kWiFiNetworkPhyModeUndef;
+    WiFiEndpoint::VendorInformation vendor_information;
+    WiFiEndpoint::ParseIEs(MakeBSSPropertiesWithIEs(ies),
+                           &phy_mode, &vendor_information);
+    EXPECT_EQ(Metrics::kWiFiNetworkPhyModeUndef, phy_mode);
+  }
+  {
+    vector<uint8_t> ies;
+    AddIE(IEEE_80211::kElemIdErp, &ies);
+    Metrics::WiFiNetworkPhyMode phy_mode = Metrics::kWiFiNetworkPhyModeUndef;
+    WiFiEndpoint::VendorInformation vendor_information;
+    WiFiEndpoint::ParseIEs(MakeBSSPropertiesWithIEs(ies),
+                           &phy_mode, &vendor_information);
+    EXPECT_EQ(Metrics::kWiFiNetworkPhyMode11g, phy_mode);
+  }
+  {
+    vector<uint8_t> ies;
+    AddIE(IEEE_80211::kElemIdHTCap, &ies);
+    Metrics::WiFiNetworkPhyMode phy_mode = Metrics::kWiFiNetworkPhyModeUndef;
+    WiFiEndpoint::VendorInformation vendor_information;
+    WiFiEndpoint::ParseIEs(MakeBSSPropertiesWithIEs(ies),
+                           &phy_mode, &vendor_information);
+    EXPECT_EQ(Metrics::kWiFiNetworkPhyMode11n, phy_mode);
+  }
+  {
+    vector<uint8_t> ies;
+    AddIE(IEEE_80211::kElemIdHTInfo, &ies);
+    Metrics::WiFiNetworkPhyMode phy_mode = Metrics::kWiFiNetworkPhyModeUndef;
+    WiFiEndpoint::VendorInformation vendor_information;
+    WiFiEndpoint::ParseIEs(MakeBSSPropertiesWithIEs(ies),
+                           &phy_mode, &vendor_information);
+    EXPECT_EQ(Metrics::kWiFiNetworkPhyMode11n, phy_mode);
+  }
+  {
+    vector<uint8_t> ies;
+    AddIE(IEEE_80211::kElemIdErp, &ies);
+    AddIE(IEEE_80211::kElemIdHTCap, &ies);
+    Metrics::WiFiNetworkPhyMode phy_mode = Metrics::kWiFiNetworkPhyModeUndef;
+    WiFiEndpoint::VendorInformation vendor_information;
+    WiFiEndpoint::ParseIEs(MakeBSSPropertiesWithIEs(ies),
+                           &phy_mode, &vendor_information);
+    EXPECT_EQ(Metrics::kWiFiNetworkPhyMode11n, phy_mode);
+  }
+}
+
+TEST_F(WiFiEndpointTest, ParseVendorIEs) {
+  {
+    ScopedMockLog log;
+    EXPECT_CALL(log, Log(logging::LOG_ERROR, _,
+                         HasSubstr("no room in IE for OUI and type field.")))
+        .Times(1);
+    vector<uint8_t> ies;
+    AddIE(IEEE_80211::kElemIdVendor, &ies);
+    Metrics::WiFiNetworkPhyMode phy_mode = Metrics::kWiFiNetworkPhyModeUndef;
+    WiFiEndpoint::VendorInformation vendor_information;
+    WiFiEndpoint::ParseIEs(MakeBSSPropertiesWithIEs(ies),
+                           &phy_mode, &vendor_information);
+  }
+  {
+    vector<uint8_t> ies;
+    Metrics::WiFiNetworkPhyMode phy_mode = Metrics::kWiFiNetworkPhyModeUndef;
+    WiFiEndpoint::VendorInformation vendor_information;
+    WiFiEndpoint::ParseIEs(MakeBSSPropertiesWithIEs(ies),
+                           &phy_mode, &vendor_information);
+    EXPECT_EQ("", vendor_information.wps_manufacturer);
+    EXPECT_EQ("", vendor_information.wps_model_name);
+    EXPECT_EQ("", vendor_information.wps_model_number);
+    EXPECT_EQ("", vendor_information.wps_device_name);
+    EXPECT_EQ(0, vendor_information.oui_list.size());
+  }
+  {
+    ScopedMockLog log;
+    EXPECT_CALL(log, Log(logging::LOG_ERROR, _,
+                         HasSubstr("IE extends past containing PDU"))).Times(1);
+    vector<uint8_t> ies;
+    AddVendorIE(0, 0, vector<uint8_t>(), &ies);
+    ies.resize(ies.size() - 1);  // Cause an underrun in the data.
+    Metrics::WiFiNetworkPhyMode phy_mode = Metrics::kWiFiNetworkPhyModeUndef;
+    WiFiEndpoint::VendorInformation vendor_information;
+    WiFiEndpoint::ParseIEs(MakeBSSPropertiesWithIEs(ies),
+                           &phy_mode, &vendor_information);
+  }
+  {
+    vector<uint8_t> ies;
+    const uint32_t kVendorOUI = 0xaabbcc;
+    AddVendorIE(kVendorOUI, 0, vector<uint8_t>(), &ies);
+    AddVendorIE(IEEE_80211::kOUIVendorMicrosoft, 0, vector<uint8_t>(), &ies);
+    AddVendorIE(IEEE_80211::kOUIVendorEpigram, 0, vector<uint8_t>(), &ies);
+    Metrics::WiFiNetworkPhyMode phy_mode = Metrics::kWiFiNetworkPhyModeUndef;
+    WiFiEndpoint::VendorInformation vendor_information;
+    WiFiEndpoint::ParseIEs(MakeBSSPropertiesWithIEs(ies),
+                           &phy_mode, &vendor_information);
+    EXPECT_EQ("", vendor_information.wps_manufacturer);
+    EXPECT_EQ("", vendor_information.wps_model_name);
+    EXPECT_EQ("", vendor_information.wps_model_number);
+    EXPECT_EQ("", vendor_information.wps_device_name);
+    EXPECT_EQ(1, vendor_information.oui_list.size());
+    EXPECT_FALSE(vendor_information.oui_list.find(kVendorOUI) ==
+                 vendor_information.oui_list.end());
+
+    WiFiEndpointRefPtr endpoint =
+        MakeOpenEndpoint(NULL, NULL, string(1, 0), "00:00:00:00:00:01");
+    endpoint->vendor_information_ = vendor_information;
+    map<string, string> vendor_stringmap(endpoint->GetVendorInformation());
+    EXPECT_FALSE(ContainsKey(vendor_stringmap, kVendorWPSManufacturerProperty));
+    EXPECT_FALSE(ContainsKey(vendor_stringmap, kVendorWPSModelNameProperty));
+    EXPECT_FALSE(ContainsKey(vendor_stringmap, kVendorWPSModelNumberProperty));
+    EXPECT_FALSE(ContainsKey(vendor_stringmap, kVendorWPSDeviceNameProperty));
+    EXPECT_EQ("aa-bb-cc", vendor_stringmap[kVendorOUIListProperty]);
+  }
+  {
+    ScopedMockLog log;
+    EXPECT_CALL(log, Log(logging::LOG_ERROR, _,
+                         HasSubstr("WPS element extends past containing PDU")))
+        .Times(1);
+    vector<uint8_t> ies;
+    vector<uint8_t> wps;
+    AddWPSElement(IEEE_80211::kWPSElementManufacturer, "foo", &wps);
+    wps.resize(wps.size() - 1);  // Cause an underrun in the data.
+    AddVendorIE(IEEE_80211::kOUIVendorMicrosoft,
+                IEEE_80211::kOUIMicrosoftWPS, wps, &ies);
+    Metrics::WiFiNetworkPhyMode phy_mode = Metrics::kWiFiNetworkPhyModeUndef;
+    WiFiEndpoint::VendorInformation vendor_information;
+    WiFiEndpoint::ParseIEs(MakeBSSPropertiesWithIEs(ies),
+                           &phy_mode, &vendor_information);
+    EXPECT_EQ("", vendor_information.wps_manufacturer);
+  }
+  {
+    vector<uint8_t> ies;
+    vector<uint8_t> wps;
+    const string kManufacturer("manufacturer");
+    const string kModelName("modelname");
+    const string kModelNumber("modelnumber");
+    const string kDeviceName("devicename");
+    AddWPSElement(IEEE_80211::kWPSElementManufacturer, kManufacturer, &wps);
+    AddWPSElement(IEEE_80211::kWPSElementModelName, kModelName, &wps);
+    AddWPSElement(IEEE_80211::kWPSElementModelNumber, kModelNumber, &wps);
+    AddWPSElement(IEEE_80211::kWPSElementDeviceName, kDeviceName, &wps);
+    AddVendorIE(IEEE_80211::kOUIVendorMicrosoft,
+                IEEE_80211::kOUIMicrosoftWPS, wps, &ies);
+    Metrics::WiFiNetworkPhyMode phy_mode = Metrics::kWiFiNetworkPhyModeUndef;
+    WiFiEndpoint::VendorInformation vendor_information;
+    WiFiEndpoint::ParseIEs(MakeBSSPropertiesWithIEs(ies),
+                           &phy_mode, &vendor_information);
+    EXPECT_EQ(kManufacturer, vendor_information.wps_manufacturer);
+    EXPECT_EQ(kModelName, vendor_information.wps_model_name);
+    EXPECT_EQ(kModelNumber, vendor_information.wps_model_number);
+    EXPECT_EQ(kDeviceName, vendor_information.wps_device_name);
+
+    WiFiEndpointRefPtr endpoint =
+        MakeOpenEndpoint(NULL, NULL, string(1, 0), "00:00:00:00:00:01");
+    endpoint->vendor_information_ = vendor_information;
+    map<string, string> vendor_stringmap(endpoint->GetVendorInformation());
+    EXPECT_EQ(kManufacturer, vendor_stringmap[kVendorWPSManufacturerProperty]);
+    EXPECT_EQ(kModelName, vendor_stringmap[kVendorWPSModelNameProperty]);
+    EXPECT_EQ(kModelNumber, vendor_stringmap[kVendorWPSModelNumberProperty]);
+    EXPECT_EQ(kDeviceName, vendor_stringmap[kVendorWPSDeviceNameProperty]);
+    EXPECT_FALSE(ContainsKey(vendor_stringmap, kVendorOUIListProperty));
+  }
+  {
+    vector<uint8_t> ies;
+    vector<uint8_t> wps;
+    const string kManufacturer("manufacturer");
+    const string kModelName("modelname");
+    AddWPSElement(IEEE_80211::kWPSElementManufacturer, kManufacturer, &wps);
+    wps.resize(wps.size() - 1);  // Insert a non-ASCII character in the WPS.
+    wps.push_back(0x80);
+    AddWPSElement(IEEE_80211::kWPSElementModelName, kModelName, &wps);
+    AddVendorIE(IEEE_80211::kOUIVendorMicrosoft,
+                IEEE_80211::kOUIMicrosoftWPS, wps, &ies);
+    Metrics::WiFiNetworkPhyMode phy_mode = Metrics::kWiFiNetworkPhyModeUndef;
+    WiFiEndpoint::VendorInformation vendor_information;
+    WiFiEndpoint::ParseIEs(MakeBSSPropertiesWithIEs(ies),
+                           &phy_mode, &vendor_information);
+    EXPECT_EQ("", vendor_information.wps_manufacturer);
+    EXPECT_EQ(kModelName, vendor_information.wps_model_name);
   }
 }
 
diff --git a/wifi_service.cc b/wifi_service.cc
index 68f528a..a602563 100644
--- a/wifi_service.cc
+++ b/wifi_service.cc
@@ -80,6 +80,8 @@
   store->RegisterConstUint16(flimflam::kWifiFrequency, &frequency_);
   store->RegisterConstUint16(flimflam::kWifiPhyMode, &physical_mode_);
   store->RegisterConstString(flimflam::kWifiBSsid, &bssid_);
+  store->RegisterConstStringmap(kWifiVendorInformationProperty,
+                                &vendor_information_);
 
   hex_ssid_ = base::HexEncode(ssid_.data(), ssid_.size());
   string ssid_string(
@@ -477,16 +479,15 @@
     }
   }
 
-  uint16 frequency;
-  int16 signal;
+  uint16 frequency = 0;
+  int16 signal = std::numeric_limits<int16>::min();
   string bssid;
-  if (!representative_endpoint) {
-    frequency = 0;
-    signal = std::numeric_limits<int16>::min();
-  } else {
+  Stringmap vendor_information;
+  if (representative_endpoint) {
     frequency = representative_endpoint->frequency();
     signal = representative_endpoint->signal_strength();
     bssid = representative_endpoint->bssid_string();
+    vendor_information = representative_endpoint->GetVendorInformation();
   }
 
   if (frequency_ != frequency) {
@@ -497,6 +498,11 @@
     bssid_ = bssid;
     adaptor()->EmitStringChanged(flimflam::kWifiBSsid, bssid_);
   }
+  if (vendor_information_ != vendor_information) {
+    vendor_information_ = vendor_information;
+    adaptor()->EmitStringmapChanged(kWifiVendorInformationProperty,
+                                    vendor_information_);
+  }
   SetStrength(SignalToStrength(signal));
 }
 
diff --git a/wifi_service.h b/wifi_service.h
index 07f34f0..fe2acf3 100644
--- a/wifi_service.h
+++ b/wifi_service.h
@@ -188,6 +188,7 @@
   std::string hex_ssid_;
   std::string storage_identifier_;
   std::string bssid_;
+  Stringmap vendor_information_;
 
   // Track whether or not we've warned about large signal values.
   // Used to avoid spamming the log.