// Copyright (c) 2020 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/bonnet/tun_device_controller.h"

#include <linux/if_addr.h>
#include <linux/rtnetlink.h>

#include "absl/strings/string_view.h"
#include "quiche/quic/platform/api/quic_test.h"
#include "quiche/quic/qbone/platform/mock_netlink.h"
#include "quiche/quic/qbone/qbone_constants.h"

ABSL_DECLARE_FLAG(bool, qbone_tun_device_replace_default_routing_rules);
ABSL_DECLARE_FLAG(int, qbone_route_init_cwnd);

namespace quic::test {
namespace {
using ::testing::Eq;

constexpr int kIfindex = 42;
constexpr char kIfname[] = "qbone0";

const IpRange kIpRange = []() {
  IpRange range;
  QCHECK(range.FromString("2604:31c0:2::/64"));
  return range;
}();

constexpr char kOldAddress[] = "1.2.3.4";
constexpr int kOldPrefixLen = 24;

using ::testing::_;
using ::testing::Invoke;
using ::testing::Return;
using ::testing::StrictMock;

MATCHER_P(IpRangeEq, range,
          absl::StrCat("expected IpRange to equal ", range.ToString())) {
  return arg == range;
}

class TunDeviceControllerTest : public QuicTest {
 public:
  TunDeviceControllerTest()
      : controller_(kIfname, true, &netlink_),
        link_local_range_(*QboneConstants::TerminatorLocalAddressRange()) {
    controller_.RegisterAddressUpdateCallback(
        [this](QuicIpAddress address) { notified_address_ = address; });
  }

 protected:
  void ExpectLinkInfo(const std::string& interface_name, int ifindex) {
    EXPECT_CALL(netlink_, GetLinkInfo(interface_name, _))
        .WillOnce(Invoke([ifindex](absl::string_view ifname,
                                   NetlinkInterface::LinkInfo* link_info) {
          link_info->index = ifindex;
          return true;
        }));
  }

  MockNetlink netlink_;
  TunDeviceController controller_;
  QuicIpAddress notified_address_;

  IpRange link_local_range_;
};

TEST_F(TunDeviceControllerTest, AddressAppliedWhenNoneExisted) {
  ExpectLinkInfo(kIfname, kIfindex);

  EXPECT_CALL(netlink_, GetAddresses(kIfindex, _, _, _)).WillOnce(Return(true));

  EXPECT_CALL(netlink_,
              ChangeLocalAddress(
                  kIfindex, NetlinkInterface::Verb::kAdd,
                  kIpRange.FirstAddressInRange(), kIpRange.prefix_length(),
                  IFA_F_PERMANENT | IFA_F_NODAD, RT_SCOPE_LINK, _))
      .WillOnce(Return(true));

  EXPECT_TRUE(controller_.UpdateAddress(kIpRange));
  EXPECT_THAT(notified_address_, Eq(kIpRange.FirstAddressInRange()));
}

TEST_F(TunDeviceControllerTest, OldAddressesAreRemoved) {
  ExpectLinkInfo(kIfname, kIfindex);

  EXPECT_CALL(netlink_, GetAddresses(kIfindex, _, _, _))
      .WillOnce(Invoke([](int interface_index, uint8_t unwanted_flags,
                          std::vector<NetlinkInterface::AddressInfo>* addresses,
                          int* num_ipv6_nodad_dadfailed_addresses) {
        NetlinkInterface::AddressInfo info{};
        info.interface_address.FromString(kOldAddress);
        info.prefix_length = kOldPrefixLen;
        addresses->emplace_back(info);
        return true;
      }));

  QuicIpAddress old_address;
  old_address.FromString(kOldAddress);

  EXPECT_CALL(netlink_,
              ChangeLocalAddress(kIfindex, NetlinkInterface::Verb::kRemove,
                                 old_address, kOldPrefixLen, _, _, _))
      .WillOnce(Return(true));

  EXPECT_CALL(netlink_,
              ChangeLocalAddress(
                  kIfindex, NetlinkInterface::Verb::kAdd,
                  kIpRange.FirstAddressInRange(), kIpRange.prefix_length(),
                  IFA_F_PERMANENT | IFA_F_NODAD, RT_SCOPE_LINK, _))
      .WillOnce(Return(true));

  EXPECT_TRUE(controller_.UpdateAddress(kIpRange));
  EXPECT_THAT(notified_address_, Eq(kIpRange.FirstAddressInRange()));
}

TEST_F(TunDeviceControllerTest, UpdateRoutesRemovedOldRoutes) {
  ExpectLinkInfo(kIfname, kIfindex);

  const int num_matching_routes = 3;
  EXPECT_CALL(netlink_, GetRouteInfo(_))
      .WillOnce(
          Invoke([](std::vector<NetlinkInterface::RoutingRule>* routing_rules) {
            NetlinkInterface::RoutingRule non_matching_route{};
            non_matching_route.table = QboneConstants::kQboneRouteTableId;
            non_matching_route.out_interface = kIfindex + 1;
            routing_rules->push_back(non_matching_route);

            NetlinkInterface::RoutingRule matching_route{};
            matching_route.table = QboneConstants::kQboneRouteTableId;
            matching_route.out_interface = kIfindex;
            matching_route.init_cwnd = NetlinkInterface::kUnspecifiedInitCwnd;
            for (int i = 0; i < num_matching_routes; i++) {
              routing_rules->push_back(matching_route);
            }

            NetlinkInterface::RoutingRule non_matching_table{};
            non_matching_table.table = QboneConstants::kQboneRouteTableId + 1;
            non_matching_table.out_interface = kIfindex;
            routing_rules->push_back(non_matching_table);
            return true;
          }));

  EXPECT_CALL(netlink_,
              ChangeRoute(NetlinkInterface::Verb::kRemove,
                          QboneConstants::kQboneRouteTableId, _, _, _, kIfindex,
                          NetlinkInterface::kUnspecifiedInitCwnd))
      .Times(num_matching_routes)
      .WillRepeatedly(Return(true));

  EXPECT_CALL(netlink_, GetRuleInfo(_)).WillOnce(Return(true));

  EXPECT_CALL(netlink_, ChangeRule(NetlinkInterface::Verb::kAdd,
                                   QboneConstants::kQboneRouteTableId,
                                   IpRangeEq(kIpRange)))
      .WillOnce(Return(true));

  EXPECT_CALL(netlink_,
              ChangeRoute(NetlinkInterface::Verb::kReplace,
                          QboneConstants::kQboneRouteTableId,
                          IpRangeEq(link_local_range_), _, _, kIfindex, _))
      .WillOnce(Return(true));

  EXPECT_TRUE(controller_.UpdateRoutes(kIpRange, {}));
}

TEST_F(TunDeviceControllerTest, UpdateRoutesAddsNewRoutes) {
  ExpectLinkInfo(kIfname, kIfindex);

  EXPECT_CALL(netlink_, GetRouteInfo(_)).WillOnce(Return(true));

  EXPECT_CALL(netlink_, GetRuleInfo(_)).WillOnce(Return(true));

  absl::SetFlag(&FLAGS_qbone_route_init_cwnd, 32);
  EXPECT_CALL(netlink_, ChangeRoute(NetlinkInterface::Verb::kReplace,
                                    QboneConstants::kQboneRouteTableId,
                                    IpRangeEq(kIpRange), _, _, kIfindex,
                                    absl::GetFlag(FLAGS_qbone_route_init_cwnd)))
      .Times(2)
      .WillRepeatedly(Return(true))
      .RetiresOnSaturation();

  EXPECT_CALL(netlink_, ChangeRule(NetlinkInterface::Verb::kAdd,
                                   QboneConstants::kQboneRouteTableId,
                                   IpRangeEq(kIpRange)))
      .WillOnce(Return(true));

  EXPECT_CALL(netlink_,
              ChangeRoute(NetlinkInterface::Verb::kReplace,
                          QboneConstants::kQboneRouteTableId,
                          IpRangeEq(link_local_range_), _, _, kIfindex, _))
      .WillOnce(Return(true));

  EXPECT_TRUE(controller_.UpdateRoutes(kIpRange, {kIpRange, kIpRange}));
}

TEST_F(TunDeviceControllerTest, EmptyUpdateRouteKeepsLinkLocalRoute) {
  ExpectLinkInfo(kIfname, kIfindex);

  EXPECT_CALL(netlink_, GetRouteInfo(_)).WillOnce(Return(true));

  EXPECT_CALL(netlink_, GetRuleInfo(_)).WillOnce(Return(true));

  EXPECT_CALL(netlink_, ChangeRule(NetlinkInterface::Verb::kAdd,
                                   QboneConstants::kQboneRouteTableId,
                                   IpRangeEq(kIpRange)))
      .WillOnce(Return(true));

  EXPECT_CALL(netlink_,
              ChangeRoute(NetlinkInterface::Verb::kReplace,
                          QboneConstants::kQboneRouteTableId,
                          IpRangeEq(link_local_range_), _, _, kIfindex, _))
      .WillOnce(Return(true));

  EXPECT_TRUE(controller_.UpdateRoutes(kIpRange, {}));
}

TEST_F(TunDeviceControllerTest, DisablingRoutingRulesSkipsRuleCreation) {
  absl::SetFlag(&FLAGS_qbone_tun_device_replace_default_routing_rules, false);
  ExpectLinkInfo(kIfname, kIfindex);

  EXPECT_CALL(netlink_, GetRouteInfo(_)).WillOnce(Return(true));

  EXPECT_CALL(netlink_, ChangeRoute(NetlinkInterface::Verb::kReplace,
                                    QboneConstants::kQboneRouteTableId,
                                    IpRangeEq(kIpRange), _, _, kIfindex, _))
      .Times(2)
      .WillRepeatedly(Return(true))
      .RetiresOnSaturation();

  EXPECT_CALL(netlink_,
              ChangeRoute(NetlinkInterface::Verb::kReplace,
                          QboneConstants::kQboneRouteTableId,
                          IpRangeEq(link_local_range_), _, _, kIfindex, _))
      .WillOnce(Return(true));

  EXPECT_TRUE(controller_.UpdateRoutes(kIpRange, {kIpRange, kIpRange}));
}

class DisabledTunDeviceControllerTest : public QuicTest {
 public:
  DisabledTunDeviceControllerTest()
      : controller_(kIfname, false, &netlink_),
        link_local_range_(*QboneConstants::TerminatorLocalAddressRange()) {}

  StrictMock<MockNetlink> netlink_;
  TunDeviceController controller_;

  IpRange link_local_range_;
};

TEST_F(DisabledTunDeviceControllerTest, UpdateRoutesIsNop) {
  EXPECT_THAT(controller_.UpdateRoutes(kIpRange, {}), Eq(true));
}

TEST_F(DisabledTunDeviceControllerTest, UpdateAddressIsNop) {
  EXPECT_THAT(controller_.UpdateAddress(kIpRange), Eq(true));
}

}  // namespace
}  // namespace quic::test
