Line data Source code
1 : /*
2 : *
3 : * Copyright (c) 2026 Project CHIP Authors
4 : *
5 : * Licensed under the Apache License, Version 2.0 (the "License");
6 : * you may not use this file except in compliance with the License.
7 : * You may obtain a copy of the License at
8 : *
9 : * http://www.apache.org/licenses/LICENSE-2.0
10 : *
11 : * Unless required by applicable law or agreed to in writing, software
12 : * distributed under the License is distributed on an "AS IS" BASIS,
13 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 : * See the License for the specific language governing permissions and
15 : * limitations under the License.
16 : */
17 :
18 : #pragma once
19 :
20 : #include <algorithm>
21 : #include <array>
22 : #include <cstddef>
23 : #include <cstdint>
24 : #include <cstdlib>
25 : #include <cstring>
26 : #include <iterator>
27 : #include <limits>
28 : #include <type_traits>
29 :
30 : #include <lib/support/Span.h>
31 :
32 : namespace chip {
33 :
34 : /**
35 : * @brief
36 : * A fixed-capacity, size-tracked buffer container.
37 : *
38 : * This provides a lightweight container abstraction for the common
39 : * pattern of a fixed-size backing array paired with a logical length
40 : * (that is, count or size). The container never allocates, never
41 : * grows beyond its compile-time capacity, and supports efficient
42 : * assignment/appending from raw memory.
43 : *
44 : * The container models a "logical (used) prefix" of the backing
45 : * storage:
46 : *
47 : * * The backing store is always @p N elements.
48 : * * The logical size is tracked in @p mSize and is always in the
49 : * range [0, N].
50 : * * Iteration and comparisons operate on the logical (used)
51 : * portion.
52 : *
53 : * Operations that may fail (e.g. assign/append/resize) return @c
54 : * bool and do not modify the logical size on failure.
55 : *
56 : * @tparam T
57 : * The element type stored in the backing array.
58 : *
59 : * @tparam N
60 : * The fixed element capacity of the buffer.
61 : *
62 : * @tparam SizeT
63 : * An integral type used to represent the logical size. This may be
64 : * selected smaller than @c std::size_t (e.g. @c uint8_t) to reduce
65 : * object footprint.
66 : *
67 : */
68 : template <typename T, std::size_t N, typename SizeT = std::size_t>
69 : class FixedBuffer
70 : {
71 : static_assert(N > 0, "FixedBuffer capacity must be > 0");
72 : static_assert(std::is_unsigned<SizeT>::value, "FixedBuffer size type must be an unsigned type");
73 :
74 : public:
75 : using value_type = T;
76 : using size_type = SizeT;
77 : using difference_type = std::ptrdiff_t;
78 : using reference = value_type &;
79 : using const_reference = const value_type &;
80 : using pointer = value_type *;
81 : using const_pointer = const value_type *;
82 : using iterator = pointer;
83 : using const_iterator = const_pointer;
84 : using reverse_iterator = std::reverse_iterator<iterator>;
85 : using const_reverse_iterator = std::reverse_iterator<const_iterator>;
86 :
87 : /**
88 : * Mutable and immutable views over the fixed buffer logical
89 : * (used) prefix size.
90 : */
91 : using span_type = Span<value_type>;
92 : using const_span_type = Span<const value_type>;
93 :
94 : /**
95 : * Mutable and immutable views over the fixed buffer capacity
96 : * size.
97 : */
98 : using fixed_span_type = FixedSpan<value_type, N>;
99 : using const_fixed_span_type = FixedSpan<const value_type, N>;
100 :
101 : public:
102 : // Construction
103 :
104 21 : FixedBuffer(void) noexcept : mData{}, mSize(0) {}
105 17 : FixedBuffer(const std::initializer_list<value_type> & inData) noexcept : mData{}, mSize(0)
106 : {
107 : // assign will return false if @a inData.begin() is null, @a
108 : // inData.size() is zero (0) or greater than the number of
109 : // allowed elements, or if @a inData.size() exceeds the
110 : // maximum representable size for this buffer's @c size_type.
111 : //
112 : // Since this is a constructor and is marked 'noexcept', there
113 : // is no error status that can be returned or thrown;
114 : // likewise, to avoid propagating project-internal assertion
115 : // macros into integrating projects or products, we elide the
116 : // return status.
117 :
118 17 : (void) assign(inData.begin(), inData.size());
119 17 : }
120 :
121 2 : FixedBuffer(const value_type * inData, const std::size_t & inSize) noexcept : mData{}, mSize(0)
122 : {
123 : // assign will return false if @a inData is null, @a inSzie is
124 : // zero (0) or greater than the number of allowed elements, or
125 : // if @a inSize exceeds the maximum representable size for
126 : // this buffer's @c size_type.
127 : //
128 : // Since this is a constructor and is marked 'noexcept', there
129 : // is no error status that can be returned or thrown;
130 : // likewise, to avoid propagating project-internal assertion
131 : // macros into integrating projects or products, we elide the
132 : // return status.
133 :
134 2 : (void) assign(inData, inSize);
135 2 : }
136 :
137 1 : FixedBuffer(const FixedBuffer & inFixedBuffer) noexcept : mData{}, mSize(0)
138 : {
139 : // assign will return false if @a inFixedBuffer.begin() is null, @a
140 : // inFixedBuffer.size() is zero (0) or greater than the number of
141 : // allowed elements, or if @a inFixedBuffer.size() exceeds the
142 : // maximum representable size for this buffer's @c size_type.
143 : //
144 : // Since this is a constructor and is marked 'noexcept', there
145 : // is no error status that can be returned or thrown;
146 : // likewise, to avoid propagating project-internal assertion
147 : // macros into integrating projects or products, we elide the
148 : // return status.
149 :
150 1 : (void) assign(inFixedBuffer.data(), static_cast<std::size_t>(inFixedBuffer.size()));
151 1 : }
152 :
153 9 : FixedBuffer(const const_span_type & inSpan) noexcept : mData{}, mSize(0)
154 : {
155 : // assign will return false if @a inSpan.data() is null and @a
156 : // inSpan.size() != 0, if @a inSpan.size() > capacity(), or if
157 : // @a inSpan.size() exceeds the maximum representable size for
158 : // this buffer's @c size_type.
159 : //
160 : // Since this is a constructor and is marked 'noexcept', there
161 : // is no error status that can be returned or thrown;
162 : // likewise, to avoid propagating project-internal assertion
163 : // macros into integrating projects or products, we elide the
164 : // return status.
165 :
166 9 : (void) assign(inSpan);
167 9 : }
168 :
169 : // Destruction
170 :
171 : ~FixedBuffer(void) noexcept = default;
172 :
173 : // Assignment
174 :
175 1 : FixedBuffer & operator=(const std::initializer_list<value_type> & inData) noexcept
176 : {
177 : // assign will return false if @a inData.begin() is null, @a
178 : // inData.size() is zero (0) or greater than the number of
179 : // allowed elements, or if @a inData.size() exceeds the
180 : // maximum representable size for this buffer's @c size_type.
181 : //
182 : // Since this is an assignment operator and is marked
183 : // 'noexcept', there is no error status that can be returned
184 : // or thrown; likewise, to avoid propagating project-internal
185 : // assertion macros into integrating projects or products, we
186 : // elide the return status.
187 :
188 1 : (void) assign(inData.begin(), inData.size());
189 :
190 1 : return *this;
191 : }
192 :
193 1 : FixedBuffer & operator=(const FixedBuffer & inFixedBuffer) noexcept
194 : {
195 1 : if (this != &inFixedBuffer)
196 : {
197 : // assign will return false if @a inFixedBuffer.begin() is
198 : // null, @a inFixedBuffer.size() is zero (0) or greater
199 : // than the number of allowed elements, or if @a
200 : // inFixedBuffer.size() exceeds the maximum representable
201 : // size for this buffer's @c size_type.
202 : //
203 : // Since this is an assignment operator and is marked
204 : // 'noexcept', there is no error status that can be
205 : // returned or thrown; likewise, to avoid propagating
206 : // project-internal assertion macros into integrating
207 : // projects or products, we elide the return status.
208 :
209 1 : (void) assign(inFixedBuffer.data(), static_cast<std::size_t>(inFixedBuffer.size()));
210 : }
211 :
212 1 : return *this;
213 : }
214 :
215 1 : FixedBuffer & operator=(const const_span_type & inSpan) noexcept
216 : {
217 : // assign will return false if @a inSpan.data() is null and @a
218 : // inSpan.size() != 0, if @a inSpan.size() > capacity(), or if
219 : // @a inSpan.size() exceeds the maximum representable size for
220 : // this buffer's @c size_type.
221 : //
222 : // Since this is an assignment operator and is marked
223 : // 'noexcept', there is no error status that can be returned
224 : // or thrown; likewise, to avoid propagating project-internal
225 : // assertion macros into integrating projects or products, we
226 : // elide the return status.
227 :
228 1 : (void) assign(inSpan);
229 :
230 1 : return *this;
231 : }
232 :
233 : // Introspection
234 :
235 8 : bool empty(void) const noexcept { return (mSize == 0); }
236 :
237 8 : bool full(void) const noexcept { return (mSize == capacity()); }
238 :
239 : // Equality
240 :
241 12 : bool operator==(const FixedBuffer & inFixedBuffer) const noexcept
242 : {
243 12 : if (mSize != inFixedBuffer.mSize)
244 : {
245 2 : return false;
246 : }
247 : else
248 : {
249 10 : const std::size_t lSize = static_cast<std::size_t>(mSize);
250 :
251 10 : if (lSize == 0)
252 : {
253 2 : return true;
254 : }
255 : else if constexpr (std::is_trivially_copyable<value_type>::value)
256 : {
257 8 : return memcmp(mData.data(), inFixedBuffer.mData.data(), lSize * sizeof(value_type)) == 0;
258 : }
259 : else
260 : {
261 : return std::equal(begin(), end(), inFixedBuffer.begin());
262 : }
263 : }
264 : }
265 :
266 24 : bool operator==(const const_span_type & inSpan) const noexcept
267 : {
268 24 : if (static_cast<std::size_t>(mSize) != inSpan.size())
269 : {
270 4 : return false;
271 : }
272 : else
273 : {
274 20 : const std::size_t lSize = inSpan.size();
275 :
276 20 : if (lSize == 0)
277 : {
278 4 : return true;
279 : }
280 : else if constexpr (std::is_trivially_copyable<value_type>::value)
281 : {
282 16 : return memcmp(mData.data(), inSpan.data(), lSize * sizeof(value_type)) == 0;
283 : }
284 : else
285 : {
286 : return std::equal(begin(), end(), inSpan.begin());
287 : }
288 : }
289 : }
290 :
291 6 : bool operator!=(const FixedBuffer & inFixedBuffer) const noexcept { return !(*this == inFixedBuffer); }
292 12 : bool operator!=(const const_span_type & inSpan) const noexcept { return !(*this == inSpan); }
293 :
294 : // Observation
295 :
296 3 : pointer at_ptr(const std::size_t & inPosition) noexcept
297 : {
298 3 : return (inPosition < static_cast<std::size_t>(mSize)) ? (mData.data() + inPosition) : nullptr;
299 : }
300 :
301 1 : const_pointer at_ptr(const std::size_t & inPosition) const noexcept
302 : {
303 1 : return (inPosition < static_cast<std::size_t>(mSize)) ? (mData.data() + inPosition) : nullptr;
304 : }
305 :
306 4 : size_type available(void) const noexcept { return capacity() - mSize; }
307 :
308 21 : static constexpr size_type capacity(void) noexcept { return static_cast<size_type>(N); }
309 :
310 35 : pointer data(void) noexcept { return mData.data(); }
311 :
312 17 : const_pointer data(void) const noexcept { return mData.data(); }
313 :
314 10 : reference operator[](const std::size_t & inPosition) noexcept { return mData[inPosition]; }
315 :
316 74 : const_reference operator[](const std::size_t & inPosition) const noexcept { return mData[inPosition]; }
317 :
318 61 : size_type size(void) const noexcept { return mSize; }
319 :
320 3 : span_type span(void) noexcept { return span_type(data(), static_cast<std::size_t>(size())); }
321 2 : const_span_type span(void) const noexcept { return const_span_type(data(), static_cast<std::size_t>(size())); }
322 : fixed_span_type fixed_span(void) noexcept { return fixed_span_type(mData); }
323 : const_fixed_span_type fixed_span(void) const noexcept { return const_fixed_span_type(mData); }
324 :
325 : // Mutation
326 :
327 : /**
328 : * @brief
329 : * Append span to the fixed buffer.
330 : *
331 : * This attempts to append @a inSpan to the fixed buffer,
332 : * starting at the tail.
333 : *
334 : * @param[in] inSpan
335 : * A reference to the immutable span to append to the fixed
336 : * buffer.
337 : *
338 : * @returns
339 : * True if the @a inSpan was successfully appended to this
340 : * fixed buffer; otherwise, false if @a inSpan.data() was null,
341 : * @a inSpan.size() was zero (0) or greater than the number of
342 : * available elements, or if the current size plus @a
343 : * inSpan.size() would exceed the maximum representable size
344 : * for this buffer's @c size_type.
345 : *
346 : */
347 4 : bool append(const const_span_type & inSpan) noexcept { return append(inSpan.data(), inSpan.size()); }
348 :
349 : /**
350 : * @brief
351 : * Append elements to the fixed buffer.
352 : *
353 : * This attempts to append @a inSize elements at @a inData to the
354 : * fixed buffer, starting at the tail.
355 : *
356 : * @param[in] inData
357 : * A pointer to the immutable elements to append to the fixed
358 : * buffer.
359 : *
360 : * @param[in] inSize
361 : * A reference to the immutable number of elements in @a inData
362 : * to append to the fixed buffer.
363 : *
364 : * @returns
365 : * True if the @a inSize elements at @a inData were
366 : * successfully appended to this fixed buffer; otherwise, false
367 : * if @a inData was null, @a inSize was zero (0) or greater
368 : * than the number of available elements, or if the current
369 : * size plus @a inSize would exceed the maximum representable
370 : * size for this buffer's @c size_type.
371 : *
372 : */
373 9 : bool append(const value_type * inData, const std::size_t & inSize) noexcept
374 : {
375 9 : const std::size_t lCurSize = static_cast<std::size_t>(mSize);
376 9 : const std::size_t lNewSize = lCurSize + inSize;
377 :
378 9 : if (inData == nullptr && inSize != 0)
379 : {
380 1 : return false;
381 : }
382 :
383 8 : if (inSize > (N - lCurSize))
384 : {
385 2 : return false;
386 : }
387 :
388 6 : if (lNewSize > static_cast<std::size_t>(std::numeric_limits<size_type>::max()))
389 : {
390 0 : return false;
391 : }
392 :
393 6 : if (inSize > 0)
394 : {
395 6 : memmove(mData.data() + lCurSize, inData, inSize * sizeof(value_type));
396 : }
397 :
398 6 : mSize = static_cast<size_type>(lNewSize);
399 :
400 6 : return true;
401 : }
402 :
403 : /**
404 : * @brief
405 : * Assign span to the fixed buffer.
406 : *
407 : * This attempts to assign @a inSpan to the fixed buffer,
408 : * starting at the head.
409 : *
410 : * @param[in] inSpan
411 : * A reference to the immutable span to assign to the fixed
412 : * buffer.
413 : *
414 : * @returns
415 : * True if the @a inSpan was successfully assigned to this
416 : * fixed buffer; otherwise, false if @a inSpan.data() was null,
417 : * @a inSpan.size() was zero (0) or greater than the number of
418 : * allowed elements, or if @a inSpan.size() exceeds the maximum
419 : * representable size for this buffer's @c size_type.
420 : *
421 : */
422 19 : bool assign(const const_span_type & inSpan) noexcept { return assign(inSpan.data(), inSpan.size()); }
423 :
424 : /**
425 : * @brief
426 : * Assign elements to the fixed buffer.
427 : *
428 : * This attempts to assign @a inSize elements at @a inData to the
429 : * fixed buffer, starting at the head.
430 : *
431 : * @param[in] inData
432 : * A pointer to the immutable elements to assign to the fixed
433 : * buffer.
434 : *
435 : * @param[in] inSize
436 : * A reference to the immutable number of elements in @a inData
437 : * to assign to the fixed buffer.
438 : *
439 : * @returns
440 : * True if the @a inSize elements at @a inData were
441 : * successfully assigned to this fixed buffer; otherwise, false
442 : * if @a inData was null, @a inSize was zero (0) or greater
443 : * than the number of allowed elements, or if @a inSize exceeds
444 : * the maximum representable size for this buffer's @c
445 : * size_type.
446 : *
447 : */
448 53 : bool assign(const value_type * inData, const std::size_t & inSize) noexcept
449 : {
450 53 : if (inData == nullptr && inSize != 0)
451 : {
452 1 : return false;
453 : }
454 :
455 52 : if (inSize > N)
456 : {
457 2 : return false;
458 : }
459 :
460 50 : if (inSize > static_cast<std::size_t>(std::numeric_limits<size_type>::max()))
461 : {
462 0 : return false;
463 : }
464 :
465 50 : if (inSize > 0)
466 : {
467 49 : memmove(mData.data(), inData, inSize * sizeof(value_type));
468 : }
469 :
470 50 : mSize = static_cast<size_type>(inSize);
471 :
472 50 : return true;
473 : }
474 :
475 : /**
476 : * @brief
477 : * Remove all elements from the fixed buffer.
478 : *
479 : * This removes all elements from the fixed buffer, resetting the
480 : * size to zero (0) and invalidating all pointers, references,
481 : * and iterators to the fixed buffer contents.
482 : *
483 : * @sa fill
484 : * @sa reset
485 : *
486 : */
487 1 : void clear(void) noexcept { mSize = 0; }
488 :
489 : /**
490 : * @brief
491 : * Assigns the specified value to all elements in the fixed
492 : * buffer.
493 : *
494 : * This assigns the specified value to all elements in the fixed
495 : * buffer and sets the size to the fixed buffer capacity. Callers
496 : * looking to do the same but @b without setting the size to
497 : * capacity and instead setting the size to zero (0) should use
498 : * #reset.
499 : *
500 : * @param[in] inValue
501 : * A reference to the immutable value to assign to the elements
502 : * of the fixed buffer.
503 : *
504 : * @sa clear
505 : * @sa reset
506 : *
507 : */
508 5 : void fill(const_reference inValue) noexcept
509 : {
510 5 : mData.fill(inValue);
511 :
512 5 : mSize = capacity();
513 5 : }
514 :
515 : /**
516 : * @brief
517 : * Assigns the default value to all elements in the fixed
518 : * buffer.
519 : *
520 : * This assigns the default value for @c value_type to all
521 : * elements in the fixed buffer and sets the size to zero
522 : * (0). Callers looking to do the same but @b without setting the
523 : * size to zero (0) and instead setting the size to capacity
524 : * should use #fill.
525 : *
526 : * @sa clear
527 : * @sa fill
528 : *
529 : */
530 : void reset(void) noexcept { reset(value_type{}); }
531 :
532 : /**
533 : * @brief
534 : * Assigns the specified value to all elements in the fixed
535 : * buffer.
536 : *
537 : * This assigns the specified value to all elements in the fixed
538 : * buffer and sets the size to zero (0). Callers
539 : * looking to do the same but @b without setting the size to
540 : * zero (0) and instead setting the size to capacity should use
541 : * #fill.
542 : *
543 : * @param[in] inValue
544 : * A reference to the immutable value to assign to the elements
545 : * of the fixed buffer.
546 : *
547 : * @sa clear
548 : * @sa fill
549 : *
550 : */
551 1 : void reset(const_reference inValue) noexcept
552 : {
553 1 : mData.fill(inValue);
554 1 : mSize = 0;
555 1 : }
556 :
557 : /**
558 : * @brief
559 : * Resize the fixed buffer.
560 : *
561 : * This attempts to resize the fixed buffer to @a inSize.
562 : *
563 : * @note
564 : * No attempt is made to fill or otherwise invalidate elements
565 : * between the tail of the former size and that of @a inSize.
566 : *
567 : * @param[in] inSize
568 : * A reference to the immutable size, in number of elements, to
569 : * set the fixed buffer to.
570 : *
571 : * @returns
572 : * True if the fixed buffer was resized to @a inSize elements;
573 : * otherwise, false if @a inSize was greater than the number of
574 : * allowed elements or if @a inSize exceeds the maximum
575 : * representable size for this buffer's @c size_type.
576 : *
577 : * @sa append
578 : * @sa assign
579 : * @sa available
580 : * @sa capacity
581 : * @sa clear
582 : * @sa fill
583 : * @sa reset
584 : * @sa size
585 : *
586 : */
587 3 : bool resize(const std::size_t & inSize) noexcept
588 : {
589 3 : if (inSize > N)
590 : {
591 1 : return false;
592 : }
593 :
594 2 : if (inSize > static_cast<std::size_t>(std::numeric_limits<size_type>::max()))
595 : {
596 0 : return false;
597 : }
598 :
599 2 : mSize = static_cast<size_type>(inSize);
600 :
601 2 : return true;
602 : }
603 :
604 : // Iteration
605 :
606 10 : iterator begin(void) noexcept { return data(); }
607 5 : const_iterator begin(void) const noexcept { return data(); }
608 1 : const_iterator cbegin(void) const noexcept { return data(); }
609 10 : iterator end(void) noexcept { return data() + static_cast<std::size_t>(mSize); }
610 1 : const_iterator end(void) const noexcept { return data() + static_cast<std::size_t>(mSize); }
611 5 : const_iterator cend(void) const noexcept { return data() + static_cast<std::size_t>(mSize); }
612 :
613 2 : reverse_iterator rbegin(void) noexcept { return reverse_iterator(end()); }
614 1 : const_reverse_iterator rbegin(void) const noexcept { return const_reverse_iterator(end()); }
615 : const_reverse_iterator crbegin(void) const noexcept { return const_reverse_iterator(end()); }
616 6 : reverse_iterator rend(void) noexcept { return reverse_iterator(begin()); }
617 5 : const_reverse_iterator rend(void) const noexcept { return const_reverse_iterator(begin()); }
618 : const_reverse_iterator crend(void) const noexcept { return const_reverse_iterator(begin()); }
619 :
620 : private:
621 : std::array<value_type, N> mData;
622 : size_type mSize;
623 : };
624 :
625 : /**
626 : * @brief
627 : * A fixed-capacity, size-tracked buffer of bytes.
628 : *
629 : * This specializes and extends @c FixedBuffer as a fixed-capacity,
630 : * size-tracked buffer of bytes.
631 : *
632 : * @tparam N
633 : * The fixed byte capacity of the buffer.
634 : *
635 : * @tparam SizeT
636 : * An integral type used to represent the logical size.
637 : *
638 : */
639 : template <std::size_t N, typename SizeT = std::size_t>
640 : using FixedByteBuffer = FixedBuffer<std::uint8_t, N, SizeT>;
641 :
642 : /**
643 : * @brief
644 : * A fixed-capacity, size-tracked buffer of characters.
645 : *
646 : * This specializes and extends @c FixedBuffer as a fixed-capacity,
647 : * size-tracked buffer of characters.
648 : *
649 : * @tparam N
650 : * The fixed character capacity of the buffer.
651 : *
652 : * @tparam SizeT
653 : * An integral type used to represent the logical size.
654 : *
655 : */
656 : template <std::size_t N, typename SizeT = std::size_t>
657 : using FixedCharBuffer = FixedBuffer<char, N, SizeT>;
658 :
659 : } // namespace chip
|