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

#include <algorithm>
#include <memory>

#include <base/strings/string_util.h>
#if defined(__ANDROID__)
#include <dbus/service_constants.h>
#else
#include <chromeos/dbus/service_constants.h>
#endif  // __ANDROID__

#include "shill/error.h"
#include "shill/logging.h"
#include "shill/manager.h"
#include "shill/process_manager.h"
#include "shill/profile.h"
#include "shill/store_interface.h"
#include "shill/vpn/l2tp_ipsec_driver.h"
#include "shill/vpn/openvpn_driver.h"
#include "shill/vpn/third_party_vpn_driver.h"
#include "shill/vpn/vpn_service.h"

using std::set;
using std::string;
using std::vector;

namespace shill {

namespace Logging {
static auto kModuleLogScope = ScopeLogger::kVPN;
static string ObjectID(const VPNProvider* v) { return "(vpn_provider)"; }
}

VPNProvider::VPNProvider(ControlInterface* control_interface,
                         EventDispatcher* dispatcher,
                         Metrics* metrics,
                         Manager* manager)
    : control_interface_(control_interface),
      dispatcher_(dispatcher),
      metrics_(metrics),
      manager_(manager) {}

VPNProvider::~VPNProvider() {}

void VPNProvider::Start() {}

void VPNProvider::Stop() {}

// static
bool VPNProvider::GetServiceParametersFromArgs(const KeyValueStore& args,
                                               string* type_ptr,
                                               string* name_ptr,
                                               string* host_ptr,
                                               Error* error) {
  SLOG(nullptr, 2) << __func__;
  string type = args.LookupString(kProviderTypeProperty, "");
  if (type.empty()) {
    Error::PopulateAndLog(
        FROM_HERE, error, Error::kNotSupported, "Missing VPN type property.");
    return false;
  }

  string host = args.LookupString(kProviderHostProperty, "");
  if (host.empty()) {
    Error::PopulateAndLog(
        FROM_HERE, error, Error::kNotSupported, "Missing VPN host property.");
    return false;
  }

  *type_ptr = type,
  *host_ptr = host,
  *name_ptr = args.LookupString(kNameProperty, "");

  return true;
}

// static
bool VPNProvider::GetServiceParametersFromStorage(const StoreInterface* storage,
                                                  const string& entry_name,
                                                  string* vpn_type_ptr,
                                                  string* name_ptr,
                                                  string* host_ptr,
                                                  Error* error) {
  string service_type;
  if (!storage->GetString(entry_name, kTypeProperty, &service_type) ||
      service_type != kTypeVPN) {
    Error::PopulateAndLog(FROM_HERE, error, Error::kInvalidArguments,
                          "Unspecified or invalid network type");
    return false;
  }
  if (!storage->GetString(entry_name, kProviderTypeProperty, vpn_type_ptr) ||
      vpn_type_ptr->empty()) {
    Error::PopulateAndLog(FROM_HERE, error, Error::kInvalidArguments,
                          "VPN type not specified");
    return false;
  }
  if (!storage->GetString(entry_name, kNameProperty, name_ptr) ||
      name_ptr->empty()) {
    Error::PopulateAndLog(FROM_HERE, error, Error::kInvalidArguments,
                          "Network name not specified");
    return false;
  }
  if (!storage->GetString(entry_name, kProviderHostProperty, host_ptr) ||
      host_ptr->empty()) {
    Error::PopulateAndLog(FROM_HERE, error, Error::kInvalidArguments,
                          "Host not specified");
    return false;
  }
  return true;
}

ServiceRefPtr VPNProvider::GetService(const KeyValueStore& args,
                                      Error* error) {
  SLOG(this, 2) << __func__;
  string type;
  string name;
  string host;

  if (!GetServiceParametersFromArgs(args, &type, &name, &host, error)) {
    return nullptr;
  }

  string storage_id = VPNService::CreateStorageIdentifier(args, error);
  if (storage_id.empty()) {
    return nullptr;
  }

  // Find a service in the provider list which matches these parameters.
  VPNServiceRefPtr service = FindService(type, name, host);
  if (service == nullptr) {
    service = CreateService(type, name, storage_id, error);
  }
  return service;
}

ServiceRefPtr VPNProvider::FindSimilarService(const KeyValueStore& args,
                                              Error* error) const {
  SLOG(this, 2) << __func__;
  string type;
  string name;
  string host;

  if (!GetServiceParametersFromArgs(args, &type, &name, &host, error)) {
    return nullptr;
  }

  // Find a service in the provider list which matches these parameters.
  VPNServiceRefPtr service = FindService(type, name, host);
  if (!service) {
    error->Populate(Error::kNotFound, "Matching service was not found");
  }

  return service;
}

bool VPNProvider::OnDeviceInfoAvailable(const string& link_name,
                                        int interface_index) {
  for (const auto& service : services_) {
    if (service->driver()->ClaimInterface(link_name, interface_index)) {
      return true;
    }
  }

  return false;
}

void VPNProvider::RemoveService(VPNServiceRefPtr service) {
  const auto it = std::find(services_.begin(), services_.end(), service);
  if (it != services_.end()) {
    services_.erase(it);
  }
}

void VPNProvider::CreateServicesFromProfile(const ProfileRefPtr& profile) {
  SLOG(this, 2) << __func__;
  const StoreInterface* storage = profile->GetConstStorage();
  KeyValueStore args;
  args.SetString(kTypeProperty, kTypeVPN);
  for (const auto& group : storage->GetGroupsWithProperties(args)) {
    string type;
    string name;
    string host;
    if (!GetServiceParametersFromStorage(storage,
                                         group,
                                         &type,
                                         &name,
                                         &host,
                                         nullptr)) {
      continue;
    }

    VPNServiceRefPtr service = FindService(type, name, host);
    if (service != nullptr) {
      // If the service already exists, it does not need to be configured,
      // since PushProfile would have already called ConfigureService on it.
      SLOG(this, 2) << "Service already exists " << group;
      continue;
    }

    Error error;
    service = CreateService(type, name, group, &error);

    if (service == nullptr) {
      LOG(ERROR) << "Could not create service for " << group;
      continue;
    }

    if (!profile->ConfigureService(service)) {
      LOG(ERROR) << "Could not configure service for " << group;
      continue;
    }
  }
}

VPNServiceRefPtr VPNProvider::CreateServiceInner(const string& type,
                                                 const string& name,
                                                 const string& storage_id,
                                                 Error* error) {
  SLOG(this, 2) << __func__ << " type " << type << " name " << name
                << " storage id " << storage_id;
#if defined(DISABLE_VPN)

  Error::PopulateAndLog(
      FROM_HERE, error, Error::kNotSupported, "VPN is not supported.");
  return nullptr;

#else

  std::unique_ptr<VPNDriver> driver;
  if (type == kProviderOpenVpn) {
    driver.reset(new OpenVPNDriver(
        control_interface_, dispatcher_, metrics_, manager_,
        manager_->device_info(), ProcessManager::GetInstance()));
  } else if (type == kProviderL2tpIpsec) {
    driver.reset(new L2TPIPSecDriver(
        control_interface_, dispatcher_, metrics_, manager_,
        manager_->device_info(), ProcessManager::GetInstance()));
  } else if (type == kProviderThirdPartyVpn) {
    // For third party VPN host contains extension ID
    driver.reset(new ThirdPartyVpnDriver(
        control_interface_, dispatcher_, metrics_, manager_,
        manager_->device_info()));
  } else {
    Error::PopulateAndLog(
        FROM_HERE, error, Error::kNotSupported,
        "Unsupported VPN type: " + type);
    return nullptr;
  }

  VPNServiceRefPtr service = new VPNService(
      control_interface_, dispatcher_, metrics_, manager_, driver.release());
  service->set_storage_id(storage_id);
  service->InitDriverPropertyStore();
  if (!name.empty()) {
    service->set_friendly_name(name);
  }
  return service;

#endif  // DISABLE_VPN
}

VPNServiceRefPtr VPNProvider::CreateService(const string& type,
                                            const string& name,
                                            const string& storage_id,
                                            Error* error) {
  VPNServiceRefPtr service = CreateServiceInner(type, name, storage_id, error);
  if (service) {
    services_.push_back(service);
    manager_->RegisterService(service);
  }

  return service;
}

VPNServiceRefPtr VPNProvider::FindService(const string& type,
                                          const string& name,
                                          const string& host) const {
  for (const auto& service : services_) {
    if (type == service->driver()->GetProviderType() &&
        name == service->friendly_name() &&
        host == service->driver()->GetHost()) {
      return service;
    }
  }
  return nullptr;
}

ServiceRefPtr VPNProvider::CreateTemporaryService(
      const KeyValueStore& args, Error* error) {
  string type;
  string name;
  string host;

  if (!GetServiceParametersFromArgs(args, &type, &name, &host, error)) {
    return nullptr;
  }

  string storage_id = VPNService::CreateStorageIdentifier(args, error);
  if (storage_id.empty()) {
    return nullptr;
  }

  return CreateServiceInner(type, name, storage_id, error);
}

ServiceRefPtr VPNProvider::CreateTemporaryServiceFromProfile(
    const ProfileRefPtr& profile, const std::string& entry_name, Error* error) {
  string type;
  string name;
  string host;
  if (!GetServiceParametersFromStorage(profile->GetConstStorage(),
                                       entry_name,
                                       &type,
                                       &name,
                                       &host,
                                       error)) {
    return nullptr;
  }

  return CreateServiceInner(type, name, entry_name, error);
}

bool VPNProvider::HasActiveService() const {
  for (const auto& service : services_) {
    if (service->IsConnecting() || service->IsConnected()) {
      return true;
    }
  }
  return false;
}

void VPNProvider::DisconnectAll() {
  for (const auto& service : services_) {
    if (service->IsConnecting() || service->IsConnected()) {
      service->Disconnect(nullptr, "user selected new config");
    }
  }
}

}  // namespace shill