//
// 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/vpn/openvpn_driver.h"

#include <algorithm>

#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#if defined(__ANDROID__)
#include <dbus/service_constants.h>
#else
#include <chromeos/dbus/service_constants.h>
#endif  // __ANDROID__
#include <gtest/gtest.h>

#include "shill/error.h"
#include "shill/ipconfig.h"
#include "shill/logging.h"
#include "shill/mock_adaptors.h"
#include "shill/mock_certificate_file.h"
#include "shill/mock_device_info.h"
#include "shill/mock_event_dispatcher.h"
#include "shill/mock_manager.h"
#include "shill/mock_metrics.h"
#include "shill/mock_process_manager.h"
#include "shill/mock_service.h"
#include "shill/mock_store.h"
#include "shill/mock_virtual_device.h"
#include "shill/nice_mock_control.h"
#include "shill/rpc_task.h"
#include "shill/technology.h"
#include "shill/virtual_device.h"
#include "shill/vpn/mock_openvpn_management_server.h"
#include "shill/vpn/mock_vpn_service.h"
#include "shill/vpn/vpn_service.h"

using base::FilePath;
using base::WeakPtr;
using std::map;
using std::string;
using std::vector;
using testing::_;
using testing::AnyNumber;
using testing::DoAll;
using testing::ElementsAreArray;
using testing::Field;
using testing::Mock;
using testing::Ne;
using testing::NiceMock;
using testing::Return;
using testing::SetArgumentPointee;
using testing::StrictMock;

namespace shill {

struct AuthenticationExpectations {
  AuthenticationExpectations()
      : remote_authentication_type(Metrics::kVpnRemoteAuthenticationTypeMax) {}
  AuthenticationExpectations(
      const string& ca_cert_in,
      const string& client_cert_in,
      const string& user_in,
      const string& otp_in,
      const string& token_in,
      Metrics::VpnRemoteAuthenticationType remote_authentication_type_in,
      const vector<Metrics::VpnUserAuthenticationType>
          &user_authentication_types_in)
      : ca_cert(ca_cert_in),
        client_cert(client_cert_in),
        user(user_in),
        otp(otp_in),
        token(token_in),
        remote_authentication_type(remote_authentication_type_in),
        user_authentication_types(user_authentication_types_in) {}
  string ca_cert;
  string client_cert;
  string user;
  string otp;
  string token;
  Metrics::VpnRemoteAuthenticationType remote_authentication_type;
  vector<Metrics::VpnUserAuthenticationType> user_authentication_types;
};

class OpenVPNDriverTest
    : public testing::TestWithParam<AuthenticationExpectations>,
      public RPCTaskDelegate {
 public:
  OpenVPNDriverTest()
      : device_info_(&control_, &dispatcher_, &metrics_, &manager_),
        metrics_(&dispatcher_),
        manager_(&control_, &dispatcher_, &metrics_),
        driver_(new OpenVPNDriver(&control_, &dispatcher_, &metrics_, &manager_,
                                  &device_info_, &process_manager_)),
        service_(new MockVPNService(&control_, &dispatcher_, &metrics_,
                                    &manager_, driver_)),
        device_(new MockVirtualDevice(
            &control_, &dispatcher_, &metrics_, &manager_,
            kInterfaceName, kInterfaceIndex, Technology::kVPN)),
        certificate_file_(new MockCertificateFile()),
        extra_certificates_file_(new MockCertificateFile()),
        management_server_(new NiceMock<MockOpenVPNManagementServer>()) {
    driver_->management_server_.reset(management_server_);
    driver_->certificate_file_.reset(certificate_file_);  // Passes ownership.
    driver_->extra_certificates_file_.reset(
        extra_certificates_file_);  // Passes ownership.
    CHECK(temporary_directory_.CreateUniqueTempDir());
    driver_->openvpn_config_directory_ =
        temporary_directory_.path().Append(kOpenVPNConfigDirectory);
  }

  virtual ~OpenVPNDriverTest() {}

  virtual void TearDown() {
    driver_->default_service_callback_tag_ = 0;
    driver_->pid_ = 0;
    driver_->device_ = nullptr;
    driver_->service_ = nullptr;
    if (!lsb_release_file_.empty()) {
      EXPECT_TRUE(base::DeleteFile(lsb_release_file_, false));
      lsb_release_file_.clear();
    }
  }

 protected:
  static const char kOption[];
  static const char kProperty[];
  static const char kValue[];
  static const char kOption2[];
  static const char kProperty2[];
  static const char kValue2[];
  static const char kGateway1[];
  static const char kNetmask1[];
  static const char kNetwork1[];
  static const char kGateway2[];
  static const char kNetmask2[];
  static const char kNetwork2[];
  static const char kInterfaceName[];
  static const int kInterfaceIndex;
  static const char kOpenVPNConfigDirectory[];

  void SetArg(const string& arg, const string& value) {
    driver_->args()->SetString(arg, value);
  }

  void SetArgArray(const string& arg, const vector<string>& value) {
    driver_->args()->SetStrings(arg, value);
  }

  KeyValueStore* GetArgs() {
    return driver_->args();
  }

  KeyValueStore GetProviderProperties(const PropertyStore& store) {
    KeyValueStore props;
    Error error;
    EXPECT_TRUE(
        store.GetKeyValueStoreProperty(kProviderProperty, &props, &error));
    return props;
  }

  void RemoveStringArg(const string& arg) {
    driver_->args()->RemoveString(arg);
  }

  const ServiceRefPtr& GetSelectedService() {
    return device_->selected_service();
  }

  bool InitManagementChannelOptions(
      vector<vector<string>>* options, Error* error) {
    return driver_->InitManagementChannelOptions(options, error);
  }

  Sockets* GetSockets() {
    return &driver_->sockets_;
  }

  void SetDevice(const VirtualDeviceRefPtr& device) {
    driver_->device_ = device;
  }

  void SetService(const VPNServiceRefPtr& service) {
    driver_->service_ = service;
  }

  VPNServiceRefPtr GetService() {
    return driver_->service_;
  }

  void OnConnectionDisconnected() {
    driver_->OnConnectionDisconnected();
  }

  void OnConnectTimeout() {
    driver_->OnConnectTimeout();
  }

  void StartConnectTimeout(int timeout_seconds) {
    driver_->StartConnectTimeout(timeout_seconds);
  }

  bool IsConnectTimeoutStarted() {
    return driver_->IsConnectTimeoutStarted();
  }

  static int GetDefaultConnectTimeoutSeconds() {
    return OpenVPNDriver::kDefaultConnectTimeoutSeconds;
  }

  static int GetReconnectOfflineTimeoutSeconds() {
    return OpenVPNDriver::kReconnectOfflineTimeoutSeconds;
  }

  static int GetReconnectTLSErrorTimeoutSeconds() {
    return OpenVPNDriver::kReconnectTLSErrorTimeoutSeconds;
  }

  static int GetReconnectTimeoutSeconds(OpenVPNDriver::ReconnectReason reason) {
    return OpenVPNDriver::GetReconnectTimeoutSeconds(reason);
  }

  void SetClientState(const string& state) {
    management_server_->state_ = state;
  }

  // Used to assert that a flag appears in the options.
  void ExpectInFlags(const vector<vector<string>>& options, const string& flag);
  void ExpectInFlags(const vector<vector<string>>& options, const string& flag,
                     const string& value);
  void ExpectInFlags(const vector<vector<string>>& options,
                     const vector<string>& arguments);
  void ExpectNotInFlags(const vector<vector<string>>& options,
                        const string& flag);

  void SetupLSBRelease();

  // Inherited from RPCTaskDelegate.
  virtual void GetLogin(string* user, string* password);
  virtual void Notify(const string& reason, const map<string, string>& dict);

  NiceMockControl control_;
  NiceMock<MockDeviceInfo> device_info_;
  MockEventDispatcher dispatcher_;
  MockMetrics metrics_;
  MockProcessManager process_manager_;
  MockManager manager_;
  OpenVPNDriver* driver_;  // Owned by |service_|.
  scoped_refptr<MockVPNService> service_;
  scoped_refptr<MockVirtualDevice> device_;
  MockCertificateFile* certificate_file_;  // Owned by |driver_|.
  MockCertificateFile* extra_certificates_file_;  // Owned by |driver_|.
  base::ScopedTempDir temporary_directory_;

  // Owned by |driver_|.
  NiceMock<MockOpenVPNManagementServer>* management_server_;

  FilePath lsb_release_file_;
};

const char OpenVPNDriverTest::kOption[] = "openvpn-option";
const char OpenVPNDriverTest::kProperty[] = "OpenVPN.SomeProperty";
const char OpenVPNDriverTest::kValue[] = "some-property-value";
const char OpenVPNDriverTest::kOption2[] = "openvpn-option2";
const char OpenVPNDriverTest::kProperty2[] = "OpenVPN.SomeProperty2";
const char OpenVPNDriverTest::kValue2[] = "some-property-value2";
const char OpenVPNDriverTest::kGateway1[] = "10.242.2.13";
const char OpenVPNDriverTest::kNetmask1[] = "255.255.255.255";
const char OpenVPNDriverTest::kNetwork1[] = "10.242.2.1";
const char OpenVPNDriverTest::kGateway2[] = "10.242.2.14";
const char OpenVPNDriverTest::kNetmask2[] = "255.255.0.0";
const char OpenVPNDriverTest::kNetwork2[] = "192.168.0.0";
const char OpenVPNDriverTest::kInterfaceName[] = "tun0";
const int OpenVPNDriverTest::kInterfaceIndex = 123;
const char OpenVPNDriverTest::kOpenVPNConfigDirectory[] = "openvpn";

void OpenVPNDriverTest::GetLogin(string* /*user*/, string* /*password*/) {}

void OpenVPNDriverTest::Notify(const string& /*reason*/,
                               const map<string, string>& /*dict*/) {}

void OpenVPNDriverTest::ExpectInFlags(const vector<vector<string>>& options,
                                      const string& flag) {
  ExpectInFlags(options, vector<string> { flag });
}

void OpenVPNDriverTest::ExpectInFlags(const vector<vector<string>>& options,
                                      const string& flag,
                                      const string& value) {
  ExpectInFlags(options, vector<string> { flag, value });
}

void OpenVPNDriverTest::ExpectInFlags(const vector<vector<string>>& options,
                                      const vector<string>& arguments) {
  EXPECT_TRUE(std::find(options.begin(), options.end(), arguments) !=
              options.end());
}

void OpenVPNDriverTest::ExpectNotInFlags(const vector<vector<string>>& options,
                                         const string& flag) {
  for (const auto& option : options) {
    EXPECT_NE(flag, option[0]);
  }
}

void OpenVPNDriverTest::SetupLSBRelease() {
  static const char kLSBReleaseContents[] =
      "\n"
      "=\n"
      "foo=\n"
      "=bar\n"
      "zoo==\n"
      "CHROMEOS_RELEASE_BOARD=x86-alex\n"
      "CHROMEOS_RELEASE_NAME=Chromium OS\n"
      "CHROMEOS_RELEASE_VERSION=2202.0\n";
  EXPECT_TRUE(base::CreateTemporaryFile(&lsb_release_file_));
  EXPECT_EQ(arraysize(kLSBReleaseContents),
            base::WriteFile(lsb_release_file_,
                            kLSBReleaseContents,
                            arraysize(kLSBReleaseContents)));
  EXPECT_EQ(OpenVPNDriver::kLSBReleaseFile, driver_->lsb_release_file_.value());
  driver_->lsb_release_file_ = lsb_release_file_;
}

TEST_F(OpenVPNDriverTest, Connect) {
  EXPECT_CALL(*service_, SetState(Service::kStateConfiguring));
  const string interface = kInterfaceName;
  EXPECT_CALL(device_info_, CreateTunnelInterface(_))
      .WillOnce(DoAll(SetArgumentPointee<0>(interface), Return(true)));
  Error error;
  driver_->Connect(service_, &error);
  EXPECT_TRUE(error.IsSuccess());
  EXPECT_EQ(kInterfaceName, driver_->tunnel_interface_);
  EXPECT_TRUE(driver_->IsConnectTimeoutStarted());
}

TEST_F(OpenVPNDriverTest, ConnectTunnelFailure) {
  EXPECT_CALL(*service_, SetState(Service::kStateConfiguring));
  EXPECT_CALL(device_info_, CreateTunnelInterface(_)).WillOnce(Return(false));
  EXPECT_CALL(*service_, SetFailure(Service::kFailureInternal));
  Error error;
  driver_->Connect(service_, &error);
  EXPECT_EQ(Error::kInternalError, error.type());
  EXPECT_TRUE(driver_->tunnel_interface_.empty());
  EXPECT_FALSE(driver_->IsConnectTimeoutStarted());
}

namespace {
MATCHER_P(IsIPAddress, address, "") {
  IPAddress ip_address(IPAddress::kFamilyIPv4);
  EXPECT_TRUE(ip_address.SetAddressFromString(address));
  return ip_address.Equals(arg);
}
}  // namespace

TEST_F(OpenVPNDriverTest, Notify) {
  map<string, string> config;
  driver_->service_ = service_;
  driver_->device_ = device_;
  StartConnectTimeout(0);
  EXPECT_CALL(*device_,
              UpdateIPConfig(Field(&IPConfig::Properties::address, "")));
  driver_->Notify("up", config);
  EXPECT_FALSE(driver_->IsConnectTimeoutStarted());
  EXPECT_TRUE(GetSelectedService().get() == service_.get());

  // Tests that existing properties are reused if no new ones provided.
  driver_->ip_properties_.address = "1.2.3.4";
  EXPECT_CALL(*device_,
              UpdateIPConfig(Field(&IPConfig::Properties::address, "1.2.3.4")));
  driver_->Notify("up", config);
}

TEST_P(OpenVPNDriverTest, NotifyUMA) {
  map<string, string> config;
  driver_->service_ = service_;
  driver_->device_ = device_;

  // Check that UMA metrics are emitted on Notify.
  EXPECT_CALL(*device_, UpdateIPConfig(_));
  EXPECT_CALL(metrics_, SendEnumToUMA(
      Metrics::kMetricVpnDriver,
      Metrics::kVpnDriverOpenVpn,
      Metrics::kMetricVpnDriverMax));
  EXPECT_CALL(metrics_, SendEnumToUMA(
      Metrics::kMetricVpnRemoteAuthenticationType,
      GetParam().remote_authentication_type,
      Metrics::kVpnRemoteAuthenticationTypeMax));
  for (const auto& authentication_type : GetParam().user_authentication_types) {
    EXPECT_CALL(metrics_, SendEnumToUMA(
        Metrics::kMetricVpnUserAuthenticationType,
        authentication_type,
        Metrics::kVpnUserAuthenticationTypeMax));
  }

  Error unused_error;
  PropertyStore store;
  driver_->InitPropertyStore(&store);
  if (!GetParam().ca_cert.empty()) {
    store.SetStringsProperty(kOpenVPNCaCertPemProperty,
                             vector<string>{ GetParam().ca_cert },
                             &unused_error);
  }
  if (!GetParam().client_cert.empty()) {
    store.SetStringProperty(kOpenVPNClientCertIdProperty,
                            GetParam().client_cert,
                            &unused_error);
  }
  if (!GetParam().user.empty()) {
    store.SetStringProperty(kOpenVPNUserProperty, GetParam().user,
                            &unused_error);
  }
  if (!GetParam().otp.empty()) {
    store.SetStringProperty(kOpenVPNOTPProperty, GetParam().otp, &unused_error);
  }
  if (!GetParam().token.empty()) {
    store.SetStringProperty(kOpenVPNTokenProperty, GetParam().token,
                            &unused_error);
  }
  driver_->Notify("up", config);
  Mock::VerifyAndClearExpectations(&metrics_);
}

INSTANTIATE_TEST_CASE_P(
    OpenVPNDriverAuthenticationTypes,
    OpenVPNDriverTest,
    ::testing::Values(
      AuthenticationExpectations(
          "", "", "", "", "",
          Metrics::kVpnRemoteAuthenticationTypeOpenVpnDefault,
          vector<Metrics::VpnUserAuthenticationType> {
              Metrics::kVpnUserAuthenticationTypeOpenVpnNone }),
      AuthenticationExpectations(
          "", "client_cert", "", "", "",
          Metrics::kVpnRemoteAuthenticationTypeOpenVpnDefault,
          vector<Metrics::VpnUserAuthenticationType> {
              Metrics::kVpnUserAuthenticationTypeOpenVpnCertificate }),
      AuthenticationExpectations(
          "", "client_cert", "user", "", "",
          Metrics::kVpnRemoteAuthenticationTypeOpenVpnDefault,
          vector<Metrics::VpnUserAuthenticationType> {
              Metrics::kVpnUserAuthenticationTypeOpenVpnCertificate,
              Metrics::kVpnUserAuthenticationTypeOpenVpnUsernamePassword }),
      AuthenticationExpectations(
          "", "", "user", "", "",
          Metrics::kVpnRemoteAuthenticationTypeOpenVpnDefault,
          vector<Metrics::VpnUserAuthenticationType> {
              Metrics::kVpnUserAuthenticationTypeOpenVpnUsernamePassword }),
      AuthenticationExpectations(
          "", "client_cert", "user", "otp", "",
          Metrics::kVpnRemoteAuthenticationTypeOpenVpnDefault,
          vector<Metrics::VpnUserAuthenticationType> {
              Metrics::kVpnUserAuthenticationTypeOpenVpnCertificate,
              Metrics::kVpnUserAuthenticationTypeOpenVpnUsernamePassword,
              Metrics::kVpnUserAuthenticationTypeOpenVpnUsernamePasswordOtp }),
      AuthenticationExpectations(
          "", "client_cert", "user", "otp", "token",
          Metrics::kVpnRemoteAuthenticationTypeOpenVpnDefault,
          vector<Metrics::VpnUserAuthenticationType> {
              Metrics::kVpnUserAuthenticationTypeOpenVpnCertificate,
              Metrics::kVpnUserAuthenticationTypeOpenVpnUsernamePassword,
              Metrics::kVpnUserAuthenticationTypeOpenVpnUsernamePasswordOtp,
              Metrics::kVpnUserAuthenticationTypeOpenVpnUsernameToken }),
      AuthenticationExpectations(
          "ca_cert", "client_cert", "user", "otp", "token",
          Metrics::kVpnRemoteAuthenticationTypeOpenVpnCertificate,
          vector<Metrics::VpnUserAuthenticationType> {
              Metrics::kVpnUserAuthenticationTypeOpenVpnCertificate,
              Metrics::kVpnUserAuthenticationTypeOpenVpnUsernamePassword,
              Metrics::kVpnUserAuthenticationTypeOpenVpnUsernamePasswordOtp,
              Metrics::kVpnUserAuthenticationTypeOpenVpnUsernameToken })));

TEST_F(OpenVPNDriverTest, NotifyFail) {
  map<string, string> dict;
  driver_->device_ = device_;
  StartConnectTimeout(0);
  EXPECT_CALL(*device_, DropConnection());
  driver_->Notify("fail", dict);
  EXPECT_TRUE(driver_->IsConnectTimeoutStarted());
}

TEST_F(OpenVPNDriverTest, GetRouteOptionEntry) {
  OpenVPNDriver::RouteOptions routes;
  EXPECT_EQ(nullptr, OpenVPNDriver::GetRouteOptionEntry("foo", "bar", &routes));
  EXPECT_TRUE(routes.empty());
  EXPECT_EQ(nullptr, OpenVPNDriver::GetRouteOptionEntry("foo", "foo", &routes));
  EXPECT_TRUE(routes.empty());
  EXPECT_EQ(nullptr,
            OpenVPNDriver::GetRouteOptionEntry("foo", "fooz", &routes));
  EXPECT_TRUE(routes.empty());
  IPConfig::Route* route =
      OpenVPNDriver::GetRouteOptionEntry("foo", "foo12", &routes);
  EXPECT_EQ(1, routes.size());
  EXPECT_EQ(route, &routes[12]);
  route = OpenVPNDriver::GetRouteOptionEntry("foo", "foo13", &routes);
  EXPECT_EQ(2, routes.size());
  EXPECT_EQ(route, &routes[13]);
}

TEST_F(OpenVPNDriverTest, ParseRouteOption) {
  OpenVPNDriver::RouteOptions routes;
  OpenVPNDriver::ParseRouteOption("foo", "bar", &routes);
  EXPECT_TRUE(routes.empty());
  OpenVPNDriver::ParseRouteOption("gateway_2", kGateway2, &routes);
  OpenVPNDriver::ParseRouteOption("netmask_2", kNetmask2, &routes);
  OpenVPNDriver::ParseRouteOption("network_2", kNetwork2, &routes);
  EXPECT_EQ(1, routes.size());
  OpenVPNDriver::ParseRouteOption("gateway_1", kGateway1, &routes);
  OpenVPNDriver::ParseRouteOption("netmask_1", kNetmask1, &routes);
  OpenVPNDriver::ParseRouteOption("network_1", kNetwork1, &routes);
  EXPECT_EQ(2, routes.size());
  EXPECT_EQ(kGateway1, routes[1].gateway);
  EXPECT_EQ(kNetmask1, routes[1].netmask);
  EXPECT_EQ(kNetwork1, routes[1].host);
  EXPECT_EQ(kGateway2, routes[2].gateway);
  EXPECT_EQ(kNetmask2, routes[2].netmask);
  EXPECT_EQ(kNetwork2, routes[2].host);
}

TEST_F(OpenVPNDriverTest, SetRoutes) {
  OpenVPNDriver::RouteOptions routes;
  routes[1].gateway = "1.2.3.4";
  routes[1].host = "1.2.3.4";
  routes[2].host = "2.3.4.5";
  routes[2].netmask = "255.0.0.0";
  routes[3].netmask = "255.0.0.0";
  routes[3].gateway = "1.2.3.5";
  routes[5].host = kNetwork2;
  routes[5].netmask = kNetmask2;
  routes[5].gateway = kGateway2;
  routes[4].host = kNetwork1;
  routes[4].netmask = kNetmask1;
  routes[4].gateway = kGateway1;
  IPConfig::Properties props;
  OpenVPNDriver::SetRoutes(routes, &props);
  ASSERT_EQ(2, props.routes.size());
  EXPECT_EQ(kGateway1, props.routes[0].gateway);
  EXPECT_EQ(kNetmask1, props.routes[0].netmask);
  EXPECT_EQ(kNetwork1, props.routes[0].host);
  EXPECT_EQ(kGateway2, props.routes[1].gateway);
  EXPECT_EQ(kNetmask2, props.routes[1].netmask);
  EXPECT_EQ(kNetwork2, props.routes[1].host);

  // Tests that the routes are not reset if no new routes are supplied.
  OpenVPNDriver::SetRoutes(OpenVPNDriver::RouteOptions(), &props);
  EXPECT_EQ(2, props.routes.size());
}

TEST_F(OpenVPNDriverTest, SplitPortFromHost) {
  string name, port;
  EXPECT_FALSE(OpenVPNDriver::SplitPortFromHost("", nullptr, nullptr));
  EXPECT_FALSE(OpenVPNDriver::SplitPortFromHost("", &name, &port));
  EXPECT_FALSE(OpenVPNDriver::SplitPortFromHost("v.com", &name, &port));
  EXPECT_FALSE(OpenVPNDriver::SplitPortFromHost("v.com:", &name, &port));
  EXPECT_FALSE(OpenVPNDriver::SplitPortFromHost(":1234", &name, &port));
  EXPECT_FALSE(OpenVPNDriver::SplitPortFromHost("v.com:f:1234", &name, &port));
  EXPECT_FALSE(OpenVPNDriver::SplitPortFromHost("v.com:x", &name, &port));
  EXPECT_FALSE(OpenVPNDriver::SplitPortFromHost("v.com:-1", &name, &port));
  EXPECT_FALSE(OpenVPNDriver::SplitPortFromHost("v.com:+1", &name, &port));
  EXPECT_FALSE(OpenVPNDriver::SplitPortFromHost("v.com:65536", &name, &port));
  EXPECT_TRUE(OpenVPNDriver::SplitPortFromHost("v.com:0", &name, &port));
  EXPECT_EQ("v.com", name);
  EXPECT_EQ("0", port);
  EXPECT_TRUE(OpenVPNDriver::SplitPortFromHost("w.com:65535", &name, &port));
  EXPECT_EQ("w.com", name);
  EXPECT_EQ("65535", port);
  EXPECT_TRUE(OpenVPNDriver::SplitPortFromHost("x.com:12345", &name, &port));
  EXPECT_EQ("x.com", name);
  EXPECT_EQ("12345", port);
}

TEST_F(OpenVPNDriverTest, ParseForeignOption) {
  vector<string> domain_search;
  vector<string> dns_servers;
  IPConfig::Properties props;
  OpenVPNDriver::ParseForeignOption("", &domain_search, &dns_servers);
  OpenVPNDriver::ParseForeignOption(
      "dhcp-option DOMAIN", &domain_search, &dns_servers);
  OpenVPNDriver::ParseForeignOption(
      "dhcp-option DOMAIN zzz.com foo", &domain_search, &dns_servers);
  OpenVPNDriver::ParseForeignOption(
      "dhcp-Option DOmAIN xyz.com", &domain_search, &dns_servers);
  ASSERT_EQ(1, domain_search.size());
  EXPECT_EQ("xyz.com", domain_search[0]);
  OpenVPNDriver::ParseForeignOption(
      "dhcp-option DnS 1.2.3.4", &domain_search, &dns_servers);
  ASSERT_EQ(1, dns_servers.size());
  EXPECT_EQ("1.2.3.4", dns_servers[0]);
}

TEST_F(OpenVPNDriverTest, ParseForeignOptions) {
  // This also tests that std::map is a sorted container.
  map<int, string> options;
  options[5] = "dhcp-option DOMAIN five.com";
  options[2] = "dhcp-option DOMAIN two.com";
  options[8] = "dhcp-option DOMAIN eight.com";
  options[7] = "dhcp-option DOMAIN seven.com";
  options[4] = "dhcp-option DOMAIN four.com";
  options[10] = "dhcp-option dns 1.2.3.4";
  IPConfig::Properties props;
  OpenVPNDriver::ParseForeignOptions(options, &props);
  ASSERT_EQ(5, props.domain_search.size());
  EXPECT_EQ("two.com", props.domain_search[0]);
  EXPECT_EQ("four.com", props.domain_search[1]);
  EXPECT_EQ("five.com", props.domain_search[2]);
  EXPECT_EQ("seven.com", props.domain_search[3]);
  EXPECT_EQ("eight.com", props.domain_search[4]);
  ASSERT_EQ(1, props.dns_servers.size());
  EXPECT_EQ("1.2.3.4", props.dns_servers[0]);

  // Test that the DNS properties are not updated if no new DNS properties are
  // supplied.
  OpenVPNDriver::ParseForeignOptions(map<int, string>(), &props);
  EXPECT_EQ(5, props.domain_search.size());
  ASSERT_EQ(1, props.dns_servers.size());
}

TEST_F(OpenVPNDriverTest, ParseIPConfiguration) {
  map<string, string> config;
  IPConfig::Properties props;

  driver_->ParseIPConfiguration(config, &props);
  EXPECT_EQ(IPAddress::kFamilyIPv4, props.address_family);
  EXPECT_EQ(32, props.subnet_prefix);

  props.subnet_prefix = 18;
  driver_->ParseIPConfiguration(config, &props);
  EXPECT_EQ(18, props.subnet_prefix);

  // An "ifconfig_remote" parameter that looks like a netmask should be
  // applied to the subnet prefix instead of to the peer address.
  config["ifconfig_remotE"] = "255.255.0.0";
  driver_->ParseIPConfiguration(config, &props);
  EXPECT_EQ(16, props.subnet_prefix);
  EXPECT_EQ("", props.peer_address);

  config["ifconfig_loCal"] = "4.5.6.7";
  config["ifconfiG_broadcast"] = "1.2.255.255";
  config["ifconFig_netmAsk"] = "255.255.255.0";
  config["ifconfig_remotE"] = "33.44.55.66";
  config["route_vpN_gateway"] = "192.168.1.1";
  config["trusted_ip"] = "99.88.77.66";
  config["tun_mtu"] = "1000";
  config["foreign_option_2"] = "dhcp-option DNS 4.4.4.4";
  config["foreign_option_1"] = "dhcp-option DNS 1.1.1.1";
  config["foreign_option_3"] = "dhcp-option DNS 2.2.2.2";
  config["route_network_2"] = kNetwork2;
  config["route_network_1"] = kNetwork1;
  config["route_netmask_2"] = kNetmask2;
  config["route_netmask_1"] = kNetmask1;
  config["route_gateway_2"] = kGateway2;
  config["route_gateway_1"] = kGateway1;
  config["foo"] = "bar";
  driver_->ParseIPConfiguration(config, &props);
  EXPECT_EQ(IPAddress::kFamilyIPv4, props.address_family);
  EXPECT_EQ("4.5.6.7", props.address);
  EXPECT_EQ("1.2.255.255", props.broadcast_address);
  EXPECT_EQ(24, props.subnet_prefix);
  EXPECT_EQ("33.44.55.66", props.peer_address);
  EXPECT_EQ("192.168.1.1", props.gateway);
  EXPECT_EQ("99.88.77.66/32", props.exclusion_list[0]);
  EXPECT_EQ(1, props.exclusion_list.size());
  EXPECT_EQ(1000, props.mtu);
  ASSERT_EQ(3, props.dns_servers.size());
  EXPECT_EQ("1.1.1.1", props.dns_servers[0]);
  EXPECT_EQ("4.4.4.4", props.dns_servers[1]);
  EXPECT_EQ("2.2.2.2", props.dns_servers[2]);
  ASSERT_EQ(2, props.routes.size());
  EXPECT_EQ(kGateway1, props.routes[0].gateway);
  EXPECT_EQ(kNetmask1, props.routes[0].netmask);
  EXPECT_EQ(kNetwork1, props.routes[0].host);
  EXPECT_EQ(kGateway2, props.routes[1].gateway);
  EXPECT_EQ(kNetmask2, props.routes[1].netmask);
  EXPECT_EQ(kNetwork2, props.routes[1].host);
  EXPECT_FALSE(props.blackhole_ipv6);

  // If the driver is configured to ignore the gateway provided, it will
  // not set the "gateway" property for the properties, however the
  // explicitly supplied routes should still be set.
  SetArg(kOpenVPNIgnoreDefaultRouteProperty, "some value");
  IPConfig::Properties props_without_gateway;
  driver_->ParseIPConfiguration(config, &props_without_gateway);
  EXPECT_EQ(kGateway1, props_without_gateway.routes[0].gateway);
  EXPECT_EQ("", props_without_gateway.gateway);

  // A pushed redirect flag should override the IgnoreDefaultRoute property.
  config["redirect_gateway"] = "def1";
  IPConfig::Properties props_with_override;
  driver_->ParseIPConfiguration(config, &props_with_override);
  EXPECT_EQ("192.168.1.1", props_with_override.gateway);
}

TEST_F(OpenVPNDriverTest, InitOptionsNoHost) {
  Error error;
  vector<vector<string>> options;
  driver_->InitOptions(&options, &error);
  EXPECT_EQ(Error::kInvalidArguments, error.type());
  EXPECT_TRUE(options.empty());
}

TEST_F(OpenVPNDriverTest, InitOptions) {
  static const char kHost[] = "192.168.2.254";
  static const char kTLSAuthContents[] = "SOME-RANDOM-CONTENTS\n";
  static const char kID[] = "TestPKCS11ID";
  static const char kKU0[] = "00";
  static const char kKU1[] = "01";
  FilePath empty_cert;
  SetArg(kProviderHostProperty, kHost);
  SetArg(kOpenVPNTLSAuthContentsProperty, kTLSAuthContents);
  SetArg(kOpenVPNClientCertIdProperty, kID);
  SetArg(kOpenVPNRemoteCertKUProperty, string(kKU0) + " " + string(kKU1));
  driver_->rpc_task_.reset(new RPCTask(&control_, this));
  driver_->tunnel_interface_ = kInterfaceName;
  EXPECT_CALL(*management_server_, Start(_, _, _)).WillOnce(Return(true));
  EXPECT_CALL(manager_, IsConnected()).WillOnce(Return(false));

  Error error;
  vector<vector<string>> options;
  driver_->InitOptions(&options, &error);
  EXPECT_TRUE(error.IsSuccess());
  EXPECT_EQ(vector<string> { "client" }, options[0]);
  ExpectInFlags(options, "remote", kHost);
  ExpectInFlags(options, vector<string> { "setenv", kRPCTaskPathVariable,
                                          RPCTaskMockAdaptor::kRpcId });
  ExpectInFlags(options, "dev", kInterfaceName);
  ExpectInFlags(options, "group", "openvpn");
  EXPECT_EQ(kInterfaceName, driver_->tunnel_interface_);
  ASSERT_FALSE(driver_->tls_auth_file_.empty());
  ExpectInFlags(options, "tls-auth", driver_->tls_auth_file_.value());
  string contents;
  EXPECT_TRUE(base::ReadFileToString(driver_->tls_auth_file_, &contents));
  EXPECT_EQ(kTLSAuthContents, contents);
  ExpectInFlags(options, "pkcs11-id", kID);
  ExpectInFlags(options, "ca", OpenVPNDriver::kDefaultCACertificates);
  ExpectInFlags(options, "syslog");
  ExpectNotInFlags(options, "auth-user-pass");
  ExpectInFlags(options, vector<string> { "remote-cert-ku", kKU0, kKU1 });
}

TEST_F(OpenVPNDriverTest, InitOptionsHostWithPort) {
  SetArg(kProviderHostProperty, "v.com:1234");
  driver_->rpc_task_.reset(new RPCTask(&control_, this));
  driver_->tunnel_interface_ = kInterfaceName;
  EXPECT_CALL(*management_server_, Start(_, _, _)).WillOnce(Return(true));
  EXPECT_CALL(manager_, IsConnected()).WillOnce(Return(false));

  Error error;
  vector<vector<string>> options;
  driver_->InitOptions(&options, &error);
  EXPECT_TRUE(error.IsSuccess());
  ExpectInFlags(options, vector<string> { "remote", "v.com", "1234" });
}

TEST_F(OpenVPNDriverTest, InitCAOptions) {
  static const char kHost[] = "192.168.2.254";
  static const char kCaCert[] = "foo";
  static const char kCaCertNSS[] = "{1234}";

  Error error;
  vector<vector<string>> options;
  EXPECT_TRUE(driver_->InitCAOptions(&options, &error));
  EXPECT_TRUE(error.IsSuccess());
  ExpectInFlags(options, "ca", OpenVPNDriver::kDefaultCACertificates);

  options.clear();
  SetArg(kOpenVPNCaCertProperty, kCaCert);
  EXPECT_TRUE(driver_->InitCAOptions(&options, &error));
  ExpectInFlags(options, "ca", kCaCert);
  EXPECT_TRUE(error.IsSuccess());

  // We should ignore the CaCertNSS property.
  SetArg(kOpenVPNCaCertNSSProperty, kCaCertNSS);
  EXPECT_TRUE(driver_->InitCAOptions(&options, &error));
  ExpectInFlags(options, "ca", kCaCert);
  EXPECT_TRUE(error.IsSuccess());

  SetArg(kOpenVPNCaCertProperty, "");
  SetArg(kProviderHostProperty, kHost);
  FilePath empty_cert;
  error.Reset();
  EXPECT_TRUE(driver_->InitCAOptions(&options, &error));
  ExpectInFlags(options, "ca", OpenVPNDriver::kDefaultCACertificates);
  EXPECT_TRUE(error.IsSuccess());

  SetArg(kOpenVPNCaCertProperty, kCaCert);
  const vector<string> kCaCertPEM{ "---PEM CONTENTS---" };
  SetArgArray(kOpenVPNCaCertPemProperty, kCaCertPEM);
  EXPECT_FALSE(driver_->InitCAOptions(&options, &error));
  EXPECT_EQ(Error::kInvalidArguments, error.type());
  EXPECT_EQ("Can't specify more than one of CACert and CACertPEM.",
            error.message());

  options.clear();
  SetArg(kOpenVPNCaCertProperty, "");
  SetArg(kProviderHostProperty, "");
  static const char kPEMCertfile[] = "/tmp/pem-cert";
  FilePath pem_cert(kPEMCertfile);
  EXPECT_CALL(*certificate_file_, CreatePEMFromStrings(kCaCertPEM))
      .WillOnce(Return(empty_cert))
      .WillOnce(Return(pem_cert));

  error.Reset();
  EXPECT_FALSE(driver_->InitCAOptions(&options, &error));
  EXPECT_EQ(Error::kInvalidArguments, error.type());
  EXPECT_EQ("Unable to extract PEM CA certificates.", error.message());

  error.Reset();
  options.clear();
  EXPECT_TRUE(driver_->InitCAOptions(&options, &error));
  ExpectInFlags(options, "ca", kPEMCertfile);
  EXPECT_TRUE(error.IsSuccess());
}

TEST_F(OpenVPNDriverTest, InitCertificateVerifyOptions) {
  {
    Error error;
    vector<vector<string>> options;
    // No options supplied.
    driver_->InitCertificateVerifyOptions(&options);
    EXPECT_TRUE(options.empty());
  }
  const char kName[] = "x509-name";
  {
    Error error;
    vector<vector<string>> options;
    // With Name property alone, we should have the 1-parameter version of the
    // "x509-verify-name" parameter provided.
    SetArg(kOpenVPNVerifyX509NameProperty, kName);
    driver_->InitCertificateVerifyOptions(&options);
    ExpectInFlags(options, "verify-x509-name", kName);
  }
  const char kType[] = "x509-type";
  {
    Error error;
    vector<vector<string>> options;
    // With both Name property and Type property set, we should have the
    // 2-parameter version of the "x509-verify-name" parameter provided.
    SetArg(kOpenVPNVerifyX509TypeProperty, kType);
    driver_->InitCertificateVerifyOptions(&options);
    ExpectInFlags(options, vector<string> { "verify-x509-name", kName, kType });
  }
  {
    Error error;
    vector<vector<string>> options;
    // We should ignore the Type parameter if no Name parameter is specified.
    SetArg(kOpenVPNVerifyX509NameProperty, "");
    driver_->InitCertificateVerifyOptions(&options);
    EXPECT_TRUE(options.empty());
  }
}

TEST_F(OpenVPNDriverTest, InitClientAuthOptions) {
  static const char kTestValue[] = "foo";
  vector<vector<string>> options;

  // No key or cert, assume user/password authentication.
  driver_->InitClientAuthOptions(&options);
  ExpectInFlags(options, "auth-user-pass");
  ExpectNotInFlags(options, "key");
  ExpectNotInFlags(options, "cert");

  // Cert available, no user/password.
  options.clear();
  SetArg(kOpenVPNCertProperty, kTestValue);
  driver_->InitClientAuthOptions(&options);
  ExpectNotInFlags(options, "auth-user-pass");
  ExpectNotInFlags(options, "key");
  ExpectInFlags(options, "cert", kTestValue);

  // Key available, no user/password.
  options.clear();
  SetArg(kOpenVPNKeyProperty, kTestValue);
  driver_->InitClientAuthOptions(&options);
  ExpectNotInFlags(options, "auth-user-pass");
  ExpectInFlags(options, "key", kTestValue);

  // Key available, AuthUserPass set.
  options.clear();
  SetArg(kOpenVPNAuthUserPassProperty, kTestValue);
  driver_->InitClientAuthOptions(&options);
  ExpectInFlags(options, "auth-user-pass");
  ExpectInFlags(options, "key", kTestValue);

  // Key available, User set.
  options.clear();
  RemoveStringArg(kOpenVPNAuthUserPassProperty);
  SetArg(kOpenVPNUserProperty, "user");
  driver_->InitClientAuthOptions(&options);
  ExpectInFlags(options, "auth-user-pass");
  ExpectInFlags(options, "key", kTestValue);

  // Empty PKCS11 certificate id, no user/password/cert.
  options.clear();
  RemoveStringArg(kOpenVPNKeyProperty);
  RemoveStringArg(kOpenVPNCertProperty);
  RemoveStringArg(kOpenVPNUserProperty);
  SetArg(kOpenVPNClientCertIdProperty, "");
  driver_->InitClientAuthOptions(&options);
  ExpectInFlags(options, "auth-user-pass");
  ExpectNotInFlags(options, "key");
  ExpectNotInFlags(options, "cert");
  ExpectNotInFlags(options, "pkcs11-id");

  // Non-empty PKCS11 certificate id, no user/password/cert.
  options.clear();
  SetArg(kOpenVPNClientCertIdProperty, kTestValue);
  driver_->InitClientAuthOptions(&options);
  ExpectNotInFlags(options, "auth-user-pass");
  ExpectNotInFlags(options, "key");
  ExpectNotInFlags(options, "cert");
  // The "--pkcs11-id" option is added in InitPKCS11Options(), not here.
  ExpectNotInFlags(options, "pkcs11-id");

  // PKCS11 certificate id available, AuthUserPass set.
  options.clear();
  SetArg(kOpenVPNAuthUserPassProperty, kTestValue);
  driver_->InitClientAuthOptions(&options);
  ExpectInFlags(options, "auth-user-pass");
  ExpectNotInFlags(options, "key");
  ExpectNotInFlags(options, "cert");

  // PKCS11 certificate id available, User set.
  options.clear();
  RemoveStringArg(kOpenVPNAuthUserPassProperty);
  SetArg(kOpenVPNUserProperty, "user");
  driver_->InitClientAuthOptions(&options);
  ExpectInFlags(options, "auth-user-pass");
  ExpectNotInFlags(options, "key");
  ExpectNotInFlags(options, "cert");
}

TEST_F(OpenVPNDriverTest, InitExtraCertOptions) {
  {
    Error error;
    vector<vector<string>> options;
    // No ExtraCertOptions supplied.
    EXPECT_TRUE(driver_->InitExtraCertOptions(&options, &error));
    EXPECT_TRUE(error.IsSuccess());
    EXPECT_TRUE(options.empty());
  }
  {
    Error error;
    vector<vector<string>> options;
    SetArgArray(kOpenVPNExtraCertPemProperty, vector<string>());
    // Empty ExtraCertOptions supplied.
    EXPECT_TRUE(driver_->InitExtraCertOptions(&options, &error));
    EXPECT_TRUE(error.IsSuccess());
    EXPECT_TRUE(options.empty());
  }
  const vector<string> kExtraCerts{ "---PEM CONTENTS---" };
  SetArgArray(kOpenVPNExtraCertPemProperty, kExtraCerts);
  static const char kPEMCertfile[] = "/tmp/pem-cert";
  FilePath pem_cert(kPEMCertfile);
  EXPECT_CALL(*extra_certificates_file_, CreatePEMFromStrings(kExtraCerts))
      .WillOnce(Return(FilePath()))
      .WillOnce(Return(pem_cert));
  // CreatePemFromStrings fails.
  {
    Error error;
    vector<vector<string>> options;
    EXPECT_FALSE(driver_->InitExtraCertOptions(&options, &error));
    EXPECT_EQ(Error::kInvalidArguments, error.type());
    EXPECT_TRUE(options.empty());
  }
  // CreatePemFromStrings succeeds.
  {
    Error error;
    vector<vector<string>> options;
    EXPECT_TRUE(driver_->InitExtraCertOptions(&options, &error));
    EXPECT_TRUE(error.IsSuccess());
    ExpectInFlags(options, "extra-certs", kPEMCertfile);
  }
}

TEST_F(OpenVPNDriverTest, InitPKCS11Options) {
  vector<vector<string>> options;
  driver_->InitPKCS11Options(&options);
  EXPECT_TRUE(options.empty());

  static const char kID[] = "TestPKCS11ID";
  SetArg(kOpenVPNClientCertIdProperty, kID);
  driver_->InitPKCS11Options(&options);
  ExpectInFlags(options, "pkcs11-id", kID);
  ExpectInFlags(options, "pkcs11-providers", "libchaps.so");

  static const char kProvider[] = "libpkcs11.so";
  SetArg(kOpenVPNProviderProperty, kProvider);
  options.clear();
  driver_->InitPKCS11Options(&options);
  ExpectInFlags(options, "pkcs11-id", kID);
  ExpectInFlags(options, "pkcs11-providers", kProvider);
}

TEST_F(OpenVPNDriverTest, InitManagementChannelOptionsServerFail) {
  vector<vector<string>> options;
  EXPECT_CALL(*management_server_, Start(&dispatcher_, GetSockets(), &options))
      .WillOnce(Return(false));
  Error error;
  EXPECT_FALSE(InitManagementChannelOptions(&options, &error));
  EXPECT_EQ(Error::kInternalError, error.type());
  EXPECT_EQ("Unable to setup management channel.", error.message());
}

TEST_F(OpenVPNDriverTest, InitManagementChannelOptionsOnline) {
  vector<vector<string>> options;
  EXPECT_CALL(*management_server_, Start(&dispatcher_, GetSockets(), &options))
      .WillOnce(Return(true));
  EXPECT_CALL(manager_, IsConnected()).WillOnce(Return(true));
  EXPECT_CALL(*management_server_, ReleaseHold());
  Error error;
  EXPECT_TRUE(InitManagementChannelOptions(&options, &error));
  EXPECT_TRUE(error.IsSuccess());
}

TEST_F(OpenVPNDriverTest, InitManagementChannelOptionsOffline) {
  vector<vector<string>> options;
  EXPECT_CALL(*management_server_, Start(&dispatcher_, GetSockets(), &options))
      .WillOnce(Return(true));
  EXPECT_CALL(manager_, IsConnected()).WillOnce(Return(false));
  EXPECT_CALL(*management_server_, ReleaseHold()).Times(0);
  Error error;
  EXPECT_TRUE(InitManagementChannelOptions(&options, &error));
  EXPECT_TRUE(error.IsSuccess());
}

TEST_F(OpenVPNDriverTest, InitLoggingOptions) {
  vector<vector<string>> options;
  bool vpn_logging = SLOG_IS_ON(VPN, 0);
  ScopeLogger::GetInstance()->EnableScopesByName("-vpn");
  driver_->InitLoggingOptions(&options);
  ASSERT_EQ(1, options.size());
  EXPECT_EQ(vector<string> { "syslog" }, options[0]);
  ScopeLogger::GetInstance()->EnableScopesByName("+vpn");
  options.clear();
  driver_->InitLoggingOptions(&options);
  ExpectInFlags(options, "verb", "3");
  ScopeLogger::GetInstance()->EnableScopesByName("-vpn");
  SetArg("OpenVPN.Verb", "2");
  options.clear();
  driver_->InitLoggingOptions(&options);
  ExpectInFlags(options, "verb", "2");
  ScopeLogger::GetInstance()->EnableScopesByName("+vpn");
  SetArg("OpenVPN.Verb", "1");
  options.clear();
  driver_->InitLoggingOptions(&options);
  ExpectInFlags(options, "verb", "1");
  if (!vpn_logging) {
    ScopeLogger::GetInstance()->EnableScopesByName("-vpn");
  }
}

TEST_F(OpenVPNDriverTest, AppendValueOption) {
  vector<vector<string>> options;
  EXPECT_FALSE(
      driver_->AppendValueOption("OpenVPN.UnknownProperty", kOption, &options));
  EXPECT_TRUE(options.empty());

  SetArg(kProperty, "");
  EXPECT_FALSE(driver_->AppendValueOption(kProperty, kOption, &options));
  EXPECT_TRUE(options.empty());

  SetArg(kProperty, kValue);
  SetArg(kProperty2, kValue2);
  EXPECT_TRUE(driver_->AppendValueOption(kProperty, kOption, &options));
  EXPECT_TRUE(driver_->AppendValueOption(kProperty2, kOption2, &options));
  EXPECT_EQ(2, options.size());
  vector<string> expected_value { kOption, kValue };
  EXPECT_EQ(expected_value, options[0]);
  vector<string> expected_value2 { kOption2, kValue2 };
  EXPECT_EQ(expected_value2, options[1]);
}

TEST_F(OpenVPNDriverTest, AppendDelimitedValueOption) {
  vector<vector<string>> options;
  EXPECT_FALSE(
      driver_->AppendDelimitedValueOption(
          "OpenVPN.UnknownProperty", kOption, ' ', &options));
  EXPECT_TRUE(options.empty());

  SetArg(kProperty, "");
  EXPECT_FALSE(
      driver_->AppendDelimitedValueOption(kProperty, kOption, ' ', &options));
  EXPECT_TRUE(options.empty());

  string kConcatenatedValues(string(kValue) + " " + string(kValue2));
  SetArg(kProperty, kConcatenatedValues);
  SetArg(kProperty2, kConcatenatedValues);
  EXPECT_TRUE(driver_->AppendDelimitedValueOption(
      kProperty, kOption, ':', &options));
  EXPECT_TRUE(driver_->AppendDelimitedValueOption(
      kProperty2, kOption2, ' ', &options));
  EXPECT_EQ(2, options.size());
  vector<string> expected_value { kOption, kConcatenatedValues };
  EXPECT_EQ(expected_value, options[0]);
  vector<string> expected_value2 { kOption2, kValue, kValue2 };
  EXPECT_EQ(expected_value2, options[1]);
}

TEST_F(OpenVPNDriverTest, AppendFlag) {
  vector<vector<string>> options;
  EXPECT_FALSE(
      driver_->AppendFlag("OpenVPN.UnknownProperty", kOption, &options));
  EXPECT_TRUE(options.empty());

  SetArg(kProperty, "");
  SetArg(kProperty2, kValue2);
  EXPECT_TRUE(driver_->AppendFlag(kProperty, kOption, &options));
  EXPECT_TRUE(driver_->AppendFlag(kProperty2, kOption2, &options));
  EXPECT_EQ(2, options.size());
  EXPECT_EQ(vector<string> { kOption }, options[0]);
  EXPECT_EQ(vector<string> { kOption2 }, options[1]);
}

TEST_F(OpenVPNDriverTest, ClaimInterface) {
  driver_->tunnel_interface_ = kInterfaceName;
  EXPECT_FALSE(driver_->ClaimInterface(string(kInterfaceName) + "XXX",
                                       kInterfaceIndex));
  EXPECT_FALSE(driver_->device_);

  static const char kHost[] = "192.168.2.254";
  SetArg(kProviderHostProperty, kHost);
  EXPECT_CALL(*management_server_, Start(_, _, _)).WillOnce(Return(true));
  EXPECT_CALL(manager_, IsConnected()).WillOnce(Return(false));
  EXPECT_CALL(
      process_manager_,
      StartProcess(_, _, _, _, false /* Don't exit with parent */, _))
      .WillOnce(Return(true));
  const int kServiceCallbackTag = 1;
  EXPECT_EQ(0, driver_->default_service_callback_tag_);
  EXPECT_CALL(manager_, RegisterDefaultServiceCallback(_))
      .WillOnce(Return(kServiceCallbackTag));
  EXPECT_TRUE(driver_->ClaimInterface(kInterfaceName, kInterfaceIndex));
  ASSERT_TRUE(driver_->device_);
  EXPECT_EQ(kInterfaceIndex, driver_->device_->interface_index());
  EXPECT_EQ(kServiceCallbackTag, driver_->default_service_callback_tag_);
}

TEST_F(OpenVPNDriverTest, IdleService) {
  SetService(service_);
  EXPECT_CALL(*service_, SetState(Service::kStateIdle));
  driver_->IdleService();
}

TEST_F(OpenVPNDriverTest, FailService) {
  static const char kErrorDetails[] = "Bad password.";
  SetService(service_);
  EXPECT_CALL(*service_, SetFailure(Service::kFailureConnect));
  driver_->FailService(Service::kFailureConnect, kErrorDetails);
  EXPECT_EQ(kErrorDetails, service_->error_details());
}

TEST_F(OpenVPNDriverTest, Cleanup) {
  // Ensure no crash.
  driver_->Cleanup(Service::kStateIdle,
                   Service::kFailureUnknown,
                   Service::kErrorDetailsNone);

  const int kPID = 123456;
  const int kServiceCallbackTag = 5;
  static const char kErrorDetails[] = "Certificate revoked.";
  driver_->default_service_callback_tag_ = kServiceCallbackTag;
  driver_->pid_ = kPID;
  driver_->rpc_task_.reset(new RPCTask(&control_, this));
  driver_->tunnel_interface_ = kInterfaceName;
  driver_->device_ = device_;
  driver_->service_ = service_;
  driver_->ip_properties_.address = "1.2.3.4";
  StartConnectTimeout(0);
  FilePath tls_auth_file;
  EXPECT_TRUE(base::CreateTemporaryFile(&tls_auth_file));
  EXPECT_FALSE(tls_auth_file.empty());
  EXPECT_TRUE(base::PathExists(tls_auth_file));
  driver_->tls_auth_file_ = tls_auth_file;
  // Stop will be called twice -- once by Cleanup and once by the destructor.
  EXPECT_CALL(*management_server_, Stop()).Times(2);
  // UpdateExitCallback will be called twice -- once to ignore exit,
  // and once to re-enabling monitoring of exit.
  EXPECT_CALL(process_manager_, UpdateExitCallback(kPID, _)).Times(2);
  EXPECT_CALL(manager_, DeregisterDefaultServiceCallback(kServiceCallbackTag));
  EXPECT_CALL(process_manager_, StopProcess(kPID));
  EXPECT_CALL(device_info_, DeleteInterface(_)).Times(0);
  EXPECT_CALL(*device_, DropConnection());
  EXPECT_CALL(*device_, SetEnabled(false));
  EXPECT_CALL(*service_, SetFailure(Service::kFailureInternal));
  driver_->Cleanup(
      Service::kStateFailure, Service::kFailureInternal,  kErrorDetails);
  EXPECT_EQ(0, driver_->default_service_callback_tag_);
  EXPECT_EQ(0, driver_->pid_);
  EXPECT_FALSE(driver_->rpc_task_.get());
  EXPECT_TRUE(driver_->tunnel_interface_.empty());
  EXPECT_FALSE(driver_->device_);
  EXPECT_FALSE(driver_->service_);
  EXPECT_EQ(kErrorDetails, service_->error_details());
  EXPECT_FALSE(base::PathExists(tls_auth_file));
  EXPECT_TRUE(driver_->tls_auth_file_.empty());
  EXPECT_TRUE(driver_->ip_properties_.address.empty());
  EXPECT_FALSE(driver_->IsConnectTimeoutStarted());
}

TEST_F(OpenVPNDriverTest, SpawnOpenVPN) {
  SetupLSBRelease();

  EXPECT_FALSE(driver_->SpawnOpenVPN());

  static const char kHost[] = "192.168.2.254";
  SetArg(kProviderHostProperty, kHost);
  driver_->tunnel_interface_ = "tun0";
  driver_->rpc_task_.reset(new RPCTask(&control_, this));
  EXPECT_CALL(*management_server_, Start(_, _, _))
      .Times(2)
      .WillRepeatedly(Return(true));
  EXPECT_CALL(manager_, IsConnected()).Times(2).WillRepeatedly(Return(false));

  const int kPID = 234678;
  const map<string, string> expected_env{
    {"IV_PLAT", "Chromium OS"},
    {"IV_PLAT_REL", "2202.0"}};
  EXPECT_CALL(process_manager_, StartProcess(_, _, _, expected_env, _, _))
      .WillOnce(Return(-1))
      .WillOnce(Return(kPID));
  EXPECT_FALSE(driver_->SpawnOpenVPN());
  EXPECT_TRUE(driver_->SpawnOpenVPN());
  EXPECT_EQ(kPID, driver_->pid_);
}

TEST_F(OpenVPNDriverTest, OnOpenVPNDied) {
  const int kPID = 99999;
  driver_->device_ = device_;
  driver_->pid_ = kPID;
  EXPECT_CALL(*device_, DropConnection());
  EXPECT_CALL(*device_, SetEnabled(false));
  EXPECT_CALL(process_manager_, StopProcess(_)).Times(0);
  EXPECT_CALL(device_info_, DeleteInterface(kInterfaceIndex));
  driver_->OnOpenVPNDied(2);
  EXPECT_EQ(0, driver_->pid_);
}

TEST_F(OpenVPNDriverTest, Disconnect) {
  driver_->device_ = device_;
  driver_->service_ = service_;
  EXPECT_CALL(*device_, DropConnection());
  EXPECT_CALL(*device_, SetEnabled(false));
  EXPECT_CALL(device_info_, DeleteInterface(kInterfaceIndex));
  EXPECT_CALL(*service_, SetState(Service::kStateIdle));
  driver_->Disconnect();
  EXPECT_FALSE(driver_->device_);
  EXPECT_FALSE(driver_->service_);
}

TEST_F(OpenVPNDriverTest, OnConnectionDisconnected) {
  EXPECT_CALL(*management_server_, Restart());
  SetDevice(device_);
  SetService(service_);
  EXPECT_CALL(*device_, DropConnection());
  EXPECT_CALL(*service_, SetState(Service::kStateAssociating));
  OnConnectionDisconnected();
  EXPECT_TRUE(IsConnectTimeoutStarted());
}

TEST_F(OpenVPNDriverTest, OnConnectTimeout) {
  StartConnectTimeout(0);
  SetService(service_);
  EXPECT_CALL(*service_, SetFailure(Service::kFailureConnect));
  OnConnectTimeout();
  EXPECT_FALSE(GetService());
  EXPECT_FALSE(IsConnectTimeoutStarted());
}

TEST_F(OpenVPNDriverTest, OnConnectTimeoutResolve) {
  StartConnectTimeout(0);
  SetService(service_);
  SetClientState(OpenVPNManagementServer::kStateResolve);
  EXPECT_CALL(*service_, SetFailure(Service::kFailureDNSLookup));
  OnConnectTimeout();
  EXPECT_FALSE(GetService());
  EXPECT_FALSE(IsConnectTimeoutStarted());
}

TEST_F(OpenVPNDriverTest, OnReconnectingUnknown) {
  EXPECT_FALSE(IsConnectTimeoutStarted());
  EXPECT_CALL(dispatcher_,
              PostDelayedTask(_, GetDefaultConnectTimeoutSeconds() * 1000));
  SetDevice(device_);
  SetService(service_);
  EXPECT_CALL(*device_, DropConnection());
  EXPECT_CALL(*service_, SetState(Service::kStateAssociating));
  driver_->OnReconnecting(OpenVPNDriver::kReconnectReasonUnknown);
  EXPECT_TRUE(IsConnectTimeoutStarted());
}

TEST_F(OpenVPNDriverTest, OnReconnectingTLSError) {
  EXPECT_CALL(dispatcher_,
              PostDelayedTask(_, GetReconnectOfflineTimeoutSeconds() * 1000));
  EXPECT_CALL(dispatcher_,
              PostDelayedTask(_, GetReconnectTLSErrorTimeoutSeconds() * 1000));

  driver_->OnReconnecting(OpenVPNDriver::kReconnectReasonOffline);
  EXPECT_TRUE(IsConnectTimeoutStarted());

  // The scheduled timeout should not be affected for unknown reason.
  driver_->OnReconnecting(OpenVPNDriver::kReconnectReasonUnknown);
  EXPECT_TRUE(IsConnectTimeoutStarted());

  // Reconnect on TLS error reschedules the timeout once.
  driver_->OnReconnecting(OpenVPNDriver::kReconnectReasonTLSError);
  EXPECT_TRUE(IsConnectTimeoutStarted());
  driver_->OnReconnecting(OpenVPNDriver::kReconnectReasonTLSError);
  EXPECT_TRUE(IsConnectTimeoutStarted());
}

TEST_F(OpenVPNDriverTest, InitPropertyStore) {
  // Sanity test property store initialization.
  PropertyStore store;
  driver_->InitPropertyStore(&store);
  const string kUser = "joe";
  Error error;
  EXPECT_TRUE(store.SetStringProperty(kOpenVPNUserProperty, kUser, &error));
  EXPECT_TRUE(error.IsSuccess());
  EXPECT_EQ(kUser, GetArgs()->LookupString(kOpenVPNUserProperty, ""));
}

TEST_F(OpenVPNDriverTest, PassphraseRequired) {
  PropertyStore store;
  driver_->InitPropertyStore(&store);
  KeyValueStore props = GetProviderProperties(store);
  EXPECT_TRUE(props.LookupBool(kPassphraseRequiredProperty, false));

  SetArg(kOpenVPNPasswordProperty, "random-password");
  props = GetProviderProperties(store);
  EXPECT_FALSE(props.LookupBool(kPassphraseRequiredProperty, true));
  // This parameter should be write-only.
  EXPECT_FALSE(props.ContainsString(kOpenVPNPasswordProperty));

  SetArg(kOpenVPNPasswordProperty, "");
  props = GetProviderProperties(store);
  EXPECT_TRUE(props.LookupBool(kPassphraseRequiredProperty, false));

  SetArg(kOpenVPNTokenProperty, "random-token");
  props = GetProviderProperties(store);
  EXPECT_FALSE(props.LookupBool(kPassphraseRequiredProperty, true));
  // This parameter should be write-only.
  EXPECT_FALSE(props.ContainsString(kOpenVPNTokenProperty));
}

TEST_F(OpenVPNDriverTest, GetEnvironment) {
  SetupLSBRelease();
  const map<string, string> expected{
    {"IV_PLAT", "Chromium OS"},
    {"IV_PLAT_REL", "2202.0"}};
  ASSERT_EQ(expected, driver_->GetEnvironment());

  EXPECT_EQ(0, base::WriteFile(lsb_release_file_, "", 0));
  EXPECT_EQ(0, driver_->GetEnvironment().size());
}

TEST_F(OpenVPNDriverTest, OnOpenVPNExited) {
  const int kExitStatus = 1;
  std::unique_ptr<MockDeviceInfo> device_info(
      new MockDeviceInfo(&control_, &dispatcher_, &metrics_, &manager_));
  EXPECT_CALL(*device_info, DeleteInterface(kInterfaceIndex))
      .WillOnce(Return(true));
  WeakPtr<DeviceInfo> weak = device_info->AsWeakPtr();
  EXPECT_TRUE(weak);
  OpenVPNDriver::OnOpenVPNExited(weak, kInterfaceIndex, kExitStatus);
  device_info.reset();
  EXPECT_FALSE(weak);
  // Expect no crash.
  OpenVPNDriver::OnOpenVPNExited(weak, kInterfaceIndex, kExitStatus);
}

TEST_F(OpenVPNDriverTest, OnDefaultServiceChanged) {
  driver_->service_ = service_;

  ServiceRefPtr null_service;
  EXPECT_CALL(*management_server_, Hold());
  driver_->OnDefaultServiceChanged(null_service);

  EXPECT_CALL(*management_server_, Hold());
  driver_->OnDefaultServiceChanged(service_);

  scoped_refptr<MockService> mock_service(
      new MockService(&control_, &dispatcher_, &metrics_, &manager_));

  EXPECT_CALL(*mock_service, IsConnected()).WillOnce(Return(false));
  EXPECT_CALL(*management_server_, Hold());
  driver_->OnDefaultServiceChanged(mock_service);

  EXPECT_CALL(*mock_service, IsConnected()).WillOnce(Return(true));
  EXPECT_CALL(*management_server_, ReleaseHold());
  driver_->OnDefaultServiceChanged(mock_service);
}

TEST_F(OpenVPNDriverTest, GetReconnectTimeoutSeconds) {
  EXPECT_EQ(GetDefaultConnectTimeoutSeconds(),
            GetReconnectTimeoutSeconds(OpenVPNDriver::kReconnectReasonUnknown));
  EXPECT_EQ(GetReconnectOfflineTimeoutSeconds(),
            GetReconnectTimeoutSeconds(OpenVPNDriver::kReconnectReasonOffline));
  EXPECT_EQ(GetReconnectTLSErrorTimeoutSeconds(),
            GetReconnectTimeoutSeconds(
                OpenVPNDriver::kReconnectReasonTLSError));
}

TEST_F(OpenVPNDriverTest, WriteConfigFile) {
  const char kOption0[] = "option0";
  const char kOption1[] = "option1";
  const char kOption1Argument0[] = "option1-argument0";
  const char kOption2[] = "option2";
  const char kOption2Argument0[] = "option2-argument0\n\t\"'\\";
  const char kOption2Argument0Transformed[] = "option2-argument0 \t\\\"'\\\\";
  const char kOption2Argument1[] = "option2-argument1 space";
  vector<vector<string>> options {
      { kOption0 },
      { kOption1, kOption1Argument0 },
      { kOption2, kOption2Argument0, kOption2Argument1 }
  };
  FilePath config_directory(
      temporary_directory_.path().Append(kOpenVPNConfigDirectory));
  FilePath config_file;
  EXPECT_FALSE(base::PathExists(config_directory));
  EXPECT_TRUE(driver_->WriteConfigFile(options, &config_file));
  EXPECT_TRUE(base::PathExists(config_directory));
  EXPECT_TRUE(base::PathExists(config_file));
  EXPECT_TRUE(config_directory.IsParent(config_file));

  string config_contents;
  EXPECT_TRUE(base::ReadFileToString(config_file, &config_contents));
  string expected_config_contents = base::StringPrintf(
      "%s\n%s %s\n%s \"%s\" \"%s\"\n",
      kOption0,
      kOption1, kOption1Argument0,
      kOption2, kOption2Argument0Transformed, kOption2Argument1);
  EXPECT_EQ(expected_config_contents, config_contents);
}

}  // namespace shill