blob: 2653375fb3da91ddc8bee67593772201014072df [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.
#ifndef SRC_CONNECTIVITY_NETWORK_DRIVERS_NETWORK_DEVICE_DEVICE_TEST_UTIL_H_
#define SRC_CONNECTIVITY_NETWORK_DRIVERS_NETWORK_DEVICE_DEVICE_TEST_UTIL_H_
#include <lib/sync/completion.h>
#include <lib/zx/event.h>
#include <memory>
#include <vector>
#include <fbl/intrusive_double_list.h>
#include <gtest/gtest.h>
#include "definitions.h"
#include "device_interface.h"
#include "src/lib/testing/predicates/status.h"
namespace network::testing {
constexpr uint16_t kDefaultRxDepth = 16;
constexpr uint16_t kDefaultTxDepth = 16;
constexpr uint16_t kDefaultDescriptorCount = 256;
constexpr uint64_t kDefaultBufferLength = ZX_PAGE_SIZE / 2;
constexpr uint32_t kAutoReturnRxLength = 512;
class RxReturnTransaction;
class TxReturnTransaction;
using VmoProvider = fit::function<zx::unowned_vmo(uint8_t)>;
class TxBuffer : public fbl::DoublyLinkedListable<std::unique_ptr<TxBuffer>> {
public:
explicit TxBuffer(const fuchsia_hardware_network_driver::wire::TxBuffer& buffer)
: buffer_(buffer) {
auto regions = buffer.data.get();
for (size_t i = 0; i < regions.size(); ++i) {
parts_[i] = regions[i];
}
buffer_.data =
fidl::VectorView<fuchsia_hardware_network_driver::wire::BufferRegion>::FromExternal(
parts_.data(), regions.size());
}
zx_status_t status() const { return status_; }
void set_status(zx_status_t status) { status_ = status; }
zx::result<std::vector<uint8_t>> GetData(const VmoProvider& vmo_provider);
fuchsia_hardware_network_driver::wire::TxResult result() {
return {
.id = buffer_.id,
.status = status_,
};
}
fuchsia_hardware_network_driver::wire::TxBuffer& buffer() { return buffer_; }
private:
fuchsia_hardware_network_driver::wire::TxBuffer buffer_{};
internal::BufferParts<fuchsia_hardware_network_driver::wire::BufferRegion> parts_{};
zx_status_t status_ = ZX_OK;
};
class RxBuffer : public fbl::DoublyLinkedListable<std::unique_ptr<RxBuffer>> {
public:
explicit RxBuffer(const fuchsia_hardware_network_driver::wire::RxSpaceBuffer& space)
: space_(space),
return_part_({
.id = space.id,
}) {}
zx_status_t WriteData(const std::vector<uint8_t>& data, const VmoProvider& vmo_provider) {
return WriteData(cpp20::span(data.data(), data.size()), vmo_provider);
}
zx_status_t WriteData(cpp20::span<const uint8_t> data, const VmoProvider& vmo_provider);
fuchsia_hardware_network_driver::wire::RxBufferPart& return_part() { return return_part_; }
fuchsia_hardware_network_driver::wire::RxSpaceBuffer& space() { return space_; }
void SetReturnLength(uint32_t length) { return_part_.length = length; }
private:
fuchsia_hardware_network_driver::wire::RxSpaceBuffer space_{};
fuchsia_hardware_network_driver::wire::RxBufferPart return_part_{};
};
class RxFidlReturn : public fbl::DoublyLinkedListable<std::unique_ptr<RxFidlReturn>> {
public:
RxFidlReturn()
: buffer_({
.meta =
{
.info = netdriver::wire::FrameInfo::WithNoInfo(netdriver::wire::NoInfo{
static_cast<uint8_t>(netdev::wire::InfoType::kNoInfo)}),
.info_type = netdev::wire::InfoType::kNoInfo,
.frame_type = netdev::wire::FrameType::kEthernet,
},
.data =
fidl::VectorView<fuchsia_hardware_network_driver::wire::RxBufferPart>::FromExternal(
parts_.data(), 0),
}) {}
// RxReturn can't be moved because it keeps pointers to the return buffer internally.
RxFidlReturn(RxFidlReturn&&) = delete;
RxFidlReturn(std::unique_ptr<RxBuffer> buffer, uint8_t port_id) : RxFidlReturn() {
PushPart(std::move(buffer));
buffer_.meta.port = port_id;
}
// Pushes buffer space into the return buffer.
//
// NB: We don't really need the unique pointer here, we just copy the information we need. But
// requiring the unique pointer to be passed enforces the buffer ownership semantics. Also
// RxBuffers usually sit in the available queue as a pointer already.
void PushPart(std::unique_ptr<RxBuffer> buffer) {
ZX_ASSERT(buffer_.data.count() < parts_.size());
parts_[buffer_.data.count()] = buffer->return_part();
buffer_.data.set_count(buffer_.data.count() + 1);
}
const fuchsia_hardware_network_driver::wire::RxBuffer& buffer() const { return buffer_; }
fuchsia_hardware_network_driver::wire::RxBuffer& buffer() { return buffer_; }
private:
internal::BufferParts<fuchsia_hardware_network_driver::wire::RxBufferPart> parts_{};
fuchsia_hardware_network_driver::wire::RxBuffer buffer_{};
};
constexpr zx_signals_t kEventStartInitiated = ZX_USER_SIGNAL_0;
constexpr zx_signals_t kEventStop = ZX_USER_SIGNAL_1;
constexpr zx_signals_t kEventTx = ZX_USER_SIGNAL_2;
constexpr zx_signals_t kEventSessionStarted = ZX_USER_SIGNAL_3;
constexpr zx_signals_t kEventRxAvailable = ZX_USER_SIGNAL_4;
constexpr zx_signals_t kEventStartCompleted = ZX_USER_SIGNAL_5;
constexpr zx_signals_t kEventPortActiveChanged = ZX_USER_SIGNAL_6;
constexpr zx_signals_t kEventSessionDied = ZX_USER_SIGNAL_7;
struct PortInfo {
netdev::wire::DeviceClass port_class;
std::vector<netdev::wire::FrameType> rx_types;
std::vector<netdev::wire::FrameTypeSupport> tx_types;
};
struct PortStatus {
uint32_t mtu;
netdev::wire::StatusFlags flags;
};
class FakeNetworkPortImpl : public fdf::WireServer<fuchsia_hardware_network_driver::NetworkPort> {
public:
using OnSetActiveCallback = fit::function<void(bool)>;
FakeNetworkPortImpl();
~FakeNetworkPortImpl() override;
void GetInfo(fdf::Arena& arena, GetInfoCompleter::Sync& completer) override;
void GetStatus(fdf::Arena& arena, GetStatusCompleter::Sync& completer) override;
void SetActive(fuchsia_hardware_network_driver::wire::NetworkPortSetActiveRequest* request,
fdf::Arena& arena, SetActiveCompleter::Sync& completer) override;
void GetMac(fdf::Arena& arena, GetMacCompleter::Sync& completer) override;
void Removed(fdf::Arena& arena, RemovedCompleter::Sync& completer) override;
PortInfo& port_info() { return port_info_; }
const PortStatus& status() const { return status_; }
zx_status_t AddPort(uint8_t port_id, const fdf::Dispatcher& dispatcher,
fidl::WireSyncClient<netdev::Device> device,
FakeNetworkDeviceImpl& ifc_client);
zx_status_t AddPortNoWait(uint8_t port_id, const fdf::Dispatcher& dispatcher,
fidl::WireSyncClient<netdev::Device> device,
FakeNetworkDeviceImpl& ifc_client);
void RemoveSync();
void SetMac(fdf::ClientEnd<fuchsia_hardware_network_driver::MacAddr> client) {
mac_client_end_ = std::move(client);
}
void SetOnSetActiveCallback(OnSetActiveCallback cb) { on_set_active_ = std::move(cb); }
void SetSupportedRxType(netdev::wire::FrameType frame_type) {
port_info_.rx_types = {frame_type};
}
void SetSupportedTxType(netdev::wire::FrameType frame_type) {
port_info_.tx_types = {{.type = frame_type,
.features = netdev::wire::kFrameFeaturesRaw,
.supported_flags = netdev::wire::TxFlags(0)}};
}
void WaitPortRemoved();
void WaitForPortRemoval() {
ASSERT_OK(sync_completion_wait_deadline(&wait_removed_, zx::time::infinite().get()));
}
bool active() const { return port_active_; }
bool removed() const { return port_removed_; }
uint8_t id() const { return id_; }
const zx::event& events() const { return event_; }
void SetOnline(bool online);
void SetStatus(const PortStatus& status);
private:
DISALLOW_COPY_ASSIGN_AND_MOVE(FakeNetworkPortImpl);
std::optional<fdf::ServerBindingRef<fuchsia_hardware_network_driver::NetworkPort>> binding_;
FakeNetworkDeviceImpl* parent_ = nullptr;
std::optional<fdf::ClientEnd<fuchsia_hardware_network_driver::MacAddr>> mac_client_end_;
sync_completion_t wait_removed_;
OnSetActiveCallback on_set_active_;
uint8_t id_;
PortInfo port_info_;
std::atomic_bool port_active_ = false;
PortStatus status_;
zx::event event_;
bool port_removed_ = false;
bool port_added_ = false;
std::optional<fidl::WireSyncClient<netdev::Device>> device_;
};
struct DeviceInfo {
uint32_t device_features;
uint16_t tx_depth;
uint16_t rx_depth;
uint16_t rx_threshold;
uint8_t max_buffer_parts;
uint32_t max_buffer_length;
uint32_t buffer_alignment;
uint32_t min_rx_buffer_length;
uint32_t min_tx_buffer_length;
uint16_t tx_head_length;
uint16_t tx_tail_length;
std::vector<netdev::wire::RxAcceleration> rx_accel;
std::vector<netdev::wire::TxAcceleration> tx_accel;
};
class FakeNetworkDeviceImpl
: public fdf::WireServer<fuchsia_hardware_network_driver::NetworkDeviceImpl> {
public:
using PrepareVmoHandler =
fit::function<void(uint8_t, const zx::vmo&, PrepareVmoCompleter::Sync&)>;
FakeNetworkDeviceImpl();
~FakeNetworkDeviceImpl() override;
zx::result<std::unique_ptr<NetworkDeviceInterface>> CreateChild(
DeviceInterfaceDispatchers dispatchers);
void Init(fuchsia_hardware_network_driver::wire::NetworkDeviceImplInitRequest* request,
fdf::Arena& arena, InitCompleter::Sync& completer) override;
void Start(fdf::Arena& arena, StartCompleter::Sync& completer) override;
void Stop(fdf::Arena& arena, StopCompleter::Sync& completer) override;
void GetInfo(fdf::Arena& arena, GetInfoCompleter::Sync& completer) override;
void QueueTx(fuchsia_hardware_network_driver::wire::NetworkDeviceImplQueueTxRequest* request,
fdf::Arena& arena, QueueTxCompleter::Sync& completer) override;
void QueueRxSpace(
fuchsia_hardware_network_driver::wire::NetworkDeviceImplQueueRxSpaceRequest* request,
fdf::Arena& arena, QueueRxSpaceCompleter::Sync& completer) override;
void PrepareVmo(
fuchsia_hardware_network_driver::wire::NetworkDeviceImplPrepareVmoRequest* request,
fdf::Arena& arena, PrepareVmoCompleter::Sync& completer) override;
void ReleaseVmo(
fuchsia_hardware_network_driver::wire::NetworkDeviceImplReleaseVmoRequest* request,
fdf::Arena& arena, ReleaseVmoCompleter::Sync& completer) override;
void SetSnoop(fuchsia_hardware_network_driver::wire::NetworkDeviceImplSetSnoopRequest* request,
fdf::Arena& arena, SetSnoopCompleter::Sync& completer) override;
fit::function<zx::unowned_vmo(uint8_t)> VmoGetter();
const zx::event& events() const { return event_; }
DeviceInfo& info() { return info_; }
std::unique_ptr<RxBuffer> PopRxBuffer() __TA_EXCLUDES(lock_) {
fbl::AutoLock lock(&lock_);
return rx_buffers_.pop_front();
}
std::unique_ptr<TxBuffer> PopTxBuffer() __TA_EXCLUDES(lock_) {
fbl::AutoLock lock(&lock_);
return tx_buffers_.pop_front();
}
fbl::SizedDoublyLinkedList<std::unique_ptr<TxBuffer>> TakeTxBuffers() __TA_EXCLUDES(lock_) {
fbl::AutoLock lock(&lock_);
fbl::SizedDoublyLinkedList<std::unique_ptr<TxBuffer>> r;
tx_buffers_.swap(r);
return r;
}
fbl::SizedDoublyLinkedList<std::unique_ptr<RxBuffer>> TakeRxBuffers() __TA_EXCLUDES(lock_) {
fbl::AutoLock lock(&lock_);
fbl::SizedDoublyLinkedList<std::unique_ptr<RxBuffer>> r;
rx_buffers_.swap(r);
return r;
}
size_t rx_buffer_count() __TA_EXCLUDES(lock_) {
fbl::AutoLock lock(&lock_);
return rx_buffers_.size();
}
size_t tx_buffer_count() __TA_EXCLUDES(lock_) {
fbl::AutoLock lock(&lock_);
return tx_buffers_.size();
}
size_t queue_rx_space_called() __TA_EXCLUDES(lock_) {
fbl::AutoLock lock(&lock_);
ZX_ASSERT(!queue_rx_space_called_.empty());
const size_t front = queue_rx_space_called_.front();
queue_rx_space_called_.pop_front();
return front;
}
size_t queue_tx_called() __TA_EXCLUDES(lock_) {
fbl::AutoLock lock(&lock_);
ZX_ASSERT(!queue_tx_called_.empty());
const size_t front = queue_tx_called_.front();
queue_tx_called_.pop_front();
return front;
}
std::optional<uint8_t> first_vmo_id() {
for (size_t i = 0; i < vmos_.size(); i++) {
if (vmos_[i].is_valid()) {
return i;
}
}
return std::nullopt;
}
void set_auto_start(std::optional<zx_status_t> auto_start) { auto_start_ = auto_start; }
void set_auto_stop(bool auto_stop) { auto_stop_ = auto_stop; }
bool TriggerStart();
bool TriggerStop();
void set_immediate_return_tx(bool auto_return) { immediate_return_tx_ = auto_return; }
void set_immediate_return_rx(bool auto_return) { immediate_return_rx_ = auto_return; }
void set_prepare_vmo_handler(PrepareVmoHandler handler) {
prepare_vmo_handler_ = std::move(handler);
}
fdf::WireSharedClient<fuchsia_hardware_network_driver::NetworkDeviceIfc>& client() {
return device_client_;
}
void WaitReleased() {
// TODO(nahurley): Figure out why we need to wait based on a signal. Why
// isn't shutting down the server/client sufficient?
bool all_released = true;
for (auto& vmo : vmos_) {
if (vmo.is_valid()) {
all_released = false;
}
}
if (!all_released) {
ASSERT_OK(sync_completion_wait_deadline(&released_completer_, zx::time::infinite().get()));
}
}
// Note that |on_start| will be called with the internal lock held, be careful of deadlocks.
void SetOnStart(fit::function<void()>&& on_start) __TA_EXCLUDES(lock_) {
fbl::AutoLock lock(&lock_);
on_start_ = std::move(on_start);
}
cpp20::span<const zx::vmo> vmos() { return cpp20::span(vmos_.begin(), vmos_.end()); }
private:
DISALLOW_COPY_ASSIGN_AND_MOVE(FakeNetworkDeviceImpl);
class Binder : public NetworkDeviceImplBinder {
public:
Binder(FakeNetworkDeviceImpl* parent, fdf_dispatcher_t* dispatcher)
: parent_(parent), dispatcher_(dispatcher) {}
zx::result<fdf::ClientEnd<netdriver::NetworkDeviceImpl>> Bind() override;
private:
DISALLOW_COPY_ASSIGN_AND_MOVE(Binder);
FakeNetworkDeviceImpl* parent_ = nullptr;
std::optional<fdf::ServerBindingRef<netdriver::NetworkDeviceImpl>> binding_;
fdf_dispatcher_t* dispatcher_ = nullptr;
};
fbl::Mutex lock_;
sync_completion_t released_completer_;
fdf_dispatcher_t* dispatcher_ = nullptr;
std::array<zx::vmo, MAX_VMOS> vmos_;
DeviceInfo info_{};
fdf::WireSharedClient<fuchsia_hardware_network_driver::NetworkDeviceIfc> device_client_;
fbl::SizedDoublyLinkedList<std::unique_ptr<RxBuffer>> rx_buffers_ __TA_GUARDED(lock_);
fbl::SizedDoublyLinkedList<std::unique_ptr<TxBuffer>> tx_buffers_ __TA_GUARDED(lock_);
std::deque<size_t> queue_tx_called_ __TA_GUARDED(lock_);
std::deque<size_t> queue_rx_space_called_ __TA_GUARDED(lock_);
zx::event event_;
std::optional<zx_status_t> auto_start_ = ZX_OK;
bool auto_stop_ = true;
bool immediate_return_tx_ = false;
bool immediate_return_rx_ = false;
bool device_started_ __TA_GUARDED(lock_) = false;
fit::function<void()> pending_start_callback_ __TA_GUARDED(lock_);
fit::function<void()> pending_stop_callback_ __TA_GUARDED(lock_);
fit::function<void()> on_start_ __TA_GUARDED(lock_);
PrepareVmoHandler prepare_vmo_handler_;
};
class FakeNetworkDeviceIfc : public fdf::WireServer<netdriver::NetworkDeviceIfc> {
public:
FakeNetworkDeviceIfc() = default;
zx::result<fdf::ClientEnd<netdriver::NetworkDeviceIfc>> Bind(fdf::Dispatcher* dispatcher);
// NetworkDeviceIfc implementation.
void PortStatusChanged(netdriver::wire::NetworkDeviceIfcPortStatusChangedRequest* request,
fdf::Arena& arena, PortStatusChangedCompleter::Sync& completer) override;
void AddPort(netdriver::wire::NetworkDeviceIfcAddPortRequest* request, fdf::Arena& arena,
AddPortCompleter::Sync& completer) override;
void RemovePort(netdriver::wire::NetworkDeviceIfcRemovePortRequest* request, fdf::Arena& arena,
RemovePortCompleter::Sync& completer) override;
void CompleteRx(netdriver::wire::NetworkDeviceIfcCompleteRxRequest* request, fdf::Arena& arena,
CompleteRxCompleter::Sync& completer) override;
void CompleteTx(netdriver::wire::NetworkDeviceIfcCompleteTxRequest* request, fdf::Arena& arena,
CompleteTxCompleter::Sync& completer) override;
void Snoop(netdriver::wire::NetworkDeviceIfcSnoopRequest* request, fdf::Arena& arena,
SnoopCompleter::Sync& completer) override;
// If assigned, these functions are called when the corresponding FIDL call is served.
fit::function<void(netdriver::wire::NetworkDeviceIfcPortStatusChangedRequest*, fdf::Arena&,
PortStatusChangedCompleter::Sync&)>
port_status_changed_;
fit::function<void(netdriver::wire::NetworkDeviceIfcAddPortRequest*, fdf::Arena&,
AddPortCompleter::Sync&)>
add_port_;
fit::function<void(netdriver::wire::NetworkDeviceIfcRemovePortRequest*, fdf::Arena&,
RemovePortCompleter::Sync&)>
remove_port_;
fit::function<void(netdriver::wire::NetworkDeviceIfcCompleteRxRequest*, fdf::Arena&,
CompleteRxCompleter::Sync&)>
complete_rx_;
fit::function<void(netdriver::wire::NetworkDeviceIfcCompleteTxRequest*, fdf::Arena&,
CompleteTxCompleter::Sync&)>
complete_tx_;
fit::function<void(netdriver::wire::NetworkDeviceIfcSnoopRequest*, fdf::Arena&,
SnoopCompleter::Sync&)>
snoop_;
private:
DISALLOW_COPY_ASSIGN_AND_MOVE(FakeNetworkDeviceIfc);
};
class RxFidlReturnTransaction {
public:
explicit RxFidlReturnTransaction(FakeNetworkDeviceImpl* impl)
: return_buffers_(impl->info().rx_depth), client_(impl->client()) {}
void Enqueue(std::unique_ptr<RxFidlReturn> buffer) {
ZX_ASSERT(count_ < std::size(return_buffers_));
return_buffers_[count_++] = buffer->buffer();
buffers_.push_back(std::move(buffer));
}
void Enqueue(std::unique_ptr<RxBuffer> buffer, uint8_t port_id) {
Enqueue(std::make_unique<RxFidlReturn>(std::move(buffer), port_id));
}
void Commit() {
size_t remaining = count_;
size_t offset = 0;
while (remaining > 0) {
const size_t batch = std::min<size_t>(remaining, MAX_RX_BUFFERS);
fdf::Arena arena('NETD');
auto results =
fidl::VectorView<fuchsia_hardware_network_driver::wire::RxBuffer>::FromExternal(
return_buffers_.data() + offset, batch);
auto result = client_.buffer(arena)->CompleteRx(results);
EXPECT_OK(result.status());
offset += batch;
remaining -= batch;
}
count_ = 0;
buffers_.clear();
}
private:
std::vector<netdriver::wire::RxBuffer> return_buffers_;
size_t count_ = 0;
fdf::WireSharedClient<fuchsia_hardware_network_driver::NetworkDeviceIfc>& client_;
fbl::DoublyLinkedList<std::unique_ptr<RxFidlReturn>> buffers_;
DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(RxFidlReturnTransaction);
};
class TxFidlReturnTransaction {
public:
explicit TxFidlReturnTransaction(FakeNetworkDeviceImpl* impl)
: return_buffers_(impl->info().tx_depth), client_(impl->client()) {}
void Enqueue(std::unique_ptr<TxBuffer> buffer) {
ZX_ASSERT(count_ < std::size(return_buffers_));
return_buffers_[count_++] = buffer->result();
}
void Commit() {
fdf::Arena arena('NETD');
auto results = fidl::VectorView<fuchsia_hardware_network_driver::wire::TxResult>::FromExternal(
return_buffers_.data(), count_);
EXPECT_TRUE(client_.buffer(arena)->CompleteTx(results).ok());
count_ = 0;
}
private:
std::vector<netdriver::wire::TxResult> return_buffers_;
size_t count_ = 0;
fdf::WireSharedClient<fuchsia_hardware_network_driver::NetworkDeviceIfc>& client_;
DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(TxFidlReturnTransaction);
};
} // namespace network::testing
#endif // SRC_CONNECTIVITY_NETWORK_DRIVERS_NETWORK_DEVICE_DEVICE_TEST_UTIL_H_