<reponame>emersion/chromiumos-platform2<filename>portier/nd_msg.cc
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "portier/nd_msg.h"

#include <netinet/icmp6.h>

#include <utility>

namespace portier {

// Type aliasing.
using std::vector;
using std::string;
using std::map;

using base::TimeDelta;

using shill::ByteString;
using shill::IPAddress;

using Type = NeighborDiscoveryMessage::Type;
using OptionType = NeighborDiscoveryMessage::OptionType;

// Static contstants.

// ND codes from netinet/icmp6.h, defined in RFC3542.
const Type NeighborDiscoveryMessage::kTypeRouterSolicit = ND_ROUTER_SOLICIT;
const Type NeighborDiscoveryMessage::kTypeRouterAdvert = ND_ROUTER_ADVERT;
const Type NeighborDiscoveryMessage::kTypeNeighborSolicit = ND_NEIGHBOR_SOLICIT;
const Type NeighborDiscoveryMessage::kTypeNeighborAdvert = ND_NEIGHBOR_ADVERT;
const Type NeighborDiscoveryMessage::kTypeRedirect = ND_REDIRECT;

const OptionType NeighborDiscoveryMessage::kOptionTypeSourceLinkLayerAddress =
    ND_OPT_SOURCE_LINKADDR;
const OptionType NeighborDiscoveryMessage::kOptionTypeTargetLinkLayerAddress =
    ND_OPT_TARGET_LINKADDR;
const OptionType NeighborDiscoveryMessage::kOptionTypePrefixInformation =
    ND_OPT_PREFIX_INFORMATION;
const OptionType NeighborDiscoveryMessage::kOptionTypeRedirectHeader =
    ND_OPT_REDIRECTED_HEADER;
const OptionType NeighborDiscoveryMessage::kOptionTypeMTU = ND_OPT_MTU;

// Local constants.
namespace {

constexpr uint32_t kTypeMinLengthRouterSolicit =
    sizeof(struct nd_router_solicit);
constexpr uint32_t kTypeMinLengthRouterAdvert = sizeof(struct nd_router_advert);
constexpr uint32_t kTypeMinLengthNeighborSolicit =
    sizeof(struct nd_neighbor_solicit);
constexpr uint32_t kTypeMinLengthNeighborAdvert =
    sizeof(struct nd_neighbor_advert);
constexpr uint32_t kTypeMinLengthRedirect = sizeof(struct nd_redirect);

// Link layer addresses are variable length.
constexpr uint32_t kOptionTypeMinLengthSourceLinkLayerAddress =
    sizeof(struct nd_opt_hdr);
constexpr uint32_t kOptionTypeMinLengthTargetLinkLayerAddress =
    sizeof(struct nd_opt_hdr);
constexpr uint32_t kOptionTypeMinLengthPrefixInformation =
    sizeof(struct nd_opt_prefix_info);
constexpr uint32_t kOptionTypeMinLengthRedirectHeader =
    sizeof(struct nd_opt_rd_hdr);
constexpr uint32_t kOptionTypeMinLengthMTU = sizeof(struct nd_opt_mtu);

// Option length to byte units.
constexpr uint32_t kBytesPerOptLen = 8;

// Route advertisement flags.
// RFC 4861 - Section 4.2.
constexpr uint8_t kRouterAdvertManagedBit = ND_RA_FLAG_MANAGED;
constexpr uint8_t kRouterAdvertOtherBit = ND_RA_FLAG_OTHER;
// RFC 4389 - Section 3.
constexpr uint8_t kRouterAdvertProxyBit = 0x04;
}  // namespace

// static
string NeighborDiscoveryMessage::GetTypeName(Type type) {
  switch (type) {
    case kTypeRouterSolicit:
      return "Router Solicitation";
    case kTypeRouterAdvert:
      return "Router Advertisement";
    case kTypeNeighborSolicit:
      return "Neighbor Solicitation";
    case kTypeNeighborAdvert:
      return "Neighbor Advertisement";
    case kTypeRedirect:
      return "Redirect";
    default:
      return "Unknown Type";
  }
}

// static
uint32_t NeighborDiscoveryMessage::GetTypeMinimumLength(Type type) {
  switch (type) {
    case kTypeRouterSolicit:
      return kTypeMinLengthRouterSolicit;
    case kTypeRouterAdvert:
      return kTypeMinLengthRouterAdvert;
    case kTypeNeighborSolicit:
      return kTypeMinLengthNeighborSolicit;
    case kTypeNeighborAdvert:
      return kTypeMinLengthNeighborAdvert;
    case kTypeRedirect:
      return kTypeMinLengthRedirect;
    default:
      return 0;
  }
}

// static
string NeighborDiscoveryMessage::GetOptionTypeName(OptionType opt_type) {
  switch (opt_type) {
    case kOptionTypeSourceLinkLayerAddress:
      return "Source Link-Layer-Address";
    case kOptionTypeTargetLinkLayerAddress:
      return "Target Link-Layer-Address";
    case kOptionTypePrefixInformation:
      return "Prefix Information";
    case kOptionTypeRedirectHeader:
      return "Redirect Header";
    case kOptionTypeMTU:
      return "MTU";
    default:
      return "Unknown Option Type";
  }
}

// static
uint32_t NeighborDiscoveryMessage::GetOptionTypeMinimumLength(
    OptionType opt_type) {
  switch (opt_type) {
    case kOptionTypeSourceLinkLayerAddress:
      return kOptionTypeMinLengthSourceLinkLayerAddress;
    case kOptionTypeTargetLinkLayerAddress:
      return kOptionTypeMinLengthTargetLinkLayerAddress;
    case kOptionTypePrefixInformation:
      return kOptionTypeMinLengthPrefixInformation;
    case kOptionTypeRedirectHeader:
      return kOptionTypeMinLengthRedirectHeader;
    case kOptionTypeMTU:
      return kOptionTypeMinLengthMTU;
    default:
      return 0;
  }
}

// Static Constructors.

NeighborDiscoveryMessage NeighborDiscoveryMessage::RouterSolicit() {
  NeighborDiscoveryMessage nd_msg;
  nd_msg.type_ = kTypeRouterSolicit;
  nd_msg.message_.Resize(kTypeMinLengthRouterSolicit);

  // Convert to raw.
  struct nd_router_solicit* nd_rs =
      reinterpret_cast<struct nd_router_solicit*>(nd_msg.GetData());
  memset(nd_rs, 0, kTypeMinLengthRouterSolicit);
  nd_rs->nd_rs_type = nd_msg.type();

  return nd_msg;
}

NeighborDiscoveryMessage NeighborDiscoveryMessage::RouterAdvert(
    uint8_t cur_hop_limit,
    bool managed_flag,
    bool other_flag,
    bool proxy_flag,
    TimeDelta router_lifetime,
    TimeDelta reachable_time,
    TimeDelta retransmit_timer) {
  NeighborDiscoveryMessage nd_msg;
  nd_msg.type_ = kTypeRouterAdvert;
  nd_msg.message_.Resize(kTypeMinLengthRouterAdvert);

  struct nd_router_advert* nd_ra =
      reinterpret_cast<struct nd_router_advert*>(nd_msg.GetData());
  memset(nd_ra, 0, kTypeMinLengthNeighborAdvert);

  // Row 0: ND type.
  nd_ra->nd_ra_type = nd_msg.type();

  // Row 1: Cur Hop Limit, M O flags, Router Lifetime (16-bits).
  nd_ra->nd_ra_curhoplimit = cur_hop_limit;
  nd_ra->nd_ra_flags_reserved =
      (managed_flag ? kRouterAdvertManagedBit : 0x00) |
      (other_flag ? kRouterAdvertOtherBit : 0x00) |
      (proxy_flag ? kRouterAdvertProxyBit : 0x00);
  nd_ra->nd_ra_router_lifetime =
      htons(static_cast<uint16_t>(router_lifetime.InSeconds()));

  // Row 2: Reachable Time (32-bits).
  nd_ra->nd_ra_reachable =
      htonl(static_cast<uint32_t>(reachable_time.InMilliseconds()));

  // Row 3: Retransmit Timer (32-bits).
  nd_ra->nd_ra_retransmit =
      htonl(static_cast<uint32_t>(retransmit_timer.InMilliseconds()));

  return nd_msg;
}

NeighborDiscoveryMessage NeighborDiscoveryMessage::NeighborSolicit(
    const IPAddress& target_address) {
  // Check that IP address is valid.
  if (target_address.family() != IPAddress::kFamilyIPv6) {
    LOG(ERROR) << "Cannot initialize with a non-IPv6 target address: "
               << target_address.ToString();
    return NeighborDiscoveryMessage();
  }

  NeighborDiscoveryMessage nd_msg;
  nd_msg.type_ = kTypeNeighborSolicit;
  nd_msg.message_.Resize(kTypeMinLengthNeighborSolicit);

  struct nd_neighbor_solicit* nd_ns =
      reinterpret_cast<struct nd_neighbor_solicit*>(nd_msg.GetData());
  memset(nd_ns, 0, kTypeMinLengthNeighborSolicit);

  // Row 0: ND type.
  nd_ns->nd_ns_type = nd_msg.type();
  // Row 1: Reserved.
  // Row 2-5: Target address.
  memcpy(&nd_ns->nd_ns_target, target_address.GetConstData(),
         target_address.GetLength());

  return nd_msg;
}

NeighborDiscoveryMessage NeighborDiscoveryMessage::NeighborAdvert(
    bool router_flag,
    bool solicited_flag,
    bool override_flag,
    const IPAddress& target_address) {
  // Check that IP address is valid.
  if (target_address.family() != IPAddress::kFamilyIPv6) {
    LOG(ERROR) << "Cannot initialize with a non-IPv6 target address: "
               << target_address.ToString();
    return NeighborDiscoveryMessage();
  }

  NeighborDiscoveryMessage nd_msg;
  nd_msg.type_ = kTypeNeighborAdvert;
  nd_msg.message_.Resize(kTypeMinLengthNeighborAdvert);

  struct nd_neighbor_advert* nd_na =
      reinterpret_cast<struct nd_neighbor_advert*>(nd_msg.GetData());
  memset(nd_na, 0, kTypeMinLengthNeighborAdvert);

  // Row 0: ND type.
  nd_na->nd_na_type = nd_msg.type();
  // Row 1: R S O flags
  nd_na->nd_na_flags_reserved = (router_flag ? ND_NA_FLAG_ROUTER : 0) |
                                (solicited_flag ? ND_NA_FLAG_SOLICITED : 0) |
                                (override_flag ? ND_NA_FLAG_OVERRIDE : 0);
  // Row 2-5: Target addresss.
  memcpy(&nd_na->nd_na_target, target_address.GetConstData(),
         target_address.GetLength());

  return nd_msg;
}

NeighborDiscoveryMessage NeighborDiscoveryMessage::Redirect(
    const IPAddress& target_address, const IPAddress& destination_address) {
  if (target_address.family() != IPAddress::kFamilyIPv6) {
    LOG(ERROR) << "Cannot initialize with a non-IPv6 target address: "
               << target_address.ToString();
    return NeighborDiscoveryMessage();
  }
  if (destination_address.family() != IPAddress::kFamilyIPv6) {
    LOG(ERROR) << "Cannot initialize with a non-IPv6 destination address: "
               << destination_address.ToString();
    return NeighborDiscoveryMessage();
  }

  NeighborDiscoveryMessage nd_msg;
  nd_msg.type_ = kTypeRedirect;
  nd_msg.message_.Resize(kTypeMinLengthRedirect);

  struct nd_redirect* nd_rd =
      reinterpret_cast<struct nd_redirect*>(nd_msg.GetData());
  memset(nd_rd, 0, kTypeMinLengthRedirect);

  // Row 0: ND type.
  nd_rd->nd_rd_type = nd_msg.type();
  // Row 1: Reserved.
  // Row 2-5: Target address.
  memcpy(&nd_rd->nd_rd_target, target_address.GetConstData(),
         target_address.GetLength());
  // Row 6-9: Destination address.
  memcpy(&nd_rd->nd_rd_dst, destination_address.GetConstData(),
         destination_address.GetLength());

  return nd_msg;
}

// Default constructor, private.

NeighborDiscoveryMessage::NeighborDiscoveryMessage() : type_(0) {}

NeighborDiscoveryMessage::NeighborDiscoveryMessage(const ByteString& raw_packet)
    : type_(0) {
  if (raw_packet.GetLength() == 0) {
    LOG(WARNING) << "ND packet is empty";
    return;
  }

  // Get the type
  Type type = raw_packet.GetConstData()[0];
  uint32_t exp_len = GetTypeMinimumLength(type);
  if (0 == exp_len) {
    LOG(ERROR) << "Unsupported ICMPv6 type " << static_cast<uint32_t>(type);
    return;
  } else if (raw_packet.GetLength() < exp_len) {
    LOG(ERROR) << "Expected length of a " << GetTypeName(type)
               << " ND packet should be at least " << exp_len << ", got "
               << raw_packet.GetLength();
    return;
  }
  type_ = type;

  message_ = raw_packet;
  if (!IndexOptions()) {
    // Issue with options.  Exact error should have been logged by
    // IndexOptions().
    type_ = 0;
    message_.Clear();
  }
}

bool NeighborDiscoveryMessage::IsValid() const {
  switch (type()) {
    case kTypeRouterSolicit:
    case kTypeRouterAdvert:
    case kTypeNeighborSolicit:
    case kTypeNeighborAdvert:
    case kTypeRedirect:
      break;
    default:
      // Not a supported type.
      return false;
  }

  if (GetLength() < GetTypeMinimumLength(type())) {
    // Length is too small.
    return false;
  }

  if ((GetLength() % 8) != 0) {
    // Packet does not align on proper 64-bit boundry.
    return false;
  }

  // TODO(sigquit): Create ND type specific validation.  Options should have
  // been validated upon indexing.

  return true;
}

// Checksum.

bool NeighborDiscoveryMessage::GetChecksum(uint16_t* checksum) const {
  if (!IsValid() || nullptr == checksum) {
    return false;
  }
  const struct icmp6_hdr* icmp6_hdr =
      reinterpret_cast<const struct icmp6_hdr*>(GetConstData());
  *checksum = icmp6_hdr->icmp6_cksum;
  return true;
}

bool NeighborDiscoveryMessage::SetChecksum(uint16_t checksum) {
  if (!IsValid()) {
    return false;
  }
  struct icmp6_hdr* icmp6_hdr = reinterpret_cast<struct icmp6_hdr*>(GetData());
  icmp6_hdr->icmp6_cksum = checksum;
  return true;
}

// RS related.

// RA related.

bool NeighborDiscoveryMessage::GetCurrentHopLimit(
    uint8_t* cur_hop_limit) const {
  if (!IsValid() || type() != kTypeRouterAdvert || nullptr == cur_hop_limit) {
    // Not a router advert.
    return false;
  }
  const struct nd_router_advert* nd_ra =
      reinterpret_cast<const struct nd_router_advert*>(GetConstData());
  *cur_hop_limit = nd_ra->nd_ra_curhoplimit;
  return true;
}

bool NeighborDiscoveryMessage::GetManagedAddressConfigurationFlag(
    bool* managed_flag) const {
  if (!IsValid() || type() != kTypeRouterAdvert || nullptr == managed_flag) {
    // Not a router advert.
    return false;
  }
  const struct nd_router_advert* nd_ra =
      reinterpret_cast<const struct nd_router_advert*>(GetConstData());
  *managed_flag = (nd_ra->nd_ra_flags_reserved & kRouterAdvertManagedBit) != 0;
  return true;
}

bool NeighborDiscoveryMessage::GetOtherConfigurationFlag(
    bool* other_flag) const {
  if (!IsValid() || type() != kTypeRouterAdvert || nullptr == other_flag) {
    // Not a router advert.
    return false;
  }
  const struct nd_router_advert* nd_ra =
      reinterpret_cast<const struct nd_router_advert*>(GetConstData());
  *other_flag = (nd_ra->nd_ra_flags_reserved & kRouterAdvertOtherBit) != 0;
  return true;
}

bool NeighborDiscoveryMessage::GetProxyFlag(bool* proxy_flag) const {
  if (!IsValid() || type() != kTypeRouterAdvert || nullptr == proxy_flag) {
    return false;
  }
  const struct nd_router_advert* nd_ra =
      reinterpret_cast<const struct nd_router_advert*>(GetConstData());
  *proxy_flag = (nd_ra->nd_ra_flags_reserved & kRouterAdvertProxyBit) != 0;
  return true;
}

bool NeighborDiscoveryMessage::SetProxyFlag(bool proxy_flag) {
  if (!IsValid() || type() != kTypeRouterAdvert) {
    return false;
  }
  struct nd_router_advert* nd_ra =
      reinterpret_cast<struct nd_router_advert*>(GetData());
  if (proxy_flag) {
    nd_ra->nd_ra_flags_reserved |= kRouterAdvertProxyBit;
  } else {
    nd_ra->nd_ra_flags_reserved &= ~kRouterAdvertProxyBit;
  }
  return true;
}

bool NeighborDiscoveryMessage::GetRouterLifetime(
    TimeDelta* router_lifetime) const {
  if (!IsValid() || type() != kTypeRouterAdvert || nullptr == router_lifetime) {
    // Not a router advert.
    return false;
  }
  const struct nd_router_advert* nd_ra =
      reinterpret_cast<const struct nd_router_advert*>(GetConstData());
  *router_lifetime =
      TimeDelta::FromSeconds(ntohs(nd_ra->nd_ra_router_lifetime));
  return true;
}

bool NeighborDiscoveryMessage::GetReachableTime(
    TimeDelta* reachable_time) const {
  if (!IsValid() || type() != kTypeRouterAdvert || nullptr == reachable_time) {
    // Not a router advert.
    return false;
  }
  const struct nd_router_advert* nd_ra =
      reinterpret_cast<const struct nd_router_advert*>(GetConstData());
  *reachable_time = TimeDelta::FromMilliseconds(ntohl(nd_ra->nd_ra_reachable));
  return true;
}

bool NeighborDiscoveryMessage::GetRetransmitTimer(
    TimeDelta* retransmit_timer) const {
  if (!IsValid() || type() != kTypeRouterAdvert ||
      nullptr == retransmit_timer) {
    // Not a router advert.
    return false;
  }
  const struct nd_router_advert* nd_ra =
      reinterpret_cast<const struct nd_router_advert*>(GetConstData());
  *retransmit_timer =
      TimeDelta::FromMilliseconds(ntohl(nd_ra->nd_ra_retransmit));
  return true;
}

// NS related.

bool NeighborDiscoveryMessage::GetTargetAddress(
    IPAddress* target_address) const {
  if (!IsValid() ||
      (type() != kTypeNeighborSolicit && type() != kTypeNeighborAdvert &&
       type() != kTypeRedirect) ||
      nullptr == target_address) {
    // Not a NS, NA or R
    return false;
  }

  if (type() == kTypeNeighborSolicit) {
    // Neighbor Solicit.
    const struct nd_neighbor_solicit* nd_ns =
        reinterpret_cast<const struct nd_neighbor_solicit*>(GetConstData());
    *target_address = IPAddress(
        IPAddress::kFamilyIPv6,
        ByteString(reinterpret_cast<const uint8_t*>(&nd_ns->nd_ns_target),
                   sizeof(nd_ns->nd_ns_target)));
    return true;
  }

  if (type() == kTypeNeighborAdvert) {
    // Neighbor Advert.
    const struct nd_neighbor_advert* nd_na =
        reinterpret_cast<const struct nd_neighbor_advert*>(GetConstData());
    *target_address = IPAddress(
        IPAddress::kFamilyIPv6,
        ByteString(reinterpret_cast<const uint8_t*>(&nd_na->nd_na_target),
                   sizeof(nd_na->nd_na_target)));
    return true;
  }

  // Redirect.
  const struct nd_redirect* nd_rd =
      reinterpret_cast<const struct nd_redirect*>(GetConstData());
  *target_address = IPAddress(
      IPAddress::kFamilyIPv6,
      ByteString(reinterpret_cast<const uint8_t*>(&nd_rd->nd_rd_target),
                 sizeof(nd_rd->nd_rd_target)));
  return true;
}

// NA related.

bool NeighborDiscoveryMessage::GetRouterFlag(bool* router_flag) const {
  if (!IsValid() || type() != kTypeNeighborAdvert || nullptr == router_flag) {
    // Not a NA.
    return false;
  }

  const struct nd_neighbor_advert* nd_na =
      reinterpret_cast<const struct nd_neighbor_advert*>(GetConstData());
  *router_flag = (nd_na->nd_na_flags_reserved & ND_NA_FLAG_ROUTER) != 0;
  return true;
}

bool NeighborDiscoveryMessage::GetSolicitedFlag(bool* solicited_flag) const {
  if (!IsValid() || type() != kTypeNeighborAdvert ||
      nullptr == solicited_flag) {
    // Not a NA.
    return false;
  }

  const struct nd_neighbor_advert* nd_na =
      reinterpret_cast<const struct nd_neighbor_advert*>(GetConstData());
  *solicited_flag = (nd_na->nd_na_flags_reserved & ND_NA_FLAG_SOLICITED) != 0;
  return true;
}

bool NeighborDiscoveryMessage::GetOverrideFlag(bool* override_flag) const {
  if (!IsValid() || type() != kTypeNeighborAdvert || nullptr == override_flag) {
    // Not a NA.
    return false;
  }

  const struct nd_neighbor_advert* nd_na =
      reinterpret_cast<const struct nd_neighbor_advert*>(GetConstData());
  *override_flag = (nd_na->nd_na_flags_reserved & ND_NA_FLAG_OVERRIDE) != 0;
  return true;
}

// Redirect related.

bool NeighborDiscoveryMessage::GetDestinationAddress(
    IPAddress* destination_address) const {
  if (!IsValid() || type() != kTypeRedirect || nullptr == destination_address) {
    // Not a Redirect.
    return false;
  }

  const struct nd_redirect* nd_rd =
      reinterpret_cast<const struct nd_redirect*>(GetConstData());
  *destination_address =
      IPAddress(IPAddress::kFamilyIPv6,
                ByteString(reinterpret_cast<const uint8_t*>(&nd_rd->nd_rd_dst),
                           sizeof(nd_rd->nd_rd_dst)));
  return true;
}

// Raw accessors.
const uint8_t* NeighborDiscoveryMessage::GetConstData() const {
  return message_.GetConstData();
}

// private
uint8_t* NeighborDiscoveryMessage::GetData() {
  return message_.GetData();
}

size_t NeighborDiscoveryMessage::GetLength() const {
  return message_.GetLength();
}

// Options.

namespace {

// Helpers for options without a well-define structure.
const uint8_t* OptionConstData(const struct nd_opt_hdr* opt_hdr) {
  return &(
      reinterpret_cast<const uint8_t*>(opt_hdr)[sizeof(struct nd_opt_hdr)]);
}

uint8_t* OptionData(struct nd_opt_hdr* opt_hdr) {
  return &(reinterpret_cast<uint8_t*>(opt_hdr)[sizeof(struct nd_opt_hdr)]);
}

uint32_t OptionDataLength(const struct nd_opt_hdr* opt_hdr) {
  return (opt_hdr->nd_opt_len * kBytesPerOptLen) - sizeof(struct nd_opt_hdr);
}

}  // namespace

bool NeighborDiscoveryMessage::HasOption(OptionType opt_type) const {
  if (!IsValid()) {
    return false;
  }
  return OptionCount(opt_type) > 0;
}

uint32_t NeighborDiscoveryMessage::OptionCount(OptionType opt_type) const {
  if (!IsValid()) {
    return 0;
  }

  auto opt_it = options_.find(opt_type);
  if (opt_it == options_.cend()) {
    // No occurance.
    return 0;
  }
  return opt_it->second.size();
}

bool NeighborDiscoveryMessage::GetRawOption(OptionType opt_type,
                                            uint32_t opt_index,
                                            ByteString* raw_option) const {
  if (opt_index >= OptionCount(opt_type) || nullptr == raw_option) {
    return false;
  }

  const uint8_t* opt_ptr = GetConstOptionPointer(opt_type, opt_index);
  CHECK(nullptr != opt_ptr);

  // Determine the length of the option.
  const struct nd_opt_hdr* opt_hdr =
      reinterpret_cast<const struct nd_opt_hdr*>(opt_ptr);
  CHECK(opt_hdr->nd_opt_type == opt_type);
  const uint32_t opt_length = (opt_hdr->nd_opt_len * kBytesPerOptLen);

  *raw_option = ByteString(opt_ptr, opt_length);
  return true;
}

void NeighborDiscoveryMessage::ClearOptions() {
  if (!IsValid()) {
    return;
  }
  // Clear indexes.
  options_.clear();
  // Truncate message.
  message_.Resize(GetTypeMinimumLength(type()));
}

// Internal option accessor.

uint8_t* NeighborDiscoveryMessage::GetOptionPointer(OptionType opt_type,
                                                    uint32_t opt_index) {
  return const_cast<uint8_t*>(GetConstOptionPointer(opt_type, opt_index));
}

const uint8_t* NeighborDiscoveryMessage::GetConstOptionPointer(
    OptionType opt_type, uint32_t opt_index) const {
  // Verify sanity
  CHECK(IsValid());
  CHECK(opt_index < OptionCount(opt_type));

  auto opt_it = options_.find(opt_type);
  uint32_t data_idx = opt_it->second[opt_index];

  CHECK(data_idx < GetLength());
  return &message_.GetConstData()[data_idx];
}

// Option - Source link-layer address.

bool NeighborDiscoveryMessage::HasSourceLinkLayerAddress() const {
  return HasOption(kOptionTypeSourceLinkLayerAddress);
}

bool NeighborDiscoveryMessage::GetSourceLinkLayerAddress(
    uint32_t opt_index, LLAddress* source_ll_address) const {
  return GetLinkLayerAddress(kOptionTypeSourceLinkLayerAddress, opt_index,
                             source_ll_address);
}

bool NeighborDiscoveryMessage::SetSourceLinkLayerAddress(
    uint32_t opt_index, const LLAddress& source_ll_address) {
  return SetLinkLayerAddress(kOptionTypeSourceLinkLayerAddress, opt_index,
                             source_ll_address);
}

bool NeighborDiscoveryMessage::PushSourceLinkLayerAddress(
    const LLAddress& source_ll_address) {
  return PushLinkLayerAddress(kOptionTypeSourceLinkLayerAddress,
                              source_ll_address);
}

// Option - Target link-layer address.

bool NeighborDiscoveryMessage::HasTargetLinkLayerAddress() const {
  return HasOption(kOptionTypeTargetLinkLayerAddress);
}

bool NeighborDiscoveryMessage::GetTargetLinkLayerAddress(
    uint32_t opt_index, LLAddress* target_ll_address) const {
  return GetLinkLayerAddress(kOptionTypeTargetLinkLayerAddress, opt_index,
                             target_ll_address);
}

bool NeighborDiscoveryMessage::SetTargetLinkLayerAddress(
    uint32_t opt_index, const LLAddress& target_ll_address) {
  return SetLinkLayerAddress(kOptionTypeTargetLinkLayerAddress, opt_index,
                             target_ll_address);
}

bool NeighborDiscoveryMessage::PushTargetLinkLayerAddress(
    const LLAddress& target_ll_address) {
  return PushLinkLayerAddress(kOptionTypeTargetLinkLayerAddress,
                              target_ll_address);
}

// Option - Prefix information.

bool NeighborDiscoveryMessage::HasPrefixInformation() const {
  return HasOption(kOptionTypePrefixInformation);
}

uint32_t NeighborDiscoveryMessage::PrefixInformationCount() const {
  return OptionCount(kOptionTypePrefixInformation);
}

bool NeighborDiscoveryMessage::GetPrefixLength(uint32_t opt_index,
                                               uint8_t* prefix_length) const {
  if (opt_index >= PrefixInformationCount() || nullptr == prefix_length) {
    return false;
  }

  const struct nd_opt_prefix_info* opt_prefix_info =
      reinterpret_cast<const struct nd_opt_prefix_info*>(
          GetConstOptionPointer(kOptionTypePrefixInformation, opt_index));

  CHECK_EQ(opt_prefix_info->nd_opt_pi_type, kOptionTypePrefixInformation);

  *prefix_length = opt_prefix_info->nd_opt_pi_prefix_len;
  return true;
}

bool NeighborDiscoveryMessage::GetOnLinkFlag(uint32_t opt_index,
                                             bool* on_link_flag) const {
  if (opt_index >= PrefixInformationCount() || nullptr == on_link_flag) {
    return false;
  }

  const struct nd_opt_prefix_info* opt_prefix_info =
      reinterpret_cast<const struct nd_opt_prefix_info*>(
          GetConstOptionPointer(kOptionTypePrefixInformation, opt_index));

  CHECK_EQ(opt_prefix_info->nd_opt_pi_type, kOptionTypePrefixInformation);

  *on_link_flag = ((opt_prefix_info->nd_opt_pi_flags_reserved &
                    ND_OPT_PI_FLAG_ONLINK) != 0);
  return true;
}

bool NeighborDiscoveryMessage::GetAutonomousAddressConfigurationFlag(
    uint32_t opt_index, bool* autonomous_flag) const {
  if (opt_index >= PrefixInformationCount() || nullptr == autonomous_flag) {
    return false;
  }

  const struct nd_opt_prefix_info* opt_prefix_info =
      reinterpret_cast<const struct nd_opt_prefix_info*>(
          GetConstOptionPointer(kOptionTypePrefixInformation, opt_index));

  CHECK_EQ(opt_prefix_info->nd_opt_pi_type, kOptionTypePrefixInformation);

  *autonomous_flag =
      ((opt_prefix_info->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_AUTO) != 0);
  return true;
}

bool NeighborDiscoveryMessage::GetPrefixValidLifetime(
    uint32_t opt_index, TimeDelta* valid_lifetime) const {
  if (opt_index >= PrefixInformationCount() || nullptr == valid_lifetime) {
    return false;
  }

  const struct nd_opt_prefix_info* opt_prefix_info =
      reinterpret_cast<const struct nd_opt_prefix_info*>(
          GetConstOptionPointer(kOptionTypePrefixInformation, opt_index));

  CHECK_EQ(opt_prefix_info->nd_opt_pi_type, kOptionTypePrefixInformation);

  *valid_lifetime =
      TimeDelta::FromSeconds(ntohl(opt_prefix_info->nd_opt_pi_valid_time));
  return true;
}

bool NeighborDiscoveryMessage::GetPrefixPreferredLifetime(
    uint32_t opt_index, TimeDelta* preferred_lifetime) const {
  if (opt_index >= PrefixInformationCount() || nullptr == preferred_lifetime) {
    return false;
  }

  const struct nd_opt_prefix_info* opt_prefix_info =
      reinterpret_cast<const struct nd_opt_prefix_info*>(
          GetConstOptionPointer(kOptionTypePrefixInformation, opt_index));

  CHECK_EQ(opt_prefix_info->nd_opt_pi_type, kOptionTypePrefixInformation);

  *preferred_lifetime =
      TimeDelta::FromSeconds(ntohl(opt_prefix_info->nd_opt_pi_preferred_time));
  return true;
}

bool NeighborDiscoveryMessage::GetPrefix(uint32_t opt_index,
                                         IPAddress* prefix) const {
  if (opt_index >= PrefixInformationCount() || nullptr == prefix) {
    return false;
  }

  const struct nd_opt_prefix_info* opt_prefix_info =
      reinterpret_cast<const struct nd_opt_prefix_info*>(
          GetConstOptionPointer(kOptionTypePrefixInformation, opt_index));

  CHECK_EQ(opt_prefix_info->nd_opt_pi_type, kOptionTypePrefixInformation);

  ByteString prefix_bytes(
      reinterpret_cast<const uint8_t*>(&opt_prefix_info->nd_opt_pi_prefix),
      sizeof(opt_prefix_info->nd_opt_pi_prefix));

  // Convert, but validate before returning.
  IPAddress temp_prefix(IPAddress::kFamilyIPv6, prefix_bytes);

  if (!temp_prefix.IsValid()) {
    return false;
  }

  *prefix = temp_prefix;
  return true;
}

bool NeighborDiscoveryMessage::PushPrefixInformation(
    uint8_t prefix_length,
    bool on_link_flag,
    bool autonomous_flag,
    const TimeDelta& valid_lifetime,
    const TimeDelta& preferred_lifetime,
    const IPAddress& prefix) {
  if (!IsValid() || !prefix.IsValid() ||
      prefix.family() != IPAddress::kFamilyIPv6) {
    return false;
  }

  ByteString opt_buf(kOptionTypeMinLengthPrefixInformation);
  memset(opt_buf.GetData(), 0, opt_buf.GetLength());

  struct nd_opt_prefix_info* opt_prefix_info =
      reinterpret_cast<struct nd_opt_prefix_info*>(opt_buf.GetData());

  // Transfer data to struct.
  opt_prefix_info->nd_opt_pi_type = kOptionTypePrefixInformation;

  opt_prefix_info->nd_opt_pi_len =
      kOptionTypeMinLengthPrefixInformation / kBytesPerOptLen;
  CHECK_EQ(opt_prefix_info->nd_opt_pi_len * kBytesPerOptLen,
           kOptionTypeMinLengthPrefixInformation);

  opt_prefix_info->nd_opt_pi_prefix_len = prefix_length;

  opt_prefix_info->nd_opt_pi_flags_reserved =
      (on_link_flag ? ND_OPT_PI_FLAG_ONLINK : 0x00) |
      (autonomous_flag ? ND_OPT_PI_FLAG_AUTO : 0x00);

  opt_prefix_info->nd_opt_pi_valid_time =
      htonl(static_cast<uint32_t>(valid_lifetime.InSeconds()));

  opt_prefix_info->nd_opt_pi_preferred_time =
      htonl(static_cast<uint32_t>(preferred_lifetime.InSeconds()));

  memcpy(reinterpret_cast<uint8_t*>(&opt_prefix_info->nd_opt_pi_prefix),
         prefix.GetConstData(), sizeof(opt_prefix_info->nd_opt_pi_prefix));

  // Add to the current message, and index new option.
  const uint32_t data_index = GetLength();
  message_.Append(opt_buf);
  AddOptionIndex(kOptionTypePrefixInformation, data_index);
  return true;
}

// Option - Redirect header.

bool NeighborDiscoveryMessage::HasRedirectedHeader() const {
  return HasOption(kOptionTypeRedirectHeader);
}

bool NeighborDiscoveryMessage::GetIpHeaderAndData(
    uint32_t opt_index, ByteString* ip_header_and_data) const {
  if (opt_index >= OptionCount(kOptionTypeRedirectHeader) ||
      nullptr == ip_header_and_data) {
    return false;
  }

  const uint8_t* opt_ptr =
      GetConstOptionPointer(kOptionTypeRedirectHeader, opt_index);
  const struct nd_opt_rd_hdr* opt_rd_hdr =
      reinterpret_cast<const struct nd_opt_rd_hdr*>(opt_ptr);

  CHECK_EQ(opt_rd_hdr->nd_opt_rh_type, kOptionTypeRedirectHeader);

  const uint32_t header_length = (opt_rd_hdr->nd_opt_rh_len * kBytesPerOptLen) -
                                 sizeof(struct nd_opt_rd_hdr);

  if (header_length == 0) {
    // Nothing to returns.
    ip_header_and_data->Clear();
    return true;
  }

  *ip_header_and_data =
      ByteString(&opt_ptr[sizeof(struct nd_opt_rd_hdr)], header_length);
  return true;
}

bool NeighborDiscoveryMessage::PushRedirectedHeader(
    const shill::ByteString& ip_header_and_data) {
  if (!IsValid()) {
    return false;
  }

  // Allocate space for the option header.
  ByteString opt_buf(kOptionTypeMinLengthRedirectHeader);
  memset(opt_buf.GetData(), 0, opt_buf.GetLength());
  struct nd_opt_rd_hdr* opt_rd_hdr =
      reinterpret_cast<struct nd_opt_rd_hdr*>(opt_buf.GetData());

  opt_rd_hdr->nd_opt_rh_type = kOptionTypeRedirectHeader;
  opt_rd_hdr->nd_opt_rh_len =
      (kOptionTypeMinLengthRedirectHeader / kBytesPerOptLen) +
      (ip_header_and_data.GetLength() / kBytesPerOptLen) +
      // Possible that there is an incomplete 64-bit block.
      ((ip_header_and_data.GetLength() % kBytesPerOptLen) != 0 ? 1 : 0);

  opt_rd_hdr = nullptr;  // Appending can invalidate the header pointer.
  opt_buf.Append(ip_header_and_data);

  // Add padding as needed.
  if ((opt_buf.GetLength() % kBytesPerOptLen) != 0) {
    ByteString pad(kBytesPerOptLen - (opt_buf.GetLength() % kBytesPerOptLen));
    memset(pad.GetData(), 0, pad.GetLength());
    opt_buf.Append(pad);
  }

  // Append option and index it.
  const uint32_t data_index = GetLength();
  message_.Append(opt_buf);
  AddOptionIndex(kOptionTypeRedirectHeader, data_index);
  return true;
}

// Option - MTU

bool NeighborDiscoveryMessage::HasMTU() const {
  return HasOption(kOptionTypeMTU);
}

bool NeighborDiscoveryMessage::GetMTU(uint32_t opt_index, uint32_t* mtu) const {
  if (opt_index >= OptionCount(kOptionTypeMTU) || nullptr == mtu) {
    return false;
  }

  const struct nd_opt_mtu* opt_mtu = reinterpret_cast<const struct nd_opt_mtu*>(
      GetConstOptionPointer(kOptionTypeMTU, opt_index));

  CHECK_EQ(opt_mtu->nd_opt_mtu_type, kOptionTypeMTU);

  *mtu = ntohl(opt_mtu->nd_opt_mtu_mtu);
  return true;
}

bool NeighborDiscoveryMessage::PushMTU(uint32_t mtu) {
  if (!IsValid()) {
    return false;
  }

  ByteString opt_buf(kOptionTypeMinLengthMTU);
  memset(opt_buf.GetData(), 0, opt_buf.GetLength());
  struct nd_opt_mtu* opt_mtu =
      reinterpret_cast<struct nd_opt_mtu*>(opt_buf.GetData());

  opt_mtu->nd_opt_mtu_type = kOptionTypeMTU;
  opt_mtu->nd_opt_mtu_len = 1;
  opt_mtu->nd_opt_mtu_mtu = htonl(mtu);

  const uint32_t data_index = GetLength();
  message_.Append(opt_buf);
  AddOptionIndex(kOptionTypeMTU, data_index);
  return true;
}

// Option - Generic link-layer address (internal).

bool NeighborDiscoveryMessage::GetLinkLayerAddress(
    OptionType opt_type, uint32_t opt_index, LLAddress* ll_address) const {
  CHECK(kOptionTypeSourceLinkLayerAddress == opt_type ||
        kOptionTypeTargetLinkLayerAddress == opt_type);

  if (nullptr == ll_address || opt_index >= OptionCount(opt_type)) {
    return false;
  }

  const uint8_t* opt_ptr = GetConstOptionPointer(opt_type, opt_index);
  const struct nd_opt_hdr* opt_hdr =
      reinterpret_cast<const struct nd_opt_hdr*>(opt_ptr);

  CHECK_EQ(opt_hdr->nd_opt_type, opt_type);

  ByteString opt_data(OptionConstData(opt_hdr), OptionDataLength(opt_hdr));

  if (opt_data.GetLength() !=
      LLAddress::GetTypeLength(LLAddress::Type::kEui48)) {
    // Unknown length of EUI
    return false;
  }

  *ll_address = LLAddress(LLAddress::Type::kEui48, opt_data);
  return true;
}

// private
bool NeighborDiscoveryMessage::SetLinkLayerAddress(
    OptionType opt_type, uint32_t opt_index, const LLAddress& ll_address) {
  CHECK(kOptionTypeSourceLinkLayerAddress == opt_type ||
        kOptionTypeTargetLinkLayerAddress == opt_type);

  if (opt_index >= OptionCount(opt_type) || !ll_address.IsValid()) {
    return false;
  }
  if (ll_address.type() != LLAddress::Type::kEui48) {
    NOTIMPLEMENTED() << "Current can't handle non-Ethernet link-layer "
                     << "addresses, got "
                     << LLAddress::GetTypeName(ll_address.type());
    return false;
  }

  uint8_t* opt_ptr = GetOptionPointer(opt_type, opt_index);
  struct nd_opt_hdr* opt_hdr = reinterpret_cast<struct nd_opt_hdr*>(opt_ptr);

  if (ll_address.GetLength() != OptionDataLength(opt_hdr)) {
    // Currently cannot handle the case where the stored link-layer
    // address is a different size than the one replacing it.
    return false;
  }

  memcpy(OptionData(opt_hdr), ll_address.GetConstData(),
         ll_address.GetLength());
  return true;
}

// private
bool NeighborDiscoveryMessage::PushLinkLayerAddress(
    OptionType opt_type, const LLAddress& ll_address) {
  CHECK(kOptionTypeSourceLinkLayerAddress == opt_type ||
        kOptionTypeTargetLinkLayerAddress == opt_type);

  if (!IsValid()) {
    return false;
  }
  if (ll_address.type() != LLAddress::Type::kEui48) {
    NOTIMPLEMENTED() << "Current can't handle non-Ethernet link-layer "
                     << "addresses, got "
                     << LLAddress::GetTypeName(ll_address.type());
    return false;
  }

  // Allocate space for the option.
  ByteString opt_buf(kBytesPerOptLen);
  struct nd_opt_hdr* opt_hdr =
      reinterpret_cast<struct nd_opt_hdr*>(opt_buf.GetData());

  // Populate the option.
  opt_hdr->nd_opt_type = opt_type;
  opt_hdr->nd_opt_len = 1;
  memcpy(OptionData(opt_hdr), ll_address.GetConstData(),
         ll_address.GetLength());

  // Append to the current message and update the index.
  const uint32_t data_index = GetLength();
  message_.Append(opt_buf);
  AddOptionIndex(opt_type, data_index);
  return true;
}

// Indexing Options.

bool NeighborDiscoveryMessage::IndexOptions() {
  const uint32_t min_len = GetTypeMinimumLength(type());
  CHECK_GT(min_len, 0);
  CHECK(GetLength() >= min_len);

  // Clear all of the current indexes.
  options_.clear();

  // If there are none, then return.
  if (GetLength() == min_len) {
    // No options, done.
    return true;
  }

  uint32_t bytes_remaining = GetLength() - min_len;
  if ((bytes_remaining % kBytesPerOptLen) != 0) {
    // Packet does not align on 64-bit boundaries.
    return false;
  }

  uint32_t data_index = min_len;
  do {
    const uint8_t* opt_ptr = &GetConstData()[data_index];
    const struct nd_opt_hdr* opt_hdr =
        reinterpret_cast<const struct nd_opt_hdr*>(opt_ptr);
    const uint32_t opt_len = (opt_hdr->nd_opt_len * kBytesPerOptLen);

    // Check if invalid option length.  Packet might have been received using
    // AF_PACKET which does not validate ICMPv6 frames in the kernel before
    // passing up to user-space.
    if (0 == opt_len) {
      // RFC 4861: Nodes MUST silently discard an ND packet that contains an
      // option with length zero.
      LOG(ERROR) << "Received option with zero-length option: "
                 << GetOptionTypeName(opt_hdr->nd_opt_type) << " ("
                 << static_cast<uint32_t>(opt_hdr->nd_opt_type) << ")";
      options_.clear();
      return false;
    }

    if (opt_len > bytes_remaining) {
      // Not possible unless packet was truncated.
      LOG(ERROR) << "Option length is greater than remaining packet size";
      options_.clear();
      return false;
    }

    const OptionType opt_type = opt_hdr->nd_opt_type;
    const uint32_t opt_min_len = GetOptionTypeMinimumLength(opt_type);

    if (opt_min_len == 0) {
      // RFC 4861: Receivers MUST silently ignore any options they do not
      // recognize and continue processing the message.
      LOG(WARNING) << "Indexing unknown option type "
                   << static_cast<uint32_t>(opt_type);
      AddOptionIndex(opt_type, data_index);
    } else {
      switch (opt_type) {
        case kOptionTypeSourceLinkLayerAddress:
        case kOptionTypeTargetLinkLayerAddress:
          // Technically, the LL address can be any length necessary, so
          // it we must assume it is valid.
          AddOptionIndex(opt_type, data_index);
          break;
        case kOptionTypePrefixInformation:
        case kOptionTypeRedirectHeader:
        case kOptionTypeMTU:
          // These options have fixed lengths and are invalid if their size
          // does not match the expected length.  RFC 4861 does not specify
          // how this is to be handled, for now, we silently ignore the option,
          // but we do not index it.
          if (opt_len >= opt_min_len) {
            AddOptionIndex(opt_type, data_index);
          }
          break;
        default:
          // Should not reach this branch.
          CHECK(false);
      }
    }
    data_index += opt_len;
    bytes_remaining -= opt_len;
  } while (bytes_remaining > 0);

  return true;
}

void NeighborDiscoveryMessage::AddOptionIndex(OptionType opt_type,
                                              uint32_t data_index) {
  using OptPair = std::pair<OptionType, vector<uint32_t>>;

  CHECK(GetLength() > GetTypeMinimumLength(type()));

  // If index vector does not exsts, create new index vector for option type.
  if (options_.find(opt_type) == options_.end()) {
    options_.insert(OptPair(opt_type, vector<uint32_t>()));
  }
  options_[opt_type].push_back(data_index);
}

}  // namespace portier
