//
// Copyright (C) 2015 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/dhcp/dhcpv6_config.h"

#include <base/files/file_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 "shill/dhcp/dhcp_provider.h"
#include "shill/logging.h"
#include "shill/net/ip_address.h"

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

namespace shill {

namespace Logging {
static auto kModuleLogScope = ScopeLogger::kDHCP;
static string ObjectID(DHCPv6Config* d) {
  if (d == nullptr)
    return "(DHCPv6_config)";
  else
    return d->device_name();
}
}

// static
const char DHCPv6Config::kDHCPCDPathFormatPID[] =
    "var/run/dhcpcd/dhcpcd-%s-6.pid";

const char DHCPv6Config::kConfigurationKeyDelegatedPrefix[] =
    "DHCPv6DelegatedPrefix";
const char DHCPv6Config::kConfigurationKeyDelegatedPrefixLength[] =
    "DHCPv6DelegatedPrefixLength";
const char DHCPv6Config::kConfigurationKeyDelegatedPrefixLeaseTime[] =
    "DHCPv6DelegatedPrefixLeaseTime";
const char DHCPv6Config::kConfigurationKeyDNS[] = "DHCPv6NameServers";
const char DHCPv6Config::kConfigurationKeyDomainSearch[] = "DHCPv6DomainSearch";
const char DHCPv6Config::kConfigurationKeyIPAddress[] = "DHCPv6Address";
const char DHCPv6Config::kConfigurationKeyIPAddressLeaseTime[] =
    "DHCPv6AddressLeaseTime";
const char DHCPv6Config::kConfigurationKeyServerIdentifier[] =
    "DHCPv6ServerIdentifier";

const char DHCPv6Config::kReasonBound[] = "BOUND6";
const char DHCPv6Config::kReasonFail[] = "FAIL6";
const char DHCPv6Config::kReasonRebind[] = "REBIND6";
const char DHCPv6Config::kReasonReboot[] = "REBOOT6";
const char DHCPv6Config::kReasonRenew[] = "RENEW6";

const char DHCPv6Config::kType[] = "dhcp6";

DHCPv6Config::DHCPv6Config(ControlInterface* control_interface,
                           EventDispatcher* dispatcher,
                           DHCPProvider* provider,
                           const string& device_name,
                           const string& lease_file_suffix)
    : DHCPConfig(control_interface,
                 dispatcher,
                 provider,
                 device_name,
                 kType,
                 lease_file_suffix) {
  SLOG(this, 2) << __func__ << ": " << device_name;
}

DHCPv6Config::~DHCPv6Config() {
  SLOG(this, 2) << __func__ << ": " << device_name();
}

void DHCPv6Config::ProcessEventSignal(const string& reason,
                                      const KeyValueStore& configuration) {
  LOG(INFO) << "Event reason: " << reason;
  if (reason == kReasonFail) {
    LOG(ERROR) << "Received failure event from DHCPv6 client.";
    NotifyFailure();
    return;
  } else if (reason != kReasonBound &&
             reason != kReasonRebind &&
             reason != kReasonReboot &&
             reason != kReasonRenew) {
    LOG(WARNING) << "Event ignored.";
    return;
  }

  CHECK(ParseConfiguration(configuration));

  // This needs to be set before calling UpdateProperties() below since
  // those functions may indirectly call other methods like ReleaseIP that
  // depend on or change this value.
  set_is_lease_active(true);

  DHCPConfig::UpdateProperties(properties_, true);
}

void DHCPv6Config::ProcessStatusChangeSignal(const string& status) {
  SLOG(this, 2) << __func__ << ": " << status;
  // TODO(zqiu): metric reporting for status.
}

void DHCPv6Config::CleanupClientState() {
  DHCPConfig::CleanupClientState();

  // Delete lease file if it is ephemeral.
  if (IsEphemeralLease()) {
    base::DeleteFile(root().Append(
        base::StringPrintf(DHCPProvider::kDHCPCDPathFormatLease6,
                           device_name().c_str())), false);
  }
  base::DeleteFile(root().Append(
      base::StringPrintf(kDHCPCDPathFormatPID, device_name().c_str())), false);

  // Reset configuration data.
  properties_ = IPConfig::Properties();
}

vector<string> DHCPv6Config::GetFlags() {
  // Get default flags first.
  vector<string> flags = DHCPConfig::GetFlags();

  flags.push_back("-6");  // IPv6 only.
  flags.push_back("-a");  // Request ia_na and ia_pd.
  return flags;
}

bool DHCPv6Config::ParseConfiguration(const KeyValueStore& configuration) {
  SLOG(nullptr, 2) << __func__;
  properties_.method = kTypeDHCP6;
  properties_.address_family = IPAddress::kFamilyIPv6;
  for (const auto it :  configuration.properties()) {
    const string& key = it.first;
    const brillo::Any& value = it.second;
    SLOG(nullptr, 2) << "Processing key: " << key;
    if (key == kConfigurationKeyIPAddress) {
      properties_.address = value.Get<string>();
    } else if (key == kConfigurationKeyDNS) {
      properties_.dns_servers = value.Get<vector<string>>();
    } else if (key == kConfigurationKeyDomainSearch) {
      properties_.domain_search = value.Get<vector<string>>();
    } else if (key == kConfigurationKeyIPAddressLeaseTime ||
               key == kConfigurationKeyDelegatedPrefixLeaseTime) {
      UpdateLeaseTime(value.Get<uint32_t>());
    } else if (key == kConfigurationKeyDelegatedPrefix) {
      properties_.delegated_prefix = value.Get<string>();
    } else if (key == kConfigurationKeyDelegatedPrefixLength) {
      properties_.delegated_prefix_length = value.Get<uint32_t>();
    } else {
      SLOG(nullptr, 2) << "Key ignored.";
    }
  }
  return true;
}

void DHCPv6Config::UpdateLeaseTime(uint32_t lease_time) {
  // IP address and delegated prefix are provided as separate lease. Use
  // the shorter time of the two lease as the lease time.
  if (properties_.lease_duration_seconds == 0 ||
      lease_time < properties_.lease_duration_seconds) {
    properties_.lease_duration_seconds = lease_time;
  }
}

}  // namespace shill