// Copyright (c) 2019 The Chromium 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 "quiche/quic/qbone/platform/rtnetlink_message.h"

#include <utility>

namespace quic {

RtnetlinkMessage::RtnetlinkMessage(uint16_t type, uint16_t flags, uint32_t seq,
                                   uint32_t pid, const void* payload_header,
                                   size_t payload_header_length) {
  auto* buf = new uint8_t[NLMSG_SPACE(payload_header_length)];
  memset(buf, 0, NLMSG_SPACE(payload_header_length));

  auto* message_header = reinterpret_cast<struct nlmsghdr*>(buf);
  message_header->nlmsg_len = NLMSG_LENGTH(payload_header_length);
  message_header->nlmsg_type = type;
  message_header->nlmsg_flags = flags;
  message_header->nlmsg_seq = seq;
  message_header->nlmsg_pid = pid;

  if (payload_header != nullptr) {
    memcpy(NLMSG_DATA(message_header), payload_header, payload_header_length);
  }
  message_.push_back({buf, NLMSG_SPACE(payload_header_length)});
}

RtnetlinkMessage::~RtnetlinkMessage() {
  for (const auto& iov : message_) {
    delete[] reinterpret_cast<uint8_t*>(iov.iov_base);
  }
}

void RtnetlinkMessage::AppendAttribute(uint16_t type, const void* data,
                                       uint16_t data_length) {
  auto* buf = new uint8_t[RTA_SPACE(data_length)];
  memset(buf, 0, RTA_SPACE(data_length));

  auto* rta = reinterpret_cast<struct rtattr*>(buf);
  static_assert(sizeof(uint16_t) == sizeof(rta->rta_len),
                "struct rtattr uses unsigned short, it's no longer 16bits");
  static_assert(sizeof(uint16_t) == sizeof(rta->rta_type),
                "struct rtattr uses unsigned short, it's no longer 16bits");

  rta->rta_len = RTA_LENGTH(data_length);
  rta->rta_type = type;
  memcpy(RTA_DATA(rta), data, data_length);

  message_.push_back({buf, RTA_SPACE(data_length)});
  AdjustMessageLength(rta->rta_len);
}

std::unique_ptr<struct iovec[]> RtnetlinkMessage::BuildIoVec() const {
  auto message = std::make_unique<struct iovec[]>(message_.size());
  int idx = 0;
  for (const auto& vec : message_) {
    message[idx++] = vec;
  }
  return message;
}

size_t RtnetlinkMessage::IoVecSize() const { return message_.size(); }

void RtnetlinkMessage::AdjustMessageLength(size_t additional_data_length) {
  MessageHeader()->nlmsg_len =
      NLMSG_ALIGN(MessageHeader()->nlmsg_len) + additional_data_length;
}

struct nlmsghdr* RtnetlinkMessage::MessageHeader() {
  return reinterpret_cast<struct nlmsghdr*>(message_[0].iov_base);
}

LinkMessage LinkMessage::New(RtnetlinkMessage::Operation request_operation,
                             uint16_t flags, uint32_t seq, uint32_t pid,
                             const struct ifinfomsg* interface_info_header) {
  uint16_t request_type;
  switch (request_operation) {
    case RtnetlinkMessage::Operation::NEW:
      request_type = RTM_NEWLINK;
      break;
    case RtnetlinkMessage::Operation::DEL:
      request_type = RTM_DELLINK;
      break;
    case RtnetlinkMessage::Operation::GET:
      request_type = RTM_GETLINK;
      break;
  }
  bool is_get = request_type == RTM_GETLINK;

  if (is_get) {
    struct rtgenmsg g = {AF_UNSPEC};
    return LinkMessage(request_type, flags, seq, pid, &g, sizeof(g));
  }
  return LinkMessage(request_type, flags, seq, pid, interface_info_header,
                     sizeof(struct ifinfomsg));
}

AddressMessage AddressMessage::New(
    RtnetlinkMessage::Operation request_operation, uint16_t flags, uint32_t seq,
    uint32_t pid, const struct ifaddrmsg* interface_address_header) {
  uint16_t request_type;
  switch (request_operation) {
    case RtnetlinkMessage::Operation::NEW:
      request_type = RTM_NEWADDR;
      break;
    case RtnetlinkMessage::Operation::DEL:
      request_type = RTM_DELADDR;
      break;
    case RtnetlinkMessage::Operation::GET:
      request_type = RTM_GETADDR;
      break;
  }
  bool is_get = request_type == RTM_GETADDR;

  if (is_get) {
    struct rtgenmsg g = {AF_UNSPEC};
    return AddressMessage(request_type, flags, seq, pid, &g, sizeof(g));
  }
  return AddressMessage(request_type, flags, seq, pid, interface_address_header,
                        sizeof(struct ifaddrmsg));
}

RouteMessage RouteMessage::New(RtnetlinkMessage::Operation request_operation,
                               uint16_t flags, uint32_t seq, uint32_t pid,
                               const struct rtmsg* route_message_header) {
  uint16_t request_type;
  switch (request_operation) {
    case RtnetlinkMessage::Operation::NEW:
      request_type = RTM_NEWROUTE;
      break;
    case RtnetlinkMessage::Operation::DEL:
      request_type = RTM_DELROUTE;
      break;
    case RtnetlinkMessage::Operation::GET:
      request_type = RTM_GETROUTE;
      break;
  }
  return RouteMessage(request_type, flags, seq, pid, route_message_header,
                      sizeof(struct rtmsg));
}

RuleMessage RuleMessage::New(RtnetlinkMessage::Operation request_operation,
                             uint16_t flags, uint32_t seq, uint32_t pid,
                             const struct rtmsg* rule_message_header) {
  uint16_t request_type;
  switch (request_operation) {
    case RtnetlinkMessage::Operation::NEW:
      request_type = RTM_NEWRULE;
      break;
    case RtnetlinkMessage::Operation::DEL:
      request_type = RTM_DELRULE;
      break;
    case RtnetlinkMessage::Operation::GET:
      request_type = RTM_GETRULE;
      break;
  }
  return RuleMessage(request_type, flags, seq, pid, rule_message_header,
                     sizeof(rtmsg));
}
}  // namespace quic
