//
// Copyright (C) 2012 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/wifi/wifi_endpoint.h"

#include <map>
#include <set>
#include <string>
#include <vector>

#include <base/stl_util.h>
#if defined(__ANDROID__)
#include <dbus/service_constants.h>
#else
#include <chromeos/dbus/service_constants.h>
#endif  // __ANDROID__
#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include "shill/mock_log.h"
#include "shill/net/ieee80211.h"
#include "shill/property_store_unittest.h"
#include "shill/refptr_types.h"
#include "shill/supplicant/wpa_supplicant.h"
#include "shill/tethering.h"
#include "shill/wifi/mock_wifi.h"

using std::map;
using std::set;
using std::string;
using std::vector;
using ::testing::_;
using ::testing::HasSubstr;
using ::testing::Mock;
using ::testing::NiceMock;

namespace shill {

class WiFiEndpointTest : public PropertyStoreTest {
 public:
  WiFiEndpointTest() : wifi_(
      new NiceMock<MockWiFi>(
          control_interface(),
          dispatcher(),
          metrics(),
          manager(),
          "wifi",
          "aabbccddeeff",  // fake mac
          0)) {}
  virtual ~WiFiEndpointTest() {}

 protected:
  vector<string> make_string_vector1(const string& str1) {
    vector<string> strvec;
    strvec.push_back(str1);
    return strvec;
  }

  vector<string> make_string_vector2(const string& str1, const string& str2) {
    vector<string> strvec;
    strvec.push_back(str1);
    strvec.push_back(str2);
    return strvec;
  }

  KeyValueStore make_key_management_args(
      vector<string> key_management_method_strings) {
    KeyValueStore args;
    args.SetStrings(WPASupplicant::kSecurityMethodPropertyKeyManagement,
                    key_management_method_strings);
    return args;
  }

  KeyValueStore make_privacy_args(bool is_private) {
    KeyValueStore props;
    props.SetBool(WPASupplicant::kPropertyPrivacy, is_private);
    return props;
  }

  KeyValueStore make_security_args(
      const string& security_protocol,
      const string& key_management_method) {
    KeyValueStore args;
    vector<string> key_management_method_vector;
    if (!key_management_method.empty()) {
      key_management_method_vector = make_string_vector1(key_management_method);
    }
    args.SetKeyValueStore(
        security_protocol,
        make_key_management_args(key_management_method_vector));
    return args;
  }

  const char* ParseSecurity(
    const KeyValueStore& properties) {
    WiFiEndpoint::SecurityFlags security_flags;
    return WiFiEndpoint::ParseSecurity(properties, &security_flags);
  }

  void AddIEWithData(uint8_t type, vector<uint8_t> data, vector<uint8_t>* ies) {
    ies->push_back(type);           // type
    ies->push_back(data.size());    // length
    ies->insert(ies->end(), data.begin(), data.end());
  }

  void AddIE(uint8_t type, vector<uint8_t>* ies) {
    AddIEWithData(type, vector<uint8_t>(1), ies);
  }

  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() < std::numeric_limits<uint16_t>::max());
    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());
  }

  KeyValueStore MakeBSSPropertiesWithIEs(const vector<uint8_t>& ies) {
    KeyValueStore properties;
    properties.SetUint8s(WPASupplicant::kBSSPropertyIEs, ies);
    return properties;
  }

  // Creates the RSN properties string (which still requires an information
  // element prefix).
  vector<uint8_t> MakeRSNProperties(uint16_t pairwise_count,
                                    uint16_t authkey_count,
                                    uint16_t capabilities) {
    vector<uint8_t> rsn(IEEE_80211::kRSNIECipherCountOffset +
                        IEEE_80211::kRSNIECipherCountLen * 2 +
                        IEEE_80211::kRSNIESelectorLen *
                        (pairwise_count + authkey_count) +
                        IEEE_80211::kRSNIECapabilitiesLen);

    // Set both cipher counts in little endian.
    rsn[IEEE_80211::kRSNIECipherCountOffset] = pairwise_count & 0xff;
    rsn[IEEE_80211::kRSNIECipherCountOffset + 1] = pairwise_count >> 8;
    size_t authkey_offset = IEEE_80211::kRSNIECipherCountOffset +
        IEEE_80211::kRSNIECipherCountLen +
        pairwise_count * IEEE_80211::kRSNIESelectorLen;
    rsn[authkey_offset] = authkey_count & 0xff;
    rsn[authkey_offset + 1] = authkey_count >> 8;

    // Set the little-endian capabilities field.
    size_t capabilities_offset = rsn.size() - 2;
    rsn[capabilities_offset] = capabilities & 0xff;
    rsn[capabilities_offset + 1] = capabilities >> 8;

    return rsn;
  }

  bool ParseIEs(const KeyValueStore& properties,
                Metrics::WiFiNetworkPhyMode* phy_mode,
                WiFiEndpoint::VendorInformation* vendor_information,
                bool* ieee80211w_required, std::string* country_code) {
    return WiFiEndpoint::ParseIEs(properties, phy_mode, vendor_information,
                                  ieee80211w_required, country_code);
  }

  void SetVendorInformation(
      const WiFiEndpointRefPtr& endpoint,
      const WiFiEndpoint::VendorInformation& vendor_information) {
    endpoint->vendor_information_ = vendor_information;
  }

  WiFiEndpoint* MakeEndpoint(ControlInterface* control_interface,
                             const WiFiRefPtr& wifi,
                             const std::string& ssid,
                             const std::string& bssid,
                             bool has_wpa_property,
                             bool has_rsn_property) {
    return WiFiEndpoint::MakeEndpoint(
        control_interface, wifi, ssid, bssid,
        WPASupplicant::kNetworkModeInfrastructure, 0, 0, has_wpa_property,
        has_rsn_property);
  }

  WiFiEndpoint* MakeOpenEndpoint(ControlInterface* control_interface,
                                 const WiFiRefPtr& wifi,
                                 const std::string& ssid,
                                 const std::string& bssid) {
    return WiFiEndpoint::MakeOpenEndpoint(
        control_interface, wifi, ssid, bssid,
        WPASupplicant::kNetworkModeInfrastructure, 0, 0);
  }

  scoped_refptr<MockWiFi> wifi() { return wifi_; }

 private:
  scoped_refptr<MockWiFi> wifi_;
};

TEST_F(WiFiEndpointTest, ParseKeyManagementMethodsEAP) {
  set<WiFiEndpoint::KeyManagement> parsed_methods;
  WiFiEndpoint::ParseKeyManagementMethods(
      make_key_management_args(make_string_vector1("something-eap")),
      &parsed_methods);
  EXPECT_TRUE(
      ContainsKey(parsed_methods, WiFiEndpoint::kKeyManagement802_1x));
  EXPECT_FALSE(
      ContainsKey(parsed_methods, WiFiEndpoint::kKeyManagementPSK));
}

TEST_F(WiFiEndpointTest, ParseKeyManagementMethodsPSK) {
  set<WiFiEndpoint::KeyManagement> parsed_methods;
  WiFiEndpoint::ParseKeyManagementMethods(
      make_key_management_args(make_string_vector1("something-psk")),
      &parsed_methods);
  EXPECT_TRUE(
      ContainsKey(parsed_methods, WiFiEndpoint::kKeyManagementPSK));
  EXPECT_FALSE(
      ContainsKey(parsed_methods, WiFiEndpoint::kKeyManagement802_1x));
}

TEST_F(WiFiEndpointTest, ParseKeyManagementMethodsEAPAndPSK) {
  set<WiFiEndpoint::KeyManagement> parsed_methods;
  WiFiEndpoint::ParseKeyManagementMethods(
      make_key_management_args(
          make_string_vector2("something-eap", "something-psk")),
      &parsed_methods);
  EXPECT_TRUE(
      ContainsKey(parsed_methods, WiFiEndpoint::kKeyManagement802_1x));
  EXPECT_TRUE(
      ContainsKey(parsed_methods, WiFiEndpoint::kKeyManagementPSK));
}

TEST_F(WiFiEndpointTest, ParseSecurityRSN802_1x) {
  EXPECT_STREQ(kSecurity8021x,
               ParseSecurity(make_security_args("RSN", "something-eap")));
}

TEST_F(WiFiEndpointTest, ParseSecurityWPA802_1x) {
  EXPECT_STREQ(kSecurity8021x,
               ParseSecurity(make_security_args("WPA", "something-eap")));
}

TEST_F(WiFiEndpointTest, ParseSecurityRSNPSK) {
  EXPECT_STREQ(kSecurityRsn,
               ParseSecurity(make_security_args("RSN", "something-psk")));
}

TEST_F(WiFiEndpointTest, ParseSecurityWPAPSK) {
  EXPECT_STREQ(kSecurityWpa,
               ParseSecurity(make_security_args("WPA", "something-psk")));
}

TEST_F(WiFiEndpointTest, ParseSecurityWEP) {
  EXPECT_STREQ(kSecurityWep, ParseSecurity(make_privacy_args(true)));
}

TEST_F(WiFiEndpointTest, ParseSecurityNone) {
  KeyValueStore top_params;
  EXPECT_STREQ(kSecurityNone, ParseSecurity(top_params));
}

TEST_F(WiFiEndpointTest, SSIDAndBSSIDString) {
  const char kSSID[] = "The SSID";
  const char kBSSID[] = "00:01:02:03:04:05";

  // The MakeOpenEndpoint method translates both of the above parameters into
  // binary equivalents before calling the Endpoint constructor.  Let's make
  // sure the Endpoint can translate them back losslessly to strings.
  WiFiEndpointRefPtr endpoint =
      MakeOpenEndpoint(nullptr, nullptr, kSSID, kBSSID);
  EXPECT_EQ(kSSID, endpoint->ssid_string());
  EXPECT_EQ(kBSSID, endpoint->bssid_string());
}

TEST_F(WiFiEndpointTest, SSIDWithNull) {
  WiFiEndpointRefPtr endpoint =
      MakeOpenEndpoint(nullptr, nullptr, string(1, 0), "00:00:00:00:00:01");
  EXPECT_EQ("?", endpoint->ssid_string());
}

TEST_F(WiFiEndpointTest, DeterminePhyModeFromFrequency) {
  {
    KeyValueStore properties;
    EXPECT_EQ(Metrics::kWiFiNetworkPhyMode11a,
              WiFiEndpoint::DeterminePhyModeFromFrequency(properties, 3200));
  }
  {
    KeyValueStore properties;
    vector<uint32_t> rates(1, 22000000);
    properties.SetUint32s(WPASupplicant::kBSSPropertyRates, rates);
    EXPECT_EQ(Metrics::kWiFiNetworkPhyMode11b,
              WiFiEndpoint::DeterminePhyModeFromFrequency(properties, 2400));
  }
  {
    KeyValueStore properties;
    vector<uint32_t> rates(1, 54000000);
    properties.SetUint32s(WPASupplicant::kBSSPropertyRates, rates);
    EXPECT_EQ(Metrics::kWiFiNetworkPhyMode11g,
              WiFiEndpoint::DeterminePhyModeFromFrequency(properties, 2400));
  }
  {
    KeyValueStore properties;
    vector<uint32_t> rates;
    properties.SetUint32s(WPASupplicant::kBSSPropertyRates, rates);
    EXPECT_EQ(Metrics::kWiFiNetworkPhyMode11b,
              WiFiEndpoint::DeterminePhyModeFromFrequency(properties, 2400));
  }
}

TEST_F(WiFiEndpointTest, ParseIEs) {
  {
    vector<uint8_t> ies;
    Metrics::WiFiNetworkPhyMode phy_mode = Metrics::kWiFiNetworkPhyModeUndef;
    WiFiEndpoint::VendorInformation vendor_information;
    EXPECT_FALSE(ParseIEs(MakeBSSPropertiesWithIEs(ies), &phy_mode,
                          &vendor_information, nullptr, nullptr));
    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;
    EXPECT_TRUE(ParseIEs(MakeBSSPropertiesWithIEs(ies), &phy_mode,
                         &vendor_information, nullptr, nullptr));
    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;
    EXPECT_TRUE(ParseIEs(MakeBSSPropertiesWithIEs(ies), &phy_mode,
                         &vendor_information, nullptr, nullptr));
    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;
    EXPECT_TRUE(ParseIEs(MakeBSSPropertiesWithIEs(ies), &phy_mode,
                         &vendor_information, nullptr, nullptr));
    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;
    EXPECT_TRUE(ParseIEs(MakeBSSPropertiesWithIEs(ies), &phy_mode,
                         &vendor_information, nullptr, nullptr));
    EXPECT_EQ(Metrics::kWiFiNetworkPhyMode11n, phy_mode);
  }
  {
    vector<uint8_t> ies;
    AddIE(IEEE_80211::kElemIdVHTCap, &ies);
    Metrics::WiFiNetworkPhyMode phy_mode = Metrics::kWiFiNetworkPhyModeUndef;
    WiFiEndpoint::VendorInformation vendor_information;
    EXPECT_TRUE(ParseIEs(MakeBSSPropertiesWithIEs(ies), &phy_mode,
                         &vendor_information, nullptr, nullptr));
    EXPECT_EQ(Metrics::kWiFiNetworkPhyMode11ac, phy_mode);
  }
  {
    vector<uint8_t> ies;
    AddIE(IEEE_80211::kElemIdVHTOperation, &ies);
    Metrics::WiFiNetworkPhyMode phy_mode = Metrics::kWiFiNetworkPhyModeUndef;
    WiFiEndpoint::VendorInformation vendor_information;
    EXPECT_TRUE(ParseIEs(MakeBSSPropertiesWithIEs(ies), &phy_mode,
                         &vendor_information, nullptr, nullptr));
    EXPECT_EQ(Metrics::kWiFiNetworkPhyMode11ac, phy_mode);
  }
  {
    vector<uint8_t> ies;
    AddIE(IEEE_80211::kElemIdErp, &ies);
    AddIE(IEEE_80211::kElemIdHTCap, &ies);
    AddIE(IEEE_80211::kElemIdVHTCap, &ies);
    Metrics::WiFiNetworkPhyMode phy_mode = Metrics::kWiFiNetworkPhyModeUndef;
    WiFiEndpoint::VendorInformation vendor_information;
    EXPECT_TRUE(ParseIEs(MakeBSSPropertiesWithIEs(ies), &phy_mode,
                         &vendor_information, nullptr, nullptr));
    EXPECT_EQ(Metrics::kWiFiNetworkPhyMode11ac, 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;
    ParseIEs(MakeBSSPropertiesWithIEs(ies), &phy_mode, &vendor_information,
             nullptr, nullptr);
  }
  {
    vector<uint8_t> ies;
    Metrics::WiFiNetworkPhyMode phy_mode = Metrics::kWiFiNetworkPhyModeUndef;
    WiFiEndpoint::VendorInformation vendor_information;
    ParseIEs(MakeBSSPropertiesWithIEs(ies), &phy_mode, &vendor_information,
             nullptr, nullptr);
    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_set.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;
    ParseIEs(MakeBSSPropertiesWithIEs(ies), &phy_mode, &vendor_information,
             nullptr, nullptr);
  }
  {
    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;
    ParseIEs(MakeBSSPropertiesWithIEs(ies), &phy_mode, &vendor_information,
             nullptr, nullptr);
    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_set.size());
    EXPECT_FALSE(vendor_information.oui_set.find(kVendorOUI) ==
                 vendor_information.oui_set.end());

    WiFiEndpointRefPtr endpoint =
        MakeOpenEndpoint(nullptr, nullptr, string(1, 0), "00:00:00:00:00:01");
    SetVendorInformation(endpoint, 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;
    ParseIEs(MakeBSSPropertiesWithIEs(ies), &phy_mode, &vendor_information,
             nullptr, nullptr);
    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;
    ParseIEs(MakeBSSPropertiesWithIEs(ies), &phy_mode, &vendor_information,
             nullptr, nullptr);
    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(nullptr, nullptr, string(1, 0), "00:00:00:00:00:01");
    SetVendorInformation(endpoint, 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;
    ParseIEs(MakeBSSPropertiesWithIEs(ies), &phy_mode, &vendor_information,
             nullptr, nullptr);
    EXPECT_EQ("", vendor_information.wps_manufacturer);
    EXPECT_EQ(kModelName, vendor_information.wps_model_name);
  }
}

TEST_F(WiFiEndpointTest, ParseWPACapabilities) {
  {
    vector<uint8_t> ies;
    vector<uint8_t> rsn;
    AddVendorIE(IEEE_80211::kOUIVendorMicrosoft, IEEE_80211::kOUIMicrosoftWPA,
                rsn, &ies);
    AddIEWithData(IEEE_80211::kElemIdRSN, rsn, &ies);
    Metrics::WiFiNetworkPhyMode phy_mode = Metrics::kWiFiNetworkPhyModeUndef;
    WiFiEndpoint::VendorInformation vendor_information;
    bool ieee80211w_required = false;
    ParseIEs(MakeBSSPropertiesWithIEs(ies), &phy_mode, &vendor_information,
             &ieee80211w_required, nullptr);
    EXPECT_FALSE(ieee80211w_required);
  }
  {
    vector<uint8_t> ies;
    vector<uint8_t> rsn = MakeRSNProperties(
        2, 3, ~IEEE_80211::kRSNCapabilityFrameProtectionRequired);
    AddVendorIE(IEEE_80211::kOUIVendorMicrosoft, IEEE_80211::kOUIMicrosoftWPA,
                rsn, &ies);
    AddIEWithData(IEEE_80211::kElemIdRSN, rsn, &ies);
    Metrics::WiFiNetworkPhyMode phy_mode = Metrics::kWiFiNetworkPhyModeUndef;
    WiFiEndpoint::VendorInformation vendor_information;
    bool ieee80211w_required = false;
    ParseIEs(MakeBSSPropertiesWithIEs(ies), &phy_mode, &vendor_information,
             &ieee80211w_required, nullptr);
    EXPECT_FALSE(ieee80211w_required);
  }
  {
    vector<uint8_t> ies;
    vector<uint8_t> rsn = MakeRSNProperties(
        2, 3, IEEE_80211::kRSNCapabilityFrameProtectionRequired);
    AddVendorIE(IEEE_80211::kOUIVendorMicrosoft, IEEE_80211::kOUIMicrosoftWPA,
                rsn, &ies);
    Metrics::WiFiNetworkPhyMode phy_mode = Metrics::kWiFiNetworkPhyModeUndef;
    WiFiEndpoint::VendorInformation vendor_information;
    bool ieee80211w_required = false;
    ParseIEs(MakeBSSPropertiesWithIEs(ies), &phy_mode, &vendor_information,
             &ieee80211w_required, nullptr);
    EXPECT_TRUE(ieee80211w_required);
  }
  {
    vector<uint8_t> ies;
    vector<uint8_t> rsn = MakeRSNProperties(
        8, 2, IEEE_80211::kRSNCapabilityFrameProtectionRequired);
    AddIEWithData(IEEE_80211::kElemIdRSN, rsn, &ies);
    Metrics::WiFiNetworkPhyMode phy_mode = Metrics::kWiFiNetworkPhyModeUndef;
    WiFiEndpoint::VendorInformation vendor_information;
    bool ieee80211w_required = false;
    ParseIEs(MakeBSSPropertiesWithIEs(ies), &phy_mode, &vendor_information,
             &ieee80211w_required, nullptr);
    EXPECT_TRUE(ieee80211w_required);
  }
  {
    vector<uint8_t> ies;
    vector<uint8_t> rsn = MakeRSNProperties(
        8, 2, IEEE_80211::kRSNCapabilityFrameProtectionRequired);
    rsn.resize(rsn.size() + 1);
    AddIEWithData(IEEE_80211::kElemIdRSN, rsn, &ies);
    Metrics::WiFiNetworkPhyMode phy_mode = Metrics::kWiFiNetworkPhyModeUndef;
    WiFiEndpoint::VendorInformation vendor_information;
    bool ieee80211w_required = false;
    ParseIEs(MakeBSSPropertiesWithIEs(ies), &phy_mode, &vendor_information,
             &ieee80211w_required, nullptr);
    EXPECT_TRUE(ieee80211w_required);
  }
  {
    vector<uint8_t> ies;
    vector<uint8_t> rsn = MakeRSNProperties(
        8, 2, IEEE_80211::kRSNCapabilityFrameProtectionRequired);
    rsn.resize(rsn.size() - 1);
    AddIEWithData(IEEE_80211::kElemIdRSN, rsn, &ies);
    Metrics::WiFiNetworkPhyMode phy_mode = Metrics::kWiFiNetworkPhyModeUndef;
    WiFiEndpoint::VendorInformation vendor_information;
    bool ieee80211w_required = false;
    ParseIEs(MakeBSSPropertiesWithIEs(ies), &phy_mode, &vendor_information,
             &ieee80211w_required, nullptr);
    EXPECT_FALSE(ieee80211w_required);
  }
  {
    vector<uint8_t> ies;
    vector<uint8_t> rsn0 = MakeRSNProperties(
        1, 1, IEEE_80211::kRSNCapabilityFrameProtectionRequired);
    AddIEWithData(IEEE_80211::kElemIdRSN, rsn0, &ies);
    vector<uint8_t> rsn1 = MakeRSNProperties(1, 1, 0);
    AddIEWithData(IEEE_80211::kElemIdRSN, rsn1, &ies);
    Metrics::WiFiNetworkPhyMode phy_mode = Metrics::kWiFiNetworkPhyModeUndef;
    WiFiEndpoint::VendorInformation vendor_information;
    bool ieee80211w_required = false;
    ParseIEs(MakeBSSPropertiesWithIEs(ies), &phy_mode, &vendor_information,
             &ieee80211w_required, nullptr);
    EXPECT_TRUE(ieee80211w_required);
  }
}

TEST_F(WiFiEndpointTest, ParseCountryCode) {
  {
    vector<uint8_t> ies;
    Metrics::WiFiNetworkPhyMode phy_mode = Metrics::kWiFiNetworkPhyModeUndef;
    WiFiEndpoint::VendorInformation vendor_information;
    string country_code;
    ParseIEs(MakeBSSPropertiesWithIEs(ies), &phy_mode, &vendor_information,
             nullptr, &country_code);
    EXPECT_TRUE(country_code.empty());
  }
  {
    const string kCountryCode("G");
    const vector<uint8_t> kCountryCodeAsVector(
        kCountryCode.begin(), kCountryCode.end());
    vector<uint8_t> ies;
    AddIEWithData(IEEE_80211::kElemIdCountry, kCountryCodeAsVector, &ies);
    Metrics::WiFiNetworkPhyMode phy_mode = Metrics::kWiFiNetworkPhyModeUndef;
    WiFiEndpoint::VendorInformation vendor_information;
    string country_code;
    ParseIEs(MakeBSSPropertiesWithIEs(ies), &phy_mode, &vendor_information,
             nullptr, &country_code);
    EXPECT_TRUE(country_code.empty());
  }
  {
    const string kCountryCode("GO");
    const vector<uint8_t> kCountryCodeAsVector(
        kCountryCode.begin(), kCountryCode.end());
    vector<uint8_t> ies;
    AddIEWithData(IEEE_80211::kElemIdCountry, kCountryCodeAsVector, &ies);
    Metrics::WiFiNetworkPhyMode phy_mode = Metrics::kWiFiNetworkPhyModeUndef;
    WiFiEndpoint::VendorInformation vendor_information;
    string country_code;
    ParseIEs(MakeBSSPropertiesWithIEs(ies), &phy_mode, &vendor_information,
             nullptr, &country_code);
    EXPECT_EQ(kCountryCode, country_code);
  }
  {
    const string kCountryCode("GOO");
    const vector<uint8_t> kCountryCodeAsVector(
        kCountryCode.begin(), kCountryCode.end());
    vector<uint8_t> ies;
    AddIEWithData(IEEE_80211::kElemIdCountry, kCountryCodeAsVector, &ies);
    Metrics::WiFiNetworkPhyMode phy_mode = Metrics::kWiFiNetworkPhyModeUndef;
    WiFiEndpoint::VendorInformation vendor_information;
    string country_code;
    ParseIEs(MakeBSSPropertiesWithIEs(ies), &phy_mode, &vendor_information,
             nullptr, &country_code);
    EXPECT_EQ(string(kCountryCode, 0, 2), country_code);
  }
}

TEST_F(WiFiEndpointTest, PropertiesChangedNone) {
  WiFiEndpointRefPtr endpoint =
      MakeOpenEndpoint(nullptr, wifi(), "ssid", "00:00:00:00:00:01");
  EXPECT_EQ(kModeManaged, endpoint->network_mode());
  EXPECT_EQ(kSecurityNone, endpoint->security_mode());
  EXPECT_CALL(*wifi(), NotifyEndpointChanged(_)).Times(0);
  KeyValueStore no_changed_properties;
  endpoint->PropertiesChanged(no_changed_properties);
  EXPECT_EQ(kModeManaged, endpoint->network_mode());
  EXPECT_EQ(kSecurityNone, endpoint->security_mode());
}

TEST_F(WiFiEndpointTest, PropertiesChangedStrength) {
  WiFiEndpointRefPtr endpoint =
      MakeOpenEndpoint(nullptr, wifi(), "ssid", "00:00:00:00:00:01");
  KeyValueStore changed_properties;
  int16_t signal_strength = 10;

  EXPECT_NE(signal_strength, endpoint->signal_strength());
  changed_properties.SetInt16(WPASupplicant::kBSSPropertySignal,
                              signal_strength);

  EXPECT_CALL(*wifi(), NotifyEndpointChanged(_));
  endpoint->PropertiesChanged(changed_properties);
  EXPECT_EQ(signal_strength, endpoint->signal_strength());
}

TEST_F(WiFiEndpointTest, PropertiesChangedNetworkMode) {
  WiFiEndpointRefPtr endpoint =
      MakeOpenEndpoint(nullptr, wifi(), "ssid", "00:00:00:00:00:01");
  EXPECT_EQ(kModeManaged, endpoint->network_mode());
  EXPECT_CALL(*wifi(), NotifyEndpointChanged(_)).Times(1);
  KeyValueStore changed_properties;
  changed_properties.SetString(WPASupplicant::kBSSPropertyMode,
                               WPASupplicant::kNetworkModeAdHoc);
  endpoint->PropertiesChanged(changed_properties);
  EXPECT_EQ(kModeAdhoc, endpoint->network_mode());
}

TEST_F(WiFiEndpointTest, PropertiesChangedSecurityMode) {
  WiFiEndpointRefPtr endpoint =
      MakeOpenEndpoint(nullptr, wifi(), "ssid", "00:00:00:00:00:01");
  EXPECT_EQ(kSecurityNone, endpoint->security_mode());

  // Upgrade to WEP if privacy flag is added.
  EXPECT_CALL(*wifi(), NotifyEndpointChanged(_)).Times(1);
  endpoint->PropertiesChanged(make_privacy_args(true));
  Mock::VerifyAndClearExpectations(wifi().get());
  EXPECT_EQ(kSecurityWep, endpoint->security_mode());

  // Make sure we don't downgrade if no interesting arguments arrive.
  KeyValueStore no_changed_properties;
  EXPECT_CALL(*wifi(), NotifyEndpointChanged(_)).Times(0);
  endpoint->PropertiesChanged(no_changed_properties);
  Mock::VerifyAndClearExpectations(wifi().get());
  EXPECT_EQ(kSecurityWep, endpoint->security_mode());

  // Another upgrade to 802.1x.
  EXPECT_CALL(*wifi(), NotifyEndpointChanged(_)).Times(1);
  endpoint->PropertiesChanged(make_security_args("RSN", "something-eap"));
  Mock::VerifyAndClearExpectations(wifi().get());
  EXPECT_EQ(kSecurity8021x, endpoint->security_mode());

  // Add WPA-PSK, however this is trumped by RSN 802.1x above, so we don't
  // change our security nor do we notify anyone.
  EXPECT_CALL(*wifi(), NotifyEndpointChanged(_)).Times(0);
  endpoint->PropertiesChanged(make_security_args("WPA", "something-psk"));
  Mock::VerifyAndClearExpectations(wifi().get());
  EXPECT_EQ(kSecurity8021x, endpoint->security_mode());

  // If nothing changes, we should stay the same.
  EXPECT_CALL(*wifi(), NotifyEndpointChanged(_)).Times(0);
  endpoint->PropertiesChanged(no_changed_properties);
  Mock::VerifyAndClearExpectations(wifi().get());
  EXPECT_EQ(kSecurity8021x, endpoint->security_mode());

  // However, if the BSS updates to no longer support 802.1x, we degrade
  // to WPA.
  EXPECT_CALL(*wifi(), NotifyEndpointChanged(_)).Times(1);
  endpoint->PropertiesChanged(make_security_args("RSN", ""));
  Mock::VerifyAndClearExpectations(wifi().get());
  EXPECT_EQ(kSecurityWpa, endpoint->security_mode());

  // Losing WPA brings us back to WEP (since the privacy flag hasn't changed).
  EXPECT_CALL(*wifi(), NotifyEndpointChanged(_)).Times(1);
  endpoint->PropertiesChanged(make_security_args("WPA", ""));
  Mock::VerifyAndClearExpectations(wifi().get());
  EXPECT_EQ(kSecurityWep, endpoint->security_mode());

  // From WEP to open security.
  EXPECT_CALL(*wifi(), NotifyEndpointChanged(_)).Times(1);
  endpoint->PropertiesChanged(make_privacy_args(false));
  Mock::VerifyAndClearExpectations(wifi().get());
  EXPECT_EQ(kSecurityNone, endpoint->security_mode());
}

TEST_F(WiFiEndpointTest, HasRsnWpaProperties) {
  {
    WiFiEndpointRefPtr endpoint = MakeEndpoint(
        nullptr, wifi(), "ssid", "00:00:00:00:00:01", false, false);
    EXPECT_FALSE(endpoint->has_wpa_property());
    EXPECT_FALSE(endpoint->has_rsn_property());
  }
  {
    WiFiEndpointRefPtr endpoint =
        MakeEndpoint(nullptr, wifi(), "ssid", "00:00:00:00:00:01", true, false);
    EXPECT_TRUE(endpoint->has_wpa_property());
    EXPECT_FALSE(endpoint->has_rsn_property());
  }
  {
    WiFiEndpointRefPtr endpoint =
        MakeEndpoint(nullptr, wifi(), "ssid", "00:00:00:00:00:01", false, true);
    EXPECT_FALSE(endpoint->has_wpa_property());
    EXPECT_TRUE(endpoint->has_rsn_property());
  }
  {
    // Both can be true.
    WiFiEndpointRefPtr endpoint =
        MakeEndpoint(nullptr, wifi(), "ssid", "00:00:00:00:00:01", true, true);
    EXPECT_TRUE(endpoint->has_wpa_property());
    EXPECT_TRUE(endpoint->has_rsn_property());
  }
}

TEST_F(WiFiEndpointTest, HasTetheringSignature) {
  {
    WiFiEndpointRefPtr endpoint = MakeEndpoint(
        nullptr, wifi(), "ssid", "02:1a:11:00:00:01", false, false);
    EXPECT_TRUE(endpoint->has_tethering_signature());
  }
  {
    WiFiEndpointRefPtr endpoint = MakeEndpoint(
        nullptr, wifi(), "ssid", "02:1a:10:00:00:01", false, false);
    EXPECT_FALSE(endpoint->has_tethering_signature());
    endpoint->vendor_information_.oui_set.insert(Tethering::kIosOui);
    endpoint->CheckForTetheringSignature();
    EXPECT_TRUE(endpoint->has_tethering_signature());
  }
  {
    WiFiEndpointRefPtr endpoint = MakeEndpoint(
        nullptr, wifi(), "ssid", "04:1a:10:00:00:01", false, false);
    EXPECT_FALSE(endpoint->has_tethering_signature());
    endpoint->vendor_information_.oui_set.insert(Tethering::kIosOui);
    endpoint->CheckForTetheringSignature();
    EXPECT_FALSE(endpoint->has_tethering_signature());
  }
}

}  // namespace shill