// Copyright 2017 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 "net/nqe/socket_watcher.h"

#include "base/bind.h"
#include "base/run_loop.h"
#include "base/test/simple_test_tick_clock.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "net/base/address_list.h"
#include "net/base/ip_address.h"
#include "net/socket/socket_performance_watcher.h"
#include "net/socket/socket_performance_watcher_factory.h"
#include "net/test/test_with_scoped_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace net {

namespace nqe {

namespace internal {

namespace {

class NetworkQualitySocketWatcherTest : public TestWithScopedTaskEnvironment {
 protected:
  NetworkQualitySocketWatcherTest() { ResetExpectedCallbackParams(); }
  ~NetworkQualitySocketWatcherTest() override { ResetExpectedCallbackParams(); }

  static void OnUpdatedRTTAvailableStoreParams(
      SocketPerformanceWatcherFactory::Protocol protocol,
      const base::TimeDelta& rtt,
      const base::Optional<IPHash>& host) {
    // Need to verify before another callback is executed, or explicitly call
    // |ResetCallbackParams()|.
    ASSERT_FALSE(callback_executed_);
    callback_rtt_ = rtt;
    callback_host_ = host;
    callback_executed_ = true;
  }

  static void OnUpdatedRTTAvailable(
      SocketPerformanceWatcherFactory::Protocol protocol,
      const base::TimeDelta& rtt,
      const base::Optional<IPHash>& host) {
    // Need to verify before another callback is executed, or explicitly call
    // |ResetCallbackParams()|.
    ASSERT_FALSE(callback_executed_);
    callback_executed_ = true;
  }

  static void SetShouldNotifyRTTCallback(bool value) {
    should_notify_rtt_callback_ = value;
  }

  static bool ShouldNotifyRTTCallback(base::TimeTicks now) {
    return should_notify_rtt_callback_;
  }

  static void VerifyCallbackParams(const base::TimeDelta& rtt,
                                   const base::Optional<IPHash>& host) {
    ASSERT_TRUE(callback_executed_);
    EXPECT_EQ(rtt, callback_rtt_);
    if (host)
      EXPECT_EQ(host, callback_host_);
    else
      EXPECT_FALSE(callback_host_.has_value());
    ResetExpectedCallbackParams();
  }

  static void ResetExpectedCallbackParams() {
    callback_rtt_ = base::TimeDelta::FromMilliseconds(0);
    callback_host_ = base::nullopt;
    callback_executed_ = false;
    should_notify_rtt_callback_ = false;
  }

  static base::TimeDelta callback_rtt() { return callback_rtt_; }

 private:
  static base::TimeDelta callback_rtt_;
  static base::Optional<IPHash> callback_host_;
  static bool callback_executed_;
  static bool should_notify_rtt_callback_;

  DISALLOW_COPY_AND_ASSIGN(NetworkQualitySocketWatcherTest);
};

base::TimeDelta NetworkQualitySocketWatcherTest::callback_rtt_ =
    base::TimeDelta::FromMilliseconds(0);

base::Optional<IPHash> NetworkQualitySocketWatcherTest::callback_host_ =
    base::nullopt;

bool NetworkQualitySocketWatcherTest::callback_executed_ = false;

bool NetworkQualitySocketWatcherTest::should_notify_rtt_callback_ = false;

// Verify that the buffer size is never exceeded.
TEST_F(NetworkQualitySocketWatcherTest, NotificationsThrottled) {
  base::SimpleTestTickClock tick_clock;
  tick_clock.SetNowTicks(base::TimeTicks::Now());

  // Use a public IP address so that the socket watcher runs the RTT callback.
  IPAddressList ip_list;
  IPAddress ip_address;
  ASSERT_TRUE(ip_address.AssignFromIPLiteral("157.0.0.1"));
  ip_list.push_back(ip_address);
  AddressList address_list =
      AddressList::CreateFromIPAddressList(ip_list, "canonical.example.com");

  SocketWatcher socket_watcher(
      SocketPerformanceWatcherFactory::PROTOCOL_TCP, address_list,
      base::TimeDelta::FromMilliseconds(2000), false,
      base::ThreadTaskRunnerHandle::Get(), base::Bind(OnUpdatedRTTAvailable),
      base::Bind(ShouldNotifyRTTCallback), &tick_clock);

  EXPECT_TRUE(socket_watcher.ShouldNotifyUpdatedRTT());
  socket_watcher.OnUpdatedRTTAvailable(base::TimeDelta::FromSeconds(10));
  base::RunLoop().RunUntilIdle();
  ResetExpectedCallbackParams();

  EXPECT_FALSE(socket_watcher.ShouldNotifyUpdatedRTT());

  tick_clock.Advance(base::TimeDelta::FromMilliseconds(1000));
  // Minimum interval between consecutive notifications is 2000 msec.
  EXPECT_FALSE(socket_watcher.ShouldNotifyUpdatedRTT());

  // Advance the clock by 1000 msec more so that the current time is at least
  // 2000 msec more than the last time |socket_watcher| received a notification.
  tick_clock.Advance(base::TimeDelta::FromMilliseconds(1000));
  EXPECT_TRUE(socket_watcher.ShouldNotifyUpdatedRTT());
  ResetExpectedCallbackParams();
  socket_watcher.OnUpdatedRTTAvailable(base::TimeDelta::FromSeconds(10));

  EXPECT_FALSE(socket_watcher.ShouldNotifyUpdatedRTT());

  // RTT notification is allowed by the global check.
  SetShouldNotifyRTTCallback(true);
  EXPECT_TRUE(socket_watcher.ShouldNotifyUpdatedRTT());
}

TEST_F(NetworkQualitySocketWatcherTest, QuicFirstNotificationDropped) {
  base::SimpleTestTickClock tick_clock;
  tick_clock.SetNowTicks(base::TimeTicks::Now());

  // Use a public IP address so that the socket watcher runs the RTT callback.
  IPAddressList ip_list;
  IPAddress ip_address;
  ASSERT_TRUE(ip_address.AssignFromIPLiteral("157.0.0.1"));
  ip_list.push_back(ip_address);
  AddressList address_list =
      AddressList::CreateFromIPAddressList(ip_list, "canonical.example.com");

  SocketWatcher socket_watcher(
      SocketPerformanceWatcherFactory::PROTOCOL_QUIC, address_list,
      base::TimeDelta::FromMilliseconds(2000), false,
      base::ThreadTaskRunnerHandle::Get(),
      base::Bind(OnUpdatedRTTAvailableStoreParams),
      base::Bind(ShouldNotifyRTTCallback), &tick_clock);

  EXPECT_TRUE(socket_watcher.ShouldNotifyUpdatedRTT());
  socket_watcher.OnUpdatedRTTAvailable(base::TimeDelta::FromSeconds(10));
  base::RunLoop().RunUntilIdle();
  // First notification from a QUIC connection should be dropped, and it should
  // be possible to notify the |socket_watcher| again.
  EXPECT_TRUE(NetworkQualitySocketWatcherTest::callback_rtt().is_zero());
  EXPECT_TRUE(socket_watcher.ShouldNotifyUpdatedRTT());
  ResetExpectedCallbackParams();

  socket_watcher.OnUpdatedRTTAvailable(base::TimeDelta::FromSeconds(2));
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(base::TimeDelta::FromSeconds(2),
            NetworkQualitySocketWatcherTest::callback_rtt());
  ResetExpectedCallbackParams();

  EXPECT_FALSE(socket_watcher.ShouldNotifyUpdatedRTT());

  tick_clock.Advance(base::TimeDelta::FromMilliseconds(1000));
  // Minimum interval between consecutive notifications is 2000 msec.
  EXPECT_FALSE(socket_watcher.ShouldNotifyUpdatedRTT());

  // Advance the clock by 1000 msec more so that the current time is at least
  // 2000 msec more than the last time |socket_watcher| received a notification.
  tick_clock.Advance(base::TimeDelta::FromMilliseconds(1000));
  EXPECT_TRUE(socket_watcher.ShouldNotifyUpdatedRTT());
}

TEST_F(NetworkQualitySocketWatcherTest, PrivateAddressRTTNotNotified) {
  base::SimpleTestTickClock tick_clock;
  tick_clock.SetNowTicks(base::TimeTicks::Now());

  const struct {
    std::string ip_address;
    bool expect_should_notify_rtt;
  } tests[] = {
      {"157.0.0.1", true},    {"127.0.0.1", false},
      {"192.168.0.1", false}, {"::1", false},
      {"0.0.0.0", false},     {"2607:f8b0:4006:819::200e", true},
  };

  for (const auto& test : tests) {
    IPAddressList ip_list;
    IPAddress ip_address;
    ASSERT_TRUE(ip_address.AssignFromIPLiteral(test.ip_address));
    ip_list.push_back(ip_address);
    AddressList address_list =
        AddressList::CreateFromIPAddressList(ip_list, "canonical.example.com");

    SocketWatcher socket_watcher(
        SocketPerformanceWatcherFactory::PROTOCOL_TCP, address_list,
        base::TimeDelta::FromMilliseconds(2000), false,
        base::ThreadTaskRunnerHandle::Get(), base::Bind(OnUpdatedRTTAvailable),
        base::Bind(ShouldNotifyRTTCallback), &tick_clock);

    EXPECT_EQ(test.expect_should_notify_rtt,
              socket_watcher.ShouldNotifyUpdatedRTT());
    socket_watcher.OnUpdatedRTTAvailable(base::TimeDelta::FromSeconds(10));
    base::RunLoop().RunUntilIdle();
    ResetExpectedCallbackParams();

    EXPECT_FALSE(socket_watcher.ShouldNotifyUpdatedRTT());
  }
}

TEST_F(NetworkQualitySocketWatcherTest, RemoteHostIPHashComputedCorrectly) {
  base::SimpleTestTickClock tick_clock;
  tick_clock.SetNowTicks(base::TimeTicks::Now());
  const struct {
    std::string ip_address;
    uint64_t host;
  } tests[] = {
      {"112.112.112.100", 0x0000000070707064UL},  // IPv4.
      {"112.112.112.250", 0x00000000707070faUL},
      {"2001:0db8:85a3:0000:0000:8a2e:0370:7334",
       0x20010db885a30000UL},                                 // IPv6.
      {"2001:db8:85a3::8a2e:370:7334", 0x20010db885a30000UL}  // Shortened IPv6.
  };

  for (const auto& test : tests) {
    IPAddressList ip_list;
    IPAddress ip_address;
    ASSERT_TRUE(ip_address.AssignFromIPLiteral(test.ip_address));
    ip_list.push_back(ip_address);
    AddressList address_list =
        AddressList::CreateFromIPAddressList(ip_list, "canonical.example.com");

    SocketWatcher socket_watcher(
        SocketPerformanceWatcherFactory::PROTOCOL_TCP, address_list,
        base::TimeDelta::FromMilliseconds(2000), false,
        base::ThreadTaskRunnerHandle::Get(),
        base::Bind(OnUpdatedRTTAvailableStoreParams),
        base::Bind(ShouldNotifyRTTCallback), &tick_clock);
    EXPECT_TRUE(socket_watcher.ShouldNotifyUpdatedRTT());
    socket_watcher.OnUpdatedRTTAvailable(base::TimeDelta::FromSeconds(10));
    base::RunLoop().RunUntilIdle();
    VerifyCallbackParams(base::TimeDelta::FromSeconds(10), test.host);
    EXPECT_FALSE(socket_watcher.ShouldNotifyUpdatedRTT());
  }
}

}  // namespace

}  // namespace internal

}  // namespace nqe

}  // namespace net
