blob: 3454356a955c305afe14137df516ddf7a9640ad1 [file]
// 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/zx/event.h>
#include <zircon/device/network.h>
#include <memory>
#include <vector>
#include <fbl/intrusive_double_list.h>
#include <gtest/gtest.h>
#include "definitions.h"
#include "device_interface.h"
namespace network {
namespace testing {
constexpr uint16_t kRxDepth = 16;
constexpr uint16_t kTxDepth = 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 tx_buffer_t& buffer) : buffer_(buffer) {
for (size_t i = 0; i < buffer_.data_count; i++) {
parts_[i] = buffer_.data_list[i];
}
buffer_.data_list = parts_.data();
}
zx_status_t status() const { return status_; }
void set_status(zx_status_t status) { status_ = status; }
zx::status<std::vector<uint8_t>> GetData(const VmoProvider& vmo_provider);
tx_result_t result() {
return {
.id = buffer_.id,
.status = status_,
};
}
tx_buffer_t& buffer() { return buffer_; }
private:
tx_buffer_t buffer_{};
internal::BufferParts<buffer_region_t> parts_{};
zx_status_t status_ = ZX_OK;
};
class RxBuffer : public fbl::DoublyLinkedListable<std::unique_ptr<RxBuffer>> {
public:
explicit RxBuffer(const rx_space_buffer_t& space)
: space_(space),
return_part_(rx_buffer_part_t{
.id = space.id,
}) {}
zx_status_t WriteData(const std::vector<uint8_t>& data, const VmoProvider& vmo_provider) {
return WriteData(fbl::Span(data.data(), data.size()), vmo_provider);
}
zx_status_t WriteData(fbl::Span<const uint8_t> data, const VmoProvider& vmo_provider);
rx_buffer_part_t& return_part() { return return_part_; }
rx_space_buffer_t& space() { return space_; }
void SetReturnLength(uint32_t length) { return_part_.length = length; }
private:
rx_space_buffer_t space_{};
rx_buffer_part_t return_part_{};
};
class RxReturn : public fbl::DoublyLinkedListable<std::unique_ptr<RxReturn>> {
public:
RxReturn()
: buffer_(rx_buffer_t{
.meta =
{
.info_type = static_cast<uint32_t>(netdev::wire::InfoType::kNoInfo),
.frame_type = static_cast<uint8_t>(netdev::wire::FrameType::kEthernet),
},
.data_list = parts_.begin(),
.data_count = 0,
}) {}
// RxReturn can't be moved because it keeps pointers to the return buffer internally.
RxReturn(RxReturn&&) = delete;
explicit RxReturn(std::unique_ptr<RxBuffer> buffer) : RxReturn() { PushPart(std::move(buffer)); }
// 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();
}
const rx_buffer_t& buffer() const { return buffer_; }
rx_buffer_t& buffer() { return buffer_; }
private:
internal::BufferParts<rx_buffer_part_t> parts_{};
rx_buffer_t buffer_{};
};
constexpr zx_signals_t kEventStart = 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 kEventPortRemoved = ZX_USER_SIGNAL_5;
constexpr zx_signals_t kEventPortActiveChanged = ZX_USER_SIGNAL_6;
class FakeNetworkDeviceImpl;
class FakeNetworkPortImpl : public ddk::NetworkPortProtocol<FakeNetworkPortImpl> {
public:
FakeNetworkPortImpl();
~FakeNetworkPortImpl();
void NetworkPortGetInfo(port_info_t* out_info);
void NetworkPortGetStatus(port_status_t* out_status);
void NetworkPortSetActive(bool active);
void NetworkPortGetMac(mac_addr_protocol_t* out_mac_ifc);
void NetworkPortRemoved();
port_info_t& port_info() { return port_info_; }
const port_status_t& status() const { return status_; }
void AddPort(uint8_t port_id, ddk::NetworkDeviceIfcProtocolClient ifc_client);
void RemoveSync();
void SetMac(mac_addr_protocol_t proto) { mac_proto_ = proto; }
network_port_protocol_t protocol() {
return {
.ops = &network_port_protocol_ops_,
.ctx = this,
};
}
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 port_status_t& status);
private:
DISALLOW_COPY_ASSIGN_AND_MOVE(FakeNetworkPortImpl);
std::array<uint8_t, netdev::wire::kMaxFrameTypes> rx_types_;
std::array<tx_support_t, netdev::wire::kMaxFrameTypes> tx_types_;
ddk::NetworkDeviceIfcProtocolClient device_client_;
fit::callback<void()> on_removed_;
uint8_t id_;
mac_addr_protocol_t mac_proto_{};
port_info_t port_info_{};
std::atomic_bool port_active_ = false;
port_status_t status_{};
zx::event event_;
bool port_removed_ = false;
bool port_added_ = false;
};
class FakeNetworkDeviceImpl : public ddk::NetworkDeviceImplProtocol<FakeNetworkDeviceImpl> {
public:
FakeNetworkDeviceImpl();
~FakeNetworkDeviceImpl();
zx::status<std::unique_ptr<NetworkDeviceInterface>> CreateChild(async_dispatcher_t* dispatcher);
zx_status_t NetworkDeviceImplInit(const network_device_ifc_protocol_t* iface);
void NetworkDeviceImplStart(network_device_impl_start_callback callback, void* cookie);
void NetworkDeviceImplStop(network_device_impl_stop_callback callback, void* cookie);
void NetworkDeviceImplGetInfo(device_info_t* out_info);
void NetworkDeviceImplQueueTx(const tx_buffer_t* buf_list, size_t buf_count);
void NetworkDeviceImplQueueRxSpace(const rx_space_buffer_t* buf_list, size_t buf_count);
void NetworkDeviceImplPrepareVmo(uint8_t vmo_id, zx::vmo vmo) {
zx::vmo& slot = vmos_[vmo_id];
EXPECT_FALSE(slot.is_valid()) << "vmo " << static_cast<uint32_t>(vmo_id) << " already prepared";
slot = std::move(vmo);
}
void NetworkDeviceImplReleaseVmo(uint8_t vmo_id) {
zx::vmo& slot = vmos_[vmo_id];
EXPECT_TRUE(slot.is_valid()) << "vmo " << static_cast<uint32_t>(vmo_id) << " already released";
slot.reset();
}
void NetworkDeviceImplSetSnoop(bool snoop) { /* do nothing , only auto-snooping is allowed */
}
fit::function<zx::unowned_vmo(uint8_t)> VmoGetter();
const zx::event& events() const { return event_; }
device_info_t& 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::DoublyLinkedList<std::unique_ptr<TxBuffer>> TakeTxBuffers() __TA_EXCLUDES(lock_) {
fbl::AutoLock lock(&lock_);
fbl::DoublyLinkedList<std::unique_ptr<TxBuffer>> r;
tx_buffers_.swap(r);
return r;
}
fbl::DoublyLinkedList<std::unique_ptr<RxBuffer>> TakeRxBuffers() __TA_EXCLUDES(lock_) {
fbl::AutoLock lock(&lock_);
fbl::DoublyLinkedList<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_slow();
}
size_t tx_buffer_count() __TA_EXCLUDES(lock_) {
fbl::AutoLock lock(&lock_);
return tx_buffers_.size_slow();
}
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(bool auto_start) { auto_start_ = auto_start; }
void set_auto_stop(bool auto_stop) { auto_stop_ = auto_stop; }
bool TriggerStart();
bool TriggerStop();
network_device_impl_protocol_t proto() {
return network_device_impl_protocol_t{.ops = &network_device_impl_protocol_ops_, .ctx = this};
}
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; }
ddk::NetworkDeviceIfcProtocolClient& client() { return device_client_; }
fbl::Span<const zx::vmo> vmos() { return fbl::Span(vmos_.begin(), vmos_.end()); }
private:
DISALLOW_COPY_ASSIGN_AND_MOVE(FakeNetworkDeviceImpl);
fbl::Mutex lock_;
std::array<zx::vmo, MAX_VMOS> vmos_;
device_info_t info_{};
ddk::NetworkDeviceIfcProtocolClient device_client_;
fbl::DoublyLinkedList<std::unique_ptr<RxBuffer>> rx_buffers_ __TA_GUARDED(lock_);
fbl::DoublyLinkedList<std::unique_ptr<TxBuffer>> tx_buffers_ __TA_GUARDED(lock_);
zx::event event_;
bool auto_start_ = true;
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_);
};
class TestSession {
public:
static constexpr uint16_t kDefaultDescriptorCount = 256;
static constexpr uint64_t kDefaultBufferLength = ZX_PAGE_SIZE / 2;
TestSession() = default;
zx_status_t Open(fidl::WireSyncClient<netdev::Device>& netdevice, const char* name,
netdev::wire::SessionFlags flags = netdev::wire::SessionFlags::kPrimary,
uint16_t num_descriptors = kDefaultDescriptorCount,
uint64_t buffer_size = kDefaultBufferLength);
zx_status_t Init(uint16_t descriptor_count, uint64_t buffer_size);
zx::status<netdev::wire::SessionInfo> GetInfo();
void Setup(fidl::ClientEnd<netdev::Session> session, netdev::wire::Fifos fifos);
[[nodiscard]] zx_status_t AttachPort(uint8_t port_id,
std::vector<netdev::wire::FrameType> frame_types);
[[nodiscard]] zx_status_t AttachPort(FakeNetworkPortImpl& impl);
[[nodiscard]] zx_status_t DetachPort(uint8_t port_id);
[[nodiscard]] zx_status_t DetachPort(FakeNetworkPortImpl& impl);
zx_status_t Close();
zx_status_t WaitClosed(zx::time deadline);
void ZeroVmo();
buffer_descriptor_t* ResetDescriptor(uint16_t index);
buffer_descriptor_t* descriptor(uint16_t index);
uint8_t* buffer(uint64_t offset);
zx_status_t FetchRx(uint16_t* descriptors, size_t count, size_t* actual) const;
zx_status_t FetchTx(uint16_t* descriptors, size_t count, size_t* actual) const;
zx_status_t SendRx(const uint16_t* descriptor, size_t count, size_t* actual) const;
zx_status_t SendTx(const uint16_t* descriptor, size_t count, size_t* actual) const;
zx_status_t SendTxData(uint16_t descriptor_index, const std::vector<uint8_t>& data);
zx_status_t FetchRx(uint16_t* descriptor) const {
size_t actual;
return FetchRx(descriptor, 1, &actual);
}
zx_status_t FetchTx(uint16_t* descriptor) const {
size_t actual;
return FetchTx(descriptor, 1, &actual);
}
zx_status_t SendRx(uint16_t descriptor) const {
size_t actual;
return SendRx(&descriptor, 1, &actual);
}
zx_status_t SendTx(uint16_t descriptor) const {
size_t actual;
return SendTx(&descriptor, 1, &actual);
}
fidl::WireSyncClient<netdev::Session>& session() { return session_; }
uint64_t canonical_offset(uint16_t index) const { return buffer_length_ * index; }
const zx::fifo& tx_fifo() const { return fifos_.tx; }
const zx::fifo& rx_fifo() const { return fifos_.rx; }
const zx::channel& channel() const { return session_.channel(); }
private:
fidl::FidlAllocator<> alloc_;
uint16_t descriptors_count_{};
uint64_t buffer_length_{};
fidl::WireSyncClient<netdev::Session> session_;
zx::vmo data_vmo_;
fzl::VmoMapper data_;
zx::vmo descriptors_vmo_;
fzl::VmoMapper descriptors_;
netdev::wire::Fifos fifos_;
};
class RxReturnTransaction {
public:
explicit RxReturnTransaction(FakeNetworkDeviceImpl* impl) : client_(impl->client()) {}
void Enqueue(std::unique_ptr<RxReturn> buffer) {
return_buffers_[count_++] = buffer->buffer();
buffers_.push_back(std::move(buffer));
}
void Enqueue(std::unique_ptr<RxBuffer> buffer) {
Enqueue(std::make_unique<RxReturn>(std::move(buffer)));
}
void Commit() {
client_.CompleteRx(return_buffers_, count_);
count_ = 0;
buffers_.clear();
}
private:
rx_buffer_t return_buffers_[kRxDepth]{};
size_t count_ = 0;
ddk::NetworkDeviceIfcProtocolClient client_;
fbl::DoublyLinkedList<std::unique_ptr<RxReturn>> buffers_;
DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(RxReturnTransaction);
};
class TxReturnTransaction {
public:
explicit TxReturnTransaction(FakeNetworkDeviceImpl* impl) : client_(impl->client()) {}
void Enqueue(std::unique_ptr<TxBuffer> buffer) { return_buffers_[count_++] = buffer->result(); }
void Commit() {
client_.CompleteTx(return_buffers_, count_);
count_ = 0;
}
private:
tx_result_t return_buffers_[kRxDepth]{};
size_t count_ = 0;
ddk::NetworkDeviceIfcProtocolClient client_;
DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(TxReturnTransaction);
};
} // namespace testing
} // namespace network
#endif // SRC_CONNECTIVITY_NETWORK_DRIVERS_NETWORK_DEVICE_DEVICE_TEST_UTIL_H_