blob: ec29710a74b83403b5ffdfa4335a325e2eb25d3f [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 <fidl/fuchsia.hardware.network/cpp/fidl.h>
#include <fidl/fuchsia.hardware.network/cpp/markers.h>
#include <fidl/fuchsia.hardware.network/cpp/natural_types.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/driver/testing/cpp/driver_runtime.h>
#include <lib/fidl/cpp/channel.h>
#include <lib/fidl/cpp/wire/channel.h>
#include <lib/fidl/cpp/wire/internal/transport_channel.h>
#include <lib/fzl/vmo-mapper.h>
#include <lib/stdcompat/span.h>
#include <lib/syslog/global.h>
#include <lib/zx/time.h>
#include <zircon/status.h>
#include <optional>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "src/connectivity/lib/network-device/buffer_descriptor/buffer_descriptor.h"
#include "src/lib/testing/loop_fixture/real_loop_fixture.h"
#include "src/lib/testing/predicates/status.h"
#include "tun_ctl.h"
namespace network {
namespace tun {
namespace testing {
namespace {
// Enable timeouts only to test things locally, committed code should not use timeouts.
constexpr zx::duration kTimeout = zx::duration::infinite();
constexpr uint8_t kDefaultTestPort = 2;
zx::result<fidl::ClientEnd<fuchsia_hardware_network::StatusWatcher>> GetStatusWatcher(
const fidl::ClientEnd<fuchsia_hardware_network::Device>& device,
fuchsia_hardware_network::wire::PortId port, uint32_t buffer) {
zx::result port_endpoints = fidl::CreateEndpoints<fuchsia_hardware_network::Port>();
if (port_endpoints.is_error()) {
return port_endpoints.take_error();
}
{
fidl::Status result = fidl::WireCall(device)->GetPort(port, std::move(port_endpoints->server));
if (!result.ok()) {
return zx::error(result.status());
}
}
zx::result watcher_endpoints = fidl::CreateEndpoints<fuchsia_hardware_network::StatusWatcher>();
if (watcher_endpoints.is_error()) {
return watcher_endpoints.take_error();
}
{
fidl::Status result = fidl::WireCall(port_endpoints->client)
->GetStatusWatcher(std::move(watcher_endpoints->server), buffer);
if (!result.ok()) {
return zx::error(result.status());
}
}
return zx::ok(std::move(watcher_endpoints->client));
}
zx::result<fidl::ClientEnd<fuchsia_hardware_network::MacAddressing>> GetMacAddressing(
fidl::WireSyncClient<fuchsia_net_tun::Device>& tun,
fuchsia_hardware_network::wire::PortId port_id) {
auto [mac_client, mac_server] =
fidl::Endpoints<fuchsia_hardware_network::MacAddressing>::Create();
auto [device_client, device_server] = fidl::Endpoints<fuchsia_hardware_network::Device>::Create();
auto [port_client, port_server] = fidl::Endpoints<fuchsia_hardware_network::Port>::Create();
if (zx_status_t status = tun->GetDevice(std::move(device_server)).status(); status != ZX_OK) {
return zx::error(status);
}
if (zx_status_t status =
fidl::WireCall(device_client)->GetPort(port_id, std::move(port_server)).status();
status != ZX_OK) {
return zx::error(status);
}
if (zx_status_t status = fidl::WireCall(port_client)->GetMac(std::move(mac_server)).status();
status != ZX_OK) {
return zx::error(status);
}
return zx::ok(std::move(mac_client));
}
zx::result<fidl::ClientEnd<fuchsia_hardware_network::PortWatcher>> GetPortWatcher(
fidl::ClientEnd<fuchsia_hardware_network::Device>& device) {
zx::result endpoints = fidl::CreateEndpoints<fuchsia_hardware_network::PortWatcher>();
if (endpoints.is_error()) {
return endpoints.take_error();
}
{
fidl::Status result = fidl::WireCall(device)->GetPortWatcher(std::move(endpoints->server));
if (!result.ok()) {
return zx::error(result.status());
}
}
return zx::ok(std::move(endpoints->client));
}
struct OwnedPortEvent {
OwnedPortEvent(const fuchsia_hardware_network::wire::DevicePortEvent& event)
: which(event.Which()) {
switch (event.Which()) {
case fuchsia_hardware_network::wire::DevicePortEvent::Tag::kExisting:
port_id = event.existing();
break;
case fuchsia_hardware_network::wire::DevicePortEvent::Tag::kAdded:
port_id = event.added();
break;
case fuchsia_hardware_network::wire::DevicePortEvent::Tag::kRemoved:
port_id = event.removed();
break;
case fuchsia_hardware_network::wire::DevicePortEvent::Tag::kIdle:
break;
}
}
std::string describe() const {
std::stringstream ss;
switch (which) {
case fuchsia_hardware_network::wire::DevicePortEvent::Tag::kExisting:
ss << "removed";
break;
case fuchsia_hardware_network::wire::DevicePortEvent::Tag::kAdded:
ss << "added";
break;
case fuchsia_hardware_network::wire::DevicePortEvent::Tag::kRemoved:
ss << "removed";
break;
case fuchsia_hardware_network::wire::DevicePortEvent::Tag::kIdle:
ss << "idle";
}
if (port_id.has_value()) {
const fuchsia_hardware_network::wire::PortId& id = port_id.value();
ss << "(" << static_cast<uint32_t>(id.base) << ";salt=" << static_cast<uint32_t>(id.salt)
<< ")";
}
return ss.str();
}
fuchsia_hardware_network::wire::DevicePortEvent::Tag which;
std::optional<fuchsia_hardware_network::wire::PortId> port_id;
};
zx::result<OwnedPortEvent> WatchPorts(
fidl::WireSyncClient<fuchsia_hardware_network::PortWatcher>& port_watcher) {
fidl::WireResult result = port_watcher->Watch();
if (!result.ok()) {
return zx::error(result.status());
}
return zx::ok(OwnedPortEvent(result.value().event));
}
zx::result<fuchsia_hardware_network::PortInfo> GetPortInfo(
fit::function<zx_status_t(fidl::ServerEnd<fuchsia_hardware_network::Port>)> get_port) {
auto [client_end, server] = fidl::Endpoints<fuchsia_hardware_network::Port>::Create();
if (zx_status_t status = get_port(std::move(server)); status != ZX_OK) {
return zx::error(status);
}
// We want to return PortInfo from this function, which has out-of-band
// members. This means we have to deviate from the rest of this file and use
// Natural rather than Wire FIDL types, as the Wire types don't own their
// allocation, so we'd end up deallocating the backing memory of PortInfo once
// the WireResult is dropped.
fidl::SyncClient<fuchsia_hardware_network::Port> client{std::move(client_end)};
fidl::Result result = client->GetInfo();
if (!result.is_ok()) {
return zx::error(result.error_value().status());
}
fuchsia_hardware_network::PortInfo port_info = result.value().info();
if (!port_info.id().has_value()) {
return zx::error(ZX_ERR_INTERNAL);
}
return zx::ok(port_info);
}
zx::result<fuchsia_hardware_network::PortInfo> GetPortInfo(
const fidl::ClientEnd<fuchsia_net_tun::Port>& tun_port) {
return GetPortInfo([&tun_port](fidl::ServerEnd<fuchsia_hardware_network::Port> server) {
return fidl::WireCall(tun_port)->GetPort(std::move(server)).status();
});
}
zx::result<fuchsia_hardware_network::wire::PortId> GetPortId(
fit::function<zx_status_t(fidl::ServerEnd<fuchsia_hardware_network::Port>)> get_port) {
zx::result result = GetPortInfo(std::move(get_port));
if (result.is_error()) {
return result.take_error();
}
fidl::Arena arena;
return zx::ok(fidl::ToWire(arena, result.value().id().value()));
}
zx::result<fuchsia_hardware_network::wire::PortId> GetPortId(
const fidl::ClientEnd<fuchsia_net_tun::Port>& tun_port) {
return GetPortId([&tun_port](fidl::ServerEnd<fuchsia_hardware_network::Port> server) {
return fidl::WireCall(tun_port)->GetPort(std::move(server)).status();
});
}
zx::result<
std::tuple<fuchsia_hardware_network::wire::PortId, fuchsia_hardware_network::wire::PortId>>
GetPairPortIds(uint8_t port_id, const fidl::ClientEnd<fuchsia_net_tun::DevicePair>& tun_pair) {
zx::result left =
GetPortId([&tun_pair, &port_id](fidl::ServerEnd<fuchsia_hardware_network::Port> server) {
return fidl::WireCall(tun_pair)->GetLeftPort(port_id, std::move(server)).status();
});
if (left.is_error()) {
return left.take_error();
}
zx::result right =
GetPortId([&tun_pair, &port_id](fidl::ServerEnd<fuchsia_hardware_network::Port> server) {
return fidl::WireCall(tun_pair)->GetRightPort(port_id, std::move(server)).status();
});
if (right.is_error()) {
return right.take_error();
}
return zx::ok(std::make_tuple(left.value(), right.value()));
}
} // namespace
constexpr uint32_t kDefaultMtu = 1500;
// A very simple client to fuchsia.hardware.network.Device to run data path
// tests against.
class SimpleClient {
public:
static constexpr uint64_t kBufferSize = 2048;
SimpleClient() = default;
zx::result<fidl::ServerEnd<fuchsia_hardware_network::Device>> NewRequest() {
zx::result endpoints = fidl::CreateEndpoints<fuchsia_hardware_network::Device>();
if (!endpoints.is_ok()) {
return endpoints.take_error();
}
device_ = fidl::WireSyncClient(std::move(endpoints->client));
return zx::ok(std::move(endpoints->server));
}
zx_status_t OpenSession() {
fidl::WireResult info_result = device()->GetInfo();
if (!info_result.ok()) {
return info_result.status();
}
fuchsia_hardware_network::wire::DeviceInfo& device_info = info_result.value().info;
if (!device_info.has_base_info()) {
return ZX_ERR_INTERNAL;
}
const fuchsia_hardware_network::wire::DeviceBaseInfo& device_base_info =
device_info.base_info();
if (!(device_base_info.has_tx_depth() && device_base_info.has_rx_depth())) {
return ZX_ERR_INTERNAL;
}
const uint16_t tx_depth = device_base_info.tx_depth();
const uint16_t rx_depth = device_base_info.rx_depth();
const uint16_t total_buffers = tx_depth + rx_depth;
zx_status_t status;
if ((status = data_.CreateAndMap(total_buffers * kBufferSize,
ZX_VM_PERM_WRITE | ZX_VM_PERM_READ, nullptr, &data_vmo_)) !=
ZX_OK) {
return status;
}
if ((status = descriptors_.CreateAndMap(total_buffers * sizeof(buffer_descriptor_t),
ZX_VM_PERM_WRITE | ZX_VM_PERM_READ, nullptr,
&descriptors_vmo_)) != ZX_OK) {
return status;
}
descriptor_count_ = total_buffers;
rx_depth_ = rx_depth;
tx_depth_ = tx_depth;
fuchsia_hardware_network::wire::SessionInfo session_info(alloc_);
session_info.set_descriptor_version(NETWORK_DEVICE_DESCRIPTOR_VERSION);
session_info.set_descriptor_length(
static_cast<uint8_t>(sizeof(buffer_descriptor_t) / sizeof(uint64_t)));
session_info.set_descriptor_count(descriptor_count_);
session_info.set_options(fuchsia_hardware_network::wire::SessionFlags::kPrimary);
zx::vmo data;
if ((status = data_vmo_.duplicate(ZX_RIGHT_SAME_RIGHTS, &data)) != ZX_OK) {
return status;
}
session_info.set_data(std::move(data));
zx::vmo descriptors;
if ((status = descriptors_vmo_.duplicate(ZX_RIGHT_SAME_RIGHTS, &descriptors)) != ZX_OK) {
return status;
}
session_info.set_descriptors(std::move(descriptors));
fidl::WireResult session_result = device()->OpenSession("tun-test", std::move(session_info));
if (!session_result.ok()) {
return session_result.status();
}
const fit::result<int32_t, ::fuchsia_hardware_network::wire::DeviceOpenSessionResponse*>&
result = session_result.value();
if (result.is_error()) {
return result.error_value();
}
fuchsia_hardware_network::wire::DeviceOpenSessionResponse& response = *result.value();
session_ = fidl::WireSyncClient(std::move(response.session));
rx_ = std::move(response.fifos.rx);
tx_ = std::move(response.fifos.tx);
return ZX_OK;
}
zx_status_t AttachPort(fuchsia_hardware_network::wire::PortId port_id,
std::vector<fuchsia_hardware_network::wire::FrameType> frames = {
fuchsia_hardware_network::wire::FrameType::kEthernet}) {
fidl::WireResult wire_result = session_->Attach(
port_id, fidl::VectorView<fuchsia_hardware_network::wire::FrameType>::FromExternal(frames));
if (!wire_result.ok()) {
return wire_result.status();
}
const auto& result = wire_result.value();
if (result.is_error()) {
return result.error_value();
}
port_id_ = port_id;
return ZX_OK;
}
buffer_descriptor_t* descriptor(uint16_t index) {
if (index > descriptor_count_) {
return nullptr;
}
return static_cast<buffer_descriptor_t*>(descriptors_.start()) + index;
}
cpp20::span<uint8_t> data(const buffer_descriptor_t* desc) {
return cpp20::span(static_cast<uint8_t*>(data_.start()) + desc->offset, desc->data_length);
}
void MintData(uint16_t didx, uint32_t len = 0) {
auto* desc = descriptor(didx);
if (len == 0) {
len = desc->data_length;
} else {
desc->data_length = 4;
}
auto desc_data = data(desc);
uint16_t i = 0;
for (auto& b : desc_data) {
b = static_cast<uint8_t>(i++ + didx);
}
}
void ValidateDataInPlace(uint16_t desc, uint16_t mint_idx, uint32_t size = kBufferSize) {
auto* d = descriptor(desc);
ASSERT_EQ(d->data_length, size);
auto desc_data = data(d).begin();
for (uint32_t i = 0; i < size; i++) {
ASSERT_EQ(*desc_data, static_cast<uint8_t>(i + mint_idx))
<< "Data mismatch at position " << i;
desc_data++;
}
}
static void ValidateData(const fidl::VectorView<uint8_t>& data, uint16_t didx) {
ASSERT_EQ(data.count(), kBufferSize);
for (uint32_t i = 0; i < data.count(); i++) {
ASSERT_EQ(data[i], static_cast<uint8_t>(i + didx)) << "Data mismatch at position " << i;
}
}
buffer_descriptor_t* ResetDescriptor(uint16_t index) {
auto* desc = descriptor(index);
*desc = {
.frame_type = static_cast<uint8_t>(fuchsia_hardware_network::wire::FrameType::kEthernet),
.chain_length = 0,
.nxt = 0,
.info_type = static_cast<uint32_t>(fuchsia_hardware_network::wire::InfoType::kNoInfo),
.port_id =
{
.base = port_id_.base,
.salt = port_id_.salt,
},
.offset = static_cast<uint64_t>(index) * kBufferSize,
.head_length = 0,
.tail_length = 0,
.data_length = kBufferSize,
.inbound_flags = 0,
.return_flags = 0,
};
return desc;
}
zx_status_t SendDescriptors(zx::fifo* fifo, const std::vector<uint16_t>& descs, bool reset,
size_t count) {
if (count == 0) {
count = descs.size();
}
if (reset) {
for (size_t i = 0; i < count; i++) {
ResetDescriptor(descs[i]);
MintData(descs[i]);
}
}
return fifo->write(sizeof(uint16_t), descs.data(), count, nullptr);
}
zx_status_t SendTx(const std::vector<uint16_t>& descs, bool reset = false, size_t count = 0) {
return SendDescriptors(&tx_, descs, reset, count);
}
zx_status_t SendRx(const std::vector<uint16_t>& descs, bool reset = true, size_t count = 0) {
return SendDescriptors(&rx_, descs, reset, count);
}
zx_status_t FetchDescriptors(zx::fifo& fifo, uint16_t* out, size_t* count, bool wait) {
size_t c = 1;
if (!count) {
count = &c;
}
if (wait) {
auto status = fifo.wait_one(ZX_FIFO_READABLE, zx::deadline_after(kTimeout), nullptr);
if (status != ZX_OK) {
return status;
}
}
return fifo.read(sizeof(uint16_t), out, *count, count);
}
zx_status_t FetchTx(uint16_t* out, size_t* count = nullptr, bool wait = true) {
return FetchDescriptors(tx_, out, count, wait);
}
zx_status_t FetchRx(uint16_t* out, size_t* count = nullptr, bool wait = true) {
return FetchDescriptors(rx_, out, count, wait);
}
zx_status_t WaitOnline(fuchsia_hardware_network::wire::PortId port) {
zx::result watcher = GetStatusWatcher(device().client_end(), port, 5);
if (watcher.is_error()) {
return watcher.error_value();
}
bool online = false;
while (!online) {
fidl::WireResult result = fidl::WireCall(watcher.value())->WatchStatus();
if (!result.ok()) {
return result.status();
}
const fuchsia_hardware_network::wire::PortStatus status = result.value().port_status;
online = status.has_flags() &&
status.flags() & fuchsia_hardware_network::wire::StatusFlags::kOnline;
}
return ZX_OK;
}
fidl::WireSyncClient<fuchsia_hardware_network::Session>& session() { return session_; }
fidl::WireSyncClient<fuchsia_hardware_network::Device>& device() { return device_; }
zx_status_t WaitRx() {
return rx_.wait_one(ZX_FIFO_READABLE, zx::deadline_after(kTimeout), nullptr);
}
zx_status_t WaitTx() {
return tx_.wait_one(ZX_FIFO_READABLE, zx::deadline_after(kTimeout), nullptr);
}
uint16_t rx_depth() const { return rx_depth_; }
uint16_t tx_depth() const { return tx_depth_; }
private:
fidl::Arena<> alloc_;
fidl::WireSyncClient<fuchsia_hardware_network::Device> device_;
fuchsia_hardware_network::wire::PortId port_id_;
zx::vmo data_vmo_;
zx::vmo descriptors_vmo_;
uint16_t descriptor_count_;
fzl::VmoMapper data_;
fzl::VmoMapper descriptors_;
zx::fifo rx_;
zx::fifo tx_;
uint16_t tx_depth_;
uint16_t rx_depth_;
fidl::WireSyncClient<fuchsia_hardware_network::Session> session_;
};
class TunTest : public gtest::RealLoopFixture {
protected:
void SetUp() override {
fx_logger_config_t log_cfg = {
.min_severity = -2,
.tags = nullptr,
.num_tags = 0,
};
fx_log_reconfigure(&log_cfg);
ASSERT_OK(tun_ctl_loop_.StartThread("tun-test"));
auto result = TunCtl::Create(tun_ctl_loop_.dispatcher());
ASSERT_OK(result.status_value());
tun_ctl_ = std::move(result.value());
}
void TearDown() override {
// At the end of every test, all Device and DevicePair instances must be destroyed. We wait for
// tun_ctl_ to observe all of them before destroying it and the async loop.
sync_completion_t completion;
tun_ctl_->SetSafeShutdownCallback([&completion]() { sync_completion_signal(&completion); });
ASSERT_OK(sync_completion_wait(&completion, kTimeout.get()));
// Loop must be shutdown before TunCtl. Shutdown the loop here so it's explicit and not reliant
// on the order of the fields in the class.
tun_ctl_loop_.Shutdown();
}
zx::result<fidl::WireSyncClient<fuchsia_net_tun::Control>> Connect() {
zx::result endpoints = fidl::CreateEndpoints<fuchsia_net_tun::Control>();
if (endpoints.is_error()) {
return endpoints.take_error();
}
tun_ctl_->Connect(std::move(endpoints->server));
return zx::ok(fidl::WireSyncClient(std::move(endpoints->client)));
}
fuchsia_net_tun::wire::BasePortConfig DefaultBasePortConfig() {
fuchsia_net_tun::wire::BasePortConfig config(alloc_);
config.set_mtu(kDefaultMtu);
config.set_id(kDefaultTestPort);
const fuchsia_hardware_network::wire::FrameType rx_types[] = {
fuchsia_hardware_network::wire::FrameType::kEthernet,
};
fidl::VectorView<fuchsia_hardware_network::wire::FrameType> rx_types_view(alloc_,
std::size(rx_types));
std::copy(std::begin(rx_types), std::end(rx_types), rx_types_view.data());
const fuchsia_hardware_network::wire::FrameTypeSupport tx_types[] = {
fuchsia_hardware_network::wire::FrameTypeSupport{
.type = fuchsia_hardware_network::wire::FrameType::kEthernet,
},
};
fidl::VectorView<fuchsia_hardware_network::wire::FrameTypeSupport> tx_types_view(
alloc_, std::size(tx_types));
std::copy(std::begin(tx_types), std::end(tx_types), tx_types_view.data());
config.set_rx_types(alloc_, rx_types_view);
config.set_tx_types(alloc_, tx_types_view);
return config;
}
fuchsia_net_tun::wire::DeviceConfig DefaultDeviceConfig() {
fuchsia_net_tun::wire::DeviceConfig config(alloc_);
config.set_blocking(true);
return config;
}
fuchsia_net_tun::wire::DevicePairConfig DefaultDevicePairConfig() {
fuchsia_net_tun::wire::DevicePairConfig config(alloc_);
return config;
}
fuchsia_net_tun::wire::DevicePortConfig DefaultDevicePortConfig() {
fuchsia_net_tun::wire::DevicePortConfig config(alloc_);
config.set_base(alloc_, DefaultBasePortConfig());
config.set_mac(alloc_, fuchsia_net::wire::MacAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06});
return config;
}
fuchsia_net_tun::wire::DevicePairPortConfig DefaultDevicePairPortConfig() {
fuchsia_net_tun::wire::DevicePairPortConfig config(alloc_);
config.set_base(alloc_, DefaultBasePortConfig());
config.set_mac_left(alloc_, fuchsia_net::wire::MacAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06});
config.set_mac_right(alloc_, fuchsia_net::wire::MacAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x07});
return config;
}
zx::result<fidl::ClientEnd<fuchsia_net_tun::Device>> CreateDevice(
fuchsia_net_tun::wire::DeviceConfig config) {
zx::result endpoints = fidl::CreateEndpoints<fuchsia_net_tun::Device>();
if (endpoints.is_error()) {
return endpoints.take_error();
}
zx::result tun = Connect();
if (tun.is_error()) {
return tun.take_error();
}
fidl::Status result =
tun.value()->CreateDevice(std::move(config), std::move(endpoints->server));
if (!result.ok()) {
return zx::error(result.status());
}
return zx::ok(std::move(endpoints->client));
}
zx::result<
std::pair<fidl::ClientEnd<fuchsia_net_tun::Device>, fidl::ClientEnd<fuchsia_net_tun::Port>>>
CreateDeviceAndPort(fuchsia_net_tun::wire::DeviceConfig device_config,
fuchsia_net_tun::wire::DevicePortConfig port_config) {
zx::result device = CreateDevice(std::move(device_config));
if (device.is_error()) {
return device.take_error();
}
fidl::WireSyncClient client{std::move(*device)};
zx::result port_endpoints = fidl::CreateEndpoints<fuchsia_net_tun::Port>();
if (port_endpoints.is_error()) {
return port_endpoints.take_error();
}
if (zx_status_t status =
client->AddPort(std::move(port_config), std::move(port_endpoints->server)).status();
status != ZX_OK) {
return zx::error(status);
};
return zx::ok(std::make_pair(client.TakeClientEnd(), std::move(port_endpoints->client)));
}
zx::result<fidl::ClientEnd<fuchsia_net_tun::DevicePair>> CreatePair(
fuchsia_net_tun::wire::DevicePairConfig config) {
zx::result endpoints = fidl::CreateEndpoints<fuchsia_net_tun::DevicePair>();
if (endpoints.is_error()) {
return endpoints.take_error();
}
zx::result tun = Connect();
if (tun.is_error()) {
return tun.take_error();
}
fidl::Status result = tun.value()->CreatePair(std::move(config), std::move(endpoints->server));
if (!result.ok()) {
return zx::error(result.status());
}
return zx::ok(std::move(endpoints->client));
}
zx::result<fidl::ClientEnd<fuchsia_net_tun::DevicePair>> CreatePairAndPort(
fuchsia_net_tun::wire::DevicePairConfig device_config,
fuchsia_net_tun::wire::DevicePairPortConfig port_config) {
zx::result pair_status = CreatePair(std::move(device_config));
if (pair_status.is_error()) {
return pair_status.take_error();
}
fidl::ClientEnd<fuchsia_net_tun::DevicePair>& pair = pair_status.value();
fidl::WireResult result = fidl::WireCall(pair)->AddPort(std::move(port_config));
if (result.status() != ZX_OK) {
return zx::error(result.status());
}
const auto* res = result.Unwrap();
if (res->is_error()) {
return zx::error(res->error_value());
}
return zx::ok(std::move(pair));
}
DeviceAdapter& first_adapter() { return *tun_ctl_->devices().front().adapter(); }
async::Loop tun_ctl_loop_{&kAsyncLoopConfigNoAttachToCurrentThread};
fdf_testing::DriverRuntime runtime_;
std::unique_ptr<TunCtl> tun_ctl_;
fidl::Arena<> alloc_;
};
template <class T>
class CapturingEventHandler : public fidl::WireAsyncEventHandler<T> {
public:
CapturingEventHandler() = default;
CapturingEventHandler(const CapturingEventHandler&) = delete;
CapturingEventHandler(CapturingEventHandler&&) = delete;
void on_fidl_error(fidl::UnbindInfo info) override { info_ = info; }
std::optional<fidl::UnbindInfo> info_;
};
TEST_F(TunTest, InvalidPortConfigs) {
zx::result status = CreateDevice(DefaultDeviceConfig());
ASSERT_OK(status.status_value());
fidl::WireSyncClient device{std::move(status.value())};
auto wait_for_error = [this,
&device](fuchsia_net_tun::wire::DevicePortConfig config) -> zx_status_t {
zx::result port_endpoints = fidl::CreateEndpoints<fuchsia_net_tun::Port>();
if (port_endpoints.is_error()) {
return port_endpoints.status_value();
}
fidl::Status result = device->AddPort(config, std::move(port_endpoints->server));
if (result.status() != ZX_OK) {
return result.status();
}
CapturingEventHandler<fuchsia_net_tun::Port> handler;
fidl::WireClient client(std::move(port_endpoints->client), dispatcher(), &handler);
if (!RunLoopWithTimeoutOrUntil([&handler] { return handler.info_.has_value(); }, kTimeout)) {
return ZX_ERR_TIMED_OUT;
}
return handler.info_.value().status();
};
// Zero MTU
{
auto config = DefaultDevicePortConfig();
config.base().set_mtu(0);
ASSERT_STATUS(wait_for_error(std::move(config)), ZX_ERR_INVALID_ARGS);
}
// MTU too large
{
auto config = DefaultDevicePortConfig();
config.base().set_mtu(fuchsia_net_tun::wire::kMaxMtu + 1);
ASSERT_STATUS(wait_for_error(std::move(config)), ZX_ERR_INVALID_ARGS);
}
// No Rx frames
{
auto config = DefaultDevicePortConfig();
config.base().set_rx_types(nullptr);
ASSERT_STATUS(wait_for_error(std::move(config)), ZX_ERR_INVALID_ARGS);
}
// No Tx frames
{
auto config = DefaultDevicePortConfig();
config.base().set_tx_types(nullptr);
ASSERT_STATUS(wait_for_error(std::move(config)), ZX_ERR_INVALID_ARGS);
}
// Empty Rx frames
{
auto config = DefaultDevicePortConfig();
config.base().rx_types().set_count(0);
ASSERT_STATUS(wait_for_error(std::move(config)), ZX_ERR_INVALID_ARGS);
}
// Empty Tx frames
{
auto config = DefaultDevicePortConfig();
config.base().tx_types().set_count(0);
ASSERT_STATUS(wait_for_error(std::move(config)), ZX_ERR_INVALID_ARGS);
}
}
TEST_F(TunTest, ConnectNetworkDevice) {
auto device_endpoints = fidl::Endpoints<fuchsia_hardware_network::Device>::Create();
zx::result client_end = CreateDevice(DefaultDeviceConfig());
ASSERT_OK(client_end.status_value());
fidl::WireSyncClient tun{std::move(client_end.value())};
ASSERT_OK(tun->GetDevice(std::move(device_endpoints.server)).status());
fidl::WireSyncClient device{std::move(device_endpoints.client)};
fidl::WireResult info_result = device->GetInfo();
ASSERT_OK(info_result.status());
}
TEST_F(TunTest, Teardown) {
auto device_endpoints = fidl::Endpoints<fuchsia_hardware_network::Device>::Create();
zx::result port_endpoints = fidl::CreateEndpoints<fuchsia_hardware_network::Port>();
ASSERT_OK(port_endpoints.status_value());
zx::result mac_endpoints = fidl::CreateEndpoints<fuchsia_hardware_network::MacAddressing>();
ASSERT_OK(mac_endpoints.status_value());
zx::result device_and_port =
CreateDeviceAndPort(DefaultDeviceConfig(), DefaultDevicePortConfig());
ASSERT_OK(device_and_port.status_value());
auto& [device_client_end, port_client_end] = *device_and_port;
fidl::WireSyncClient tun{std::move(device_client_end)};
ASSERT_OK(tun->GetDevice(std::move(device_endpoints.server)).status());
zx::result port_id = GetPortId(port_client_end);
ASSERT_OK(port_id.status_value());
ASSERT_OK(fidl::WireCall(device_endpoints.client)
->GetPort(port_id.value(), std::move(port_endpoints->server))
.status());
ASSERT_OK(
fidl::WireCall(port_endpoints->client)->GetMac(std::move(mac_endpoints->server)).status());
// Perform a synchronous call on Mac, which validates all the pipelined calls above succeeded.
ASSERT_OK(fidl::WireCall(mac_endpoints->client)->GetUnicastAddress().status());
CapturingEventHandler<fuchsia_hardware_network::Device> device_handler;
CapturingEventHandler<fuchsia_hardware_network::Port> port_handler;
CapturingEventHandler<fuchsia_hardware_network::MacAddressing> mac_handler;
fidl::WireClient device(std::move(device_endpoints.client), dispatcher(), &device_handler);
fidl::WireClient port(std::move(port_endpoints->client), dispatcher(), &port_handler);
fidl::WireClient mac(std::move(mac_endpoints->client), dispatcher(), &mac_handler);
// get rid of tun.
tun = {};
ASSERT_TRUE(RunLoopWithTimeoutOrUntil(
[&device_handler, &mac_handler, &port_handler]() {
return device_handler.info_.has_value() && mac_handler.info_.has_value() &&
port_handler.info_.has_value();
},
kTimeout, zx::duration::infinite()))
<< "Timed out waiting for channels to close; device_dead=" << device_handler.info_.has_value()
<< ", mac_dead=" << mac_handler.info_.has_value()
<< ", port_dead=" << port_handler.info_.has_value();
ASSERT_STATUS(device_handler.info_.value().status(), ZX_ERR_PEER_CLOSED);
ASSERT_STATUS(mac_handler.info_.value().status(), ZX_ERR_PEER_CLOSED);
ASSERT_STATUS(port_handler.info_.value().status(), ZX_ERR_PEER_CLOSED);
}
TEST_F(TunTest, Status) {
auto device_endpoints = fidl::Endpoints<fuchsia_hardware_network::Device>::Create();
zx::result device_and_port =
CreateDeviceAndPort(DefaultDeviceConfig(), DefaultDevicePortConfig());
ASSERT_OK(device_and_port.status_value());
auto& [device_client_end, port_client_end] = *device_and_port;
zx::result maybe_port_id = GetPortId(port_client_end);
ASSERT_OK(maybe_port_id.status_value());
const netdev::wire::PortId port_id = maybe_port_id.value();
fidl::WireSyncClient tun{std::move(device_client_end)};
fidl::WireSyncClient tun_port{std::move(port_client_end)};
ASSERT_OK(tun->GetDevice(std::move(device_endpoints.server)).status());
fidl::WireSyncClient device{std::move(device_endpoints.client)};
auto port_endpoints = fidl::Endpoints<fuchsia_hardware_network::Port>::Create();
{
fidl::Status result = device->GetPort(port_id, std::move(port_endpoints.server));
ASSERT_OK(result.status());
}
fidl::WireSyncClient port{std::move(port_endpoints.client)};
fidl::WireResult status_result = port->GetStatus();
ASSERT_OK(status_result.status());
{
const fuchsia_hardware_network::wire::PortStatus port_status = status_result.value().status;
ASSERT_EQ(port_status.mtu(), kDefaultMtu);
ASSERT_EQ(port_status.flags(), fuchsia_hardware_network::wire::StatusFlags());
}
zx::result watcher_status = GetStatusWatcher(device.client_end(), port_id, 5);
ASSERT_OK(watcher_status.status_value());
fidl::WireSyncClient watcher{std::move(watcher_status.value())};
{
fidl::WireResult watch_status_result = watcher->WatchStatus();
ASSERT_OK(watch_status_result.status());
const fuchsia_hardware_network::wire::PortStatus port_status =
watch_status_result.value().port_status;
ASSERT_EQ(port_status.mtu(), kDefaultMtu);
ASSERT_EQ(port_status.flags(), fuchsia_hardware_network::wire::StatusFlags());
}
ASSERT_OK(tun_port->SetOnline(true).status());
{
fidl::WireResult watch_status_result = watcher->WatchStatus();
ASSERT_OK(watch_status_result.status());
const fuchsia_hardware_network::wire::PortStatus port_status =
watch_status_result.value().port_status;
ASSERT_EQ(port_status.mtu(), kDefaultMtu);
ASSERT_EQ(port_status.flags(), fuchsia_hardware_network::wire::StatusFlags::kOnline);
}
}
MATCHER(MacEq, "") {
auto [left, right] = arg;
return std::equal(left.octets.begin(), left.octets.end(), right.octets.begin(),
right.octets.end());
}
MATCHER_P(MacEq, value, "") {
return std::equal(arg.octets.begin(), arg.octets.end(), value.octets.begin(), value.octets.end());
}
TEST_F(TunTest, Mac) {
fuchsia_net_tun::wire::DevicePortConfig port_config = DefaultDevicePortConfig();
fuchsia_net::wire::MacAddress unicast = port_config.mac();
zx::result device_and_port = CreateDeviceAndPort(DefaultDeviceConfig(), std::move(port_config));
ASSERT_OK(device_and_port.status_value());
auto& [device_client_end, port_client_end] = *device_and_port;
zx::result maybe_port_id = GetPortId(port_client_end);
ASSERT_OK(maybe_port_id.status_value());
const fuchsia_hardware_network::wire::PortId port_id = maybe_port_id.value();
fidl::WireSyncClient tun{std::move(device_client_end)};
fidl::WireSyncClient tun_port{std::move(port_client_end)};
zx::result mac_status = GetMacAddressing(tun, port_id);
ASSERT_OK(mac_status.status_value());
fidl::WireSyncClient mac{std::move(mac_status.value())};
fidl::WireResult get_unicast_address_result = mac->GetUnicastAddress();
ASSERT_OK(get_unicast_address_result.status());
ASSERT_THAT(get_unicast_address_result.value().address, MacEq(unicast));
{
fidl::WireResult watch_state_result = tun_port->WatchState();
ASSERT_OK(watch_state_result.status());
const fuchsia_net_tun::wire::InternalState internal_state = watch_state_result.value().state;
ASSERT_TRUE(internal_state.has_mac());
ASSERT_EQ(internal_state.mac().mode(),
fuchsia_hardware_network::wire::MacFilterMode::kMulticastFilter);
}
fuchsia_net::wire::MacAddress multicast{1, 10, 20, 30, 40, 50};
ASSERT_OK(mac->AddMulticastAddress(multicast).status());
{
fidl::WireResult watch_state_result = tun_port->WatchState();
ASSERT_OK(watch_state_result.status());
const fuchsia_net_tun::wire::InternalState internal_state = watch_state_result.value().state;
ASSERT_TRUE(internal_state.has_mac());
ASSERT_EQ(internal_state.mac().mode(),
fuchsia_hardware_network::wire::MacFilterMode::kMulticastFilter);
std::vector<fuchsia_net::wire::MacAddress> multicast_filters;
std::copy_n(internal_state.mac().multicast_filters().data(),
internal_state.mac().multicast_filters().count(),
std::back_inserter(multicast_filters));
ASSERT_THAT(multicast_filters, ::testing::Pointwise(MacEq(), {multicast}));
}
ASSERT_OK(mac->SetMode(fuchsia_hardware_network::wire::MacFilterMode::kPromiscuous).status());
{
fidl::WireResult watch_state_result = tun_port->WatchState();
ASSERT_OK(watch_state_result.status());
const fuchsia_net_tun::wire::InternalState internal_state = watch_state_result.value().state;
ASSERT_TRUE(internal_state.has_mac());
ASSERT_EQ(internal_state.mac().mode(),
fuchsia_hardware_network::wire::MacFilterMode::kPromiscuous);
std::vector<fuchsia_net::wire::MacAddress> multicast_filters;
std::copy_n(internal_state.mac().multicast_filters().data(),
internal_state.mac().multicast_filters().count(),
std::back_inserter(multicast_filters));
ASSERT_THAT(multicast_filters, ::testing::IsEmpty());
}
}
TEST_F(TunTest, NoMac) {
fuchsia_net_tun::wire::DevicePortConfig port_config = DefaultDevicePortConfig();
// Remove mac information.
port_config.set_mac(nullptr);
zx::result device_and_port = CreateDeviceAndPort(DefaultDeviceConfig(), std::move(port_config));
ASSERT_OK(device_and_port.status_value());
auto& [device_client_end, port_client_end] = *device_and_port;
zx::result maybe_port_id = GetPortId(port_client_end);
ASSERT_OK(maybe_port_id.status_value());
const fuchsia_hardware_network::wire::PortId port_id = maybe_port_id.value();
fidl::WireSyncClient tun{std::move(device_client_end)};
fidl::WireSyncClient tun_port{std::move(port_client_end)};
zx::result mac_status = GetMacAddressing(tun, port_id);
ASSERT_OK(mac_status.status_value());
// Mac channel should be closed because we created tun without a mac information.
// Wait for the error handler to report that back to us.
CapturingEventHandler<fuchsia_hardware_network::MacAddressing> mac_handler;
fidl::WireClient mac(std::move(mac_status.value()), dispatcher(), &mac_handler);
ASSERT_TRUE(RunLoopWithTimeoutOrUntil([&mac_handler]() { return mac_handler.info_.has_value(); },
kTimeout, zx::duration::infinite()));
fidl::WireResult get_state_result = tun_port->GetState();
ASSERT_OK(get_state_result.status());
ASSERT_FALSE(get_state_result.value().state.has_mac());
}
TEST_F(TunTest, SimpleRxTx) {
fuchsia_net_tun::wire::DeviceConfig device_config = DefaultDeviceConfig();
fuchsia_net_tun::wire::DevicePortConfig port_config = DefaultDevicePortConfig();
port_config.set_online(true);
device_config.set_blocking(false);
zx::result device_and_port =
CreateDeviceAndPort(std::move(device_config), std::move(port_config));
ASSERT_OK(device_and_port.status_value());
auto [device_client_end, port_client_end] = std::move(device_and_port.value());
zx::result maybe_port_id = GetPortId(port_client_end);
ASSERT_OK(maybe_port_id.status_value());
const fuchsia_hardware_network::wire::PortId port_id = maybe_port_id.value();
fidl::WireSyncClient tun{std::move(device_client_end)};
SimpleClient client;
zx::result request = client.NewRequest();
ASSERT_OK(request.status_value());
ASSERT_OK(tun->GetDevice(std::move(request.value())).status());
ASSERT_OK(client.OpenSession());
ASSERT_OK(client.AttachPort(port_id));
fidl::WireResult get_signals_result = tun->GetSignals();
ASSERT_OK(get_signals_result.status());
zx::eventpair& signals = get_signals_result.value().signals;
// Attempting to read frame without any available buffers should fail with should_wait and the
// readable signal should not be set.
{
fidl::WireResult read_frame_wire_result = tun->ReadFrame();
ASSERT_OK(read_frame_wire_result.status());
const fit::result<int32_t, ::fuchsia_net_tun::wire::DeviceReadFrameResponse*>&
read_frame_result = read_frame_wire_result.value();
if (read_frame_result.is_error()) {
ASSERT_STATUS(read_frame_result.error_value(), ZX_ERR_SHOULD_WAIT);
} else {
GTEST_FAIL() << "Got frame with " << read_frame_result.value()->frame.data().count()
<< "bytes, expected error";
}
ASSERT_STATUS(signals.wait_one(static_cast<uint32_t>(fuchsia_net_tun::wire::Signals::kReadable),
zx::time::infinite_past(), nullptr),
ZX_ERR_TIMED_OUT);
}
ASSERT_OK(client.SendTx({0x00, 0x01}, true));
ASSERT_OK(signals.wait_one(static_cast<uint32_t>(fuchsia_net_tun::wire::Signals::kReadable),
zx::deadline_after(kTimeout), nullptr));
{
fidl::WireResult read_frame_wire_result = tun->ReadFrame();
ASSERT_OK(read_frame_wire_result.status());
const fit::result<int32_t, ::fuchsia_net_tun::wire::DeviceReadFrameResponse*>&
read_frame_result = read_frame_wire_result.value();
if (read_frame_result.is_error()) {
GTEST_FAIL() << "ReadFrame failed: " << zx_status_get_string(read_frame_result.error_value());
} else {
ASSERT_EQ(read_frame_result.value()->frame.frame_type(),
fuchsia_hardware_network::wire::FrameType::kEthernet);
ASSERT_EQ(read_frame_result.value()->frame.port(), kDefaultTestPort);
ASSERT_NO_FATAL_FAILURE(
SimpleClient::ValidateData(read_frame_result.value()->frame.data(), 0x00));
ASSERT_FALSE(read_frame_result.value()->frame.has_meta());
}
}
// After read frame, the first descriptor must've been returned.
uint16_t desc;
ASSERT_OK(client.FetchTx(&desc));
EXPECT_EQ(desc, 0x00);
// Attempting to send a frame without any available buffers should fail with should_wait and the
// writable signal should not be set.
fuchsia_net_tun::wire::DeviceWriteFrameResult write_frame_result;
{
fuchsia_net_tun::wire::Frame frame(alloc_);
frame.set_frame_type(fuchsia_hardware_network::wire::FrameType::kEthernet);
frame.set_port(kDefaultTestPort);
uint8_t data[] = {0xAA, 0xBB};
frame.set_data(alloc_, fidl::VectorView<uint8_t>::FromExternal(data));
fidl::WireResult write_frame_wire_result = tun->WriteFrame(std::move(frame));
ASSERT_OK(write_frame_wire_result.status());
const fit::result<int32_t>& write_frame_result = write_frame_wire_result.value();
if (write_frame_result.is_error()) {
ASSERT_STATUS(write_frame_result.error_value(), ZX_ERR_SHOULD_WAIT);
ASSERT_STATUS(
signals.wait_one(static_cast<uint32_t>(fuchsia_net_tun::wire::Signals::kWritable),
zx::time::infinite_past(), nullptr),
ZX_ERR_TIMED_OUT);
} else {
GTEST_FAIL() << "WriteFrame succeeded unexpectedly";
}
}
ASSERT_OK(client.SendRx({0x02}, true));
// But if we sent stuff out, now it should work after waiting for the available signal.
ASSERT_OK(signals.wait_one(static_cast<uint32_t>(fuchsia_net_tun::wire::Signals::kWritable),
zx::deadline_after(kTimeout), nullptr));
{
fuchsia_net_tun::wire::Frame frame(alloc_);
frame.set_frame_type(fuchsia_hardware_network::wire::FrameType::kEthernet);
frame.set_port(kDefaultTestPort);
uint8_t data[] = {0xAA, 0xBB};
frame.set_data(alloc_, fidl::VectorView<uint8_t>::FromExternal(data));
fidl::WireResult write_frame_wire_result = tun->WriteFrame(std::move(frame));
ASSERT_OK(write_frame_wire_result.status());
const fit::result<int32_t>& write_frame_result = write_frame_wire_result.value();
if (write_frame_result.is_error()) {
GTEST_FAIL() << "WriteFrame failed: "
<< zx_status_get_string(write_frame_result.error_value());
}
}
// Check that data was correctly written to descriptor.
ASSERT_OK(client.FetchRx(&desc));
ASSERT_EQ(desc, 0x02);
auto* d = client.descriptor(desc);
EXPECT_EQ(d->data_length, 2u);
EXPECT_EQ(d->port_id.base, port_id.base);
EXPECT_EQ(d->port_id.salt, port_id.salt);
auto data = client.data(d);
EXPECT_EQ(data[0], 0xAA);
EXPECT_EQ(data[1], 0xBB);
}
TEST_F(TunTest, PairRxTx) {
SimpleClient left, right;
zx::result left_request = left.NewRequest();
ASSERT_OK(left_request.status_value());
zx::result right_request = right.NewRequest();
ASSERT_OK(right_request.status_value());
zx::result client_end =
CreatePairAndPort(DefaultDevicePairConfig(), DefaultDevicePairPortConfig());
ASSERT_OK(client_end.status_value());
fidl::WireSyncClient device_pair{std::move(client_end.value())};
ASSERT_OK(device_pair->GetLeft(std::move(left_request.value())).status());
ASSERT_OK(device_pair->GetRight(std::move(right_request.value())).status());
zx::result maybe_port_id = GetPairPortIds(kDefaultTestPort, device_pair.client_end());
ASSERT_OK(maybe_port_id.status_value());
const auto [left_port_id, right_port_id] = maybe_port_id.value();
ASSERT_OK(left.OpenSession());
ASSERT_OK(right.OpenSession());
ASSERT_OK(right.SendRx({0x05, 0x06, 0x07}, true));
ASSERT_OK(right.AttachPort(right_port_id));
ASSERT_OK(left.AttachPort(left_port_id));
ASSERT_OK(right.WaitOnline(right_port_id));
ASSERT_OK(left.WaitOnline(left_port_id));
ASSERT_OK(left.SendTx({0x00, 0x01, 0x02}, true));
ASSERT_OK(right.WaitRx());
uint16_t ds[3];
size_t dcount = 3;
ASSERT_OK(right.FetchRx(ds, &dcount));
ASSERT_EQ(dcount, 3u);
// Check that the data was copied correctly for all three descriptors.
uint16_t ref_d = 0x00;
for (const auto& d : ds) {
ASSERT_NO_FATAL_FAILURE(right.ValidateDataInPlace(d, ref_d))
<< "Invalid in place data for " << d << " <-> " << ref_d;
ref_d++;
}
ASSERT_OK(left.WaitTx());
ASSERT_OK(left.FetchTx(ds, &dcount));
EXPECT_EQ(dcount, 3u);
}
TEST_F(TunTest, PairOnlineSignal) {
SimpleClient left, right;
zx::result left_request = left.NewRequest();
ASSERT_OK(left_request.status_value());
zx::result right_request = right.NewRequest();
ASSERT_OK(right_request.status_value());
zx::result client_end =
CreatePairAndPort(DefaultDevicePairConfig(), DefaultDevicePairPortConfig());
ASSERT_OK(client_end.status_value());
fidl::WireSyncClient device_pair{std::move(client_end.value())};
ASSERT_OK(device_pair->GetLeft(std::move(left_request.value())).status());
ASSERT_OK(device_pair->GetRight(std::move(right_request.value())).status());
zx::result maybe_port_id = GetPairPortIds(kDefaultTestPort, device_pair.client_end());
ASSERT_OK(maybe_port_id.status_value());
const auto [left_port_id, right_port_id] = maybe_port_id.value();
ASSERT_OK(left.OpenSession());
ASSERT_OK(right.OpenSession());
constexpr uint32_t kWatcherBufferLength = 2;
// Online status should be false for both sides before a session is opened.
zx::result left_watcher_status =
GetStatusWatcher(left.device().client_end(), left_port_id, kWatcherBufferLength);
ASSERT_OK(left_watcher_status.status_value());
fidl::WireSyncClient left_watcher{std::move(left_watcher_status.value())};
zx::result right_watcher_status =
GetStatusWatcher(right.device().client_end(), right_port_id, kWatcherBufferLength);
ASSERT_OK(right_watcher_status.status_value());
fidl::WireSyncClient right_watcher =
fidl::WireSyncClient(std::move(right_watcher_status.value()));
{
fidl::WireResult left_watch_status_result = left_watcher->WatchStatus();
ASSERT_OK(left_watch_status_result.status());
fuchsia_hardware_network::wire::PortStatus status_left =
left_watch_status_result.value().port_status;
EXPECT_EQ(status_left.flags() & fuchsia_hardware_network::wire::StatusFlags::kOnline,
fuchsia_hardware_network::wire::StatusFlags());
fidl::WireResult right_watch_status_result = right_watcher->WatchStatus();
ASSERT_OK(right_watch_status_result.status());
fuchsia_hardware_network::wire::PortStatus status_right =
right_watch_status_result.value().port_status;
EXPECT_EQ(status_right.flags() & fuchsia_hardware_network::wire::StatusFlags::kOnline,
fuchsia_hardware_network::wire::StatusFlags());
}
// When both sessions are unpaused, online signal must come up.
ASSERT_OK(left.AttachPort(left_port_id));
ASSERT_OK(right.AttachPort(right_port_id));
{
fidl::WireResult left_watch_status_result = left_watcher->WatchStatus();
ASSERT_OK(left_watch_status_result.status());
fuchsia_hardware_network::wire::PortStatus status_left =
left_watch_status_result.value().port_status;
EXPECT_EQ(status_left.flags() & fuchsia_hardware_network::wire::StatusFlags::kOnline,
fuchsia_hardware_network::wire::StatusFlags::kOnline);
fidl::WireResult right_watch_status_result = right_watcher->WatchStatus();
ASSERT_OK(right_watch_status_result.status());
fuchsia_hardware_network::wire::PortStatus status_right =
right_watch_status_result.value().port_status;
EXPECT_EQ(status_right.flags() & fuchsia_hardware_network::wire::StatusFlags::kOnline,
fuchsia_hardware_network::wire::StatusFlags::kOnline);
}
}
TEST_F(TunTest, PairFallibleWrites) {
SimpleClient left, right;
zx::result left_request = left.NewRequest();
ASSERT_OK(left_request.status_value());
zx::result right_request = right.NewRequest();
ASSERT_OK(right_request.status_value());
fuchsia_net_tun::wire::DevicePairConfig config = DefaultDevicePairConfig();
config.set_fallible_transmit_left(true);
zx::result client_end = CreatePairAndPort(std::move(config), DefaultDevicePairPortConfig());
ASSERT_OK(client_end.status_value());
fidl::WireSyncClient device_pair{std::move(client_end.value())};
ASSERT_OK(device_pair->GetLeft(std::move(left_request.value())).status());
ASSERT_OK(device_pair->GetRight(std::move(right_request.value())).status());
zx::result maybe_port_id = GetPairPortIds(kDefaultTestPort, device_pair.client_end());
ASSERT_OK(maybe_port_id.status_value());
const auto [left_port_id, right_port_id] = maybe_port_id.value();
ASSERT_OK(left.OpenSession());
ASSERT_OK(right.OpenSession());
ASSERT_OK(left.AttachPort(left_port_id));
ASSERT_OK(right.AttachPort(right_port_id));
ASSERT_OK(left.WaitOnline(left_port_id));
ASSERT_OK(right.WaitOnline(right_port_id));
ASSERT_OK(left.SendTx({0x00}, true));
ASSERT_OK(left.WaitTx());
uint16_t desc;
ASSERT_OK(left.FetchTx(&desc));
ASSERT_EQ(desc, 0x00);
auto* d = left.descriptor(desc);
auto flags = static_cast<fuchsia_hardware_network::wire::TxReturnFlags>(d->return_flags);
EXPECT_EQ(flags & fuchsia_hardware_network::wire::TxReturnFlags::kTxRetError,
fuchsia_hardware_network::wire::TxReturnFlags::kTxRetError);
}
TEST_F(TunTest, PairInfallibleWrites) {
SimpleClient left, right;
zx::result left_request = left.NewRequest();
ASSERT_OK(left_request.status_value());
zx::result right_request = right.NewRequest();
ASSERT_OK(right_request.status_value());
zx::result client_end =
CreatePairAndPort(DefaultDevicePairConfig(), DefaultDevicePairPortConfig());
ASSERT_OK(client_end.status_value());
fidl::WireSyncClient device_pair{std::move(client_end.value())};
ASSERT_OK(device_pair->GetLeft(std::move(left_request.value())).status());
ASSERT_OK(device_pair->GetRight(std::move(right_request.value())).status());
zx::result maybe_port_id = GetPairPortIds(kDefaultTestPort, device_pair.client_end());
ASSERT_OK(maybe_port_id.status_value());
const auto [left_port_id, right_port_id] = maybe_port_id.value();
ASSERT_OK(left.OpenSession());
ASSERT_OK(right.OpenSession());
ASSERT_OK(left.AttachPort(left_port_id));
ASSERT_OK(right.AttachPort(right_port_id));
ASSERT_OK(left.WaitOnline(left_port_id));
ASSERT_OK(right.WaitOnline(right_port_id));
ASSERT_OK(left.SendTx({0x00}, true));
uint16_t desc;
ASSERT_STATUS(left.FetchTx(&desc, nullptr, false), ZX_ERR_SHOULD_WAIT);
ASSERT_OK(right.SendRx({0x01}, true));
ASSERT_OK(right.WaitRx());
ASSERT_OK(left.WaitTx());
ASSERT_OK(left.FetchTx(&desc));
EXPECT_EQ(desc, 0x00);
ASSERT_OK(right.FetchRx(&desc));
EXPECT_EQ(desc, 0x01);
}
TEST_F(TunTest, RejectsMissingFrameFields) {
zx::result device_and_port =
CreateDeviceAndPort(DefaultDeviceConfig(), DefaultDevicePortConfig());
ASSERT_OK(device_and_port.status_value());
auto& [device_client_end, port_client_end] = *device_and_port;
fidl::WireSyncClient tun{std::move(device_client_end)};
fidl::WireSyncClient tun_port{std::move(port_client_end)};
std::vector<uint8_t> empty_vec;
const struct {
const char* name;
fit::function<void(fuchsia_net_tun::wire::Frame&)> update_frame;
zx_status_t expect;
} kTests[] = {
{
.name = "baseline",
.update_frame = [](fuchsia_net_tun::wire::Frame& frame) {},
// Baseline, port is offline.
.expect = ZX_ERR_BAD_STATE,
},
{
.name = "no frame type",
.update_frame = [](fuchsia_net_tun::wire::Frame& frame) { frame.clear_frame_type(); },
.expect = ZX_ERR_INVALID_ARGS,
},
{
.name = "no data",
.update_frame = [](fuchsia_net_tun::wire::Frame& frame) { frame.clear_data(); },
.expect = ZX_ERR_INVALID_ARGS,
},
{
.name = "empty data",
.update_frame =
[this, &empty_vec](fuchsia_net_tun::wire::Frame& frame) {
frame.set_data(alloc_, fidl::VectorView<uint8_t>::FromExternal(empty_vec));
},
.expect = ZX_ERR_INVALID_ARGS,
},
{
.name = "no port ID",
.update_frame = [](fuchsia_net_tun::wire::Frame& frame) { frame.clear_port(); },
.expect = ZX_ERR_INVALID_ARGS,
},
{
.name = "invalid port ID",
.update_frame =
[](fuchsia_net_tun::wire::Frame& frame) {
frame.set_port(fuchsia_hardware_network::wire::kMaxPorts);
},
.expect = ZX_ERR_INVALID_ARGS,
},
};
for (const auto& test : kTests) {
SCOPED_TRACE(test.name);
// Build a valid frame then let each test case update it to make it invalid.
fuchsia_net_tun::wire::Frame frame(alloc_);
frame.set_frame_type(fuchsia_hardware_network::wire::FrameType::kEthernet);
uint8_t data[] = {0x01, 0x02, 0x03};
frame.set_data(alloc_, fidl::VectorView<uint8_t>::FromExternal(data));
frame.set_port(kDefaultTestPort);
test.update_frame(frame);
fidl::WireResult write_frame_wire_result = tun->WriteFrame(std::move(frame));
ASSERT_OK(write_frame_wire_result.status());
const fit::result<int32_t>& write_frame_result = write_frame_wire_result.value();
if (write_frame_result.is_error()) {
ASSERT_STATUS(write_frame_result.error_value(), test.expect);
} else {
GTEST_FAIL() << "WriteFrame succeeded unexpectedly";
}
}
}
TEST_F(TunTest, RejectsIfOffline) {
zx::result device_and_port =
CreateDeviceAndPort(DefaultDeviceConfig(), DefaultDevicePortConfig());
ASSERT_OK(device_and_port.status_value());
auto& [device_client_end, port_client_end] = *device_and_port;
zx::result maybe_port_id = GetPortId(port_client_end);
ASSERT_OK(maybe_port_id.status_value());
const fuchsia_hardware_network::wire::PortId port_id = maybe_port_id.value();
fidl::WireSyncClient tun{std::move(device_client_end)};
fidl::WireSyncClient tun_port{std::move(port_client_end)};
SimpleClient client;
zx::result request = client.NewRequest();
ASSERT_OK(request.status_value());
ASSERT_OK(tun->GetDevice(std::move(request.value())).status());
ASSERT_OK(client.OpenSession());
ASSERT_OK(client.AttachPort(port_id));
// Can't send from the tun end.
{
fuchsia_net_tun::wire::Frame frame(alloc_);
frame.set_frame_type(fuchsia_hardware_network::wire::FrameType::kEthernet);
uint8_t data[] = {0x01, 0x02, 0x03};
frame.set_data(alloc_, fidl::VectorView<uint8_t>::FromExternal(data));
frame.set_port(kDefaultTestPort);
fidl::WireResult write_frame_wire_result = tun->WriteFrame(std::move(frame));
ASSERT_OK(write_frame_wire_result.status());
const fit::result<int32_t>& write_frame_result = write_frame_wire_result.value();
if (write_frame_result.is_error()) {
ASSERT_STATUS(write_frame_result.error_value(), ZX_ERR_BAD_STATE);
} else {
GTEST_FAIL() << "WriteFrame succeeded unexpectedly";
}
}
// Can't send from client end.
{
ASSERT_OK(client.SendTx({0x00}, true));
ASSERT_OK(client.WaitTx());
uint16_t desc;
ASSERT_OK(client.FetchTx(&desc));
ASSERT_EQ(desc, 0x00);
ASSERT_TRUE(
fuchsia_hardware_network::wire::TxReturnFlags(client.descriptor(desc)->return_flags) &
fuchsia_hardware_network::wire::TxReturnFlags::kTxRetError)
<< "Bad return flags " << client.descriptor(desc)->return_flags;
}
// If we set online we'll be able to send.
ASSERT_OK(tun_port->SetOnline(true).status());
// Send from client end once more and read a single frame.
{
ASSERT_OK(client.SendTx({0x00, 0x01}, true));
fidl::WireResult read_frame_wire_result = tun->ReadFrame();
ASSERT_OK(read_frame_wire_result.status());
const fit::result<int32_t, ::fuchsia_net_tun::wire::DeviceReadFrameResponse*>&
read_frame_result = read_frame_wire_result.value();
if (read_frame_result.is_error()) {
GTEST_FAIL() << "ReadFrame failed: " << zx_status_get_string(read_frame_result.error_value());
}
ASSERT_EQ(read_frame_result.value()->frame.frame_type(),
fuchsia_hardware_network::wire::FrameType::kEthernet);
ASSERT_NO_FATAL_FAILURE(
SimpleClient::ValidateData(read_frame_result.value()->frame.data(), 0x00));
ASSERT_FALSE(read_frame_result.value()->frame.has_meta());
}
// Set offline and see if client received their tx buffers back.
ASSERT_OK(tun_port->SetOnline(false).status());
uint16_t desc;
ASSERT_OK(client.WaitTx());
// No error on first descriptor.
ASSERT_OK(client.FetchTx(&desc));
ASSERT_EQ(desc, 0x00);
EXPECT_FALSE(
fuchsia_hardware_network::wire::TxReturnFlags(client.descriptor(desc)->return_flags) &
fuchsia_hardware_network::wire::TxReturnFlags::kTxRetError)
<< "Bad return flags " << client.descriptor(desc)->return_flags;
// Error on second.
ASSERT_OK(client.FetchTx(&desc));
ASSERT_EQ(desc, 0x01);
EXPECT_TRUE(fuchsia_hardware_network::wire::TxReturnFlags(client.descriptor(desc)->return_flags) &
fuchsia_hardware_network::wire::TxReturnFlags::kTxRetError)
<< "Bad return flags " << client.descriptor(desc)->return_flags;
}
TEST_F(TunTest, PairEcho) {
SimpleClient left, right;
zx::result left_request = left.NewRequest();
ASSERT_OK(left_request.status_value());
zx::result right_request = right.NewRequest();
ASSERT_OK(right_request.status_value());
zx::result client_end =
CreatePairAndPort(DefaultDevicePairConfig(), DefaultDevicePairPortConfig());
ASSERT_OK(client_end.status_value());
fidl::WireSyncClient device_pair{std::move(client_end.value())};
ASSERT_OK(device_pair->GetLeft(std::move(left_request.value())).status());
ASSERT_OK(device_pair->GetRight(std::move(right_request.value())).status());
zx::result maybe_port_id = GetPairPortIds(kDefaultTestPort, device_pair.client_end());
ASSERT_OK(maybe_port_id.status_value());
const auto [left_port_id, right_port_id] = maybe_port_id.value();
ASSERT_OK(left.OpenSession());
ASSERT_OK(right.OpenSession());
ASSERT_OK(left.AttachPort(left_port_id));
ASSERT_OK(right.AttachPort(right_port_id));
ASSERT_OK(left.WaitOnline(left_port_id));
ASSERT_OK(right.WaitOnline(right_port_id));
auto rx_depth = left.rx_depth();
auto tx_depth = left.tx_depth();
std::vector<uint16_t> tx_descriptors;
std::vector<uint16_t> rx_descriptors;
tx_descriptors.reserve(tx_depth);
rx_descriptors.reserve(rx_depth);
for (uint16_t i = 0; i < static_cast<uint16_t>(tx_depth); i++) {
tx_descriptors.push_back(i);
left.ResetDescriptor(i);
left.MintData(i, 4);
right.ResetDescriptor(i);
}
for (uint16_t i = tx_depth; i < static_cast<uint16_t>(tx_depth + rx_depth); i++) {
rx_descriptors.push_back(i);
left.ResetDescriptor(i);
right.ResetDescriptor(i);
}
ASSERT_OK(right.SendRx(rx_descriptors));
ASSERT_OK(left.SendRx(rx_descriptors));
ASSERT_OK(left.SendTx(tx_descriptors));
uint32_t echoed = 0;
std::vector<uint16_t> rx_buffer(rx_depth, 0);
std::vector<uint16_t> tx_buffer(tx_depth, 0);
while (echoed < tx_depth) {
ASSERT_OK(right.WaitRx());
size_t count = rx_depth;
ASSERT_OK(right.FetchRx(rx_buffer.data(), &count));
for (size_t i = 0; i < count; i++) {
tx_buffer[i] = tx_descriptors[i + echoed];
auto* rx_desc = right.descriptor(rx_buffer[i]);
auto* tx_desc = right.descriptor(tx_buffer[i]);
auto rx_data = right.data(rx_desc);
auto tx_data = right.data(tx_desc);
std::copy(rx_data.begin(), rx_data.end(), tx_data.begin());
tx_desc->frame_type = rx_desc->frame_type;
tx_desc->data_length = rx_desc->data_length;
rx_desc->data_length = SimpleClient::kBufferSize;
}
echoed += count;
ASSERT_OK(right.SendTx(tx_buffer, false, count));
ASSERT_OK(right.SendRx(rx_buffer, false, count));
}
uint32_t received = 0;
while (received < tx_depth) {
ASSERT_OK(left.WaitRx());
size_t count = rx_depth;
ASSERT_OK(left.FetchRx(rx_buffer.data(), &count));
for (size_t i = 0; i < count; i++) {
auto orig_desc = tx_descriptors[received + i];
ASSERT_NO_FATAL_FAILURE(left.ValidateDataInPlace(rx_buffer[i], orig_desc, 4));
}
received += count;
}
}
TEST_F(TunTest, ReportsInternalTxErrors) {
fuchsia_net_tun::wire::DeviceConfig device_config = DefaultDeviceConfig();
fuchsia_net_tun::wire::DevicePortConfig port_config = DefaultDevicePortConfig();
port_config.set_online(true);
// We need tun to be nonblocking so we're able to excite the path that attempts to copy a tx
// buffer into FIDL and fails because we've removed the VMOs. If the call was blocking it'd block
// forever and we wouldn't be able to use a sync client here.
device_config.set_blocking(false);
zx::result device_and_port =
CreateDeviceAndPort(std::move(device_config), std::move(port_config));
ASSERT_OK(device_and_port.status_value());
auto& [device_client_end, port_client_end] = *device_and_port;
zx::result maybe_port_id = GetPortId(port_client_end);
ASSERT_OK(maybe_port_id.status_value());
const fuchsia_hardware_network::wire::PortId port_id = maybe_port_id.value();
fidl::WireSyncClient tun{std::move(device_client_end)};
fidl::WireSyncClient tun_port{std::move(port_client_end)};
SimpleClient client;
zx::result request = client.NewRequest();
ASSERT_OK(request.status_value());
ASSERT_OK(tun->GetDevice(std::move(request.value())).status());
ASSERT_OK(client.OpenSession());
ASSERT_OK(client.AttachPort(port_id));
// Wait for the device to observe the online session. This guarantees the Session's VMO will be
// installed by the time we're done waiting.
for (;;) {
fidl::WireResult watch_state_result = tun_port->WatchState();
ASSERT_OK(watch_state_result.status());
fuchsia_net_tun::wire::InternalState internal_state = watch_state_result.value().state;
if (internal_state.has_has_session() && internal_state.has_session()) {
break;
}
}
// Release all VMOs to make copying the buffer fail later.
DeviceAdapter& adapter = first_adapter();
for (uint8_t i = 0; i < MAX_VMOS; i++) {
adapter.NetworkDeviceImplReleaseVmo(i);
}
ASSERT_OK(client.SendTx({0x00}, true));
uint16_t descriptor;
// Attempt to fetch the buffer back, calling into ReadFrame through FIDL each round to excite the
// rx path to attempt the copy into the VMO we invalidated and cause the buffer to be returned to
// the client.
for (;;) {
zx_status_t status = client.FetchTx(&descriptor, nullptr, false);
if (status == ZX_OK) {
break;
}
ASSERT_STATUS(status, ZX_ERR_SHOULD_WAIT);
fidl::WireResult read_frame_wire_result = tun->ReadFrame();
ASSERT_OK(read_frame_wire_result.status());
ASSERT_TRUE(read_frame_wire_result.value().is_error());
ASSERT_STATUS(read_frame_wire_result.value().error_value(), ZX_ERR_SHOULD_WAIT);
}
const buffer_descriptor_t* desc = client.descriptor(descriptor);
EXPECT_EQ(desc->return_flags,
static_cast<uint32_t>(fuchsia_hardware_network::wire::TxReturnFlags::kTxRetError));
}
TEST_F(TunTest, ChainsRxBuffers) {
constexpr uint32_t kRxBufferSize = 16;
constexpr uint16_t kChainedBuffers = 3;
fuchsia_net_tun::wire::DeviceConfig device_config = DefaultDeviceConfig();
fuchsia_net_tun::wire::DevicePortConfig port_config = DefaultDevicePortConfig();
port_config.set_online(true);
fuchsia_net_tun::wire::BaseDeviceConfig base_device_config(alloc_);
base_device_config.set_min_rx_buffer_length(kRxBufferSize);
device_config.set_base(alloc_, std::move(base_device_config));
zx::result device_and_port =
CreateDeviceAndPort(std::move(device_config), std::move(port_config));
ASSERT_OK(device_and_port.status_value());
auto& [device_client_end, port_client_end] = *device_and_port;
zx::result maybe_port_id = GetPortId(port_client_end);
ASSERT_OK(maybe_port_id.status_value());
const fuchsia_hardware_network::wire::PortId port_id = maybe_port_id.value();
fidl::WireSyncClient tun{std::move(device_client_end)};
SimpleClient client;
zx::result request = client.NewRequest();
ASSERT_OK(request.status_value());
ASSERT_OK(tun->GetDevice(std::move(request.value())).status());
ASSERT_OK(client.OpenSession());
ASSERT_OK(client.AttachPort(port_id));
for (uint16_t i = 0; i < kChainedBuffers; i++) {
buffer_descriptor_t* desc = client.ResetDescriptor(i);
desc->data_length = kRxBufferSize;
ASSERT_OK(client.SendRx({i}, false));
}
std::vector<uint8_t> send_data;
send_data.reserve(kRxBufferSize * kChainedBuffers);
for (uint32_t i = 0; i < kRxBufferSize * kChainedBuffers; i++) {
send_data.push_back(static_cast<uint8_t>(i));
}
fuchsia_net_tun::wire::Frame frame(alloc_);
frame.set_port(kDefaultTestPort);
frame.set_frame_type(fuchsia_hardware_network::wire::FrameType::kEthernet);
frame.set_data(alloc_, fidl::VectorView<uint8_t>::FromExternal(send_data));
fidl::WireResult write_frame_wire_result = tun->WriteFrame(std::move(frame));
ASSERT_OK(write_frame_wire_result.status());
ASSERT_TRUE(write_frame_wire_result->is_ok())
<< zx_status_get_string(write_frame_wire_result->error_value());
uint16_t desc_idx;
ASSERT_OK(client.FetchRx(&desc_idx));
for (uint16_t i = 0; i < kChainedBuffers; i++) {
SCOPED_TRACE(i);
buffer_descriptor_t* desc = client.descriptor(desc_idx);
ASSERT_EQ(desc->chain_length, kChainedBuffers - i - 1);
ASSERT_EQ(desc->data_length, kRxBufferSize);
ASSERT_EQ(memcmp(&send_data[kRxBufferSize * i], client.data(desc).data(), kRxBufferSize), 0);
desc_idx = desc->nxt;
}
}
MATCHER(IsIdlePortEvent, "") {
*result_listener << "which is " << arg.describe();
return arg.which == fuchsia_hardware_network::wire::DevicePortEvent::Tag::kIdle;
}
MATCHER_P(IsAddedPortEvent, port, "") {
*result_listener << "which is " << arg.describe();
return arg.which == fuchsia_hardware_network::wire::DevicePortEvent::Tag::kAdded &&
arg.port_id.value().base == port.base && arg.port_id.value().salt == port.salt;
}
MATCHER_P(IsRemovedPortEvent, port, "") {
*result_listener << "which is " << arg.describe();
return arg.which == fuchsia_hardware_network::wire::DevicePortEvent::Tag::kRemoved &&
arg.port_id.value().base == port.base && arg.port_id.value().salt == port.salt;
}
TEST_F(TunTest, SetPortClass) {
zx::result device_result = CreateDevice(DefaultDeviceConfig());
ASSERT_OK(device_result.status_value());
fidl::WireSyncClient tun{std::move(*device_result)};
fidl::ClientEnd<fuchsia_hardware_network::Device> device;
{
zx::result server_end = fidl::CreateEndpoints(&device);
ASSERT_OK(server_end.status_value());
ASSERT_OK(tun->GetDevice(std::move(*server_end)).status());
}
struct {
const char* name;
fuchsia_hardware_network::wire::PortId id;
std::optional<fuchsia_hardware_network::wire::PortClass> port_class;
fuchsia_hardware_network::wire::PortClass expects_port_class;
} ports[] = {
{
.name = "port with virtual class",
.id = {.base = 1},
.port_class = fuchsia_hardware_network::wire::PortClass::kVirtual,
.expects_port_class = fuchsia_hardware_network::wire::PortClass::kVirtual,
},
{
.name = "port with no explicit class",
.id = {.base = 2},
.port_class = std::nullopt,
.expects_port_class = fuchsia_hardware_network::wire::PortClass::kVirtual,
},
{
.name = "port with non-virtual class",
.id = {.base = 3},
.port_class = fuchsia_hardware_network::wire::PortClass::kWlanAp,
.expects_port_class = fuchsia_hardware_network::wire::PortClass::kWlanAp,
},
};
for (auto& port : ports) {
SCOPED_TRACE(port.name);
fidl::ClientEnd<fuchsia_net_tun::Port> client_end;
zx::result server_end = fidl::CreateEndpoints(&client_end);
ASSERT_OK(server_end.status_value());
fuchsia_net_tun::wire::DevicePortConfig port_config = DefaultDevicePortConfig();
port_config.base().set_id(port.id.base);
if (port.port_class.has_value()) {
port_config.base().set_port_class(port.port_class.value());
}
ASSERT_OK(tun->AddPort(port_config, std::move(*server_end)).status());
zx::result port_info = GetPortInfo(client_end);
ASSERT_OK(port_info.status_value());
ASSERT_EQ(port_info->base_info()->port_class(), port.expects_port_class);
}
}
TEST_F(TunTest, AddRemovePorts) {
zx::result device_result = CreateDevice(DefaultDeviceConfig());
ASSERT_OK(device_result.status_value());
fidl::WireSyncClient tun{std::move(*device_result)};
fidl::ClientEnd<fuchsia_hardware_network::Device> device;
{
zx::result server_end = fidl::CreateEndpoints(&device);
ASSERT_OK(server_end.status_value());
ASSERT_OK(tun->GetDevice(std::move(*server_end)).status());
}
zx::result port_watcher_result = GetPortWatcher(device);
ASSERT_OK(port_watcher_result.status_value());
fidl::WireSyncClient port_watcher{std::move(*port_watcher_result)};
{
zx::result event = WatchPorts(port_watcher);
ASSERT_OK(event.status_value());
ASSERT_THAT(event.value(), IsIdlePortEvent());
}
struct {
const char* name;
fuchsia_hardware_network::wire::PortId id;
fidl::ClientEnd<fuchsia_net_tun::Port> client_end;
bool sync;
} ports[] = {
{
.name = "port 2",
.id = {.base = 2},
.sync = false,
},
{
.name = "port 5",
.id = {.base = 5},
.sync = true,
},
};
for (auto& port : ports) {
SCOPED_TRACE(port.name);
zx::result server_end = fidl::CreateEndpoints(&port.client_end);
ASSERT_OK(server_end.status_value());
fuchsia_net_tun::wire::DevicePortConfig port_config = DefaultDevicePortConfig();
port_config.base().set_id(port.id.base);
ASSERT_OK(tun->AddPort(std::move(port_config), std::move(*server_end)).status());
zx::result port_id = GetPortId(port.client_end);
ASSERT_OK(port_id.status_value());
ASSERT_EQ(port_id.value().base, port.id.base);
port.id = port_id.value();
zx::result event = WatchPorts(port_watcher);
ASSERT_OK(event.status_value());
ASSERT_THAT(event.value(), IsAddedPortEvent(port.id));
}
// Adding the same port again returns the appropriate error.
{
fidl::ClientEnd<fuchsia_net_tun::Port> client_end;
zx::result server_end = fidl::CreateEndpoints(&client_end);
ASSERT_OK(server_end.status_value());
fuchsia_net_tun::wire::DevicePortConfig port_config = DefaultDevicePortConfig();
port_config.base().set_id(ports[0].id.base);
ASSERT_OK(tun->AddPort(std::move(port_config), std::move(*server_end)).status());
CapturingEventHandler<fuchsia_net_tun::Port> handler;
fidl::WireClient client(std::move(client_end), dispatcher(), &handler);
ASSERT_TRUE(
RunLoopWithTimeoutOrUntil([&handler] { return handler.info_.has_value(); }, kTimeout));
ASSERT_EQ(handler.info_.value().status(), ZX_ERR_ALREADY_EXISTS);
}
for (auto& port : ports) {
SCOPED_TRACE(port.name);
if (port.sync) {
ASSERT_OK(fidl::WireCall(port.client_end)->Remove().status());
ASSERT_OK(port.client_end.channel().wait_one(ZX_CHANNEL_PEER_CLOSED, zx::time::infinite(),
nullptr));
} else {
port.client_end.reset();
}
zx::result event = WatchPorts(port_watcher);
ASSERT_OK(event.status_value());
ASSERT_THAT(event.value(), IsRemovedPortEvent(port.id));
}
}
TEST_F(TunTest, AddRemovePairPorts) {
zx::result pair_result = CreatePair(DefaultDevicePairConfig());
ASSERT_OK(pair_result.status_value());
fidl::WireSyncClient tun{std::move(*pair_result)};
struct {
const char* name;
bool left;
fidl::ClientEnd<fuchsia_hardware_network::Device> device;
fidl::WireSyncClient<fuchsia_hardware_network::PortWatcher> watcher;
} ends[] = {
{.name = "left", .left = true},
{.name = "right", .left = false},
};
for (auto& end : ends) {
SCOPED_TRACE(end.name);
zx::result server_end = fidl::CreateEndpoints(&end.device);
ASSERT_OK(server_end.status_value());
if (end.left) {
ASSERT_OK(tun->GetLeft(std::move(*server_end)).status());
} else {
ASSERT_OK(tun->GetRight(std::move(*server_end)).status());
}
zx::result port_watcher_result = GetPortWatcher(end.device);
ASSERT_OK(port_watcher_result.status_value());
end.watcher = fidl::WireSyncClient(std::move(*port_watcher_result));
zx::result event = WatchPorts(end.watcher);
ASSERT_OK(event.status_value());
ASSERT_THAT(event.value(), IsIdlePortEvent());
}
struct {
const char* name;
uint8_t base_id;
fuchsia_hardware_network::wire::PortId left_id;
fuchsia_hardware_network::wire::PortId right_id;
} ports[] = {
{
.name = "port 2",
.base_id = 2,
},
{
.name = "port 5",
.base_id = 5,
},
};
for (auto& port : ports) {
SCOPED_TRACE(port.name);
fuchsia_net_tun::wire::DevicePairPortConfig port_config = DefaultDevicePairPortConfig();
port_config.base().set_id(port.base_id);
fidl::WireResult result = tun->AddPort(std::move(port_config));
ASSERT_OK(result.status());
ASSERT_TRUE(result->is_ok()) << zx_status_get_string(result->error_value());
zx::result port_id = GetPairPortIds(port.base_id, tun.client_end());
ASSERT_OK(port_id.status_value());
const auto [left_id, right_id] = port_id.value();
ASSERT_EQ(left_id.base, port.base_id);
port.left_id = left_id;
ASSERT_EQ(right_id.base, port.base_id);
port.right_id = right_id;
for (auto& end : ends) {
SCOPED_TRACE(end.name);
zx::result event = WatchPorts(end.watcher);
ASSERT_OK(event.status_value());
const fuchsia_hardware_network::wire::PortId port_id = [&port, &end]() {
if (end.left) {
return port.left_id;
}
return port.right_id;
}();
ASSERT_THAT(event.value(), IsAddedPortEvent(port_id));
}
}
// Adding the same port again returns the appropriate error.
{
fuchsia_net_tun::wire::DevicePairPortConfig port_config = DefaultDevicePairPortConfig();
port_config.base().set_id(ports[0].base_id);
fidl::WireResult result = tun->AddPort(std::move(port_config));
ASSERT_OK(result.status());
ASSERT_TRUE(result->is_error());
ASSERT_EQ(result->error_value(), ZX_ERR_ALREADY_EXISTS);
}
for (auto& port : ports) {
SCOPED_TRACE(port.name);
fidl::WireResult result = tun->RemovePort(port.base_id);
ASSERT_OK(result.status());
ASSERT_TRUE(result->is_ok()) << zx_status_get_string(result->error_value());
for (auto& end : ends) {
SCOPED_TRACE(end.name);
zx::result event = WatchPorts(end.watcher);
ASSERT_OK(event.status_value());
const fuchsia_hardware_network::wire::PortId port_id = [&port, &end]() {
if (end.left) {
return port.left_id;
}
return port.right_id;
}();
ASSERT_THAT(event.value(), IsRemovedPortEvent(port_id));
}
}
// Removing a port that doesn't exist anymore returns the appropriate error.
fidl::WireResult result = tun->RemovePort(ports[0].base_id);
ASSERT_OK(result.status());
ASSERT_TRUE(result->is_error());
ASSERT_EQ(result->error_value(), ZX_ERR_NOT_FOUND);
}
} // namespace testing
} // namespace tun
} // namespace network