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

#include <utility>

#include "absl/strings/string_view.h"
#include "quiche/quic/platform/api/quic_test.h"
#include "quiche/quic/qbone/qbone_packet_processor_test_tools.h"
#include "quiche/common/quiche_text_utils.h"

namespace quic::test {
namespace {

using Direction = QbonePacketProcessor::Direction;
using ProcessingResult = QbonePacketProcessor::ProcessingResult;
using OutputInterface = QbonePacketProcessor::OutputInterface;
using ::testing::_;
using ::testing::Eq;
using ::testing::Invoke;
using ::testing::Return;
using ::testing::WithArgs;

// clang-format off
static const char kReferenceClientPacketData[] = {
    // IPv6 with zero TOS and flow label.
    0x60, 0x00, 0x00, 0x00,
    // Payload size is 8 bytes.
    0x00, 0x08,
    // Next header is UDP
    17,
    // TTL is 50.
    50,
    // IP address of the sender is fd00:0:0:1::1
    0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
    // IP address of the receiver is fd00:0:0:5::1
    0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
    // Source port 12345
    0x30, 0x39,
    // Destination port 443
    0x01, 0xbb,
    // UDP content length is zero
    0x00, 0x00,
    // Checksum is not actually checked in any of the tests, so we leave it as
    // zero
    0x00, 0x00,
};

static const char kReferenceClientPacketDataAF4[] = {
    // IPv6 with 0x80 TOS and zero flow label.
    0x68, 0x00, 0x00, 0x00,
    // Payload size is 8 bytes.
    0x00, 0x08,
    // Next header is UDP
    17,
    // TTL is 50.
    50,
    // IP address of the sender is fd00:0:0:1::1
    0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
    // IP address of the receiver is fd00:0:0:5::1
    0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
    // Source port 12345
    0x30, 0x39,
    // Destination port 443
    0x01, 0xbb,
    // UDP content length is zero
    0x00, 0x00,
    // Checksum is not actually checked in any of the tests, so we leave it as
    // zero
    0x00, 0x00,
};

static const char kReferenceClientPacketDataAF3[] = {
    // IPv6 with 0x60 TOS and zero flow label.
    0x66, 0x00, 0x00, 0x00,
    // Payload size is 8 bytes.
    0x00, 0x08,
    // Next header is UDP
    17,
    // TTL is 50.
    50,
    // IP address of the sender is fd00:0:0:1::1
    0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
    // IP address of the receiver is fd00:0:0:5::1
    0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
    // Source port 12345
    0x30, 0x39,
    // Destination port 443
    0x01, 0xbb,
    // UDP content length is zero
    0x00, 0x00,
    // Checksum is not actually checked in any of the tests, so we leave it as
    // zero
    0x00, 0x00,
};

static const char kReferenceClientPacketDataAF2[] = {
    // IPv6 with 0x40 TOS and zero flow label.
    0x64, 0x00, 0x00, 0x00,
    // Payload size is 8 bytes.
    0x00, 0x08,
    // Next header is UDP
    17,
    // TTL is 50.
    50,
    // IP address of the sender is fd00:0:0:1::1
    0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
    // IP address of the receiver is fd00:0:0:5::1
    0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
    // Source port 12345
    0x30, 0x39,
    // Destination port 443
    0x01, 0xbb,
    // UDP content length is zero
    0x00, 0x00,
    // Checksum is not actually checked in any of the tests, so we leave it as
    // zero
    0x00, 0x00,
};

static const char kReferenceClientPacketDataAF1[] = {
    // IPv6 with 0x20 TOS and zero flow label.
    0x62, 0x00, 0x00, 0x00,
    // Payload size is 8 bytes.
    0x00, 0x08,
    // Next header is UDP
    17,
    // TTL is 50.
    50,
    // IP address of the sender is fd00:0:0:1::1
    0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
    // IP address of the receiver is fd00:0:0:5::1
    0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
    // Source port 12345
    0x30, 0x39,
    // Destination port 443
    0x01, 0xbb,
    // UDP content length is zero
    0x00, 0x00,
    // Checksum is not actually checked in any of the tests, so we leave it as
    // zero
    0x00, 0x00,
};

static const char kReferenceNetworkPacketData[] = {
    // IPv6 with zero TOS and flow label.
    0x60, 0x00, 0x00, 0x00,
    // Payload size is 8 bytes.
    0x00, 0x08,
    // Next header is UDP
    17,
    // TTL is 50.
    50,
    // IP address of the sender is fd00:0:0:5::1
    0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
    // IP address of the receiver is fd00:0:0:1::1
    0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
    // Source port 443
    0x01, 0xbb,
    // Destination port 12345
    0x30, 0x39,
    // UDP content length is zero
    0x00, 0x00,
    // Checksum is not actually checked in any of the tests, so we leave it as
    // zero
    0x00, 0x00,
};

static const char kReferenceClientSubnetPacketData[] = {
    // IPv6 with zero TOS and flow label.
    0x60, 0x00, 0x00, 0x00,
    // Payload size is 8 bytes.
    0x00, 0x08,
    // Next header is UDP
    17,
    // TTL is 50.
    50,
    // IP address of the sender is fd00:0:0:2::1, which is within the /62 of the
    // client.
    0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
    // IP address of the receiver is fd00:0:0:5::1
    0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
    // Source port 12345
    0x30, 0x39,
    // Destination port 443
    0x01, 0xbb,
    // UDP content length is zero
    0x00, 0x00,
    // Checksum is not actually checked in any of the tests, so we leave it as
    // zero
    0x00, 0x00,
};

static const char kReferenceEchoRequestData[] = {
    // IPv6 with zero TOS and flow label.
    0x60, 0x00, 0x00, 0x00,
    // Payload size is 64 bytes.
    0x00, 64,
    // Next header is ICMP
    58,
    // TTL is 127.
    127,
    // IP address of the sender is fd00:0:0:1::1
    0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
    // IP address of the receiver is fe80::71:626f:6e6f
    0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x71, 0x62, 0x6f, 0x6e, 0x6f,
    // ICMP Type ping request
    128,
    // ICMP Code 0
    0,
    // Checksum is not actually checked in any of the tests, so we leave it as
    // zero
    0x00, 0x00,
    // ICMP Identifier (0xcafe to be memorable)
    0xca, 0xfe,
    // Sequence number
    0x00, 0x01,
    // Data, starting with unix timeval then 0x10..0x37
    0x67, 0x37, 0x8a, 0x63, 0x00, 0x00, 0x00, 0x00,
    0x96, 0x58, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
    0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
    0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
    0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
};

static const char kReferenceEchoReplyData[] = {
    // IPv6 with zero TOS and flow label.
    0x60, 0x00, 0x00, 0x00,
    // Payload size is 64 bytes.
    0x00, 64,
    // Next header is ICMP
    58,
    // TTL is 255.
    255,
    // IP address of the sender is fd00:4:0:1::1
    0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
    // IP address of the receiver is fd00:0:0:1::1
    0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
    // ICMP Type ping reply
    129,
    // ICMP Code 0
    0,
    // Checksum
    0x66, 0xb6,
    // ICMP Identifier (0xcafe to be memorable)
    0xca, 0xfe,
    // Sequence number
    0x00, 0x01,
    // Data, starting with unix timeval then 0x10..0x37
    0x67, 0x37, 0x8a, 0x63, 0x00, 0x00, 0x00, 0x00,
    0x96, 0x58, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
    0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
    0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
    0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
};

// clang-format on

static const absl::string_view kReferenceClientPacket(
    kReferenceClientPacketData, ABSL_ARRAYSIZE(kReferenceClientPacketData));

static const absl::string_view kReferenceClientPacketAF4(
    kReferenceClientPacketDataAF4,
    ABSL_ARRAYSIZE(kReferenceClientPacketDataAF4));
static const absl::string_view kReferenceClientPacketAF3(
    kReferenceClientPacketDataAF3,
    ABSL_ARRAYSIZE(kReferenceClientPacketDataAF3));
static const absl::string_view kReferenceClientPacketAF2(
    kReferenceClientPacketDataAF2,
    ABSL_ARRAYSIZE(kReferenceClientPacketDataAF2));
static const absl::string_view kReferenceClientPacketAF1(
    kReferenceClientPacketDataAF1,
    ABSL_ARRAYSIZE(kReferenceClientPacketDataAF1));

static const absl::string_view kReferenceNetworkPacket(
    kReferenceNetworkPacketData, ABSL_ARRAYSIZE(kReferenceNetworkPacketData));

static const absl::string_view kReferenceClientSubnetPacket(
    kReferenceClientSubnetPacketData,
    ABSL_ARRAYSIZE(kReferenceClientSubnetPacketData));

static const absl::string_view kReferenceEchoRequest(
    kReferenceEchoRequestData, ABSL_ARRAYSIZE(kReferenceEchoRequestData));

MATCHER_P(IsIcmpMessage, icmp_type,
          "Checks whether the argument is an ICMP message of supplied type") {
  if (arg.size() < kTotalICMPv6HeaderSize) {
    return false;
  }

  return arg[40] == icmp_type;
}

class MockPacketFilter : public QbonePacketProcessor::Filter {
 public:
  MOCK_METHOD(ProcessingResult, FilterPacket,
              (Direction, absl::string_view, absl::string_view, icmp6_hdr*,
               OutputInterface*),
              (override));
};

class QbonePacketProcessorTest : public QuicTest {
 protected:
  QbonePacketProcessorTest() {
    QUICHE_CHECK(client_ip_.FromString("fd00:0:0:1::1"));
    QUICHE_CHECK(self_ip_.FromString("fd00:0:0:4::1"));
    QUICHE_CHECK(network_ip_.FromString("fd00:0:0:5::1"));

    processor_ = std::make_unique<QbonePacketProcessor>(
        self_ip_, client_ip_, /*client_ip_subnet_length=*/62, &output_,
        &stats_);
  }

  void SendPacketFromClient(absl::string_view packet) {
    std::string packet_buffer(packet.data(), packet.size());
    processor_->ProcessPacket(&packet_buffer, Direction::FROM_OFF_NETWORK);
  }

  void SendPacketFromNetwork(absl::string_view packet) {
    std::string packet_buffer(packet.data(), packet.size());
    processor_->ProcessPacket(&packet_buffer, Direction::FROM_NETWORK);
  }

  QuicIpAddress client_ip_;
  QuicIpAddress self_ip_;
  QuicIpAddress network_ip_;

  std::unique_ptr<QbonePacketProcessor> processor_;
  testing::StrictMock<MockPacketProcessorOutput> output_;
  testing::StrictMock<MockPacketProcessorStats> stats_;
};

TEST_F(QbonePacketProcessorTest, EmptyPacket) {
  EXPECT_CALL(stats_, OnPacketDroppedSilently(Direction::FROM_OFF_NETWORK));
  SendPacketFromClient("");

  EXPECT_CALL(stats_, OnPacketDroppedSilently(Direction::FROM_NETWORK));
  SendPacketFromNetwork("");
}

TEST_F(QbonePacketProcessorTest, RandomGarbage) {
  EXPECT_CALL(stats_, OnPacketDroppedSilently(Direction::FROM_OFF_NETWORK));
  SendPacketFromClient(std::string(1280, 'a'));

  EXPECT_CALL(stats_, OnPacketDroppedSilently(Direction::FROM_NETWORK));
  SendPacketFromNetwork(std::string(1280, 'a'));
}

TEST_F(QbonePacketProcessorTest, RandomGarbageWithCorrectLengthFields) {
  std::string packet(40, 'a');
  packet[4] = 0;
  packet[5] = 0;

  EXPECT_CALL(stats_, OnPacketDroppedWithIcmp(Direction::FROM_OFF_NETWORK));
  EXPECT_CALL(output_, SendPacketToClient(IsIcmpMessage(ICMP6_DST_UNREACH)));
  SendPacketFromClient(packet);
}

TEST_F(QbonePacketProcessorTest, GoodPacketFromClient) {
  EXPECT_CALL(stats_, OnPacketForwarded(Direction::FROM_OFF_NETWORK));
  EXPECT_CALL(output_, SendPacketToNetwork(_));
  SendPacketFromClient(kReferenceClientPacket);
}

TEST_F(QbonePacketProcessorTest, GoodPacketFromClientSubnet) {
  EXPECT_CALL(stats_, OnPacketForwarded(Direction::FROM_OFF_NETWORK));
  EXPECT_CALL(output_, SendPacketToNetwork(_));
  SendPacketFromClient(kReferenceClientSubnetPacket);
}

TEST_F(QbonePacketProcessorTest, GoodPacketFromNetwork) {
  EXPECT_CALL(stats_, OnPacketForwarded(Direction::FROM_NETWORK));
  EXPECT_CALL(output_, SendPacketToClient(_));
  SendPacketFromNetwork(kReferenceNetworkPacket);
}

TEST_F(QbonePacketProcessorTest, GoodPacketFromNetworkWrongDirection) {
  EXPECT_CALL(stats_, OnPacketDroppedWithIcmp(Direction::FROM_OFF_NETWORK));
  EXPECT_CALL(output_, SendPacketToClient(IsIcmpMessage(ICMP6_DST_UNREACH)));
  SendPacketFromClient(kReferenceNetworkPacket);
}

TEST_F(QbonePacketProcessorTest, TtlExpired) {
  std::string packet(kReferenceNetworkPacket);
  packet[7] = 1;

  EXPECT_CALL(stats_, OnPacketDroppedWithIcmp(Direction::FROM_NETWORK));
  EXPECT_CALL(output_, SendPacketToNetwork(IsIcmpMessage(ICMP6_TIME_EXCEEDED)));
  SendPacketFromNetwork(packet);
}

TEST_F(QbonePacketProcessorTest, UnknownProtocol) {
  std::string packet(kReferenceNetworkPacket);
  packet[6] = IPPROTO_SCTP;

  EXPECT_CALL(stats_, OnPacketDroppedWithIcmp(Direction::FROM_NETWORK));
  EXPECT_CALL(output_, SendPacketToNetwork(IsIcmpMessage(ICMP6_PARAM_PROB)));
  SendPacketFromNetwork(packet);
}

TEST_F(QbonePacketProcessorTest, FilterFromClient) {
  auto filter = std::make_unique<MockPacketFilter>();
  EXPECT_CALL(*filter, FilterPacket(_, _, _, _, _))
      .WillRepeatedly(Return(ProcessingResult::SILENT_DROP));
  processor_->set_filter(std::move(filter));

  EXPECT_CALL(stats_, OnPacketDroppedSilently(Direction::FROM_OFF_NETWORK));
  SendPacketFromClient(kReferenceClientPacket);
}

class TestFilter : public QbonePacketProcessor::Filter {
 public:
  TestFilter(QuicIpAddress client_ip, QuicIpAddress network_ip)
      : client_ip_(client_ip), network_ip_(network_ip) {}
  ProcessingResult FilterPacket(Direction direction,
                                absl::string_view full_packet,
                                absl::string_view payload,
                                icmp6_hdr* icmp_header,
                                OutputInterface* output) override {
    EXPECT_EQ(kIPv6HeaderSize, full_packet.size() - payload.size());
    EXPECT_EQ(IPPROTO_UDP, TransportProtocolFromHeader(full_packet));
    EXPECT_EQ(client_ip_, SourceIpFromHeader(full_packet));
    EXPECT_EQ(network_ip_, DestinationIpFromHeader(full_packet));

    last_tos_ = TrafficClassFromHeader(full_packet);
    called_++;
    return ProcessingResult::SILENT_DROP;
  }

  int called() const { return called_; }
  uint8_t last_tos() const { return last_tos_; }

 private:
  int called_ = 0;
  uint8_t last_tos_ = 0;

  QuicIpAddress client_ip_;
  QuicIpAddress network_ip_;
};

// Verify that the parameters are passed correctly into the filter, and that the
// helper functions of the filter class work.
TEST_F(QbonePacketProcessorTest, FilterHelperFunctions) {
  auto filter_owned = std::make_unique<TestFilter>(client_ip_, network_ip_);
  TestFilter* filter = filter_owned.get();
  processor_->set_filter(std::move(filter_owned));

  EXPECT_CALL(stats_, OnPacketDroppedSilently(Direction::FROM_OFF_NETWORK));
  SendPacketFromClient(kReferenceClientPacket);
  ASSERT_EQ(1, filter->called());
}

TEST_F(QbonePacketProcessorTest, FilterHelperFunctionsTOS) {
  auto filter_owned = std::make_unique<TestFilter>(client_ip_, network_ip_);
  TestFilter* filter = filter_owned.get();
  processor_->set_filter(std::move(filter_owned));

  EXPECT_CALL(stats_, OnPacketDroppedSilently(Direction::FROM_OFF_NETWORK))
      .Times(testing::AnyNumber());
  SendPacketFromClient(kReferenceClientPacket);
  ASSERT_EQ(0, filter->last_tos());
  SendPacketFromClient(kReferenceClientPacketAF4);
  ASSERT_EQ(0x80, filter->last_tos());
  SendPacketFromClient(kReferenceClientPacketAF3);
  ASSERT_EQ(0x60, filter->last_tos());
  SendPacketFromClient(kReferenceClientPacketAF2);
  ASSERT_EQ(0x40, filter->last_tos());
  SendPacketFromClient(kReferenceClientPacketAF1);
  ASSERT_EQ(0x20, filter->last_tos());
}

TEST_F(QbonePacketProcessorTest, Icmp6EchoResponseHasRightPayload) {
  auto filter = std::make_unique<MockPacketFilter>();
  EXPECT_CALL(*filter, FilterPacket(_, _, _, _, _))
      .WillOnce(WithArgs<2, 3>(
          Invoke([](absl::string_view payload, icmp6_hdr* icmp_header) {
            icmp_header->icmp6_type = ICMP6_ECHO_REPLY;
            icmp_header->icmp6_code = 0;
            auto* request_header =
                reinterpret_cast<const icmp6_hdr*>(payload.data());
            icmp_header->icmp6_id = request_header->icmp6_id;
            icmp_header->icmp6_seq = request_header->icmp6_seq;
            return ProcessingResult::ICMP;
          })));
  processor_->set_filter(std::move(filter));

  EXPECT_CALL(stats_, OnPacketDroppedWithIcmp(Direction::FROM_OFF_NETWORK));
  EXPECT_CALL(output_, SendPacketToClient(_))
      .WillOnce(Invoke([](absl::string_view packet) {
        // Explicit conversion because otherwise it is treated as a null
        // terminated string.
        absl::string_view expected = absl::string_view(
            kReferenceEchoReplyData, sizeof(kReferenceEchoReplyData));

        EXPECT_THAT(packet, Eq(expected));
        QUIC_LOG(INFO) << "ICMP response:\n"
                       << quiche::QuicheTextUtils::HexDump(packet);
      }));
  SendPacketFromClient(kReferenceEchoRequest);
}

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