blob: 90f599ed28be96d01e7ba452f15bfbc4d0b520ba [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 "test_util.h"
#include <lib/sync/cpp/completion.h>
#include <iostream>
#include <gtest/gtest.h>
#include "network_device_shim.h"
#include "src/lib/testing/predicates/status.h"
namespace network::testing {
zx::result<std::vector<uint8_t>> TxBuffer::GetData(const VmoProvider& vmo_provider) {
if (!vmo_provider) {
return zx::error(ZX_ERR_INTERNAL);
}
// We don't support copying chained buffers.
if (buffer_.data.count() != 1) {
return zx::error(ZX_ERR_INTERNAL);
}
const fuchsia_hardware_network_driver::wire::BufferRegion& region = buffer_.data.at(0);
zx::unowned_vmo vmo = vmo_provider(region.vmo);
if (!vmo->is_valid()) {
return zx::error(ZX_ERR_INTERNAL);
}
std::vector<uint8_t> copy;
copy.resize(region.length);
zx_status_t status = vmo->read(copy.data(), region.offset, region.length);
if (status != ZX_OK) {
return zx::error(status);
}
return zx::ok(std::move(copy));
}
zx_status_t RxBuffer::WriteData(cpp20::span<const uint8_t> data, const VmoProvider& vmo_provider) {
if (!vmo_provider) {
return ZX_ERR_INTERNAL;
}
if (data.size() > space_.region.length) {
return ZX_ERR_INVALID_ARGS;
}
zx::unowned_vmo vmo = vmo_provider(space_.region.vmo);
return_part_.length = static_cast<uint32_t>(data.size());
return vmo->write(data.data(), space_.region.offset, data.size());
}
FakeNetworkPortImpl::FakeNetworkPortImpl()
: port_info_({
.port_class = netdev::wire::DeviceClass::kEthernet,
.rx_types = {netdev::wire::FrameType::kEthernet},
.tx_types = {{.type = netdev::wire::FrameType::kEthernet,
.features = netdev::wire::kFrameFeaturesRaw,
.supported_flags = netdev::wire::TxFlags(0)}},
}) {
EXPECT_OK(zx::event::create(0, &event_));
}
FakeNetworkPortImpl::~FakeNetworkPortImpl() {
if (port_added_) {
EXPECT_TRUE(port_removed_) << "port was added but remove was not called";
}
}
void FakeNetworkPortImpl::WaitPortRemoved() {
if (port_added_) {
WaitForPortRemoval();
ASSERT_TRUE(port_removed_) << "port was added but remove was not called";
}
}
void FakeNetworkPortImpl::GetInfo(fdf::Arena& arena, GetInfoCompleter::Sync& completer) {
fidl::Arena fidl_arena;
auto builder = fuchsia_hardware_network::wire::PortBaseInfo::Builder(fidl_arena);
auto rx_types = fidl::VectorView<netdev::wire::FrameType>::FromExternal(port_info_.rx_types);
auto tx_types =
fidl::VectorView<netdev::wire::FrameTypeSupport>::FromExternal(port_info_.tx_types);
builder.port_class(port_info_.port_class)
.tx_types(fidl::ObjectView<decltype(tx_types)>::FromExternal(&tx_types))
.rx_types(fidl::ObjectView<decltype(rx_types)>::FromExternal(&rx_types));
completer.buffer(arena).Reply(builder.Build());
}
void FakeNetworkPortImpl::GetStatus(fdf::Arena& arena, GetStatusCompleter::Sync& completer) {
fidl::Arena fidl_arena;
auto builder = fuchsia_hardware_network::wire::PortStatus::Builder(fidl_arena);
builder.mtu(status_.mtu).flags(status_.flags);
completer.buffer(arena).Reply(builder.Build());
}
void FakeNetworkPortImpl::SetActive(
fuchsia_hardware_network_driver::wire::NetworkPortSetActiveRequest* request, fdf::Arena& arena,
SetActiveCompleter::Sync& completer) {
port_active_ = request->active;
if (on_set_active_) {
on_set_active_(request->active);
}
ASSERT_OK(event_.signal(0, kEventPortActiveChanged));
}
void FakeNetworkPortImpl::Removed(fdf::Arena& arena, RemovedCompleter::Sync& completer) {
ASSERT_FALSE(port_removed_) << "removed same port twice";
port_removed_ = true;
sync_completion_signal(&wait_removed_);
}
void FakeNetworkPortImpl::GetMac(fdf::Arena& arena, GetMacCompleter::Sync& completer) {
fdf::ClientEnd<fuchsia_hardware_network_driver::MacAddr> client{};
if (mac_client_end_.has_value()) {
client = std::move(*mac_client_end_);
mac_client_end_ = {};
}
completer.buffer(arena).Reply(std::move(client));
}
zx_status_t FakeNetworkPortImpl::AddPort(uint8_t port_id, const fdf::Dispatcher& dispatcher,
fidl::WireSyncClient<netdev::Device> device,
FakeNetworkDeviceImpl& parent) {
if (port_added_) {
return ZX_ERR_ALREADY_EXISTS;
}
id_ = port_id;
parent_ = &parent;
zx::result fidl_endpoints = fidl::CreateEndpoints<fuchsia_hardware_network::PortWatcher>();
if (fidl_endpoints.is_error()) {
return fidl_endpoints.status_value();
}
auto status = device->GetPortWatcher(std::move(fidl_endpoints->server));
if (!status.ok()) {
return status.status();
}
fidl::WireSyncClient port_watcher(std::move(fidl_endpoints->client));
bool found_idle = false;
while (!found_idle) {
auto result = port_watcher->Watch();
if (!result.ok()) {
return result.status();
}
found_idle = result->event.Which() == netdev::wire::DevicePortEvent::Tag::kIdle;
}
auto endpoints = fdf::CreateEndpoints<fuchsia_hardware_network_driver::NetworkPort>();
if (endpoints.is_error()) {
return endpoints.status_value();
}
binding_ = fdf::BindServer(dispatcher.get(), std::move(endpoints->server), this);
fdf::Arena arena('NETD');
auto add_port_status =
parent.client().sync().buffer(arena)->AddPort(port_id, std::move(endpoints->client));
if (!add_port_status.ok() || add_port_status->status != ZX_OK) {
return add_port_status.ok() ? add_port_status->status : add_port_status.status();
}
auto result = port_watcher->Watch();
if (!result.ok()) {
return result.status();
}
if (result->event.Which() != netdev::wire::DevicePortEvent::Tag::kAdded) {
return ZX_ERR_BAD_STATE;
}
port_added_ = true;
device_ = std::move(device);
return ZX_OK;
}
zx_status_t FakeNetworkPortImpl::AddPortNoWait(uint8_t port_id, const fdf::Dispatcher& dispatcher,
fidl::WireSyncClient<netdev::Device> device,
FakeNetworkDeviceImpl& parent) {
if (port_added_) {
return ZX_ERR_ALREADY_EXISTS;
}
id_ = port_id;
parent_ = &parent;
auto endpoints = fdf::CreateEndpoints<fuchsia_hardware_network_driver::NetworkPort>();
if (endpoints.is_error()) {
return endpoints.status_value();
}
binding_ = fdf::BindServer(
dispatcher.get(), std::move(endpoints->server), this,
[](fdf::WireServer<fuchsia_hardware_network_driver::NetworkPort>*, fidl::UnbindInfo foo,
fdf::ServerEnd<fuchsia_hardware_network_driver::NetworkPort> /*unused*/) {});
fdf::Arena arena('NETD');
auto status =
parent.client().sync().buffer(arena)->AddPort(port_id, std::move(endpoints->client));
if (!status.ok() || status->status != ZX_OK) {
return status.ok() ? status->status : status.status();
}
port_added_ = true;
device_ = std::move(device);
return ZX_OK;
}
void FakeNetworkPortImpl::RemoveSync() {
// Already removed.
if (!port_added_ || port_removed_) {
return;
}
fdf::Arena arena('NETD');
EXPECT_TRUE(parent_->client().buffer(arena)->RemovePort(id_).ok());
WaitForPortRemoval();
}
void FakeNetworkPortImpl::SetOnline(bool online) {
PortStatus status = status_;
status.flags = online ? netdev::wire::StatusFlags::kOnline : netdev::wire::StatusFlags();
SetStatus(status);
}
void FakeNetworkPortImpl::SetStatus(const PortStatus& status) {
status_ = status;
if (parent_ != nullptr && parent_->client().is_valid()) {
fidl::Arena fidl_arena;
auto builder = fuchsia_hardware_network::wire::PortStatus::Builder(fidl_arena);
builder.mtu(status_.mtu).flags(status_.flags);
fdf::Arena arena('NETD');
EXPECT_TRUE(parent_->client().buffer(arena)->PortStatusChanged(id_, builder.Build()).ok());
}
}
FakeNetworkDeviceImpl::FakeNetworkDeviceImpl()
: info_({
.tx_depth = kDefaultTxDepth,
.rx_depth = kDefaultRxDepth,
.rx_threshold = kDefaultRxDepth / 2,
.max_buffer_length = ZX_PAGE_SIZE / 2,
.buffer_alignment = ZX_PAGE_SIZE,
}) {
EXPECT_OK(zx::event::create(0, &event_));
}
FakeNetworkDeviceImpl::~FakeNetworkDeviceImpl() {
// ensure that all VMOs were released
for (auto& vmo : vmos_) {
ZX_ASSERT(!vmo.is_valid());
}
}
void FakeNetworkDeviceImpl::Init(
fuchsia_hardware_network_driver::wire::NetworkDeviceImplInitRequest* request, fdf::Arena& arena,
InitCompleter::Sync& completer) {
device_client_ = fdf::WireSharedClient(std::move(request->iface), dispatcher_);
completer.buffer(arena).Reply(ZX_OK);
}
void FakeNetworkDeviceImpl::Start(fdf::Arena& arena, StartCompleter::Sync& completer) {
fbl::AutoLock lock(&lock_);
EXPECT_FALSE(device_started_) << "called start on already started device";
if (auto_start_.has_value()) {
const zx_status_t auto_start = auto_start_.value();
if (auto_start == ZX_OK) {
device_started_ = true;
}
if (on_start_) {
on_start_();
}
completer.buffer(arena).Reply(auto_start);
} else {
ZX_ASSERT(!(pending_start_callback_ || pending_stop_callback_));
pending_start_callback_ = [completer = completer.ToAsync(), this]() mutable {
{
fbl::AutoLock lock(&lock_);
device_started_ = true;
if (on_start_) {
on_start_();
}
}
fdf::Arena arena('NETD');
completer.buffer(arena).Reply(ZX_OK);
};
}
EXPECT_OK(event_.signal(0, kEventStartInitiated));
}
void FakeNetworkDeviceImpl::Stop(fdf::Arena& arena, StopCompleter::Sync& completer) {
fbl::AutoLock lock(&lock_);
EXPECT_TRUE(device_started_) << "called stop on already stopped device";
device_started_ = false;
zx_signals_t clear;
if (auto_stop_) {
RxFidlReturnTransaction rx_return(this);
while (!rx_buffers_.is_empty()) {
std::unique_ptr rx_buffer = rx_buffers_.pop_front();
// Return unfulfilled buffers with zero length and an invalid port number.
// Zero length buffers are returned to the pool and the port metadata is ignored.
rx_buffer->return_part().length = 0;
rx_return.Enqueue(std::move(rx_buffer), MAX_PORTS);
}
rx_return.Commit();
TxFidlReturnTransaction tx_return(this);
while (!tx_buffers_.is_empty()) {
std::unique_ptr tx_buffer = tx_buffers_.pop_front();
tx_buffer->set_status(ZX_ERR_UNAVAILABLE);
tx_return.Enqueue(std::move(tx_buffer));
}
tx_return.Commit();
fdf::Arena arena('NETD');
completer.buffer(arena).Reply();
// Must clear the queue signals if we're clearing the queues automatically.
clear = kEventTx | kEventRxAvailable;
} else {
ZX_ASSERT(!(pending_start_callback_ || pending_stop_callback_));
pending_stop_callback_ = [completer = completer.ToAsync()]() mutable {
fdf::Arena arena('NETD');
completer.buffer(arena).Reply();
};
clear = 0;
}
EXPECT_OK(event_.signal(clear, kEventStop));
}
void FakeNetworkDeviceImpl::GetInfo(fdf::Arena& arena, GetInfoCompleter::Sync& completer) {
fidl::Arena fidl_arena;
auto builder = fuchsia_hardware_network_driver::wire::DeviceImplInfo::Builder(fidl_arena);
auto tx_accel = fidl::VectorView<netdev::wire::TxAcceleration>::FromExternal(info_.tx_accel);
auto rx_accel = fidl::VectorView<netdev::wire::RxAcceleration>::FromExternal(info_.rx_accel);
builder.device_features(info_.device_features)
.tx_depth(info_.tx_depth)
.rx_depth(info_.rx_depth)
.rx_threshold(info_.rx_threshold)
.max_buffer_parts(info_.max_buffer_parts)
.max_buffer_length(info_.max_buffer_length)
.buffer_alignment(info_.buffer_alignment)
.buffer_alignment(info_.buffer_alignment)
.min_rx_buffer_length(info_.min_rx_buffer_length)
.min_tx_buffer_length(info_.min_tx_buffer_length)
.tx_head_length(info_.tx_head_length)
.tx_tail_length(info_.tx_tail_length)
.tx_accel(fidl::ObjectView<decltype(tx_accel)>::FromExternal(&tx_accel))
.rx_accel(fidl::ObjectView<decltype(rx_accel)>::FromExternal(&rx_accel));
completer.buffer(arena).Reply(builder.Build());
}
void FakeNetworkDeviceImpl::QueueTx(
fuchsia_hardware_network_driver::wire::NetworkDeviceImplQueueTxRequest* request,
fdf::Arena& arena, QueueTxCompleter::Sync& completer) {
EXPECT_NE(request->buffers.count(), 0u);
ASSERT_TRUE(device_client_.is_valid());
fbl::AutoLock lock(&lock_);
cpp20::span buffers = request->buffers.get();
queue_tx_called_.push_back(buffers.size());
if (immediate_return_tx_ || !device_started_) {
const zx_status_t return_status = device_started_ ? ZX_OK : ZX_ERR_UNAVAILABLE;
ASSERT_LE(request->buffers.count(), kDefaultTxDepth);
std::array<fuchsia_hardware_network_driver::wire::TxResult, kDefaultTxDepth> results;
auto results_iter = results.begin();
for (const fuchsia_hardware_network_driver::wire::TxBuffer& buff : buffers) {
*results_iter++ = {
.id = buff.id,
.status = return_status,
};
}
auto output = fidl::VectorView<fuchsia_hardware_network_driver::wire::TxResult>::FromExternal(
results.data(), request->buffers.count());
EXPECT_TRUE(device_client_.buffer(arena)->CompleteTx(output).ok());
return;
}
for (const fuchsia_hardware_network_driver::wire::TxBuffer& buff : buffers) {
auto back = std::make_unique<TxBuffer>(buff);
tx_buffers_.push_back(std::move(back));
}
EXPECT_OK(event_.signal(0, kEventTx));
}
void FakeNetworkDeviceImpl::QueueRxSpace(
fuchsia_hardware_network_driver::wire::NetworkDeviceImplQueueRxSpaceRequest* request,
fdf::Arena& arena, QueueRxSpaceCompleter::Sync& completer) {
ASSERT_TRUE(device_client_.is_valid());
size_t buf_count = request->buffers.count();
fbl::AutoLock lock(&lock_);
queue_rx_space_called_.push_back(buf_count);
auto buffers = request->buffers.get();
if (immediate_return_rx_ || !device_started_) {
const uint32_t length = device_started_ ? kAutoReturnRxLength : 0;
ASSERT_TRUE(buf_count < kDefaultTxDepth);
std::array<fuchsia_hardware_network_driver::wire::RxBuffer, kDefaultTxDepth> results;
std::array<fuchsia_hardware_network_driver::wire::RxBufferPart, kDefaultTxDepth> parts;
auto results_iter = results.begin();
auto parts_iter = parts.begin();
for (const fuchsia_hardware_network_driver::wire::RxSpaceBuffer& space : buffers) {
fuchsia_hardware_network_driver::wire::RxBufferPart& part = *parts_iter++;
fuchsia_hardware_network_driver::wire::RxBuffer& rx_buffer = *results_iter++;
part = {
.id = space.id,
.length = length,
};
rx_buffer = {
.meta =
{
.info = netdriver::wire::FrameInfo::WithNoInfo(netdriver::wire::NoInfo{
static_cast<uint8_t>(netdev::wire::InfoType::kNoInfo)}),
.frame_type = fuchsia_hardware_network::wire::FrameType::kEthernet,
},
.data =
fidl::VectorView<fuchsia_hardware_network_driver::wire::RxBufferPart>::FromExternal(
&part, 1),
};
}
auto output = fidl::VectorView<fuchsia_hardware_network_driver::wire::RxBuffer>::FromExternal(
results.data(), buf_count);
EXPECT_TRUE(device_client_.buffer(arena)->CompleteRx(output).ok());
return;
}
for (const fuchsia_hardware_network_driver::wire::RxSpaceBuffer& buff : buffers) {
auto back = std::make_unique<RxBuffer>(buff);
rx_buffers_.push_back(std::move(back));
}
EXPECT_OK(event_.signal(0, kEventRxAvailable));
}
void FakeNetworkDeviceImpl::PrepareVmo(
fuchsia_hardware_network_driver::wire::NetworkDeviceImplPrepareVmoRequest* request,
fdf::Arena& arena, PrepareVmoCompleter::Sync& completer) {
zx::vmo& slot = vmos_[request->id];
EXPECT_FALSE(slot.is_valid()) << "vmo " << static_cast<uint32_t>(request->id)
<< " already prepared";
slot = std::move(request->vmo);
if (prepare_vmo_handler_) {
prepare_vmo_handler_(request->id, slot, completer);
} else {
completer.buffer(arena).Reply(ZX_OK);
}
}
void FakeNetworkDeviceImpl::ReleaseVmo(
fuchsia_hardware_network_driver::wire::NetworkDeviceImplReleaseVmoRequest* request,
fdf::Arena& arena, ReleaseVmoCompleter::Sync& completer) {
zx::vmo& slot = vmos_[request->id];
EXPECT_TRUE(slot.is_valid()) << "vmo " << static_cast<uint32_t>(request->id)
<< " already released";
slot.reset();
bool all_released = true;
for (auto& vmo : vmos_) {
if (vmo.is_valid()) {
all_released = false;
}
}
if (all_released) {
sync_completion_signal(&released_completer_);
}
completer.buffer(arena).Reply();
}
void FakeNetworkDeviceImpl::SetSnoop(
fuchsia_hardware_network_driver::wire::NetworkDeviceImplSetSnoopRequest* request,
fdf::Arena& arena, SetSnoopCompleter::Sync& completer) {
// Do nothing , only auto-snooping is allowed.
}
fit::function<zx::unowned_vmo(uint8_t)> FakeNetworkDeviceImpl::VmoGetter() {
return [this](uint8_t id) { return zx::unowned_vmo(vmos_[id]); };
}
bool FakeNetworkDeviceImpl::TriggerStart() {
fbl::AutoLock lock(&lock_);
auto cb = std::move(pending_start_callback_);
lock.release();
if (cb) {
cb();
return true;
}
return false;
}
bool FakeNetworkDeviceImpl::TriggerStop() {
fbl::AutoLock lock(&lock_);
auto cb = std::move(pending_stop_callback_);
lock.release();
if (cb) {
cb();
return true;
}
return false;
}
zx::result<fdf::ClientEnd<fuchsia_hardware_network_driver::NetworkDeviceImpl>>
FakeNetworkDeviceImpl::Binder::Bind() {
auto endpoints = fdf::CreateEndpoints<
fuchsia_hardware_network_driver::Service::NetworkDeviceImpl::ProtocolType>();
if (endpoints.is_error()) {
return endpoints.take_error();
}
binding_ = fdf::BindServer(dispatcher_, std::move(endpoints->server), parent_);
return zx::ok(std::move(endpoints->client));
}
zx::result<std::unique_ptr<NetworkDeviceInterface>> FakeNetworkDeviceImpl::CreateChild(
DeviceInterfaceDispatchers dispatchers) {
dispatcher_ = dispatchers.impl_->get();
auto binder = std::make_unique<Binder>(this, dispatcher_);
zx::result device = internal::DeviceInterface::Create(dispatchers, std::move(binder));
if (device.is_error()) {
return device.take_error();
}
auto& value = device.value();
value->evt_session_started_.Set(
[this](const char* session) { event_.signal(0, kEventSessionStarted); });
value->evt_session_died_.Set(
[this](const char* session) { event_.signal(0, kEventSessionDied); });
return zx::ok(std::move(value));
}
zx::result<fdf::ClientEnd<netdriver::NetworkDeviceIfc>> FakeNetworkDeviceIfc::Bind(
fdf::Dispatcher* dispatcher) {
auto endpoints = fdf::CreateEndpoints<netdriver::NetworkDeviceIfc>();
if (endpoints.is_error()) {
return endpoints.take_error();
}
fdf::BindServer(dispatcher->get(), std::move(endpoints->server), this);
return zx::ok(std::move(endpoints->client));
}
void FakeNetworkDeviceIfc::PortStatusChanged(
netdriver::wire::NetworkDeviceIfcPortStatusChangedRequest* request, fdf::Arena& arena,
PortStatusChangedCompleter::Sync& completer) {
if (port_status_changed_) {
port_status_changed_(request, arena, completer);
}
}
void FakeNetworkDeviceIfc::AddPort(netdriver::wire::NetworkDeviceIfcAddPortRequest* request,
fdf::Arena& arena, AddPortCompleter::Sync& completer) {
if (add_port_) {
add_port_(request, arena, completer);
}
}
void FakeNetworkDeviceIfc::RemovePort(netdriver::wire::NetworkDeviceIfcRemovePortRequest* request,
fdf::Arena& arena, RemovePortCompleter::Sync& completer) {
if (remove_port_) {
remove_port_(request, arena, completer);
}
}
void FakeNetworkDeviceIfc::CompleteRx(netdriver::wire::NetworkDeviceIfcCompleteRxRequest* request,
fdf::Arena& arena, CompleteRxCompleter::Sync& completer) {
if (complete_rx_) {
complete_rx_(request, arena, completer);
}
}
void FakeNetworkDeviceIfc::CompleteTx(netdriver::wire::NetworkDeviceIfcCompleteTxRequest* request,
fdf::Arena& arena, CompleteTxCompleter::Sync& completer) {
if (complete_tx_) {
complete_tx_(request, arena, completer);
}
}
void FakeNetworkDeviceIfc::Snoop(netdriver::wire::NetworkDeviceIfcSnoopRequest* request,
fdf::Arena& arena, SnoopCompleter::Sync& completer) {
if (snoop_) {
snoop_(request, arena, completer);
}
}
} // namespace network::testing