blob: 741732c8867b7270df1a3e56286fec4dab2af3db [file] [log] [blame]
/*
*
* Copyright (c) 2026 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <iterator>
#include <limits>
#include <type_traits>
#include <lib/support/Span.h>
namespace chip {
/**
* @brief
* A fixed-capacity, size-tracked buffer container.
*
* This provides a lightweight container abstraction for the common
* pattern of a fixed-size backing array paired with a logical length
* (that is, count or size). The container never allocates, never
* grows beyond its compile-time capacity, and supports efficient
* assignment/appending from raw memory.
*
* The container models a "logical (used) prefix" of the backing
* storage:
*
* * The backing store is always @p N elements.
* * The logical size is tracked in @p mSize and is always in the
* range [0, N].
* * Iteration and comparisons operate on the logical (used)
* portion.
*
* Operations that may fail (e.g. assign/append/resize) return @c
* bool and do not modify the logical size on failure.
*
* @tparam T
* The element type stored in the backing array.
*
* @tparam N
* The fixed element capacity of the buffer.
*
* @tparam SizeT
* An integral type used to represent the logical size. This may be
* selected smaller than @c std::size_t (e.g. @c uint8_t) to reduce
* object footprint.
*
*/
template <typename T, std::size_t N, typename SizeT = std::size_t>
class FixedBuffer
{
static_assert(N > 0, "FixedBuffer capacity must be > 0");
static_assert(std::is_unsigned<SizeT>::value, "FixedBuffer size type must be an unsigned type");
public:
using value_type = T;
using size_type = SizeT;
using difference_type = std::ptrdiff_t;
using reference = value_type &;
using const_reference = const value_type &;
using pointer = value_type *;
using const_pointer = const value_type *;
using iterator = pointer;
using const_iterator = const_pointer;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
/**
* Mutable and immutable views over the fixed buffer logical
* (used) prefix size.
*/
using span_type = Span<value_type>;
using const_span_type = Span<const value_type>;
/**
* Mutable and immutable views over the fixed buffer capacity
* size.
*/
using fixed_span_type = FixedSpan<value_type, N>;
using const_fixed_span_type = FixedSpan<const value_type, N>;
public:
// Construction
FixedBuffer(void) noexcept : mData{}, mSize(0) {}
FixedBuffer(const std::initializer_list<value_type> & inData) noexcept : mData{}, mSize(0)
{
// assign will return false if @a inData.begin() is null, @a
// inData.size() is zero (0) or greater than the number of
// allowed elements, or if @a inData.size() exceeds the
// maximum representable size for this buffer's @c size_type.
//
// Since this is a constructor and is marked 'noexcept', there
// is no error status that can be returned or thrown;
// likewise, to avoid propagating project-internal assertion
// macros into integrating projects or products, we elide the
// return status.
(void) assign(inData.begin(), inData.size());
}
FixedBuffer(const value_type * inData, const std::size_t & inSize) noexcept : mData{}, mSize(0)
{
// assign will return false if @a inData is null, @a inSzie is
// zero (0) or greater than the number of allowed elements, or
// if @a inSize exceeds the maximum representable size for
// this buffer's @c size_type.
//
// Since this is a constructor and is marked 'noexcept', there
// is no error status that can be returned or thrown;
// likewise, to avoid propagating project-internal assertion
// macros into integrating projects or products, we elide the
// return status.
(void) assign(inData, inSize);
}
FixedBuffer(const FixedBuffer & inFixedBuffer) noexcept : mData{}, mSize(0)
{
// assign will return false if @a inFixedBuffer.begin() is null, @a
// inFixedBuffer.size() is zero (0) or greater than the number of
// allowed elements, or if @a inFixedBuffer.size() exceeds the
// maximum representable size for this buffer's @c size_type.
//
// Since this is a constructor and is marked 'noexcept', there
// is no error status that can be returned or thrown;
// likewise, to avoid propagating project-internal assertion
// macros into integrating projects or products, we elide the
// return status.
(void) assign(inFixedBuffer.data(), static_cast<std::size_t>(inFixedBuffer.size()));
}
FixedBuffer(const const_span_type & inSpan) noexcept : mData{}, mSize(0)
{
// assign will return false if @a inSpan.data() is null and @a
// inSpan.size() != 0, if @a inSpan.size() > capacity(), or if
// @a inSpan.size() exceeds the maximum representable size for
// this buffer's @c size_type.
//
// Since this is a constructor and is marked 'noexcept', there
// is no error status that can be returned or thrown;
// likewise, to avoid propagating project-internal assertion
// macros into integrating projects or products, we elide the
// return status.
(void) assign(inSpan);
}
// Destruction
~FixedBuffer(void) noexcept = default;
// Assignment
FixedBuffer & operator=(const std::initializer_list<value_type> & inData) noexcept
{
// assign will return false if @a inData.begin() is null, @a
// inData.size() is zero (0) or greater than the number of
// allowed elements, or if @a inData.size() exceeds the
// maximum representable size for this buffer's @c size_type.
//
// Since this is an assignment operator and is marked
// 'noexcept', there is no error status that can be returned
// or thrown; likewise, to avoid propagating project-internal
// assertion macros into integrating projects or products, we
// elide the return status.
(void) assign(inData.begin(), inData.size());
return *this;
}
FixedBuffer & operator=(const FixedBuffer & inFixedBuffer) noexcept
{
if (this != &inFixedBuffer)
{
// assign will return false if @a inFixedBuffer.begin() is
// null, @a inFixedBuffer.size() is zero (0) or greater
// than the number of allowed elements, or if @a
// inFixedBuffer.size() exceeds the maximum representable
// size for this buffer's @c size_type.
//
// Since this is an assignment operator and is marked
// 'noexcept', there is no error status that can be
// returned or thrown; likewise, to avoid propagating
// project-internal assertion macros into integrating
// projects or products, we elide the return status.
(void) assign(inFixedBuffer.data(), static_cast<std::size_t>(inFixedBuffer.size()));
}
return *this;
}
FixedBuffer & operator=(const const_span_type & inSpan) noexcept
{
// assign will return false if @a inSpan.data() is null and @a
// inSpan.size() != 0, if @a inSpan.size() > capacity(), or if
// @a inSpan.size() exceeds the maximum representable size for
// this buffer's @c size_type.
//
// Since this is an assignment operator and is marked
// 'noexcept', there is no error status that can be returned
// or thrown; likewise, to avoid propagating project-internal
// assertion macros into integrating projects or products, we
// elide the return status.
(void) assign(inSpan);
return *this;
}
// Introspection
bool empty(void) const noexcept { return (mSize == 0); }
bool full(void) const noexcept { return (mSize == capacity()); }
// Equality
bool operator==(const FixedBuffer & inFixedBuffer) const noexcept
{
if (mSize != inFixedBuffer.mSize)
{
return false;
}
else
{
const std::size_t lSize = static_cast<std::size_t>(mSize);
if (lSize == 0)
{
return true;
}
else if constexpr (std::is_trivially_copyable<value_type>::value)
{
return memcmp(mData.data(), inFixedBuffer.mData.data(), lSize * sizeof(value_type)) == 0;
}
else
{
return std::equal(begin(), end(), inFixedBuffer.begin());
}
}
}
bool operator==(const const_span_type & inSpan) const noexcept
{
if (static_cast<std::size_t>(mSize) != inSpan.size())
{
return false;
}
else
{
const std::size_t lSize = inSpan.size();
if (lSize == 0)
{
return true;
}
else if constexpr (std::is_trivially_copyable<value_type>::value)
{
return memcmp(mData.data(), inSpan.data(), lSize * sizeof(value_type)) == 0;
}
else
{
return std::equal(begin(), end(), inSpan.begin());
}
}
}
bool operator!=(const FixedBuffer & inFixedBuffer) const noexcept { return !(*this == inFixedBuffer); }
bool operator!=(const const_span_type & inSpan) const noexcept { return !(*this == inSpan); }
// Observation
pointer at_ptr(const std::size_t & inPosition) noexcept
{
return (inPosition < static_cast<std::size_t>(mSize)) ? (mData.data() + inPosition) : nullptr;
}
const_pointer at_ptr(const std::size_t & inPosition) const noexcept
{
return (inPosition < static_cast<std::size_t>(mSize)) ? (mData.data() + inPosition) : nullptr;
}
size_type available(void) const noexcept { return capacity() - mSize; }
static constexpr size_type capacity(void) noexcept { return static_cast<size_type>(N); }
pointer data(void) noexcept { return mData.data(); }
const_pointer data(void) const noexcept { return mData.data(); }
reference operator[](const std::size_t & inPosition) noexcept { return mData[inPosition]; }
const_reference operator[](const std::size_t & inPosition) const noexcept { return mData[inPosition]; }
size_type size(void) const noexcept { return mSize; }
span_type span(void) noexcept { return span_type(data(), static_cast<std::size_t>(size())); }
const_span_type span(void) const noexcept { return const_span_type(data(), static_cast<std::size_t>(size())); }
fixed_span_type fixed_span(void) noexcept { return fixed_span_type(mData); }
const_fixed_span_type fixed_span(void) const noexcept { return const_fixed_span_type(mData); }
// Mutation
/**
* @brief
* Append span to the fixed buffer.
*
* This attempts to append @a inSpan to the fixed buffer,
* starting at the tail.
*
* @param[in] inSpan
* A reference to the immutable span to append to the fixed
* buffer.
*
* @returns
* True if the @a inSpan was successfully appended to this
* fixed buffer; otherwise, false if @a inSpan.data() was null,
* @a inSpan.size() was zero (0) or greater than the number of
* available elements, or if the current size plus @a
* inSpan.size() would exceed the maximum representable size
* for this buffer's @c size_type.
*
*/
bool append(const const_span_type & inSpan) noexcept { return append(inSpan.data(), inSpan.size()); }
/**
* @brief
* Append elements to the fixed buffer.
*
* This attempts to append @a inSize elements at @a inData to the
* fixed buffer, starting at the tail.
*
* @param[in] inData
* A pointer to the immutable elements to append to the fixed
* buffer.
*
* @param[in] inSize
* A reference to the immutable number of elements in @a inData
* to append to the fixed buffer.
*
* @returns
* True if the @a inSize elements at @a inData were
* successfully appended to this fixed buffer; otherwise, false
* if @a inData was null, @a inSize was zero (0) or greater
* than the number of available elements, or if the current
* size plus @a inSize would exceed the maximum representable
* size for this buffer's @c size_type.
*
*/
bool append(const value_type * inData, const std::size_t & inSize) noexcept
{
const std::size_t lCurSize = static_cast<std::size_t>(mSize);
const std::size_t lNewSize = lCurSize + inSize;
if (inData == nullptr && inSize != 0)
{
return false;
}
if (inSize > (N - lCurSize))
{
return false;
}
if (lNewSize > static_cast<std::size_t>(std::numeric_limits<size_type>::max()))
{
return false;
}
if (inSize > 0)
{
memmove(mData.data() + lCurSize, inData, inSize * sizeof(value_type));
}
mSize = static_cast<size_type>(lNewSize);
return true;
}
/**
* @brief
* Assign span to the fixed buffer.
*
* This attempts to assign @a inSpan to the fixed buffer,
* starting at the head.
*
* @param[in] inSpan
* A reference to the immutable span to assign to the fixed
* buffer.
*
* @returns
* True if the @a inSpan was successfully assigned to this
* fixed buffer; otherwise, false if @a inSpan.data() was null,
* @a inSpan.size() was zero (0) or greater than the number of
* allowed elements, or if @a inSpan.size() exceeds the maximum
* representable size for this buffer's @c size_type.
*
*/
bool assign(const const_span_type & inSpan) noexcept { return assign(inSpan.data(), inSpan.size()); }
/**
* @brief
* Assign elements to the fixed buffer.
*
* This attempts to assign @a inSize elements at @a inData to the
* fixed buffer, starting at the head.
*
* @param[in] inData
* A pointer to the immutable elements to assign to the fixed
* buffer.
*
* @param[in] inSize
* A reference to the immutable number of elements in @a inData
* to assign to the fixed buffer.
*
* @returns
* True if the @a inSize elements at @a inData were
* successfully assigned to this fixed buffer; otherwise, false
* if @a inData was null, @a inSize was zero (0) or greater
* than the number of allowed elements, or if @a inSize exceeds
* the maximum representable size for this buffer's @c
* size_type.
*
*/
bool assign(const value_type * inData, const std::size_t & inSize) noexcept
{
if (inData == nullptr && inSize != 0)
{
return false;
}
if (inSize > N)
{
return false;
}
if (inSize > static_cast<std::size_t>(std::numeric_limits<size_type>::max()))
{
return false;
}
if (inSize > 0)
{
memmove(mData.data(), inData, inSize * sizeof(value_type));
}
mSize = static_cast<size_type>(inSize);
return true;
}
/**
* @brief
* Remove all elements from the fixed buffer.
*
* This removes all elements from the fixed buffer, resetting the
* size to zero (0) and invalidating all pointers, references,
* and iterators to the fixed buffer contents.
*
* @sa fill
* @sa reset
*
*/
void clear(void) noexcept { mSize = 0; }
/**
* @brief
* Assigns the specified value to all elements in the fixed
* buffer.
*
* This assigns the specified value to all elements in the fixed
* buffer and sets the size to the fixed buffer capacity. Callers
* looking to do the same but @b without setting the size to
* capacity and instead setting the size to zero (0) should use
* #reset.
*
* @param[in] inValue
* A reference to the immutable value to assign to the elements
* of the fixed buffer.
*
* @sa clear
* @sa reset
*
*/
void fill(const_reference inValue) noexcept
{
mData.fill(inValue);
mSize = capacity();
}
/**
* @brief
* Assigns the default value to all elements in the fixed
* buffer.
*
* This assigns the default value for @c value_type to all
* elements in the fixed buffer and sets the size to zero
* (0). Callers looking to do the same but @b without setting the
* size to zero (0) and instead setting the size to capacity
* should use #fill.
*
* @sa clear
* @sa fill
*
*/
void reset(void) noexcept { reset(value_type{}); }
/**
* @brief
* Assigns the specified value to all elements in the fixed
* buffer.
*
* This assigns the specified value to all elements in the fixed
* buffer and sets the size to zero (0). Callers
* looking to do the same but @b without setting the size to
* zero (0) and instead setting the size to capacity should use
* #fill.
*
* @param[in] inValue
* A reference to the immutable value to assign to the elements
* of the fixed buffer.
*
* @sa clear
* @sa fill
*
*/
void reset(const_reference inValue) noexcept
{
mData.fill(inValue);
mSize = 0;
}
/**
* @brief
* Resize the fixed buffer.
*
* This attempts to resize the fixed buffer to @a inSize.
*
* @note
* No attempt is made to fill or otherwise invalidate elements
* between the tail of the former size and that of @a inSize.
*
* @param[in] inSize
* A reference to the immutable size, in number of elements, to
* set the fixed buffer to.
*
* @returns
* True if the fixed buffer was resized to @a inSize elements;
* otherwise, false if @a inSize was greater than the number of
* allowed elements or if @a inSize exceeds the maximum
* representable size for this buffer's @c size_type.
*
* @sa append
* @sa assign
* @sa available
* @sa capacity
* @sa clear
* @sa fill
* @sa reset
* @sa size
*
*/
bool resize(const std::size_t & inSize) noexcept
{
if (inSize > N)
{
return false;
}
if (inSize > static_cast<std::size_t>(std::numeric_limits<size_type>::max()))
{
return false;
}
mSize = static_cast<size_type>(inSize);
return true;
}
// Iteration
iterator begin(void) noexcept { return data(); }
const_iterator begin(void) const noexcept { return data(); }
const_iterator cbegin(void) const noexcept { return data(); }
iterator end(void) noexcept { return data() + static_cast<std::size_t>(mSize); }
const_iterator end(void) const noexcept { return data() + static_cast<std::size_t>(mSize); }
const_iterator cend(void) const noexcept { return data() + static_cast<std::size_t>(mSize); }
reverse_iterator rbegin(void) noexcept { return reverse_iterator(end()); }
const_reverse_iterator rbegin(void) const noexcept { return const_reverse_iterator(end()); }
const_reverse_iterator crbegin(void) const noexcept { return const_reverse_iterator(end()); }
reverse_iterator rend(void) noexcept { return reverse_iterator(begin()); }
const_reverse_iterator rend(void) const noexcept { return const_reverse_iterator(begin()); }
const_reverse_iterator crend(void) const noexcept { return const_reverse_iterator(begin()); }
private:
std::array<value_type, N> mData;
size_type mSize;
};
/**
* @brief
* A fixed-capacity, size-tracked buffer of bytes.
*
* This specializes and extends @c FixedBuffer as a fixed-capacity,
* size-tracked buffer of bytes.
*
* @tparam N
* The fixed byte capacity of the buffer.
*
* @tparam SizeT
* An integral type used to represent the logical size.
*
*/
template <std::size_t N, typename SizeT = std::size_t>
using FixedByteBuffer = FixedBuffer<std::uint8_t, N, SizeT>;
/**
* @brief
* A fixed-capacity, size-tracked buffer of characters.
*
* This specializes and extends @c FixedBuffer as a fixed-capacity,
* size-tracked buffer of characters.
*
* @tparam N
* The fixed character capacity of the buffer.
*
* @tparam SizeT
* An integral type used to represent the logical size.
*
*/
template <std::size_t N, typename SizeT = std::size_t>
using FixedCharBuffer = FixedBuffer<char, N, SizeT>;
} // namespace chip