// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/base/network_change_notifier_apple.h"

#include <optional>
#include <string>

#include "base/apple/scoped_cftyperef.h"
#include "base/location.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/thread.h"
#include "build/build_config.h"
#include "net/base/features.h"
#include "net/base/network_change_notifier.h"
#include "net/base/network_config_watcher_apple.h"
#include "net/test/test_with_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace net {
namespace {

static const char kIPv4PrivateAddrString1[] = "192.168.0.1";
static const char kIPv4PrivateAddrString2[] = "192.168.0.2";

static const char kIPv6PublicAddrString1[] =
    "2401:fa00:4:1000:be30:5b30:50e5:c0";
static const char kIPv6PublicAddrString2[] =
    "2401:fa00:4:1000:be30:5b30:50e5:c1";
static const char kIPv6LinkLocalAddrString1[] = "fe80::0:1:1:1";
static const char kIPv6LinkLocalAddrString2[] = "fe80::0:2:2:2";

class TestIPAddressObserver : public NetworkChangeNotifier::IPAddressObserver {
 public:
  TestIPAddressObserver() { NetworkChangeNotifier::AddIPAddressObserver(this); }

  TestIPAddressObserver(const TestIPAddressObserver&) = delete;
  TestIPAddressObserver& operator=(const TestIPAddressObserver&) = delete;

  ~TestIPAddressObserver() override {
    NetworkChangeNotifier::RemoveIPAddressObserver(this);
  }

  // Implements NetworkChangeNotifier::IPAddressObserver:
  void OnIPAddressChanged(
      NetworkChangeNotifier::IPAddressChangeType change_type) override {
    ip_address_changed_ = true;
  }

  bool ip_address_changed() const { return ip_address_changed_; }

 private:
  bool ip_address_changed_ = false;
};

}  // namespace

class NetworkChangeNotifierAppleTest : public WithTaskEnvironment,
                                       public ::testing::Test {
 public:
  NetworkChangeNotifierAppleTest() = default;
  NetworkChangeNotifierAppleTest(const NetworkChangeNotifierAppleTest&) =
      delete;
  NetworkChangeNotifierAppleTest& operator=(
      const NetworkChangeNotifierAppleTest&) = delete;
  ~NetworkChangeNotifierAppleTest() override = default;

  void TearDown() override { RunUntilIdle(); }

 protected:
  std::unique_ptr<NetworkChangeNotifierApple>
  CreateNetworkChangeNotifierApple() {
    auto notifier = std::make_unique<NetworkChangeNotifierApple>();
    base::RunLoop run_loop;
    notifier->SetCallbacksForTest(
        run_loop.QuitClosure(),
        base::BindRepeating(
            [](std::optional<NetworkInterfaceList>* network_interface_list,
               NetworkInterfaceList* list_out, int) {
              if (!network_interface_list->has_value()) {
                return false;
              }
              *list_out = **network_interface_list;
              return true;
            },
            &network_interface_list_),
        base::BindRepeating(
            [](std::string* ipv4_primary_interface_name, SCDynamicStoreRef)
                -> std::string { return *ipv4_primary_interface_name; },
            &ipv4_primary_interface_name_),
        base::BindRepeating(
            [](std::string* ipv6_primary_interface_name, SCDynamicStoreRef)
                -> std::string { return *ipv6_primary_interface_name; },
            &ipv6_primary_interface_name_));
    run_loop.Run();
    return notifier;
  }

  void SimulateDynamicStoreCallback(NetworkChangeNotifierApple& notifier,
                                    CFStringRef entity) {
    base::RunLoop run_loop;
    notifier.config_watcher_->GetNotifierThreadForTest()
        ->task_runner()
        ->PostTask(
            FROM_HERE, base::BindLambdaForTesting([&]() {
              base::apple::ScopedCFTypeRef<CFMutableArrayRef> array(
                  CFArrayCreateMutable(nullptr,
                                       /*capacity=*/0, &kCFTypeArrayCallBacks));
              base::apple::ScopedCFTypeRef<CFStringRef> entry_key(
                  SCDynamicStoreKeyCreateNetworkGlobalEntity(
                      nullptr, kSCDynamicStoreDomainState, entity));
              CFArrayAppendValue(array.get(), entry_key.get());
              notifier.OnNetworkConfigChange(array.get());
              run_loop.Quit();
            }));
    run_loop.Run();
  }

 protected:
  std::optional<NetworkInterfaceList> network_interface_list_ =
      NetworkInterfaceList();
  std::string ipv4_primary_interface_name_ = "en0";
  std::string ipv6_primary_interface_name_ = "en0";

 private:
  // Allows us to allocate our own NetworkChangeNotifier for unit testing.
  NetworkChangeNotifier::DisableForTest disable_for_test_;
};

TEST_F(NetworkChangeNotifierAppleTest, NoInterfaceChange) {
  net::IPAddress ip_address;
  EXPECT_TRUE(ip_address.AssignFromIPLiteral(kIPv4PrivateAddrString1));
  network_interface_list_->push_back(net::NetworkInterface(
      "en0", "en0", 1, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
      ip_address, 0, net::IP_ADDRESS_ATTRIBUTE_NONE));

  std::unique_ptr<NetworkChangeNotifierApple> notifier =
      CreateNetworkChangeNotifierApple();

  // Simulate OnNetworkConfigChange callback without any change in
  // NetworkInterfaceList
  TestIPAddressObserver observer;
  SimulateDynamicStoreCallback(*notifier, kSCEntNetIPv4);
  RunUntilIdle();
  // We ignore the OnNetworkConfigChange callback without any network interface
  // change.
  EXPECT_FALSE(observer.ip_address_changed());
}

TEST_F(NetworkChangeNotifierAppleTest, IPv4AddressChange) {
  net::IPAddress ip_address;
  EXPECT_TRUE(ip_address.AssignFromIPLiteral(kIPv4PrivateAddrString1));
  network_interface_list_->push_back(net::NetworkInterface(
      "en0", "en0", 1, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
      ip_address, 0, net::IP_ADDRESS_ATTRIBUTE_NONE));

  std::unique_ptr<NetworkChangeNotifierApple> notifier =
      CreateNetworkChangeNotifierApple();

  // Simulate OnNetworkConfigChange callback with IPv4 address change.
  EXPECT_TRUE((*network_interface_list_)[0].address.AssignFromIPLiteral(
      kIPv4PrivateAddrString2));
  TestIPAddressObserver observer;
  SimulateDynamicStoreCallback(*notifier, kSCEntNetIPv4);
  RunUntilIdle();
  EXPECT_TRUE(observer.ip_address_changed());
}

TEST_F(NetworkChangeNotifierAppleTest, PublicIPv6AddressChange) {
  net::IPAddress ip_address;
  EXPECT_TRUE(ip_address.AssignFromIPLiteral(kIPv6PublicAddrString1));
  network_interface_list_->push_back(net::NetworkInterface(
      "en0", "en0", 1, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
      ip_address, 64, net::IP_ADDRESS_ATTRIBUTE_NONE));

  std::unique_ptr<NetworkChangeNotifierApple> notifier =
      CreateNetworkChangeNotifierApple();

  // Simulate OnNetworkConfigChange callback with a public IPv6 address change.
  EXPECT_TRUE((*network_interface_list_)[0].address.AssignFromIPLiteral(
      kIPv6PublicAddrString2));
  TestIPAddressObserver observer;
  SimulateDynamicStoreCallback(*notifier, kSCEntNetIPv6);
  RunUntilIdle();
  EXPECT_TRUE(observer.ip_address_changed());
}

TEST_F(NetworkChangeNotifierAppleTest,
       LinkLocalIPv6AddressChangeOnPrimaryInterface) {
  net::IPAddress ip_address;
  EXPECT_TRUE(ip_address.AssignFromIPLiteral(kIPv6LinkLocalAddrString1));
  network_interface_list_->push_back(net::NetworkInterface(
      "en0", "en0", 1, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
      ip_address, 64, net::IP_ADDRESS_ATTRIBUTE_NONE));

  std::unique_ptr<NetworkChangeNotifierApple> notifier =
      CreateNetworkChangeNotifierApple();

  // Simulate OnNetworkConfigChange callback with a link local IPv6 address
  // change on the primary interface "en0".
  EXPECT_TRUE((*network_interface_list_)[0].address.AssignFromIPLiteral(
      kIPv6LinkLocalAddrString2));
  TestIPAddressObserver observer;
  SimulateDynamicStoreCallback(*notifier, kSCEntNetIPv4);
  RunUntilIdle();
  EXPECT_TRUE(observer.ip_address_changed());
}

TEST_F(NetworkChangeNotifierAppleTest,
       LinkLocalIPv6AddressChangeOnNonPrimaryInterface) {
  net::IPAddress ip_address1;
  EXPECT_TRUE(ip_address1.AssignFromIPLiteral(kIPv4PrivateAddrString1));
  network_interface_list_->push_back(net::NetworkInterface(
      "en0", "en0", 1, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
      ip_address1, 0, net::IP_ADDRESS_ATTRIBUTE_NONE));

  net::IPAddress ip_address2;
  EXPECT_TRUE(ip_address2.AssignFromIPLiteral(kIPv6LinkLocalAddrString1));
  network_interface_list_->push_back(net::NetworkInterface(
      "en1", "en1", 2, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
      ip_address2, 0, net::IP_ADDRESS_ATTRIBUTE_NONE));

  std::unique_ptr<NetworkChangeNotifierApple> notifier =
      CreateNetworkChangeNotifierApple();

  // Simulate OnNetworkConfigChange callback with a link local IPv6 address
  // change on the non-primary interface "en1".
  EXPECT_TRUE((*network_interface_list_)[1].address.AssignFromIPLiteral(
      kIPv6LinkLocalAddrString2));
  TestIPAddressObserver observer;
  SimulateDynamicStoreCallback(*notifier, kSCEntNetIPv4);
  RunUntilIdle();
  // We ignore the link local IPv6 address change on the non-primary interface.
  EXPECT_FALSE(observer.ip_address_changed());
}

TEST_F(NetworkChangeNotifierAppleTest, NewInterfaceWithIpV4) {
  net::IPAddress ip_address;
  EXPECT_TRUE(ip_address.AssignFromIPLiteral(kIPv4PrivateAddrString1));
  network_interface_list_->push_back(net::NetworkInterface(
      "en0", "en0", 1, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
      ip_address, 0, net::IP_ADDRESS_ATTRIBUTE_NONE));

  std::unique_ptr<NetworkChangeNotifierApple> notifier =
      CreateNetworkChangeNotifierApple();

  // Simulate OnNetworkConfigChange callback with a new interface with a IPv4
  // address.
  net::IPAddress ip_address2;
  EXPECT_TRUE(ip_address2.AssignFromIPLiteral(kIPv4PrivateAddrString2));
  network_interface_list_->push_back(net::NetworkInterface(
      "en1", "en1", 1, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
      ip_address2, 0, net::IP_ADDRESS_ATTRIBUTE_NONE));

  TestIPAddressObserver observer;
  SimulateDynamicStoreCallback(*notifier, kSCEntNetIPv4);
  RunUntilIdle();
  EXPECT_TRUE(observer.ip_address_changed());
}

TEST_F(NetworkChangeNotifierAppleTest, NewInterfaceWithLinkLocalIpV6) {
  net::IPAddress ip_address;
  EXPECT_TRUE(ip_address.AssignFromIPLiteral(kIPv4PrivateAddrString1));
  network_interface_list_->push_back(net::NetworkInterface(
      "en0", "en0", 2, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
      ip_address, 0, net::IP_ADDRESS_ATTRIBUTE_NONE));

  std::unique_ptr<NetworkChangeNotifierApple> notifier =
      CreateNetworkChangeNotifierApple();

  // Simulate OnNetworkConfigChange callback with a new interface with a link
  // local IPv6 address.
  net::IPAddress ip_address2;
  EXPECT_TRUE(ip_address2.AssignFromIPLiteral(kIPv6LinkLocalAddrString1));
  EXPECT_FALSE(ip_address2.IsPubliclyRoutable());
  network_interface_list_->push_back(net::NetworkInterface(
      "en1", "en1", 1, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
      ip_address2, 64, net::IP_ADDRESS_ATTRIBUTE_NONE));

  TestIPAddressObserver observer;
  SimulateDynamicStoreCallback(*notifier, kSCEntNetIPv4);
  RunUntilIdle();
  // We ignore the new link local IPv6 interface.
  EXPECT_FALSE(observer.ip_address_changed());
}

TEST_F(NetworkChangeNotifierAppleTest, NewInterfaceWithPublicIpV6) {
  net::IPAddress ip_address;
  EXPECT_TRUE(ip_address.AssignFromIPLiteral(kIPv4PrivateAddrString1));
  network_interface_list_->push_back(net::NetworkInterface(
      "en0", "en0", 2, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
      ip_address, 0, net::IP_ADDRESS_ATTRIBUTE_NONE));

  std::unique_ptr<NetworkChangeNotifierApple> notifier =
      CreateNetworkChangeNotifierApple();

  // Simulate OnNetworkConfigChange callback with a new interface with a
  // public IPv6 address.
  net::IPAddress ip_address2;
  EXPECT_TRUE(ip_address2.AssignFromIPLiteral(kIPv6PublicAddrString1));
  EXPECT_TRUE(ip_address2.IsPubliclyRoutable());
  network_interface_list_->push_back(net::NetworkInterface(
      "en1", "en1", 2, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
      ip_address2, 64, net::IP_ADDRESS_ATTRIBUTE_NONE));

  TestIPAddressObserver observer;
  SimulateDynamicStoreCallback(*notifier, kSCEntNetIPv4);
  RunUntilIdle();
  EXPECT_TRUE(observer.ip_address_changed());
}

TEST_F(NetworkChangeNotifierAppleTest, IPv4PrimaryInterfaceChange) {
  net::IPAddress ip_address;
  EXPECT_TRUE(ip_address.AssignFromIPLiteral(kIPv4PrivateAddrString1));
  network_interface_list_->push_back(net::NetworkInterface(
      "en0", "en0", 1, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
      ip_address, 0, net::IP_ADDRESS_ATTRIBUTE_NONE));
  net::IPAddress ip_address2;
  EXPECT_TRUE(ip_address2.AssignFromIPLiteral(kIPv4PrivateAddrString2));
  network_interface_list_->push_back(net::NetworkInterface(
      "en1", "en1", 1, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
      ip_address2, 0, net::IP_ADDRESS_ATTRIBUTE_NONE));

  std::unique_ptr<NetworkChangeNotifierApple> notifier =
      CreateNetworkChangeNotifierApple();

  // Simulate OnNetworkConfigChange callback for the IPv4 primary interface
  // change.
  TestIPAddressObserver observer;
  ipv4_primary_interface_name_ = "en1";
  SimulateDynamicStoreCallback(*notifier, kSCEntNetIPv4);
  RunUntilIdle();
  EXPECT_TRUE(observer.ip_address_changed());
}

TEST_F(NetworkChangeNotifierAppleTest, IPv6PrimaryInterfaceChange) {
  net::IPAddress ip_address;
  EXPECT_TRUE(ip_address.AssignFromIPLiteral(kIPv6PublicAddrString1));
  network_interface_list_->push_back(net::NetworkInterface(
      "en0", "en0", 1, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
      ip_address, 0, net::IP_ADDRESS_ATTRIBUTE_NONE));
  net::IPAddress ip_address2;
  EXPECT_TRUE(ip_address2.AssignFromIPLiteral(kIPv6PublicAddrString2));
  network_interface_list_->push_back(net::NetworkInterface(
      "en1", "en1", 1, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
      ip_address2, 0, net::IP_ADDRESS_ATTRIBUTE_NONE));

  std::unique_ptr<NetworkChangeNotifierApple> notifier =
      CreateNetworkChangeNotifierApple();

  // Simulate OnNetworkConfigChange callback for the IPv6 primary interface
  // change.
  TestIPAddressObserver observer;
  ipv6_primary_interface_name_ = "en1";
  SimulateDynamicStoreCallback(*notifier, kSCEntNetIPv6);
  RunUntilIdle();
  EXPECT_TRUE(observer.ip_address_changed());
}

#if BUILDFLAG(IS_MAC)

class NetworkChangeNotifierApplePathMonitorTest : public WithTaskEnvironment,
                                                  public ::testing::Test {
 public:
  NetworkChangeNotifierApplePathMonitorTest() = default;

  std::unique_ptr<NetworkChangeNotifierApple> CreateNotifier() {
    auto notifier = std::make_unique<NetworkChangeNotifierApple>();
    base::RunLoop run_loop;
    // Wait for initialization to complete to avoid racing with the
    // background notifier thread.
    notifier->SetCallbacksForTest(
        run_loop.QuitClosure(),
        base::BindRepeating([](NetworkInterfaceList*, int) { return false; }),
        base::BindRepeating([](SCDynamicStoreRef) { return std::string(); }),
        base::BindRepeating([](SCDynamicStoreRef) { return std::string(); }));
    run_loop.Run();
    return notifier;
  }

 protected:
  NetworkChangeNotifier::DisableForTest disable_for_test_;

  bool ShouldUseMonitor(NetworkChangeNotifierApple* notifier) {
    return notifier->ShouldUseNetworkPathMonitor();
  }
  bool EnsureMonitorStarted(NetworkChangeNotifierApple* notifier) {
    return notifier->EnsureNetworkPathMonitorStarted();
  }
  void StopMonitor(NetworkChangeNotifierApple* notifier) {
    notifier->StopNetworkPathMonitor();
  }
};

TEST_F(NetworkChangeNotifierApplePathMonitorTest,
       MonitorDisabledWhenFeatureOff) {
  AddScopedFeatureList().InitAndDisableFeature(
      features::kUseNetworkPathMonitorForNetworkChangeNotifier);

  auto notifier = CreateNotifier();
  EXPECT_FALSE(ShouldUseMonitor(notifier.get()));
  EXPECT_FALSE(EnsureMonitorStarted(notifier.get()));
  StopMonitor(notifier.get());
}

TEST_F(NetworkChangeNotifierApplePathMonitorTest, MonitorStartsWhenFeatureOn) {
  AddScopedFeatureList().InitAndEnableFeature(
      features::kUseNetworkPathMonitorForNetworkChangeNotifier);

  auto notifier = CreateNotifier();
  EXPECT_TRUE(ShouldUseMonitor(notifier.get()));
  EXPECT_TRUE(EnsureMonitorStarted(notifier.get()));
  StopMonitor(notifier.get());
}

TEST_F(NetworkChangeNotifierApplePathMonitorTest, MonitorStartIsIdempotent) {
  AddScopedFeatureList().InitAndEnableFeature(
      features::kUseNetworkPathMonitorForNetworkChangeNotifier);

  auto notifier = CreateNotifier();
  // Precondition: monitor must start successfully.
  ASSERT_TRUE(EnsureMonitorStarted(notifier.get()));
  // Verify idempotency: subsequent starts should also succeed.
  EXPECT_TRUE(EnsureMonitorStarted(notifier.get()));
  StopMonitor(notifier.get());
}

#endif  // BUILDFLAG(IS_MAC)

}  // namespace net
