blob: 97b13cf1a6c1c68f9ef6456bf1e201c28d86293e [file] [log] [blame]
// 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 <ddktl/device.h>
#include <gtest/gtest.h>
#include "device/test_session.h"
#include "device/test_util_banjo.h"
#include "mac/test_util_banjo.h"
#include "src/devices/testing/mock-ddk/mock-device.h"
#include "src/lib/testing/predicates/status.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 ::testing::Test {
protected:
// Use a nonzero port identifier to avoid default value traps.
static constexpr uint8_t kPortId = 11;
NetDeviceDriverTest() : loop_(&kAsyncLoopConfigNeverAttachToThread) {
loop_.StartThread("net-device-driver-test");
}
void TearDown() override { UnbindAndRelease(); }
void UnbindAndRelease() {
if (MockDevice* dev = parent_->GetLatestChild(); dev != nullptr) {
dev->UnbindOp();
EXPECT_OK(dev->WaitUntilUnbindReplyCalled(zx::deadline_after(kTestTimeout)));
dev->ReleaseOp();
}
}
zx_status_t CreateDevice(bool with_mac = false) {
auto proto = device_impl_.proto();
parent_->AddProtocol(ZX_PROTOCOL_NETWORK_DEVICE_IMPL, proto.ops, proto.ctx);
if (with_mac) {
port_impl_.SetMac(mac_impl_.proto());
}
port_impl_.SetStatus(
{.flags = static_cast<uint32_t>(netdev::wire::StatusFlags::kOnline), .mtu = 2048});
if (zx_status_t status = NetworkDevice::Create(nullptr, parent_.get()); status != ZX_OK) {
return status;
}
if (zx_status_t status = port_impl_.AddPort(kPortId, device_impl_.client()); status != ZX_OK) {
return status;
}
return ZX_OK;
}
zx::result<fidl::WireSyncClient<netdev::Device>> ConnectNetDevice() {
zx::result endpoints = fidl::CreateEndpoints<netdev::Device>();
if (endpoints.is_error()) {
return endpoints.take_error();
}
zx::result client = [this]() -> zx::result<fidl::WireSyncClient<netdev::DeviceInstance>> {
auto [client_end, server_end] = fidl::Endpoints<netdev::DeviceInstance>::Create();
fidl::BindServer(loop_.dispatcher(), std::move(server_end),
parent_->GetLatestChild()->GetDeviceContext<NetworkDevice>());
return zx::ok(fidl::WireSyncClient(std::move(client_end)));
}();
if (client.is_error()) {
return client.take_error();
}
auto [client_end, server_end] = std::move(*endpoints);
fidl::Status result = client->GetDevice(std::move(server_end));
if (zx_status_t status = result.status(); status != ZX_OK) {
return zx::error(status);
}
return zx::ok(fidl::WireSyncClient(std::move(client_end)));
}
const banjo::FakeNetworkDeviceImpl& device_impl() const { return device_impl_; }
banjo::FakeNetworkPortImpl& port_impl() { return port_impl_; }
zx::result<netdev::wire::PortId> GetSaltedPortId(uint8_t base_id) {
// List all existing ports from the device until we find the right port id.
zx::result connect_result = ConnectNetDevice();
if (connect_result.is_error()) {
return connect_result.take_error();
}
fidl::WireSyncClient<netdev::Device>& netdevice = connect_result.value();
auto [client_end, server_end] = fidl::Endpoints<netdev::PortWatcher>::Create();
if (zx_status_t status = netdevice->GetPortWatcher(std::move(server_end)).status();
status != ZX_OK) {
return zx::error(status);
}
fidl::WireSyncClient watcher{std::move(client_end)};
for (;;) {
fidl::WireResult result = watcher->Watch();
if (!result.ok()) {
return zx::error(result.status());
}
const netdev::wire::DevicePortEvent& event = result.value().event;
netdev::wire::PortId id;
switch (event.Which()) {
case netdev::wire::DevicePortEvent::Tag::kAdded:
id = event.added();
break;
case netdev::wire::DevicePortEvent::Tag::kExisting:
id = event.existing();
break;
case netdev::wire::DevicePortEvent::Tag::kIdle:
case netdev::wire::DevicePortEvent::Tag::kRemoved:
ADD_FAILURE() << "Unexpected port watcher event " << static_cast<uint32_t>(event.Which());
return zx::error(ZX_ERR_INTERNAL);
}
if (id.base == base_id) {
return zx::ok(id);
}
}
}
zx_status_t AttachSessionPort(TestSession& session, banjo::FakeNetworkPortImpl& impl) {
std::vector<netdev::wire::FrameType> rx_types;
for (uint8_t frame_type :
cpp20::span(impl.port_info().rx_types_list, impl.port_info().rx_types_count)) {
rx_types.push_back(static_cast<netdev::wire::FrameType>(frame_type));
}
zx::result port_id = GetSaltedPortId(impl.id());
if (port_id.is_error()) {
return port_id.status_value();
}
return session.AttachPort(port_id.value(), std::move(rx_types));
}
private:
const std::shared_ptr<MockDevice> parent_ = MockDevice::FakeRootParent();
async::Loop loop_;
banjo::FakeMacDeviceImpl mac_impl_;
banjo::FakeNetworkDeviceImpl device_impl_;
banjo::FakeNetworkPortImpl port_impl_;
};
TEST_F(NetDeviceDriverTest, TestCreateSimple) { ASSERT_OK(CreateDevice()); }
TEST_F(NetDeviceDriverTest, TestOpenSession) {
ASSERT_OK(CreateDevice());
TestSession session;
zx::result connect_result = ConnectNetDevice();
ASSERT_OK(connect_result.status_value());
fidl::WireSyncClient<netdev::Device>& netdevice = connect_result.value();
ASSERT_OK(session.Open(netdevice, "test-session"));
ASSERT_OK(AttachSessionPort(session, port_impl()));
ASSERT_OK(device_impl().events().wait_one(kEventStartInitiated, zx::deadline_after(kTestTimeout),
nullptr));
UnbindAndRelease();
ASSERT_OK(session.WaitClosed(zx::deadline_after(kTestTimeout)));
// netdevice should also have been closed after device unbind:
ASSERT_OK(netdevice.client_end().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());
zx::result connect_result = ConnectNetDevice();
ASSERT_OK(connect_result.status_value());
fidl::WireSyncClient<netdev::Device>& netdevice = connect_result.value();
zx::result maybe_port_id = GetSaltedPortId(kPortId);
ASSERT_OK(maybe_port_id.status_value());
const netdev::wire::PortId& port_id = maybe_port_id.value();
auto [port_client_end, port_server_end] = fidl::Endpoints<netdev::Port>::Create();
ASSERT_OK(netdevice->GetPort(port_id, std::move(port_server_end)).status());
fidl::WireSyncClient port{std::move(port_client_end)};
auto [client_end, server_end] = fidl::Endpoints<netdev::StatusWatcher>::Create();
ASSERT_OK(port->GetStatusWatcher(std::move(server_end), 1).status());
fidl::WireSyncClient watcher{std::move(client_end)};
ASSERT_OK(watcher->WatchStatus().status());
UnbindAndRelease();
// Watcher, port, and netdevice should all observe channel closure.
ASSERT_OK(watcher.client_end().channel().wait_one(ZX_CHANNEL_PEER_CLOSED,
zx::deadline_after(kTestTimeout), nullptr));
ASSERT_OK(port.client_end().channel().wait_one(ZX_CHANNEL_PEER_CLOSED,
zx::deadline_after(kTestTimeout), nullptr));
ASSERT_OK(netdevice.client_end().channel().wait_one(ZX_CHANNEL_PEER_CLOSED,
zx::deadline_after(kTestTimeout), nullptr));
}
} // namespace testing
} // namespace network