| // Copyright 2023 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_banjo.h" |
| |
| #include <lib/sync/cpp/completion.h> |
| |
| #include "network_device_shim.h" |
| |
| namespace network::testing::banjo { |
| |
| zx::result<std::vector<uint8_t>> TxBuffer::GetData(const VmoProvider& vmo_provider) const { |
| 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 buffer_region_t& region = buffer_.data_list[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 = static_cast<uint8_t>(netdev::wire::DeviceClass::kEthernet), |
| .rx_types_list = rx_types_.data(), |
| .rx_types_count = 1, |
| .tx_types_list = tx_types_.data(), |
| .tx_types_count = 1, |
| }) { |
| rx_types_[0] = static_cast<uint8_t>(netdev::wire::FrameType::kEthernet); |
| tx_types_[0] = { |
| .type = static_cast<uint8_t>(netdev::wire::FrameType::kEthernet), |
| .features = netdev::wire::kFrameFeaturesRaw, |
| .supported_flags = 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::NetworkPortGetInfo(port_base_info_t* out_info) { *out_info = port_info_; } |
| |
| void FakeNetworkPortImpl::NetworkPortGetStatus(port_status_t* out_status) { *out_status = status_; } |
| |
| void FakeNetworkPortImpl::NetworkPortSetActive(bool active) { |
| port_active_ = active; |
| if (on_set_active_) { |
| on_set_active_(active); |
| } |
| ASSERT_OK(event_.signal(0, kEventPortActiveChanged)); |
| } |
| |
| void FakeNetworkPortImpl::NetworkPortGetMac(mac_addr_protocol_t** out_mac_ifc) { |
| if (out_mac_ifc) { |
| *out_mac_ifc = &mac_proto_; |
| } |
| } |
| |
| void FakeNetworkPortImpl::NetworkPortRemoved() { |
| EXPECT_FALSE(port_removed_) << "removed same port twice"; |
| port_removed_ = true; |
| if (on_removed_) { |
| on_removed_(); |
| } |
| } |
| |
| zx_status_t FakeNetworkPortImpl::AddPort(uint8_t port_id, |
| ddk::NetworkDeviceIfcProtocolClient ifc_client) { |
| if (port_added_) { |
| return ZX_ERR_ALREADY_EXISTS; |
| } |
| |
| using Context = std::tuple<libsync::Completion, zx_status_t>; |
| Context context; |
| |
| ifc_client.AddPort( |
| port_id, this, &network_port_protocol_ops_, |
| [](void* ctx, zx_status_t status) { |
| auto& [port_added, out_status] = *static_cast<Context*>(ctx); |
| out_status = status; |
| port_added.Signal(); |
| }, |
| &context); |
| auto& [port_added, status] = context; |
| port_added.Wait(); |
| if (status != ZX_OK) { |
| return status; |
| } |
| id_ = port_id; |
| port_added_ = true; |
| device_client_ = ifc_client; |
| return ZX_OK; |
| } |
| |
| void FakeNetworkPortImpl::RemoveSync() { |
| // Already removed. |
| if (!port_added_ || port_removed_) { |
| return; |
| } |
| sync_completion_t signal; |
| on_removed_ = [&signal]() { sync_completion_signal(&signal); }; |
| device_client_.RemovePort(id_); |
| sync_completion_wait(&signal, zx::time::infinite().get()); |
| } |
| |
| void FakeNetworkPortImpl::SetOnline(bool online) { |
| port_status_t status = status_; |
| status.flags = static_cast<uint32_t>(online ? netdev::wire::StatusFlags::kOnline |
| : netdev::wire::StatusFlags()); |
| SetStatus(status); |
| } |
| |
| void FakeNetworkPortImpl::SetStatus(const port_status_t& status) { |
| status_ = status; |
| if (device_client_.is_valid()) { |
| device_client_.PortStatusChanged(id_, &status); |
| } |
| } |
| |
| 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::NetworkDeviceImplInit(const network_device_ifc_protocol_t* iface, |
| network_device_impl_init_callback callback, |
| void* cookie) { |
| device_client_ = ddk::NetworkDeviceIfcProtocolClient(iface); |
| callback(cookie, ZX_OK); |
| } |
| |
| void FakeNetworkDeviceImpl::NetworkDeviceImplStart(network_device_impl_start_callback callback, |
| void* cookie) { |
| 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; |
| } |
| callback(cookie, auto_start); |
| } else { |
| ZX_ASSERT(!(pending_start_callback_ || pending_stop_callback_)); |
| pending_start_callback_ = [cookie, callback, this]() { |
| { |
| fbl::AutoLock lock(&lock_); |
| device_started_ = true; |
| } |
| callback(cookie, ZX_OK); |
| }; |
| } |
| // Approximate the behavior of the FIDL counterpart which signals both of these. Since start |
| // completes synchronously in the Banjo case it's safe to also signal that start completed. |
| EXPECT_OK(event_.signal(0, kEventStartInitiated)); |
| EXPECT_OK(event_.signal(0, kEventStartCompleted)); |
| } |
| |
| void FakeNetworkDeviceImpl::NetworkDeviceImplStop(network_device_impl_stop_callback callback, |
| void* cookie) { |
| fbl::AutoLock lock(&lock_); |
| EXPECT_TRUE(device_started_) << "called stop on already stopped device"; |
| device_started_ = false; |
| zx_signals_t clear; |
| if (auto_stop_) { |
| RxReturnTransaction 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(); |
| |
| TxReturnTransaction 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(); |
| callback(cookie); |
| // 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_ = [cookie, callback]() { callback(cookie); }; |
| clear = 0; |
| } |
| EXPECT_OK(event_.signal(clear, kEventStop)); |
| } |
| |
| void FakeNetworkDeviceImpl::NetworkDeviceImplGetInfo(device_impl_info_t* out_info) { |
| *out_info = info_; |
| } |
| |
| void FakeNetworkDeviceImpl::NetworkDeviceImplQueueTx(const tx_buffer_t* buf_list, |
| size_t buf_count) { |
| EXPECT_NE(buf_count, 0u); |
| ASSERT_TRUE(device_client_.is_valid()); |
| |
| fbl::AutoLock lock(&lock_); |
| queue_tx_called_.push_back(buf_count); |
| cpp20::span buffers(buf_list, buf_count); |
| if (immediate_return_tx_ || !device_started_) { |
| const zx_status_t return_status = device_started_ ? ZX_OK : ZX_ERR_UNAVAILABLE; |
| ASSERT_LE(buf_count, info_.tx_depth); |
| std::vector<tx_result_t> results(info_.tx_depth); |
| auto results_iter = results.begin(); |
| for (const tx_buffer_t& buff : buffers) { |
| *results_iter++ = { |
| .id = buff.id, |
| .status = return_status, |
| }; |
| } |
| device_client_.CompleteTx(results.data(), buf_count); |
| return; |
| } |
| |
| for (const tx_buffer_t& buff : buffers) { |
| auto back = std::make_unique<TxBuffer>(buff); |
| tx_buffers_.push_back(std::move(back)); |
| } |
| EXPECT_OK(event_.signal(0, kEventTx)); |
| } |
| |
| void FakeNetworkDeviceImpl::NetworkDeviceImplQueueRxSpace(const rx_space_buffer_t* buf_list, |
| size_t buf_count) { |
| ASSERT_TRUE(device_client_.is_valid()); |
| |
| fbl::AutoLock lock(&lock_); |
| queue_rx_space_called_.push_back(buf_count); |
| cpp20::span buffers(buf_list, buf_count); |
| if (immediate_return_rx_ || !device_started_) { |
| const uint32_t length = device_started_ ? kAutoReturnRxLength : 0; |
| ASSERT_TRUE(buf_count < info_.rx_depth); |
| std::vector<rx_buffer_t> results(info_.rx_depth); |
| std::vector<rx_buffer_part_t> parts(info_.rx_depth); |
| auto results_iter = results.begin(); |
| auto parts_iter = parts.begin(); |
| for (const rx_space_buffer_t& space : buffers) { |
| rx_buffer_part_t& part = *parts_iter++; |
| rx_buffer_t& rx_buffer = *results_iter++; |
| part = { |
| .id = space.id, |
| .length = length, |
| }; |
| rx_buffer = { |
| .meta = |
| { |
| .frame_type = |
| static_cast<uint8_t>(fuchsia_hardware_network::wire::FrameType::kEthernet), |
| }, |
| .data_list = &part, |
| .data_count = 1, |
| }; |
| } |
| device_client_.CompleteRx(results.data(), buf_count); |
| return; |
| } |
| |
| for (const rx_space_buffer_t& buff : buffers) { |
| auto back = std::make_unique<RxBuffer>(buff); |
| rx_buffers_.push_back(std::move(back)); |
| } |
| EXPECT_OK(event_.signal(0, kEventRxAvailable)); |
| } |
| |
| 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<std::unique_ptr<NetworkDeviceInterface>> FakeNetworkDeviceImpl::CreateChild( |
| DeviceInterfaceDispatchers dispatchers, ShimDispatchers shim_dispatchers) { |
| network_device_impl_protocol_t protocol = proto(); |
| std::unique_ptr shim = std::make_unique<NetworkDeviceShim>( |
| ddk::NetworkDeviceImplProtocolClient(&protocol), shim_dispatchers); |
| |
| zx::result device = internal::DeviceInterface::Create(dispatchers, std::move(shim)); |
| if (device.is_error()) { |
| return device.take_error(); |
| } |
| |
| auto& value = device.value(); |
| value->evt_session_started_ = [this](const char* session) { |
| event_.signal(0, kEventSessionStarted); |
| }; |
| value->evt_session_died_ = [this](const char* session) { event_.signal(0, kEventSessionDied); }; |
| return zx::ok(std::move(value)); |
| } |
| |
| } // namespace network::testing::banjo |