text: // Copyright 2020 The Fuchsia 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 "network_device.h"
  
  #include <lib/fake_ddk/fake_ddk.h>
  
  #include <ddktl/device.h>
  #include <zxtest/cpp/zxtest.h>
  #include <zxtest/zxtest.h>
  
  #include "device/test_util.h"
  #include "mac/test_util.h"
  
  namespace {
  // Enable timeouts only to test things locally, committed code should not use timeouts.
  constexpr zx::duration kTestTimeout = zx::duration::infinite();
  }  // namespace
  
  namespace network {
  namespace testing {
  
  class NetDeviceDriverTest : public zxtest::Test, public fake_ddk::Bind {
   protected:
    typedef void(ReleaseOp)(void* ctx);
  
    void TearDown() override {
      if (device_created_) {
        RemoveDeviceSync();
      }
    }
  
    zx_status_t DeviceAdd(zx_driver_t* drv, zx_device_t* parent, device_add_args_t* args,
                          zx_device_t** out) override {
      auto status = Bind::DeviceAdd(drv, parent, args, out);
      if (status == ZX_OK) {
        release_op_ = args->ops->release;
        device_created_ = true;
      }
      return status;
    }
  
    void UnbindDeviceSync() {
      if (!unbind_started_) {
        DeviceAsyncRemove(fake_ddk::kFakeDevice);
        EXPECT_OK(sync_completion_wait_deadline(&remove_called_sync_,
                                                zx::deadline_after(kTestTimeout).get()));
      }
    }
  
    void RemoveDeviceSync() {
      UnbindDeviceSync();
      if (release_op_) {
        release_op_(op_ctx_);
      }
      device_created_ = false;
    }
  
    zx_status_t CreateDevice(bool with_mac = false) {
      auto proto_count = with_mac ? 2 : 1;
      auto protos = new fake_ddk::ProtocolEntry[proto_count];
      auto device_proto = device_impl_.proto();
      protos[0] = {ZX_PROTOCOL_NETWORK_DEVICE_IMPL, {device_proto.ops, device_proto.ctx}};
      if (with_mac) {
        auto mac_proto = mac_impl_.proto();
        protos[1] = {ZX_PROTOCOL_MAC_ADDR_IMPL, {mac_proto.ops, mac_proto.ctx}};
      }
  
      SetProtocols(fbl::Array<fake_ddk::ProtocolEntry>(protos, proto_count));
      return NetworkDevice::Create(nullptr, fake_ddk::kFakeParent);
    }
  
    fit::result<netdev::Device::SyncClient, zx_status_t> ConnectNetDevice() {
      zx::channel client, req;
      zx_status_t status;
      if ((status = zx::channel::create(0, &client, &req)) != ZX_OK) {
        return fit::error(status);
      }
  
      auto result =
          netdev::DeviceInstance::Call::GetDevice(zx::unowned(FidlClient()), std::move(req));
      if (!result.ok()) {
        return fit::error(result.status());
      }
  
      return fit::ok(netdev::Device::SyncClient(std::move(client)));
    }
  
    fit::result<netdev::MacAddressing::SyncClient, zx_status_t> ConnectMac() {
      zx::channel client, req;
      zx_status_t status;
      if ((status = zx::channel::create(0, &client, &req)) != ZX_OK) {
        return fit::error(status);
      }
  
      auto result =
          netdev::DeviceInstance::Call::GetMacAddressing(zx::unowned(FidlClient()), std::move(req));
      if (!result.ok()) {
        return fit::error(result.status());
      }
  
      return fit::ok(netdev::MacAddressing::SyncClient(std::move(client)));
    }
  
    bool device_created_ = false;
    FakeMacDeviceImpl mac_impl_;
    FakeNetworkDeviceImpl device_impl_;
    ReleaseOp* release_op_;
  };
  
  TEST_F(NetDeviceDriverTest, TestCreateSimple) { ASSERT_OK(CreateDevice()); }
  
  TEST_F(NetDeviceDriverTest, TestOpenSession) {
    ASSERT_OK(CreateDevice());
    TestSession session;
    auto connect_result = ConnectNetDevice();
    ASSERT_TRUE(connect_result.is_ok(), "Connect failed: %s",
                zx_status_get_string(connect_result.error()));
    auto netdevice = connect_result.take_value();
    ASSERT_OK(session.Open(zx::unowned(netdevice.channel()), "test-session"));
    session.SetPaused(false);
    ASSERT_OK(device_impl_.events().wait_one(kEventStart, zx::deadline_after(kTestTimeout), nullptr));
    UnbindDeviceSync();
    ASSERT_OK(session.WaitClosed(zx::deadline_after(kTestTimeout)));
    // netdevice should also have been closed after device unbind:
    ASSERT_OK(netdevice.channel().wait_one(ZX_CHANNEL_PEER_CLOSED, zx::deadline_after(kTestTimeout),
                                           nullptr));
  }
  
  TEST_F(NetDeviceDriverTest, TestWatcherDestruction) {
    // Test that on device removal watcher channels get closed.
    ASSERT_OK(CreateDevice());
  
    auto connect_result = ConnectNetDevice();
    ASSERT_TRUE(connect_result.is_ok(), "Connect failed: %s",
                zx_status_get_string(connect_result.error()));
    auto netdevice = connect_result.take_value();
  
    zx::channel req, watcher_channel;
    ASSERT_OK(zx::channel::create(0, &req, &watcher_channel));
    ASSERT_OK(netdevice.GetStatusWatcher(std::move(req), 1).status());
    netdev::StatusWatcher::SyncClient watcher(std::move(watcher_channel));
    ASSERT_OK(watcher.WatchStatus().status());
    UnbindDeviceSync();
    ASSERT_OK(watcher.channel().wait_one(ZX_CHANNEL_PEER_CLOSED, zx::deadline_after(kTestTimeout),
                                         nullptr));
    // netdevice should also have been closed after device unbind:
    ASSERT_OK(netdevice.channel().wait_one(ZX_CHANNEL_PEER_CLOSED, zx::deadline_after(kTestTimeout),
                                           nullptr));
  }
  
  TEST_F(NetDeviceDriverTest, TestMac) {
    ASSERT_OK(CreateDevice(true));
  
    auto connect_result = ConnectMac();
    ASSERT_TRUE(connect_result.is_ok(), "Connect failed: %s",
                zx_status_get_string(connect_result.error()));
    auto mac = connect_result.take_value();
  
    ASSERT_OK(mac.GetUnicastAddress().status());
    UnbindDeviceSync();
    // mac connection should be closed on device unbind:
    ASSERT_OK(
        mac.channel().wait_one(ZX_CHANNEL_PEER_CLOSED, zx::deadline_after(kTestTimeout), nullptr));
  }
  
  TEST_F(NetDeviceDriverTest, TestNoMac) {
    ASSERT_OK(CreateDevice(false));
  
    auto connect_result = ConnectMac();
    ASSERT_TRUE(connect_result.is_ok(), "Connect failed: %s",
                zx_status_get_string(connect_result.error()));
    auto mac = connect_result.take_value();
  
    ASSERT_EQ(mac.GetUnicastAddress().status(), ZX_ERR_PEER_CLOSED);
  }
  
  }  // namespace testing
  }  // namespace network
  