| // 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_TUN_NETWORK_TUN_BUFFER_H_ |
| #define SRC_CONNECTIVITY_NETWORK_TUN_NETWORK_TUN_BUFFER_H_ |
| |
| #include <fidl/fuchsia.net.tun/cpp/wire.h> |
| #include <fuchsia/hardware/network/driver/cpp/banjo.h> |
| #include <lib/stdcompat/span.h> |
| |
| #include <array> |
| |
| #include "src/lib/vmo_store/vmo_store.h" |
| |
| namespace network { |
| namespace tun { |
| |
| class TxBuffer; |
| class RxBuffer; |
| |
| // A data structure that stores keyed VMOs and allocates buffers. |
| // |
| // `VmoStore` stores up to `MAX_VMOS` VMOs keyed by an identifier bound to the range [0, |
| // `MAX_VMOS`). `VmoStore` can be used to allocate buffers backed by the VMOs it contains. |
| // |
| // This class is used to fulfill the VMO registration mechanism used by |
| // `fuchsia.hardware.network.driver`. |
| class VmoStore { |
| public: |
| VmoStore() |
| : store_(vmo_store::Options{ |
| .map = |
| vmo_store::MapOptions{ |
| .vm_option = ZX_VM_PERM_READ | ZX_VM_PERM_WRITE | ZX_VM_REQUIRE_NON_RESIZABLE, |
| }, |
| }) {} |
| |
| ~VmoStore() = default; |
| |
| // Reads `len` bytes at `offset` from the VMO identified by `id` into `data`, which must be a |
| // `uint8_t` iterator. |
| // Returns an error if the specified region is invalid or `id` is not registered. |
| template <class T> |
| zx_status_t Read(uint8_t id, size_t offset, size_t len, T data) { |
| zx::result vmo_data = GetMappedVmo(id); |
| if (vmo_data.is_error()) { |
| return vmo_data.status_value(); |
| } |
| if (offset + len > vmo_data->size()) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| std::copy_n(vmo_data->begin() + offset, len, data); |
| return ZX_OK; |
| } |
| |
| // Writes `len` bytes at `offset` into the VMO identified by `id` from `data`, which must be an |
| // `uint8_t` iterator. |
| // Returns an error if the specified region is invalid or `id` is not registered. |
| template <class T> |
| zx_status_t Write(uint8_t id, size_t offset, size_t len, T data) { |
| zx::result vmo_data = GetMappedVmo(id); |
| if (vmo_data.is_error()) { |
| return vmo_data.status_value(); |
| } |
| if (offset + len > vmo_data->size()) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| std::copy_n(data, len, vmo_data->begin() + offset); |
| return ZX_OK; |
| } |
| |
| // Registers and maps `vmo` identified by `id`. |
| // `id` comes from a `NetworkDeviceInterface` and is part of the NetworkDevice contract. |
| // Returns an error if the identifier is invalid or already in use, or the mapping fails. |
| zx_status_t RegisterVmo(uint8_t id, zx::vmo vmo); |
| // Unregister a previously registered VMO with `id`, unmapping it from memory and releasing the |
| // VMO handle. |
| // Returns an error if the identifier is invalid or does not map to a registered VMO. |
| zx_status_t UnregisterVmo(uint8_t id); |
| |
| // Copies `len` bytes from `src_store`'s VMO with `src_id` at `src_offset` to `dst_store`'s VMO |
| // with `dst_id` at `dst_offset`. |
| // |
| // Equivalent to: |
| // T data; |
| // src_store.Read(src_id, src_offset, len, back_inserter(data)); |
| // dst_store.Write(dst_id, dst_offset, len, data.begin()); |
| static zx_status_t Copy(VmoStore& src_store, uint8_t src_id, size_t src_offset, |
| VmoStore& dst_store, uint8_t dst_id, size_t dst_offset, size_t len); |
| |
| TxBuffer MakeTxBuffer(const tx_buffer_t& tx, bool get_meta); |
| RxBuffer MakeRxSpaceBuffer(const rx_space_buffer_t& space); |
| RxBuffer MakeEmptyRxBuffer(); |
| |
| private: |
| zx::result<cpp20::span<uint8_t>> GetMappedVmo(uint8_t id); |
| vmo_store::VmoStore<vmo_store::SlabStorage<uint8_t>> store_; |
| }; |
| |
| // A device buffer. |
| // Device buffers can be created from VMO stores. They're used to store references to buffers |
| // retrieved from a NetworkDeviceInterface, which point to data regions within a VMO. |
| // `Buffer` can represent either a tx (application-filled data) buffer or an rx (empty space for |
| // inbound data) buffer. |
| class Buffer { |
| public: |
| Buffer(Buffer&&) = default; |
| Buffer(const Buffer&) = delete; |
| |
| // Reads this buffer's data into `vec`. |
| // Used to serve `fuchsia.net.tun/Device.ReadFrame`. |
| // Returns an error if this buffer's definition does not map to valid data (see `VmoStore::Write` |
| // for specific error codes). |
| zx_status_t Read(std::vector<uint8_t>& vec); |
| // Writes `data` into this buffer. |
| // If this `data` does not fit in this buffer, `ZX_ERR_OUT_OF_RANGE` is returned. |
| // Returns an error if this buffer's definition does not map to valid data (see `VmoStore::Write` |
| // for specific error codes). |
| // Used to serve `fuchsia.net.tun/Device.WriteFrame`. |
| zx_status_t Write(const uint8_t* data, size_t count); |
| zx_status_t Write(const fidl::VectorView<uint8_t>& data); |
| zx_status_t Write(const std::vector<uint8_t>& data); |
| // Copies data from `other` into this buffer, returning the number of bytes written on success. |
| zx::result<size_t> CopyFrom(Buffer& other); |
| // Returns this buffer's length in bytes. |
| uint64_t length() const { return total_length_; } |
| |
| protected: |
| struct BufferPart { |
| uint32_t buffer_id; |
| buffer_region_t region; |
| }; |
| |
| void PushPart(const BufferPart& part); |
| explicit Buffer(VmoStore* vmo_store) : vmo_store_(vmo_store) {} |
| |
| cpp20::span<BufferPart> parts() { return cpp20::span(parts_.data(), parts_count_); } |
| cpp20::span<const BufferPart> parts() const { return cpp20::span(parts_.data(), parts_count_); } |
| |
| private: |
| // Pointer to parent VMO store, not owned. |
| VmoStore* const vmo_store_; |
| std::array<BufferPart, MAX_BUFFER_PARTS> parts_; |
| size_t parts_count_ = 0; |
| uint64_t total_length_ = 0; |
| }; |
| |
| class TxBuffer : public Buffer { |
| public: |
| inline fuchsia_hardware_network::wire::FrameType frame_type() const { return frame_type_; } |
| uint32_t id() const { return parts().begin()->buffer_id; } |
| uint8_t port_id() const { return port_id_; } |
| |
| inline std::optional<fuchsia_net_tun::wire::FrameMetadata> TakeMetadata() { |
| return std::exchange(meta_, std::nullopt); |
| } |
| |
| protected: |
| friend VmoStore; |
| TxBuffer(const tx_buffer_t& tx, bool get_meta, VmoStore* vmo_store); |
| |
| private: |
| const uint8_t port_id_; |
| const fuchsia_hardware_network::wire::FrameType frame_type_; |
| std::optional<fuchsia_net_tun::wire::FrameMetadata> meta_; |
| }; |
| |
| class RxBuffer : public Buffer { |
| public: |
| // Adds more rx buffer space to this buffer. |
| void PushRxSpace(const rx_space_buffer_t& space); |
| |
| // Calls the provided closure function once for each buffer space in this RxBuffer. |
| template <typename F> |
| void WithSpace(F fn) { |
| for (BufferPart& part : parts()) { |
| fn(rx_space_buffer_t{ |
| .id = part.buffer_id, |
| .region = part.region, |
| }); |
| } |
| } |
| |
| // Calls the provided closure function once for each return buffer part in this RxBuffer. The |
| // total length of the buffer parts is capped to `written_length` bytes. |
| template <typename F> |
| void WithReturn(size_t written_length, F fn) { |
| for (BufferPart& part : parts()) { |
| size_t length = std::min(written_length, part.region.length); |
| // Length is expected to be less than max uint32 because of max MTU, but guard it here with a |
| // debug assertion so the cast is still clearly safe. |
| ZX_DEBUG_ASSERT(length <= std::numeric_limits<uint32_t>::max()); |
| fn(rx_buffer_part_t{ |
| .id = part.buffer_id, |
| .length = static_cast<uint32_t>(length), |
| }); |
| written_length -= length; |
| } |
| } |
| |
| protected: |
| friend VmoStore; |
| explicit RxBuffer(VmoStore* vmo_store) : Buffer(vmo_store) {} |
| }; |
| |
| } // namespace tun |
| } // namespace network |
| |
| #endif // SRC_CONNECTIVITY_NETWORK_TUN_NETWORK_TUN_BUFFER_H_ |