pw_multibuf#
A buffer API optimized for zero-copy messaging
Unstable C++17
Sending or receiving messages via RPC, transfer, or sockets often requires a
series of intermediate buffers, each requiring their own copy of the data.
pw_multibuf
allows data to be written once, eliminating the memory, CPU
and latency overhead of copying.
How does it work?#
pw_multibuf
uses several techniques to minimize copying of data:
Header and Footer Reservation: Lower-level components can reserve space within a buffer for headers and/or footers. This allows headers and footers to be added to user-provided data without moving users’ data.
Native Scatter/Gather and Fragmentation Support: Buffers can refer to multiple separate chunks of memory. Messages can be built up from discontiguous allocations, and users’ data can be fragmented across multiple packets.
Divisible Memory Regions: Incoming buffers can be divided without a copy, allowing incoming data to be freely demultiplexed.
What kinds of data is this for?#
pw_multibuf
is best used in code that wants to read, write, or pass along
data which are one of the following:
Large:
pw_multibuf
is designed to allow breaking up data into multiple chunks. It also supports asynchronous allocation for when there may not be sufficient space for incoming data.Communications-Oriented: Data which is being received or sent across sockets, various packets, or shared-memory protocols can benefit from the fragmentation, multiplexing, and header/footer-reservation properties of
pw_multibuf
.Copy-Averse:
pw_multibuf
is structured to allow users to pass around and mutate buffers without copying or moving data in-memory. This can be especially useful when working in systems that are latency-sensitive, need to pass large amounts of data, or when memory usage is constrained.
Why are there two versions?#
We are currently investigating an alternate approach that keeps many of the
aspects described above, while separating out the concern of memory allocation
and instead using MultiBuf
to present different logical, span-like views of
sequences of buffers. This version is currently experimental, but we welcome
feedback on the direction it is taking!
MultiBuf v1#
API Reference#
Most users of pw_multibuf
will start by allocating a MultiBuf
using
a MultiBufAllocator
class, such as the SimpleAllocator
.
MultiBuf
s consist of a number of Chunk
s of contiguous memory regions.
Chunk
s can be grown or shrunk which allows MultiBuf
s to be grown or
shrunk. This allows, for example, lower layers to reserve part of a
MultiBuf
for a header or footer (see Chunk
for more details).
MultiBuf
exposes an std::byte
iterator interface as well as a Chunk
iterator available through the Chunks()
method. It allows extracting a
Chunk
as an RAII-style OwnedChunk
which manages its own lifetime.
-
class Chunk#
A handle to a contiguous slice of data.
A
Chunk
is similar to aByteSpan
, but is aware of its underlying memory region, and is able to split, shrink, and grow into neighboring empty space within its region.This class is optimized to allow multiple owners to write into neighboring regions of the same allocation. In order to support zero-copy DMA of communications buffers, allocators can create properly-aligned
Chunk
regions inside an allocation.One example usecase for this is communication drivers that want to reserve space at the front or rear of a buffer for headers or footers. A driver can
DiscardPrefix
in order to reserve bytes for headers,Truncate
in order to reserve bytes for footers, and then pass theChunk
to the user to fill in. These discarded bytes are still held by the underlying region, so the header and footer space can later be reclaimed using theClaimPrefix
andClaimSuffix
methods. The region itself is only released once there are no remaining Chunks within it.Public Functions
-
bool CanMerge(const Chunk &next_chunk) const#
Returns if
next_chunk
is mergeable into the end of thisChunk
.This will only succeed when the two
Chunk
s are adjacent in memory and originated from the same region.
-
bool Merge(OwnedChunk &next_chunk)#
Attempts to merge
next_chunk
into the end of thisChunk
.If the chunks are successfully merged, this
Chunk
will be extended forwards to encompass the space ofnext_chunk
, andnext_chunk
will be emptied andRelease
d.This will only succeed when the two
Chunk
s are adjacent in memory and originated from the same region.If the chunks are not mergeable, neither
Chunk
will be modified.
-
bool ClaimPrefix(size_t bytes_to_claim)#
Attempts to add
bytes_to_claim
to the front of this buffer by advancing its range backwards in memory. Returnstrue
if the operation succeeded.This will only succeed if this
Chunk
points to a section of a region that has unreferenced bytes preceding it. For example, aChunk
which has been shrunk usingDiscardPrefix
can be re-expanded usingClaimPrefix
.This method will acquire a mutex and is not IRQ safe.
-
bool ClaimSuffix(size_t bytes_to_claim)#
Attempts to add
bytes_to_claim
to the front of this buffer by advancing its range forwards in memory. Returnstrue
if the operation succeeded.This will only succeed if this
Chunk
points to a section of a region that has unreferenced bytes following it. For example, aChunk
which has been shrunk usingTruncate
can be re-expanded usingThis method will acquire a mutex and is not IRQ safe.
-
void DiscardPrefix(size_t bytes_to_discard)#
Shrinks this handle to refer to the data beginning at offset
bytes_to_discard
.Does not modify the underlying data. The discarded memory continues to be held by the underlying region as long as any
Chunk
s exist within it. This allows the memory to be later reclaimed usingClaimPrefix
.This method will acquire a mutex and is not IRQ safe.
-
void Slice(size_t begin, size_t end)#
Shrinks this handle to refer to data in the range
begin..<end
.Does not modify the underlying data. The discarded memory continues to be held by the underlying region as long as any
Chunk
s exist within it. This allows the memory to be later reclaimed usingClaimPrefix
orClaimSuffix
.This method will acquire a mutex and is not IRQ safe.
-
void Truncate(size_t len)#
Shrinks this handle to refer to only the first
len
bytes.Does not modify the underlying data. The discarded memory continues to be held by the underlying region as long as any
Chunk
s exist within it. This allows the memory to be later reclaimed usingClaimSuffix
.This method will acquire a mutex and is not IRQ safe.
-
std::optional<OwnedChunk> TakePrefix(size_t bytes_to_take)#
Attempts to shrink this handle to refer to the data beginning at offset
bytes_to_take
, returning the firstbytes_to_take
bytes as a newOwnedChunk
.If the inner call to
AllocateChunkClass
fails, this function will returnstd::nullopt
and this handle’s span will not change.This method will acquire a mutex and is not IRQ safe.
-
std::optional<OwnedChunk> TakeSuffix(size_t bytes_to_take)#
Attempts to shrink this handle to refer only the first
len - bytes_to_take
bytes, returning the lastbytes_to_take
bytes as a newOwnedChunk
.If the inner call to
AllocateChunkClass
fails, this function will returnstd::nullopt
and this handle’s span will not change.This method will acquire a mutex and is not IRQ safe.
-
bool CanMerge(const Chunk &next_chunk) const#
-
class OwnedChunk#
An RAII handle to a contiguous slice of data.
Note:
OwnedChunk
may acquire apw::sync::Mutex
during destruction, and so must not be destroyed within ISR contexts.Public Functions
-
inline ~OwnedChunk()#
This method will acquire a mutex and is not IRQ safe.
-
void Release()#
Decrements the reference count on the underlying chunk of data and empties this handle so that
span()
now returns an empty (zero-sized) span.Does not modify the underlying data, but may cause it to be deallocated if this was the only remaining
Chunk
referring to its region.This method is equivalent to
{ Chunk _unused = std::move(chunk_ref); }
This method will acquire a mutex and is not IRQ safe.
-
inline Chunk *Take() &&#
Returns the contained
Chunk*
and empties thisOwnedChunk
without releasing the underlyingChunk
.
-
inline ~OwnedChunk()#
-
class MultiBuf : private pw::multibuf::MultiBufChunks#
A byte buffer optimized for zero-copy data transfer.
A
MultiBuf
consists of multipleChunk
s of data.MultiBuf
inherits privately fromMultiBufChunks
. This allows one class to provide either a byte-oriented or aChunk
-oriented interface, and keeps those interfaces separate.Public Functions
-
inline void Release() noexcept#
Decrements the reference count on the underlying chunks of data and empties this
MultiBuf
so thatsize() == 0
.Does not modify the underlying data, but may cause it to be deallocated.
This method is equivalent to
{ MultiBuf _unused = std::move(multibuf); }
This method will acquire a mutex and is not IRQ safe.
-
~MultiBuf() = default#
This destructor will acquire a mutex and is not IRQ safe.
-
inline size_t size() const#
Returns the number of bytes in this container.
-
bool empty() const#
Returns whether the
MultiBuf
contains any bytes (size() == 0
).This method’s complexity is
O(Chunks().size())
, but will be more efficient thansize() == 0
in most cases.
-
inline bool IsContiguous() const#
Returns if the
MultiBuf
is contiguous. AMultiBuf
is contiguous if it is comprised of either:one non-empty chunk,
only empty chunks, or
no chunks at all.
-
inline std::optional<ByteSpan> ContiguousSpan()#
If the
MultiBuf
is contiguous, returns it as a span. The span will be empty if theMultiBuf
is empty.A
MultiBuf
is contiguous if it is comprised of either:one non-empty chunk,
only empty chunks, or
no chunks at all.
-
inline const_iterator begin() const#
Returns a const iterator pointing to the first byte of this
MultiBuf
.
-
inline const_iterator cbegin() const#
Returns a const iterator pointing to the first byte of this
MultiBuf
.
-
inline const_iterator end() const#
Returns a const iterator pointing to the end of this
MultiBuf
.
-
inline const_iterator cend() const#
Returns a const iterator pointing to the end of this
MultiBuf
.
-
bool ClaimPrefix(size_t bytes_to_claim)#
Attempts to add
bytes_to_claim
to the front of this buffer by advancing its range backwards in memory. Returnstrue
if the operation succeeded.This will only succeed if the first
Chunk
in this buffer points to a section of a region that has unreferenced bytes preceding it. See alsoChunk::ClaimPrefix
.This method will acquire a mutex and is not IRQ safe.
-
bool ClaimSuffix(size_t bytes_to_claim)#
Attempts to add
bytes_to_claim
to the front of this buffer by advancing its range forwards in memory. Returnstrue
if the operation succeeded.This will only succeed if the last
Chunk
in this buffer points to a section of a region that has unreferenced bytes following it. See alsoChunk::ClaimSuffix
.This method will acquire a mutex and is not IRQ safe.
-
void DiscardPrefix(size_t bytes_to_discard)#
Shrinks this handle to refer to the data beginning at offset
bytes_to_discard
.Does not modify the underlying data. The discarded memory continues to be held by the underlying region as long as any
Chunk
s exist within it. This allows the memory to be later reclaimed usingClaimPrefix
.This method will acquire a mutex and is not IRQ safe.
-
void Slice(size_t begin, size_t end)#
Shrinks this handle to refer to data in the range
begin..<end
.Does not modify the underlying data. The discarded memory continues to be held by the underlying region as long as any
Chunk
s exist within it. This allows the memory to be later reclaimed usingClaimPrefix
orClaimSuffix
.This method will acquire a mutex and is not IRQ safe.
-
void Truncate(size_t len)#
Shrinks this handle to refer to only the first
len
bytes.Does not modify the underlying data. The discarded memory continues to be held by the underlying region as long as any
Chunk
s exist within it. This allows the memory to be later reclaimed usingClaimSuffix
.This method will acquire a mutex and is not IRQ safe.
-
void TruncateAfter(iterator pos)#
Truncates the
MultiBuf
after the current iterator. All bytes following the iterator are removed.Does not modify the underlying data.
This method will acquire a mutex and is not IRQ safe.
-
std::optional<MultiBuf> TakePrefix(size_t bytes_to_take)#
Attempts to shrink this handle to refer to the data beginning at offset
bytes_to_take
, returning the firstbytes_to_take
bytes as a newMultiBuf
.If the inner call to
AllocateChunkClass
fails, this function will returnstd::nullopt
and this handle’s span will not change.This method will acquire a mutex and is not IRQ safe.
-
std::optional<MultiBuf> TakeSuffix(size_t bytes_to_take)#
Attempts to shrink this handle to refer only the first
len - bytes_to_take
bytes, returning the lastbytes_to_take
bytes as a newMultiBuf
.If the inner call to
AllocateChunkClass
fails, this function will returnstd::nullopt
and this handle’s span will not change.This method will acquire a mutex and is not IRQ safe.
-
void PushPrefix(MultiBuf &&front)#
Pushes
front
onto the front of thisMultiBuf
.This operation does not move any data and is
O(front.Chunks().size())
.
-
inline void PushSuffix(MultiBuf &&tail)#
Pushes
tail
onto the end of thisMultiBuf
.This operation does not move any data and is
O(Chunks().size())
.
-
StatusWithSize CopyTo(ByteSpan dest, size_t position = 0) const#
Copies bytes from the multibuf into the provided buffer.
- Parameters:
- Returns:
Code
Description
All bytes were copied into the destination. The
pw::StatusWithSize
includes the number of bytes copied, which is the size of theMultiBuf
.Some bytes were copied, but the
MultiBuf
was larger than the destination buffer. Thepw::StatusWithSize
includes the number of bytes copied.
-
inline StatusWithSize CopyFrom(ConstByteSpan source, size_t position = 0)#
Copies bytes from the provided buffer into the multibuf.
- Parameters:
- Returns:
Code
Description
All bytes were copied. The
pw::StatusWithSize
includes the number of bytes copied, which is the size of the MultiBuf.Some bytes were copied, but the source was larger than the destination. The
pw::StatusWithSize
includes the number of bytes copied.
-
inline StatusWithSize CopyFromAndTruncate(ConstByteSpan source, size_t position = 0)#
Copies bytes from the provided buffer into this
MultiBuf
and truncates it to the end of the copied data. This is a more efficient version of:if (multibuf.CopyFrom(destination).ok()) { multibuf.Truncate(destination.size()); }
- Parameters:
- Returns:
Code
Description
All bytes were copied and the
MultiBuf
was truncated. Thepw::StatusWithSize
includes the newMultiBuf::size()
.Some bytes were copied, but the source buffer was larger than the
MultiBuf
. The returnedpw::StatusWithSize
includes the number of bytes copied, which is the size of theMultiBuf
.
-
inline void PushFrontChunk(OwnedChunk &&chunk)#
Pushes
Chunk
onto the front of theMultiBuf
.This operation does not move any data and is
O(1)
.
-
inline void PushBackChunk(OwnedChunk &&chunk)#
Pushes
Chunk
onto the end of theMultiBuf
.This operation does not move any data and is
O(Chunks().size())
.
-
inline OwnedChunk TakeFrontChunk()#
Removes the first
Chunk
.This operation does not move any data and is
O(1)
.
-
inline MultiBufChunks::iterator InsertChunk(MultiBufChunks::iterator position, OwnedChunk &&chunk)#
Inserts
chunk
into the specified position in theMultiBuf
. TheChunk
atposition
will be after the new chunk.This operation does not move any data and is
O(Chunks().size())
.Returns an iterator pointing to the newly-inserted
Chunk
.
-
inline std::tuple<MultiBufChunks::iterator, OwnedChunk> TakeChunk(MultiBufChunks::iterator position)#
Removes and returns
Chunk
from the specified position.This operation does not move any data and is
O(Chunks().size())
.Returns an iterator pointing to the
Chunk
after the removedChunk
, orChunks().end()
if this was the lastChunk
in theMultiBuf
.
-
inline constexpr MultiBufChunks &Chunks()#
-
inline constexpr const MultiBufChunks &Chunks() const#
-
inline constexpr const MultiBufChunks &ConstChunks() const#
-
inline void Release() noexcept#
- std::optional<MultiBuf> pw::multibuf::FromSpan( )#
Creates a multibuf from an existing span and a
deleter
callback.The provided allocator is used to allocate storage for the chunk-tracking metadata. The allocator’s lifetime must outlive the returned
MultiBuf
.Returns
nullopt
if themetadata_allocator
fails to allocate a metadata region to track the provided buffer. In this case,deleter
will not be invoked and the caller will retain ownership of the provided region.
-
class MultiBufChunks#
A
Chunk
-oriented view of aMultiBuf
.Subclassed by pw::multibuf::MultiBuf
Public Functions
-
inline constexpr Chunk &front()#
Returns a reference to the first chunk.
The behavior of this method is undefined when
size() == 0
.
-
inline Chunk &back()#
Returns a reference to the final chunk.
The behavior of this method is undefined when
size() == 0
.NOTE: this method is
O(size())
.
-
void push_front(OwnedChunk &&chunk)#
Pushes
Chunk
onto the front of theMultiBuf
.This operation does not move any data and is
O(1)
.
-
void push_back(OwnedChunk &&chunk)#
Pushes
Chunk
onto the end of theMultiBuf
.This operation does not move any data and is
O(Chunks().size())
.
-
OwnedChunk take_front()#
Removes the first
Chunk
.This operation does not move any data and is
O(1)
.
-
iterator insert(iterator position, OwnedChunk &&chunk)#
Inserts
chunk
into the specified position in theMultiBuf
. TheChunk
atposition
will be after the new chunk.This operation does not move any data and is
O(Chunks().size())
.Returns an iterator pointing to the newly-inserted
Chunk
.
-
std::tuple<iterator, OwnedChunk> take(iterator position)#
Removes and returns
Chunk
from the specified position.This operation does not move any data and is
O(Chunks().size())
.Returns an iterator pointing to the
Chunk
after the removedChunk
, orChunks().end()
if this was the lastChunk
in theMultiBuf
.
-
inline constexpr Chunk &front()#
-
class MultiBufAllocator#
Interface for allocating
MultiBuf
objects.A
MultiBufAllocator
differs from a regularpw::allocator::Allocator
in that they may provide support for:Asynchronous allocation.
Non-contiguous buffer allocation.
Internal header/footer reservation.
Size-range allocation.
In order to accomplish this, they return
MultiBuf
objects rather than arbitrary pieces of memory.Additionally,
MultiBufAllocator
implementations may choose to store their allocation metadata separately from the data itself. This allows for things like allocation headers to be kept out of restricted DMA-capable or shared-memory regions.NOTE:
MultiBufAllocator
s must outlive any futures created from them.Subclassed by pw::multibuf::SimpleAllocator
Public Functions
-
MultiBufAllocator(MultiBufAllocator&) = delete#
`
MultiBufAllocator
is not copyable or movable.
-
std::optional<MultiBuf> Allocate(size_t size)#
Attempts to allocate a
MultiBuf
of exactlysize
bytes.Memory allocated by an arbitrary
MultiBufAllocator
does not provide any alignment requirements, preferring instead to allow the allocator maximum flexibility for placing regions (especially discontiguous regions).- Return values:
``MultiBuf`` – if the allocation was successful.
``nullopt_t`` – if the memory is not currently available.
-
std::optional<MultiBuf> Allocate(size_t min_size, size_t desired_size)#
Attempts to allocate a
MultiBuf
of at leastmin_size
bytes and at mostdesired_size
bytes.Memory allocated by an arbitrary
MultiBufAllocator
does not provide any alignment requirements, preferring instead to allow the allocator maximum flexibility for placing regions (especially discontiguous regions).- Return values:
``MultiBuf`` – if the allocation was successful.
``nullopt_t`` – if the memory is not currently available.
-
std::optional<MultiBuf> AllocateContiguous(size_t size)#
Attempts to allocate a contiguous
MultiBuf
of exactlysize
bytes.Memory allocated by an arbitrary
MultiBufAllocator
does not provide any alignment requirements, preferring instead to allow the allocator maximum flexibility for placing regions (especially discontiguous regions).- Return values:
``MultiBuf`` – with a single
Chunk
if the allocation was successful.``nullopt_t`` – if the memory is not currently available.
-
std::optional<MultiBuf> AllocateContiguous(size_t min_size, size_t desired_size)#
Attempts to allocate a contiguous
MultiBuf
of at leastmin_size
bytes and at mostdesired_size
bytes.Memory allocated by an arbitrary
MultiBufAllocator
does not provide any alignment requirements, preferring instead to allow the allocator maximum flexibility for placing regions (especially discontiguous regions).- Return values:
``MultiBuf`` – with a single
Chunk
if the allocation was successful.``nullopt_t`` – if the memory is not currently available.
-
inline std::optional<size_t> GetBackingCapacity()#
Returns the total amount of memory provided by this object.
This is an optional method. Some memory providers may not have an easily defined capacity, e.g. the system allocator.
- Return values:
the – total memory if known.
``nullopt_t`` – if the total memory is not knowable.
-
class MultiBufAllocatorAsync#
Public Functions
-
MultiBufAllocatorAsync(MultiBufAllocatorAsync&) = delete#
`
MultiBufAllocatorAsync
is not copyable or movable.
-
MultiBufAllocationFuture AllocateAsync(size_t size)#
Asynchronously allocates a
MultiBuf
of exactlysize
bytes.Memory allocated by an arbitrary
MultiBufAllocator
does not provide any alignment requirements, preferring instead to allow the allocator maximum flexibility for placing regions (especially discontiguous regions).- Return values:
A –
MultiBufAllocationFuture
which will yield aMultiBuf
when one is available.
-
MultiBufAllocationFuture AllocateAsync(size_t min_size, size_t desired_size)#
Asynchronously allocates a
MultiBuf
of at leastmin_size
bytes and at most `desired_size
bytes.Memory allocated by an arbitrary
MultiBufAllocator
does not provide any alignment requirements, preferring instead to allow the allocator maximum flexibility for placing regions (especially discontiguous regions).- Return values:
A –
MultiBufAllocationFuture
which will yield aMultiBuf
when one is available.
-
MultiBufAllocationFuture AllocateContiguousAsync(size_t size)#
Asynchronously allocates a contiguous
MultiBuf
of exactlysize
bytes.Memory allocated by an arbitrary
MultiBufAllocator
does not provide any alignment requirements, preferring instead to allow the allocator maximum flexibility for placing regions (especially discontiguous regions).- Return values:
A –
MultiBufAllocationFuture
which will yield anMultiBuf
consisting of a singleChunk
when one is available.
-
MultiBufAllocationFuture AllocateContiguousAsync(size_t min_size, size_t desired_size)#
Asynchronously allocates an
OwnedChunk
of at leastmin_size
bytes and at mostdesired_size
bytes.- Return values:
A –
MultiBufAllocationFuture
which will yield anMultiBuf
consisting of a singleChunk
when one is available.
-
MultiBufAllocatorAsync(MultiBufAllocatorAsync&) = delete#
-
class MultiBufAllocationFuture : public pw::multibuf::MultiBufAllocator::MemoryAvailableDelegate#
An object that asynchronously yields a
MultiBuf
whenPend
ed.See
pw::async2
for details onPend
and how it is used to build asynchronous tasks.Public Functions
-
inline MultiBufAllocator &allocator()#
Returns the
allocator
associated with this future.
-
inline MultiBufAllocator &allocator()#
-
class SimpleAllocator : public pw::multibuf::MultiBufAllocator#
A simple first-fit
MultiBufAllocator
.Subclassed by pw::multibuf::test::SimpleAllocatorForTest< kDataSizeBytes, kMetaSizeBytes >
Public Functions
-
SimpleAllocator(ByteSpan data_area, pw::allocator::Allocator &metadata_alloc, size_t alignment = 1)#
Creates a new
SimpleAllocator
.- Parameters:
data_area – [in] The region to use for storing chunk memory.
metadata_alloc – [in] The allocator to use for metadata tracking the in-use regions. This allocator must be thread-safe if the resulting buffers may travel to another thread.
SynchronizedAllocator
can be used to create a thread-safe allocator from a non-thread-safe allocator.alignment – [in] The alignment to use. All chunks allocated by this allocator will start aligned with the specified alignment. The alignment can change if the prefix Chunk methods are used. The supplied
data_area
must be aligned (both start and end) to the specified alignment. Defaults to 1.
-
SimpleAllocator(ByteSpan data_area, pw::allocator::Allocator &metadata_alloc, size_t alignment = 1)#
-
class Stream : public pw::stream::SeekableReaderWriter#
A readable, writable, and seekable
Stream
implementation backed by aMultiBuf
.
Test-only features#
-
template<size_t kDataSizeBytes = 1024, size_t kMetaSizeBytes = kDataSizeBytes>
class SimpleAllocatorForTest : public pw::multibuf::SimpleAllocator# Simple, self-contained
pw::multibuf::MultiBufAllocator
for test use.Public Functions
Public Static Functions
-
static inline constexpr size_t data_size_bytes()#
Size of the data area.
-
static inline constexpr size_t data_size_bytes()#
Allocator Implementors’ API#
Some users will need to directly implement the MultiBufAllocator
interface
in order to provide allocation out of a particular region, provide particular
allocation policy, fix Chunks to some size (such as MTU size - header for
socket implementations), or specify other custom behavior.
These users will also need to understand and implement the following APIs:
-
class ChunkRegionTracker#
An object that manages a single allocated region which is referenced by one or more
Chunk
objects.This class is typically implemented by
MultiBufAllocator
implementations in order to customize the behavior of region deallocation.ChunkRegionTracker
s have three responsibilities:Tracking the region of memory into which
Chunk
s can expand. This is reported via theRegion
method.Chunk
s in this region can refer to memory within this region sparsely, but they can grow or shrink so long as they stay within the bounds of theRegion
.Deallocating the region and the
ChunkRegionTracker
itself. This is implemented via theDestroy
method, which is called once all of theChunk
s in a region have been released.Allocating and deallocating space for
Chunk
classes. This is merely allocating space for theChunk
object itself, not the memory to which it refers. This can be implemented straightforwardly by delegating to an existing generic allocator such asmalloc
or apw::allocator::Allocator
implementation.
Subclassed by pw::multibuf::HeaderChunkRegionTracker, pw::multibuf::SingleChunkRegionTracker, pw::multibuf::internal::LinkedRegionTracker
Public Functions
-
std::optional<OwnedChunk> CreateFirstChunk()#
Creates the first
Chunk
referencing a whole region of memory.This must only be called once per
ChunkRegionTracker
, when the region is first created. Multiple calls will result in undefined behavior.Returns
std::nullopt
ifAllocateChunkStorage
returnsnullptr
.
A simple implementation of a ChunkRegionTracker
is provided, called
HeaderChunkRegionTracker
. It stores its Chunk
and region metadata in a
Allocator
allocation alongside the data. The allocation process is
synchronous, making this class suitable for testing. The allocated region or
Chunk
must not outlive the provided allocator.
-
class HeaderChunkRegionTracker : public pw::multibuf::ChunkRegionTracker#
A
ChunkRegionTracker
which stores itsChunk
and region metadata in aallocator::Allocator
allocation alongside the data.This is useful when testing and when there is no need for asynchronous allocation.
Public Functions
-
inline virtual ByteSpan Region() const final#
Returns the entire span of the region being managed.
Chunk
s referencing this tracker will not expand beyond this region, nor into one another’s portions of the region.This region does not provide any alignment guarantees by default.
This region must not change for the lifetime of this
ChunkRegionTracker
.
Public Static Functions
-
static inline std::optional<OwnedChunk> AllocateRegionAsChunk(allocator::Allocator &alloc, size_t size)#
Allocates a new
Chunk
region ofsize
bytes inalloc
.The underlyiing allocation will also store the
HeaderChunkRegionTracker
itself. The allocated memory must not outlive the provided allocatoralloc
.Returns the newly-created
OwnedChunk
if successful.
-
static inline HeaderChunkRegionTracker *AllocateRegion(allocator::Allocator &alloc, size_t size)#
Allocates a new region of
size
bytes inalloc
.The underlyiing allocation will also store the
HeaderChunkRegionTracker
itself. The allocated memory must not outlive the provided allocatoralloc
.Returns a pointer to the newly-created
HeaderChunkRegionTracker
ornullptr
if the allocation failed.
-
inline virtual ByteSpan Region() const final#
Another ChunkRegionTracker
specialization is the lightweight
SingleChunkRegionTracker
, which does not rely on Allocator
and uses the
provided memory view to create a single chunk. This is useful when a single
Chunk
is sufficient at no extra overhead. However, the user needs to own
the provided memory and know when a new Chunk
can be requested.
-
class SingleChunkRegionTracker : public pw::multibuf::ChunkRegionTracker#
A
ChunkRegionTracker
that uses inline memory to create a singleChunk
with the only caveat that the providedChunk
cannot be split. All attempts will result instd::nullopt
.Public Functions
-
inline explicit SingleChunkRegionTracker(ByteSpan region)#
Constructs a region tracker with a single
Chunk
that maps toregion
, which must outlive this tracker and anyOwnedChunk
it creates.
-
inline std::optional<OwnedChunk> GetChunk(size_t size)#
Gets a
Chunk
of a given size, which must be less than or equal to the provided region.Returns: An
OwnedChunk
if theChunk
is free, otherwisestd::nullopt
, in which caseGetChunk()
can be called again.
-
inline virtual void Destroy() final#
Destroys the
ChunkRegionTracker
.Typical implementations will call
std::destroy_at(this)
and then free the memory associated with the region and the tracker.
-
inline virtual ByteSpan Region() const final#
Returns the entire span of the region being managed.
Chunk
s referencing this tracker will not expand beyond this region, nor into one another’s portions of the region.This region does not provide any alignment guarantees by default.
This region must not change for the lifetime of this
ChunkRegionTracker
.
-
inline virtual void *AllocateChunkClass() final#
Returns a pointer to
sizeof(Chunk)
bytes withalignas(Chunk)
. Returnsnullptr
on failure.
-
inline virtual void DeallocateChunkClass(void *chunk) final#
Deallocates a pointer returned by
AllocateChunkClass
.
-
inline explicit SingleChunkRegionTracker(ByteSpan region)#
MultiBuf v2#
Everything in this section is experimental and subject to change without warning!
API Reference#
-
template<Property... kProperties>
class BasicMultiBuf# Logical byte sequence representing a sequence of memory buffers.
MultiBufs have been designed with network data processing in mind. They facilitate assembling and disassembling multiple buffers to and from a single sequence. This class refers to these individual spans of bytes as
Chunks
.Small amounts of this data, such as protocol headers, can be accessed using methods such as
Get
,Visit
, and the byte iterators. These methods avoid copying unless strictly necessary, such as when operating over chunks that are not contiguous in memory.Larger amounts of data, such as application data, can be accessed using methods such as
CopyFrom
,CopyTo
, and the chunk iterators.MultiBufs are defined with zero or more properties which add or constrain additional behavior:
Property::kConst: A const MultiBuf contains read-only data. The structure of the MultiBuf may still be changed, but the data within the chunks may not. Certain methods that modify or allow mutable access to the data, such as the non-const
operator[]
andCopyFrom
, are not available when this property is set.Property::kLayerable: A layerable MultiBuf can add or remove “layers”. Each layer acts as a subspan of the layer or MultiBuf below it. These layers can be used to represent encapsulated protocols, such as TCP segments within IP packets spread across multiple Ethernet frames.
Property::kObservable: And observable MultiBuf can have an observer set on it that it will notify whenever bytes or layers are added or removed.
Type aliases are provided for standard combinations of these properties.
A MultiBuf may be converted to another with different properties using the
as
method, provided those properties are compatible. This allows writing method and function signatures that only specify the necessary behavior. For example:extern void AdjustLayers(LayerableMultiBuf& mb); MyMultiBufInstance mb = InitMyMultiBufInstance(); AdjustLayers(mb.as<LayerableMultiBuf>());
In order to provide for such conversions, this class only represents the interface of a particular MultiBuf type, and not its instantiation. To create a concrete instantiation of
BasicMultiBuf<kProperties>
, useInstance<BasicMultiBuf<kProperties>
, as described below.MultiBufs are designed to be built either “bottom-up” or “top-down”:
A “bottom-up” approach may concatenate together several layerable MultiBufs representing lower-level protocol messages, and then add layers to present the amalgamated payloads as a higher-level protocol or application message.
A “top-down” approach may take a higher-level protocol or application message and insert headers and footers into it in order to represent a sequence of lower-level protocol messages. Alternatively, another approach would be to successively remove lower-level protocol payloads from the higher-level message and add headers and footers to those.
The “bottom-up” approach also provides the concept of message “fragments”. Whenever a layer is added, a MultiBuf considers the new top layer to be a “fragment”. These boundaries are preserved when appending additional data, and can be used to break the MultiBuf back up into lower-level messages when writing data to the network.
For example, consider a TCP/IP example. You could use a “bottom-up”, layerable MultiBuf to access and manipulate TCP data. In the following:
”TCP n” represents the n-th TCP segment of a stream.
”IP m.n” represents the n-th fragment of the m-th IP packet.
”Ethernet n” represents the n-th Ethernet frame.
Layer 3: +----+ TCP 0 +----+ TCP 0 +----+ TCP 0 +----+ TCP 1 +----+ TCP 1 + Layer 2: +--+ IP 0.0 +--+ IP 0.1 +--+ IP 0.2 +--+ IP 1.0 +--+ IP 1.1 + Layer 1: + Ethernet 0 + Ethernet 1 + Ethernet 2 + Ethernet 3 + Ethernet 4 + + 0x5CAFE000 + 0x5CAFE400 + 0x5CAFE800 + 0x5CAFEC00 + 0x5CAFF000 +
Alterantively, you could use a “top-down”, unlayered MultiBuf to represent same. The following might be the result of adding statically owned headers and footers to a portion of the application data:
No layer: + Eth header + IP header + TCP header + App data + Eth footer + + 0x5AAA0000 + 0x5AAA0020 + 0x5AAA0050 + 0x5CAFEC00 + 0x5AAA0040 +
- Template Parameters:
kProperties – Zero or more
Property
values. These must not be duplicated, and must appear in the order specified by that type.
Unnamed Group
-
template<bool kMutable = !is_const()>
inline std::enable_if_t<kMutable, reference> at(size_t index)# Returns a reference to the byte at specified index.
Use
CopyTo
orGet
to read ranges of bytes.Warning
Do not use addresses of returned references for ranges! The underlying memory is not guaranteed to be contiguous, so statements such as
std::memcpy(data.bytes(), &multibuf[0], data.size())
may corrupt memory.
Unnamed Group
Unnamed Group
-
template<bool kMutable = !is_const()>
inline constexpr std::enable_if_t<kMutable, iterator> begin()# Returns an iterator to the start of the MultiBuf’s bytes.
Use
CopyTo
orGet
, orVisit
to read ranges of bytes.Warning
Iterator-based algorithms such as
std::copy
may perform worse than expected due to overhead of advancing iterators across multiple, potentially noncontiguous memory regions.
Unnamed Group
-
template<bool kMutable = !is_const()>
inline constexpr std::enable_if_t<kMutable, iterator> end()# Returns an iterator past the end of the MultiBuf’s bytes.
Use
CopyTo
orGet
to read ranges of bytes.Warning
Iterator-based algorithms such as
std::copy
may perform worse than expected due to overhead of advancing iterators across multiple, potentially noncontiguous memory regions.
Unnamed Group
-
inline bool IsCompatible(const GenericMultiBuf &other) const#
Returns whether chunks associated the given argument could be added to this object.
To be compatible, the memory for chunks must be one of the following:
Externally managed, i.e. “unowned”.
Deallocatable by the same deallocator as other chunks, if any.
Part of the same shared memory allocation as any other shared chunks.
Unnamed Group
-
template<Property... kOtherProperties>
bool TryReserveForInsert( - const_iterator pos,
- const BasicMultiBuf<kOtherProperties...> &mb,
Attempts to modify this object to be able to accept the given argument, and returns whether successful.
It is an error to call this method with an invalid iterator or incompatible MultiBuf, if applicable.
If unable to allocate space for the metadata, returns false and leaves the object unchanged. Otherwise, returns true.
Unnamed Group
-
template<Property... kOtherProperties>
void Insert(const_iterator pos, BasicMultiBuf<kOtherProperties...> &&mb)# Insert memory before the given iterator.
It is a fatal error if this method cannot allocate space for necessary metadata. See also
TryReserveForInsert
, which can be used to try to pre-allocate the needed space without crashing.
Unnamed Group
-
template<Property... kOtherProperties>
bool TryReserveForPushBack(const BasicMultiBuf<kOtherProperties...> &mb)# Attempts to modify this object to be able to move bytes to the end of this object.
If unable to allocate space for the metadata, returns false and leaves the object unchanged. Otherwise, returns true.
Unnamed Group
-
template<Property... kOtherProperties>
void PushBack(BasicMultiBuf<kOtherProperties...> &&mb)# Moves bytes to the end of this object.
It is a fatal error if this method cannot allocate space for necessary metadata. See also
TryReserveForPushBack
, which can be used to try to pre-allocate the needed space without crashing.
Public Functions
-
inline constexpr bool empty() const#
Returns whether the MultiBuf is empty, i.e. whether it has no chunks or fragments.
-
inline constexpr size_t size() const#
Returns the size of a MultiBuf in bytes, which is the sum of the lengths of the views that make up its topmost layer.
-
inline Status TryReserveChunks(size_type num_chunks)#
Attempts to reserves memory to hold metadata for the given number of total chunks.
- Returns:
Code
Description
The object has space for the chunks.
Out of memory; cannot add the chunks.
-
Result<Instance<BasicMultiBuf>> Remove(const_iterator pos, size_t size)#
Removes if a range of bytes from this object.
On successful completion, this method will return a MultiBuf populated with entries corresponding to the removed memory range.
Note that “owned” chunks, i.e. those added using a
UniquePtr
, cannot be split between different MultiBufs. If the given range begins or ends in middle of owned chunk, the call will fail.On failure, the original MultiBuf is unmodified.
The range given by
pos
andsize
MUST fall within this MultiBuf.- Returns:
Code
Description
The returned MultiBuf contains the removed chunks.
The given range requires splitting either or both of the leading and trailing chunks, at least one of which is owned.
Failed to allocate memory for the new MultiBuf’s metadata.
-
Result<Instance<BasicMultiBuf>> PopFrontFragment()#
Removes the first fragment from this object and returns it.
Without any layers, each chunk is a fragment. Layered MultiBufs may group multiple chunks into a single fragment.
It is an error to call this method when the MultiBuf is empty.
- Returns:
Code
Description
Returns the fragment in a new MultiBuf.
Attempting to reserve space for the new MultiBuf failed.
-
inline Result<iterator> Discard(const_iterator pos, size_t size)#
Removes if a range of bytes from this object.
On successful completion, this method will return a valid iterator pointing to the memory following that which was discarded.
“Owned” chunks, i.e. those added using a
UniquePtr
, which are fully discarded as a result of this call will be deallocated.On failure, the original MultiBuf is unmodified.
The range given by
pos
andsize
MUST fall within this MultiBuf.- Returns:
Code
Description
The memory range has been discarded.
Failed to allocate memory for the new MultiBuf’s metadata.
-
inline bool IsReleasable(const_iterator pos) const#
Returns whether the given iterator refers to a location within an “owned” chunk, that is, memory that was added as a
UniquePtr
.
-
UniquePtr<value_type[]> Release(const_iterator pos)#
Removes a memory allocation from this object and releases ownership of it.
On successful completion, this method will return a
UniquePtr
which now owns the memory following that was removed.It is an error for memory location given by the iterator to not correspond to an “owned” chunk. That is, the memory in question must have been added using a
UniquePtr
. See alsoIsReleasable
.The entire owned chunk containing the location indicated by the iterator will be removed and returned, thus an iterator to the middle of an owned chunk will result in some bytes before the iterator being removed.
-
inline size_t CopyTo(ByteSpan dst, size_t offset = 0) const#
Writes data from the MultiBuf at the given
offset
todst
.The length of the data is determined by the length of the destination span.
-
inline size_t CopyFrom(ConstByteSpan src, size_t offset = 0)#
Writes data from
src
to the MultiBuf at the givenoffset
.The length of the data is determined by the length of the source span.
-
inline ConstByteSpan Get(ByteSpan copy, size_t offset = 0) const#
Returns a byte span containing data at the given
offset
.The length of the data is determined by the length of the given span. If the data is contiguous, a view to it is returned directly. Otherwise, it will be copied from the non-contiguous buffers into the provided span, which will then be returned.
As a result, this method should only be used on small regions of data, e.g. packet headers.
-
template<int&... kExplicitGuard, typename Visitor>
inline auto Visit(Visitor visitor, ByteSpan copy, size_t offset)# Passes a byte span containing data at the given
offset
to avisitor
.The length of the data is determined by the length of the given span. This method copies data only as necessary as described for
Get
. The provided visitor must take aConstByteSpan
as an argument, and can return anything.
-
inline void Clear()#
Releases all memory from this object.
If this object has a deallocator, it will be used to free the memory owned by this object. When this method returns, the MultiBuf will be restored to an initial, empty state.
-
inline constexpr Observer *observer() const#
Returns the observer for this MultiBuf, or null if none has been set.
Observers are notified whenever fragments or layers are added or removed from the MultiBuf.
-
inline void set_observer(Observer *observer)#
Sets the observer for this MultiBuf.
Passing null effectively clears the observer from the MultiBuf.
Observers are notified whenever fragments or layers are added or removed from the MultiBuf.
-
inline size_type NumFragments() const#
Returns the number of fragments in the top layer.
Whenever a new layer is added, its boundary is marked and it is treated as a single fragment of a larger message or packet. These boundaries markers are preserved by
Insert
andPushBack
. They can be used to delineate how much memory to return whenPopFront
is called.
-
inline constexpr size_type NumLayers() const#
Returns the number layers in the MultiBuf.
This will always be at least 1.
-
inline bool AddLayer(size_t offset, size_t length = dynamic_extent)#
Adds a layer.
Each layer provides a span-like view of memory. An empty MultiBuf has no layers, while a non-empty MultiBuf has at least one. Additional layers provide a subspan-like view of the layer beneath it. This is useful in representing nested protocols, where the payloads of one level make up the messages of the next level.
This will modify the apparent byte sequence to be a view of the previous top layer.
The range given by
offset
andlength
MUST fall within this MultiBuf.If unable to allocate space for the metadata, returns false and leaves the object unchanged. Otherwise, returns true.
- Parameters:
offset – [in] Offset from the start of the previous top layer for the new top layer.
length – [in] Length of the new top layer.
-
inline void SealTopLayer()#
Marks the top layer as “sealed”, preventing it from being resized or popped.
-
inline void UnsealTopLayer()#
Clears the “sealed” flag from the top layer, allowing it to be resized or popped.
-
inline void ResizeTopLayer(size_t offset, size_t length = dynamic_extent)#
Resizes the current top layer.
The range given by
offset
andlength
MUST fall within this MultiBuf. It is an error to call this method whenNumLayers()
< 2.- Parameters:
offset – [in] New offset from the start of layer beneath the top layer.
length – [in] New length of the top layer.
-
inline void PopLayer()#
Removes the top layer.
After this call, the layer beneath the top layer will be the new top layer.
It is an error to call this method when
NumLayers()
< 2.
-
enum class pw::multibuf::Property : uint8_t#
Basic properties of a MultiBuf.
Values:
-
enumerator kConst#
Indicates the data contained within the MultiBuf is read-only. Note the difference from the MultiBuf itself being
const
, which restricts changes to its structure, e.g. adding or removing layers.
-
enumerator kLayerable#
Allows adding or removing layers to create different views of the underlying data. This is useful with a “bottoms-up” approach to building a high-level application view out of a series of low-level protocol packets.
-
enumerator kConst#
-
template<typename MultiBufType>
class Instance# An instantiation of a MultiBuf.
BasicMultiBuf
represents the interface of a particular MultiBuf type. It stores no state, and cannot be instantiated directly. Instead, this type can be used to create variables and members of a particular MultiBuf type.These can then be “deferenced” to be passed to routines that take a parameter of the same MultiBuf type, or converted to a different type using
as
, e.g.extern void AdjustLayers(LayerableMultiBuf&); extern void DoTheThing(MyMultiBuf&); MyMultiBufInstance mb = InitMyMultiBufInstance(); AdjustLayers(mb->as<LayerableMultiBuf>()); DoTheThing(*mb);
-
class Observer#
Base class for a subscriber that can be notified when a MultiBuf changes.
As an example, one possible usage is as part of a flow control scheme: An observer tracks how many bytes have been received and added to or removed and sent from one or more MultiBufs. It uses this information to update peers on how much more to send, and to update local tasks how much they may send.
Public Types
-
enum class Event#
A notification from a MultiBuf.
Each Event is paired with a value with an Event-specifc meaning.
Values:
-
enumerator kBytesAdded#
The associated value gives the number of bytes added to the MultiBuf, or added to the top layer if the MultiBuf is layerable.
-
enumerator kBytesRemoved#
The associated value gives the number of bytes removed from the MultiBuf, or removed from the top layer if the MultiBuf is layerable.
-
enumerator kLayerAdded#
The associated value gives the number of fragments in the previous top layer that have been coaleasced into a single fragment in the new top layer.
-
enumerator kLayerRemoved#
The associated value gives the number of fragments in the previous top layer that was removed.
-
enumerator kBytesAdded#
-
enum class Event#