// Copyright 2018 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/base/network_change_notifier_fuchsia.h"

#include <memory>
#include <utility>
#include <vector>

#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "net/base/ip_address.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace net {
namespace {

const int kDefaultNic = 1;
const int kSecondaryNic = kDefaultNic + 1;

fuchsia::netstack::NetAddress CreateIPv6Address(std::vector<uint8_t> addr) {
  fuchsia::netstack::NetAddress output;
  output.family = fuchsia::netstack::NetAddressFamily::IPV6;
  output.ipv6 = fuchsia::netstack::Ipv6Address::New();
  for (size_t i = 0; i < addr.size(); ++i) {
    output.ipv6->addr[i] = addr[i];
  }
  return output;
}

fuchsia::netstack::Subnet CreateSubnet(const std::vector<uint8_t>& addr,
                                       uint8_t prefix) {
  fuchsia::netstack::Subnet output;
  output.addr = CreateIPv6Address(addr);
  output.prefix_len = prefix;
  return output;
}

fuchsia::netstack::NetAddress CreateIPv4Address(uint8_t a0,
                                                uint8_t a1,
                                                uint8_t a2,
                                                uint8_t a3) {
  fuchsia::netstack::NetAddress output;
  output.family = fuchsia::netstack::NetAddressFamily::IPV4;
  output.ipv4 = std::make_unique<fuchsia::netstack::Ipv4Address>();
  output.ipv4->addr[0] = a0;
  output.ipv4->addr[1] = a1;
  output.ipv4->addr[2] = a2;
  output.ipv4->addr[3] = a3;
  return output;
}

fuchsia::netstack::RouteTableEntry CreateRouteTableEntry(uint32_t nicid,
                                                         bool is_default) {
  fuchsia::netstack::RouteTableEntry output;
  output.nicid = nicid;

  if (is_default) {
    output.netmask = CreateIPv4Address(0, 0, 0, 0);
  } else {
    output.netmask = CreateIPv4Address(255, 255, 255, 0);
  }

  return output;
}

fuchsia::netstack::NetInterface CreateNetInterface(
    uint32_t id,
    uint32_t flags,
    uint32_t features,
    fuchsia::netstack::NetAddress address,
    fuchsia::netstack::NetAddress netmask,
    std::vector<fuchsia::netstack::Subnet> ipv6) {
  fuchsia::netstack::NetInterface output;
  output.name = "foo";
  output.id = id;
  output.flags = flags;
  output.features = features;
  address.Clone(&output.addr);
  netmask.Clone(&output.netmask);
  output.hwaddr = fidl::VectorPtr<uint8_t>::New(0);

  output.ipv6addrs =
      fidl::VectorPtr<fuchsia::netstack::Subnet>::New(ipv6.size());
  for (auto& x : ipv6) {
    output.ipv6addrs.push_back(std::move(x));
  }

  return output;
}

// Partial fake implementation of a Netstack.
// GMock is not used because the methods make heavy use of move-only datatypes,
// which aren't handled well by GMock.
class FakeNetstack : public fuchsia::netstack::Netstack {
 public:
  explicit FakeNetstack(
      fidl::InterfaceRequest<fuchsia::netstack::Netstack> netstack_request)
      : binding_(this) {
    CHECK_EQ(ZX_OK, binding_.Bind(std::move(netstack_request)));
  }
  ~FakeNetstack() override = default;

  // Adds |interface| to the interface query response list.
  void PushInterface(fuchsia::netstack::NetInterface&& interface) {
    interfaces_->push_back(std::move(interface));
  }

  void PushRouteTableEntry(fuchsia::netstack::RouteTableEntry&& interface) {
    route_table_->push_back(std::move(interface));
  }

  // Sends the accumulated |interfaces_| to the OnInterfacesChanged event.
  void NotifyInterfaces() {
    binding_.events().OnInterfacesChanged(std::move(interfaces_));
    interfaces_ = fidl::VectorPtr<fuchsia::netstack::NetInterface>::New(0);
  }

  fidl::Binding<fuchsia::netstack::Netstack>& binding() { return binding_; }

 private:
  void GetInterfaces(GetInterfacesCallback callback) override {
    callback(std::move(interfaces_));
  }

  void GetRouteTable(GetRouteTableCallback callback) override {
    fidl::VectorPtr<fuchsia::netstack::RouteTableEntry> table =
        fidl::VectorPtr<fuchsia::netstack::RouteTableEntry>::New(2);
    (*table)[0].nicid = kDefaultNic;
    (*table)[0].netmask = CreateIPv4Address(0, 0, 0, 0);
    (*table)[1].nicid = kSecondaryNic;
    (*table)[1].netmask = CreateIPv4Address(255, 255, 255, 0);

    callback(std::move(table));
  }

  // No-op stubs for the methods we don't care about.
  void GetPortForService(::fidl::StringPtr service,
                         fuchsia::netstack::Protocol protocol,
                         GetPortForServiceCallback callback) override {}
  void GetAddress(::fidl::StringPtr address,
                  uint16_t port,
                  GetAddressCallback callback) override {}
  void GetStats(uint32_t nicid, GetStatsCallback callback) override {}
  void GetAggregateStats(GetAggregateStatsCallback callback) override {}
  void SetInterfaceStatus(uint32_t nicid, bool enabled) override {}
  void SetRouteTable(
      ::fidl::VectorPtr<fuchsia::netstack::RouteTableEntry> rt) override {}
  void SetInterfaceAddress(uint32_t nicid,
                           fuchsia::netstack::NetAddress addr,
                           uint8_t prefixLen,
                           SetInterfaceAddressCallback callback) override {}
  void RemoveInterfaceAddress(
      uint32_t nicid,
      fuchsia::netstack::NetAddress addr,
      uint8_t prefixLen,
      RemoveInterfaceAddressCallback callback) override {}
  void SetDhcpClientStatus(uint32_t nicid,
                           bool enabled,
                           SetDhcpClientStatusCallback callback) override {}
  void BridgeInterfaces(::fidl::VectorPtr<uint32_t> nicids,
                        BridgeInterfacesCallback callback) override {}
  void SetFilterStatus(bool enabled,
                       SetFilterStatusCallback callback) override {}
  void GetFilterStatus(GetFilterStatusCallback callback) override {}
  void SetNameServers(
      ::fidl::VectorPtr<::fuchsia::netstack::NetAddress> servers) override {}

  ::fidl::VectorPtr<fuchsia::netstack::NetInterface> interfaces_ =
      fidl::VectorPtr<fuchsia::netstack::NetInterface>::New(0);
  ::fidl::VectorPtr<fuchsia::netstack::RouteTableEntry> route_table_ =
      fidl::VectorPtr<fuchsia::netstack::RouteTableEntry>::New(0);

  fidl::Binding<fuchsia::netstack::Netstack> binding_;

  DISALLOW_COPY_AND_ASSIGN(FakeNetstack);
};

class MockNetworkChangeObserver
    : public NetworkChangeNotifier::NetworkChangeObserver {
 public:
  MOCK_METHOD1(OnNetworkChanged, void(NetworkChangeNotifier::ConnectionType));
};

}  // namespace

class NetworkChangeNotifierFuchsiaTest : public testing::Test {
 public:
  NetworkChangeNotifierFuchsiaTest() : netstack_(netstack_ptr_.NewRequest()) {}

  ~NetworkChangeNotifierFuchsiaTest() override {}

  // Creates a NetworkChangeNotifier, which will be seeded with the list of
  // interfaces which have already been added to |netstack_|.
  void CreateNotifier() {
    notifier_.reset(new NetworkChangeNotifierFuchsia(std::move(netstack_ptr_)));
    NetworkChangeNotifier::AddNetworkChangeObserver(&observer_);
  }

  void TearDown() override {
    if (notifier_) {
      NetworkChangeNotifier::RemoveNetworkChangeObserver(&observer_);
    }
  }

 protected:
  base::MessageLoopForIO message_loop_;
  testing::StrictMock<MockNetworkChangeObserver> observer_;
  fuchsia::netstack::NetstackPtr netstack_ptr_;
  FakeNetstack netstack_;

  // Allows us to allocate our own NetworkChangeNotifier for unit testing.
  NetworkChangeNotifier::DisableForTest disable_for_test_;
  std::unique_ptr<NetworkChangeNotifierFuchsia> notifier_;

  testing::InSequence seq_;

 private:
  DISALLOW_COPY_AND_ASSIGN(NetworkChangeNotifierFuchsiaTest);
};

TEST_F(NetworkChangeNotifierFuchsiaTest, NoChange) {
  netstack_.PushInterface(
      CreateNetInterface(kDefaultNic, fuchsia::netstack::NetInterfaceFlagUp, 0,
                         CreateIPv4Address(169, 254, 0, 1),
                         CreateIPv4Address(255, 255, 255, 0), {}));
  netstack_.PushRouteTableEntry(CreateRouteTableEntry(kDefaultNic, true));

  CreateNotifier();
  base::RunLoop().RunUntilIdle();

  netstack_.PushInterface(
      CreateNetInterface(kDefaultNic, fuchsia::netstack::NetInterfaceFlagUp, 0,
                         CreateIPv4Address(169, 254, 0, 1),
                         CreateIPv4Address(255, 255, 255, 0), {}));
  netstack_.PushRouteTableEntry(CreateRouteTableEntry(kDefaultNic, true));
  netstack_.NotifyInterfaces();
  base::RunLoop().RunUntilIdle();
}

TEST_F(NetworkChangeNotifierFuchsiaTest, NoChangeV6) {
  netstack_.PushInterface(
      CreateNetInterface(kDefaultNic, fuchsia::netstack::NetInterfaceFlagUp, 0,
                         CreateIPv6Address({0xfe, 0x80, 0x01}),
                         CreateIPv6Address({0xfe, 0x80}), {}));
  CreateNotifier();
  base::RunLoop().RunUntilIdle();

  netstack_.PushInterface(
      CreateNetInterface(kDefaultNic, fuchsia::netstack::NetInterfaceFlagUp, 0,
                         CreateIPv6Address({0xfe, 0x80, 0x01}),
                         CreateIPv6Address({0xfe, 0x80}), {}));
  netstack_.NotifyInterfaces();
  base::RunLoop().RunUntilIdle();
}

TEST_F(NetworkChangeNotifierFuchsiaTest, MultiInterfaceNoChange) {
  netstack_.PushInterface(
      CreateNetInterface(kDefaultNic, fuchsia::netstack::NetInterfaceFlagUp, 0,
                         CreateIPv4Address(169, 254, 0, 1),
                         CreateIPv4Address(255, 255, 255, 0), {}));
  netstack_.PushInterface(
      CreateNetInterface(kSecondaryNic, fuchsia::netstack::NetInterfaceFlagUp,
                         0, CreateIPv4Address(169, 254, 0, 2),
                         CreateIPv4Address(255, 255, 255, 0), {}));
  CreateNotifier();
  base::RunLoop().RunUntilIdle();

  netstack_.PushInterface(
      CreateNetInterface(kDefaultNic, fuchsia::netstack::NetInterfaceFlagUp, 0,
                         CreateIPv4Address(169, 254, 0, 1),
                         CreateIPv4Address(255, 255, 255, 0), {}));
  netstack_.PushInterface(
      CreateNetInterface(kSecondaryNic, fuchsia::netstack::NetInterfaceFlagUp,
                         0, CreateIPv4Address(169, 254, 0, 3),
                         CreateIPv4Address(255, 255, 255, 0), {}));
  netstack_.NotifyInterfaces();
  base::RunLoop().RunUntilIdle();
}

TEST_F(NetworkChangeNotifierFuchsiaTest, MultiV6IPNoChange) {
  std::vector<fuchsia::netstack::Subnet> addresses;
  addresses.push_back(CreateSubnet({0xfe, 0x80, 0x01}, 2));
  netstack_.PushInterface(CreateNetInterface(
      kDefaultNic, fuchsia::netstack::NetInterfaceFlagUp, 0,
      CreateIPv4Address(169, 254, 0, 1), CreateIPv4Address(255, 255, 255, 0),
      std::move(addresses)));
  CreateNotifier();
  base::RunLoop().RunUntilIdle();

  addresses.push_back(CreateSubnet({0xfe, 0x80, 0x01}, 2));
  netstack_.PushInterface(CreateNetInterface(
      kDefaultNic, fuchsia::netstack::NetInterfaceFlagUp, 0,
      CreateIPv4Address(169, 254, 0, 1), CreateIPv4Address(255, 255, 255, 0),
      std::move(addresses)));
  netstack_.NotifyInterfaces();
  base::RunLoop().RunUntilIdle();
}

TEST_F(NetworkChangeNotifierFuchsiaTest, IpChange) {
  netstack_.PushInterface(
      CreateNetInterface(kDefaultNic, fuchsia::netstack::NetInterfaceFlagUp, 0,
                         CreateIPv4Address(169, 254, 0, 1),
                         CreateIPv4Address(255, 255, 255, 0), {}));
  CreateNotifier();
  base::RunLoop().RunUntilIdle();

  EXPECT_CALL(observer_,
              OnNetworkChanged(NetworkChangeNotifier::CONNECTION_NONE));
  EXPECT_CALL(observer_,
              OnNetworkChanged(NetworkChangeNotifier::CONNECTION_UNKNOWN));
  netstack_.PushInterface(CreateNetInterface(
      kDefaultNic, fuchsia::netstack::NetInterfaceFlagUp, 0,
      CreateIPv4Address(10, 0, 0, 1), CreateIPv4Address(255, 255, 0, 0), {}));
  netstack_.NotifyInterfaces();
  base::RunLoop().RunUntilIdle();
}

TEST_F(NetworkChangeNotifierFuchsiaTest, IpChangeV6) {
  netstack_.PushInterface(
      CreateNetInterface(kDefaultNic, fuchsia::netstack::NetInterfaceFlagUp, 0,
                         CreateIPv6Address({0xfe, 0x80, 0x01}),
                         CreateIPv6Address({0xfe, 0x80}), {}));
  CreateNotifier();
  base::RunLoop().RunUntilIdle();

  EXPECT_CALL(observer_,
              OnNetworkChanged(NetworkChangeNotifier::CONNECTION_NONE));
  EXPECT_CALL(observer_,
              OnNetworkChanged(NetworkChangeNotifier::CONNECTION_UNKNOWN));
  netstack_.PushInterface(
      CreateNetInterface(kDefaultNic, fuchsia::netstack::NetInterfaceFlagUp, 0,
                         CreateIPv6Address({0xfe, 0x80, 0x02}),
                         CreateIPv6Address({0xfe, 0x80}), {}));
  netstack_.NotifyInterfaces();
  base::RunLoop().RunUntilIdle();
}

TEST_F(NetworkChangeNotifierFuchsiaTest, MultiV6IPChanged) {
  std::vector<fuchsia::netstack::Subnet> addresses;
  addresses.push_back(CreateSubnet({0xfe, 0x80, 0x01}, 2));
  netstack_.PushInterface(CreateNetInterface(
      kDefaultNic, fuchsia::netstack::NetInterfaceFlagUp, 0,
      CreateIPv4Address(169, 254, 0, 1), CreateIPv4Address(255, 255, 255, 0),
      std::move(addresses)));
  CreateNotifier();
  base::RunLoop().RunUntilIdle();

  EXPECT_CALL(observer_,
              OnNetworkChanged(NetworkChangeNotifier::CONNECTION_NONE));
  EXPECT_CALL(observer_,
              OnNetworkChanged(NetworkChangeNotifier::CONNECTION_UNKNOWN));
  addresses.push_back(CreateSubnet({0xfe, 0x80, 0x02}, 2));
  netstack_.PushInterface(CreateNetInterface(
      kDefaultNic, fuchsia::netstack::NetInterfaceFlagUp, 0,
      CreateIPv4Address(10, 0, 0, 1), CreateIPv4Address(255, 255, 0, 0),
      std::move(addresses)));
  netstack_.NotifyInterfaces();
  base::RunLoop().RunUntilIdle();
}

TEST_F(NetworkChangeNotifierFuchsiaTest, Ipv6AdditionalIpChange) {
  netstack_.PushInterface(
      CreateNetInterface(kDefaultNic, fuchsia::netstack::NetInterfaceFlagUp, 0,
                         CreateIPv4Address(169, 254, 0, 1),
                         CreateIPv4Address(255, 255, 255, 0), {}));
  CreateNotifier();
  base::RunLoop().RunUntilIdle();

  EXPECT_CALL(observer_,
              OnNetworkChanged(NetworkChangeNotifier::CONNECTION_NONE));
  EXPECT_CALL(observer_,
              OnNetworkChanged(NetworkChangeNotifier::CONNECTION_UNKNOWN));
  std::vector<fuchsia::netstack::Subnet> addresses;
  addresses.push_back(CreateSubnet({0xfe, 0x80, 0x01}, 2));
  netstack_.PushInterface(CreateNetInterface(
      kDefaultNic, fuchsia::netstack::NetInterfaceFlagUp, 0,
      CreateIPv4Address(169, 254, 0, 1), CreateIPv4Address(255, 255, 255, 0),
      std::move(addresses)));
  netstack_.NotifyInterfaces();
  base::RunLoop().RunUntilIdle();
}

TEST_F(NetworkChangeNotifierFuchsiaTest, InterfaceDown) {
  netstack_.PushInterface(
      CreateNetInterface(kDefaultNic, fuchsia::netstack::NetInterfaceFlagUp, 0,
                         CreateIPv4Address(169, 254, 0, 1),
                         CreateIPv4Address(255, 255, 255, 0), {}));
  CreateNotifier();
  base::RunLoop().RunUntilIdle();

  EXPECT_CALL(observer_,
              OnNetworkChanged(NetworkChangeNotifier::CONNECTION_NONE));
  netstack_.PushInterface(
      CreateNetInterface(1, 0, 0, CreateIPv4Address(169, 254, 0, 1),
                         CreateIPv4Address(255, 255, 0, 0), {}));
  netstack_.NotifyInterfaces();
  base::RunLoop().RunUntilIdle();
}

TEST_F(NetworkChangeNotifierFuchsiaTest, InterfaceUp) {
  netstack_.PushInterface(
      CreateNetInterface(1, 0, 0, CreateIPv4Address(169, 254, 0, 1),
                         CreateIPv4Address(255, 255, 255, 0), {}));
  CreateNotifier();
  base::RunLoop().RunUntilIdle();

  EXPECT_CALL(observer_,
              OnNetworkChanged(NetworkChangeNotifier::CONNECTION_NONE));
  EXPECT_CALL(observer_,
              OnNetworkChanged(NetworkChangeNotifier::CONNECTION_UNKNOWN));
  netstack_.PushInterface(
      CreateNetInterface(kDefaultNic, fuchsia::netstack::NetInterfaceFlagUp, 0,
                         CreateIPv4Address(169, 254, 0, 1),
                         CreateIPv4Address(255, 255, 0, 0), {}));
  netstack_.NotifyInterfaces();
  base::RunLoop().RunUntilIdle();
}

TEST_F(NetworkChangeNotifierFuchsiaTest, InterfaceDeleted) {
  netstack_.PushInterface(
      CreateNetInterface(kDefaultNic, fuchsia::netstack::NetInterfaceFlagUp, 0,
                         CreateIPv4Address(169, 254, 0, 1),
                         CreateIPv4Address(255, 255, 255, 0), {}));
  CreateNotifier();
  base::RunLoop().RunUntilIdle();

  EXPECT_CALL(observer_,
              OnNetworkChanged(NetworkChangeNotifier::CONNECTION_NONE));
  netstack_.NotifyInterfaces();
  base::RunLoop().RunUntilIdle();
}

TEST_F(NetworkChangeNotifierFuchsiaTest, InterfaceAdded) {
  // Initial interface list is intentionally left empty.
  CreateNotifier();
  base::RunLoop().RunUntilIdle();

  EXPECT_CALL(observer_,
              OnNetworkChanged(NetworkChangeNotifier::CONNECTION_NONE));
  EXPECT_CALL(observer_,
              OnNetworkChanged(NetworkChangeNotifier::CONNECTION_WIFI));
  netstack_.PushInterface(
      CreateNetInterface(kDefaultNic, fuchsia::netstack::NetInterfaceFlagUp,
                         fuchsia::netstack::interfaceFeatureWlan,
                         CreateIPv4Address(169, 254, 0, 1),
                         CreateIPv4Address(255, 255, 255, 0), {}));
  netstack_.NotifyInterfaces();
  base::RunLoop().RunUntilIdle();
}

TEST_F(NetworkChangeNotifierFuchsiaTest, SecondaryInterfaceAddedNoop) {
  netstack_.PushInterface(
      CreateNetInterface(kDefaultNic, fuchsia::netstack::NetInterfaceFlagUp, 0,
                         CreateIPv4Address(169, 254, 0, 1),
                         CreateIPv4Address(255, 255, 255, 0), {}));
  CreateNotifier();
  base::RunLoop().RunUntilIdle();

  netstack_.PushInterface(
      CreateNetInterface(kSecondaryNic, fuchsia::netstack::NetInterfaceFlagUp,
                         0, CreateIPv4Address(169, 254, 0, 2),
                         CreateIPv4Address(255, 255, 255, 0), {}));
  netstack_.PushInterface(
      CreateNetInterface(kDefaultNic, fuchsia::netstack::NetInterfaceFlagUp, 0,
                         CreateIPv4Address(169, 254, 0, 1),
                         CreateIPv4Address(255, 255, 255, 0), {}));
  netstack_.NotifyInterfaces();
  base::RunLoop().RunUntilIdle();
}

TEST_F(NetworkChangeNotifierFuchsiaTest, SecondaryInterfaceDeletedNoop) {
  netstack_.PushInterface(
      CreateNetInterface(kDefaultNic, fuchsia::netstack::NetInterfaceFlagUp, 0,
                         CreateIPv4Address(169, 254, 0, 1),
                         CreateIPv4Address(255, 255, 255, 0), {}));
  netstack_.PushInterface(
      CreateNetInterface(kSecondaryNic, fuchsia::netstack::NetInterfaceFlagUp,
                         0, CreateIPv4Address(169, 254, 0, 2),
                         CreateIPv4Address(255, 255, 255, 0), {}));
  CreateNotifier();
  base::RunLoop().RunUntilIdle();

  netstack_.PushInterface(
      CreateNetInterface(kDefaultNic, fuchsia::netstack::NetInterfaceFlagUp, 0,
                         CreateIPv4Address(169, 254, 0, 1),
                         CreateIPv4Address(255, 255, 255, 0), {}));
  netstack_.NotifyInterfaces();
  base::RunLoop().RunUntilIdle();
}

TEST_F(NetworkChangeNotifierFuchsiaTest, FoundWiFi) {
  netstack_.PushInterface(
      CreateNetInterface(kDefaultNic, fuchsia::netstack::NetInterfaceFlagUp,
                         fuchsia::netstack::interfaceFeatureWlan,
                         CreateIPv4Address(169, 254, 0, 1),
                         CreateIPv4Address(255, 255, 255, 0), {}));
  CreateNotifier();
  EXPECT_EQ(NetworkChangeNotifier::ConnectionType::CONNECTION_WIFI,
            notifier_->GetCurrentConnectionType());
}

}  // namespace net
