Matter SDK Coverage Report
Current view: top level - lib/support - Span.h (source / functions) Coverage Total Hit
Test: SHA:3f9cd168e84cd831b7699126f5296f5c5498690f Lines: 98.9 % 91 90
Test Date: 2026-04-27 19:52:19 Functions: 94.1 % 959 902

            Line data    Source code
       1              : /*
       2              :  *
       3              :  *    Copyright (c) 2021 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 <array>
      21              : #include <cstddef>
      22              : #include <cstdint>
      23              : #include <cstdlib>
      24              : #include <cstring>
      25              : #include <type_traits>
      26              : 
      27              : #include <lib/core/Unchecked.h>
      28              : #include <lib/support/CodeUtils.h>
      29              : 
      30              : namespace chip {
      31              : 
      32              : template <class T, size_t N>
      33              : class FixedSpan;
      34              : 
      35              : /**
      36              :  * @brief A wrapper class for holding objects and its length, without the ownership of it.
      37              :  * We can use C++20 std::span once we support it, the data() and size() come from C++20 std::span.
      38              :  */
      39              : template <class T>
      40              : class Span
      41              : {
      42              : public:
      43              :     using pointer   = T *;
      44              :     using reference = T &;
      45              : 
      46       287309 :     constexpr Span() : mDataBuf(nullptr), mDataLen(0) {}
      47              : 
      48              :     // Note: VerifyOrDie cannot be used inside a constexpr function, because it uses
      49              :     // "static" on some platforms (e.g. when CHIP_PW_TOKENIZER_LOGGING is true)
      50              :     // and that's not allowed in constexpr functions.
      51              : 
      52      1889162 :     Span(pointer databuf, size_t datalen) : mDataBuf(databuf), mDataLen(datalen)
      53              :     {
      54      1889162 :         VerifyOrDie(databuf != nullptr || datalen == 0); // not constexpr on some platforms
      55      1889162 :     }
      56              : 
      57              :     // The only valid length for a span pointing to null is 0 (i.e. it's an empty span).  Disallow
      58              :     // construction of spans from compile-time-known null.  The default constructor should be used
      59              :     // to construct empty Spans. All other cases involving null are invalid.
      60              :     Span(std::nullptr_t null, size_t size) = delete;
      61              : 
      62              :     // Creates a Span view of a plain array.
      63              :     //
      64              :     // Note that this constructor template explicitly excludes `const char[]`, see below.
      65              :     template <
      66              :         class U, size_t N,
      67              :         std::enable_if_t<!std::is_same_v<U, const char> && sizeof(U) == sizeof(T) && std::is_convertible_v<U *, T *>, bool> = true>
      68       116530 :     constexpr explicit Span(U (&databuf)[N]) : mDataBuf(databuf), mDataLen(N)
      69       116530 :     {}
      70              : 
      71              :     // Explicitly disallow the creation of a CharSpan from a `const char[]` to prevent the
      72              :     // creation of spans from string literals that incorrectly include the trailing '\0' byte.
      73              :     // If CharSpan("Hi!") were allowed, it would be a span of length 4, not 3 as intended.
      74              :     //
      75              :     // To create a CharSpan literal, use the `_span` operator instead, e.g. "Hi!"_span.
      76              :     template <
      77              :         class U, size_t N,
      78              :         std::enable_if_t<std::is_same_v<U, const char> && 1 == sizeof(T) && std::is_convertible_v<const char *, T *>, bool> = true>
      79              :     constexpr explicit Span(U (&databuf)[N]) = delete;
      80              : 
      81              :     // Creates a (potentially mutable) Span view of an std::array
      82              :     template <class U, size_t N, typename = std::enable_if_t<sizeof(U) == sizeof(T) && std::is_convertible<U *, T *>::value>>
      83          723 :     constexpr Span(std::array<U, N> & arr) : mDataBuf(arr.data()), mDataLen(N)
      84          723 :     {}
      85              : 
      86              :     template <class U, size_t N, typename = std::enable_if_t<sizeof(U) == sizeof(T) && std::is_convertible<U *, T *>::value>>
      87              :     constexpr Span(std::array<U, N> && arr) = delete; // would be a view of an rvalue
      88              : 
      89              :     // Creates a Span view of an std::array
      90              :     template <class U, size_t N, typename = std::enable_if_t<sizeof(U) == sizeof(T) && std::is_convertible<const U *, T *>::value>>
      91         1053 :     constexpr Span(const std::array<U, N> & arr) : mDataBuf(arr.data()), mDataLen(N)
      92         1053 :     {}
      93              : 
      94              :     template <size_t N>
      95          886 :     constexpr Span & operator=(T (&databuf)[N])
      96              :     {
      97          886 :         mDataBuf = databuf;
      98          886 :         mDataLen = N;
      99          886 :         return (*this);
     100              :     }
     101              : 
     102              :     // Allow implicit construction from a Span over a type that matches our
     103              :     // type's size, if a pointer to the other type can be treated as a pointer
     104              :     // to our type (e.g. other type is same as ours, or is a same-size
     105              :     // subclass).  The size check is really important to make sure we don't get
     106              :     // confused about where our object boundaries are.
     107              :     template <class U, typename = std::enable_if_t<sizeof(U) == sizeof(T) && std::is_convertible<U *, T *>::value>>
     108        10283 :     constexpr Span(const Span<U> & other) : Span(other.data(), other.size())
     109        10283 :     {}
     110              : 
     111              :     // Allow implicit construction from a FixedSpan over a type that matches our
     112              :     // type's size, if a pointer to the other type can be treated as a pointer
     113              :     // to our type (e.g. other type is same as ours, or is a same-size
     114              :     // subclass).  The size check is really important to make sure we don't get
     115              :     // confused about where our object boundaries are.
     116              :     template <class U, size_t N, typename = std::enable_if_t<sizeof(U) == sizeof(T) && std::is_convertible<U *, T *>::value>>
     117              :     constexpr inline Span(const FixedSpan<U, N> & other);
     118              : 
     119      3968444 :     constexpr pointer data() const { return mDataBuf; }
     120      2357267 :     constexpr size_t size() const { return mDataLen; }
     121        32731 :     constexpr bool empty() const { return size() == 0; }
     122      1782802 :     constexpr pointer begin() const { return data(); }
     123      1780382 :     constexpr pointer end() const { return data() + size(); }
     124              : 
     125              :     // Element accessors, matching the std::span API.
     126        34352 :     reference operator[](size_t index) const
     127              :     {
     128        34352 :         VerifyOrDie(index < size()); // not constexpr on some platforms
     129        34352 :         return data()[index];
     130              :     }
     131         2159 :     reference front() const { return (*this)[0]; }
     132            1 :     reference back() const { return (*this)[size() - 1]; }
     133              : 
     134         2915 :     bool data_equal(const Span<const T> & other) const
     135              :     {
     136         2915 :         return (size() == other.size()) && (empty() || (memcmp(data(), other.data(), size() * sizeof(T)) == 0));
     137              :     }
     138              : 
     139         6743 :     Span SubSpan(size_t offset, size_t length) const
     140              :     {
     141         6743 :         VerifyOrDie(offset <= mDataLen);
     142         6743 :         VerifyOrDie(length <= mDataLen - offset);
     143         6743 :         return Span(mDataBuf + offset, length);
     144              :     }
     145              : 
     146         2337 :     Span SubSpan(size_t offset) const
     147              :     {
     148         2337 :         VerifyOrDie(offset <= mDataLen);
     149         2337 :         return Span(mDataBuf + offset, mDataLen - offset);
     150              :     }
     151              : 
     152              :     // Allow reducing the size of a span.
     153        12227 :     void reduce_size(size_t new_size)
     154              :     {
     155        12227 :         VerifyOrDie(new_size <= size());
     156        12227 :         mDataLen = new_size;
     157        12227 :     }
     158              : 
     159              :     // Allow creating ByteSpans and CharSpans from ZCL octet strings, so we
     160              :     // don't have to reinvent it various places.
     161              :     template <class U,
     162              :               typename = std::enable_if_t<std::is_same<uint8_t, std::remove_const_t<U>>::value &&
     163              :                                           (std::is_same<const uint8_t, T>::value || std::is_same<const char, T>::value)>>
     164            2 :     static Span fromZclString(U * bytes)
     165              :     {
     166            2 :         size_t length = bytes[0];
     167              :         // Treat 0xFF (aka "null string") as zero-length.
     168            2 :         if (length == 0xFF)
     169              :         {
     170            0 :             length = 0;
     171              :         }
     172              :         // Need reinterpret_cast if we're a CharSpan.
     173            2 :         return Span(reinterpret_cast<T *>(&bytes[1]), length);
     174              :     }
     175              : 
     176              :     // Creates a CharSpan from a null-terminated C character string.
     177              :     //
     178              :     // Note that for string literals, the user-defined `_span` string
     179              :     // literal operator should be used instead, e.g. `"Hello"_span`.
     180              :     template <class U, typename = std::enable_if_t<std::is_same<T, const U>::value && std::is_same<const char, T>::value>>
     181         2469 :     static Span fromCharString(U * chars)
     182              :     {
     183         2469 :         return Span(chars, strlen(chars));
     184              :     }
     185              : 
     186              :     // operator== explicitly not implemented on Span, because its meaning
     187              :     // (equality of data, or pointing to the same buffer and same length) is
     188              :     // ambiguous.  Use data_equal if testing for equality of data.
     189              :     template <typename U>
     190              :     bool operator==(const Span<U> & other) const = delete;
     191              : 
     192              :     // Creates a Span without checking whether databuf is a null pointer.
     193              :     //
     194              :     // Note: The normal (checked) constructor should be used for general use;
     195              :     // this overload exists for special use cases where databuf is guaranteed
     196              :     // to be valid (not null) and a constexpr constructor is required.
     197              :     //
     198              :     // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61648 prevents making
     199              :     // operator""_span a friend (and this constructor private).
     200              : 
     201          552 :     constexpr Span(UncheckedType tag, pointer databuf, size_t datalen) : mDataBuf(databuf), mDataLen(datalen) {}
     202              : 
     203              : private:
     204              :     pointer mDataBuf;
     205              :     size_t mDataLen;
     206              : };
     207              : 
     208              : // Template deduction guides to allow construction of Span from a pointer or
     209              : // array without having to specify the type of the entries explicitly.
     210              : template <class T>
     211              : Span(T * data, size_t size) -> Span<T>;
     212              : template <class T, size_t N>
     213              : Span(T (&databuf)[N]) -> Span<T>;
     214              : template <class T, size_t N>
     215              : Span(std::array<T, N> & data) -> Span<T>;
     216              : template <class T, size_t N>
     217              : Span(const std::array<T, N> & data) -> Span<const T>;
     218              : 
     219              : inline namespace literals {
     220              : 
     221          552 : inline constexpr Span<const char> operator""_span(const char * literal, size_t size)
     222              : {
     223          552 :     return Span<const char>(Unchecked, literal, size);
     224              : }
     225              : 
     226              : } // namespace literals
     227              : 
     228              : namespace detail {
     229              : 
     230              : // To make FixedSpan (specifically various FixedByteSpan types) default constructible
     231              : // without creating a weird "empty() == true but size() != 0" state, we need an
     232              : // appropriate sized array of zeroes. With a naive definition like
     233              : //      template <class T, size_t N> constexpr T kZero[N] {};
     234              : // we would end up with separate zero arrays for each size, and might also accidentally
     235              : // increase the read-only data size of the binary by a large amount. Instead, we define
     236              : // a per-type limit for the zero array, FixedSpan won't be default constructible for
     237              : // T / N combinations that exceed the limit. The default limit is 0.
     238              : template <class T>
     239              : struct zero_limit : std::integral_constant<size_t, 0>
     240              : {
     241              : };
     242              : 
     243              : // FixedByteSpan types up to N=65 currently need to be default-constructible.
     244              : template <>
     245              : struct zero_limit<uint8_t> : std::integral_constant<size_t, 65>
     246              : {
     247              : };
     248              : 
     249              : template <class T>
     250              : inline constexpr T kZeroes[zero_limit<T>::value]{};
     251              : 
     252              : template <class T, size_t N>
     253        56500 : constexpr T const * shared_zeroes()
     254              : {
     255              :     static_assert(N <= zero_limit<typename std::remove_const<T>::type>::value, "N exceeds zero_limit<T>");
     256        56500 :     return kZeroes<typename std::remove_const<T>::type>;
     257              : }
     258              : 
     259              : } // namespace detail
     260              : 
     261              : /**
     262              :  * Similar to a Span but with a fixed size.
     263              :  */
     264              : template <class T, size_t N>
     265              : class FixedSpan
     266              : {
     267              : public:
     268              :     using pointer   = T *;
     269              :     using reference = T &;
     270              : 
     271              :     // Creates a FixedSpan pointing to a sequence of zeroes.
     272        56500 :     constexpr FixedSpan() : mDataBuf(detail::shared_zeroes<T, N>()) {}
     273              : 
     274              :     // We want to allow construction from things that look like T*, but we want
     275              :     // to make construction from an array use the constructor that asserts the
     276              :     // array is big enough.  This requires that both constructors be templates
     277              :     // (because otherwise the non-template would be favored by overload
     278              :     // resolution, since due to decay to pointer it matches just as well as the
     279              :     // template).
     280              :     //
     281              :     // To do that we have a template constructor enabled only when the type
     282              :     // passed to it is a pointer type, and that pointer is to a type that
     283              :     // matches T's size and can convert to T*.
     284              :     template <class U,
     285              :               typename = std::enable_if_t<std::is_pointer<U>::value && sizeof(std::remove_pointer_t<U>) == sizeof(T) &&
     286              :                                           std::is_convertible<U, T *>::value>>
     287        14765 :     explicit FixedSpan(U databuf) : mDataBuf(databuf)
     288              :     {
     289        14765 :         VerifyOrDie(databuf != nullptr || N == 0); // not constexpr on some platforms
     290        14765 :     }
     291              : 
     292              :     // FixedSpan does not support an empty / null state.
     293              :     FixedSpan(std::nullptr_t null) = delete;
     294              : 
     295              :     template <class U, size_t M, typename = std::enable_if_t<sizeof(U) == sizeof(T) && std::is_convertible<U *, T *>::value>>
     296          854 :     constexpr explicit FixedSpan(U (&databuf)[M]) : mDataBuf(databuf)
     297              :     {
     298              :         static_assert(M >= N, "Passed-in buffer too small for FixedSpan");
     299          854 :     }
     300              : 
     301              :     // Creates a (potentially mutable) FixedSpan view of an std::array
     302              :     template <class U, typename = std::enable_if_t<sizeof(U) == sizeof(T) && std::is_convertible<U *, T *>::value>>
     303        60484 :     constexpr FixedSpan(std::array<U, N> & arr) : mDataBuf(arr.data())
     304        60484 :     {}
     305              : 
     306              :     template <class U, typename = std::enable_if_t<sizeof(U) == sizeof(T) && std::is_convertible<U *, T *>::value>>
     307              :     constexpr FixedSpan(std::array<U, N> && arr) = delete; // would be a view of an rvalue
     308              : 
     309              :     // Creates a FixedSpan view of an std::array
     310              :     template <class U, typename = std::enable_if_t<sizeof(U) == sizeof(T) && std::is_convertible<const U *, T *>::value>>
     311            5 :     constexpr FixedSpan(const std::array<U, N> & arr) : mDataBuf(arr.data())
     312            5 :     {}
     313              : 
     314              :     // Allow implicit construction from a FixedSpan of sufficient size over a
     315              :     // type that has the same size as ours, as long as the pointers are convertible.
     316              :     template <class U, size_t M, typename = std::enable_if_t<sizeof(U) == sizeof(T) && std::is_convertible<U *, T *>::value>>
     317           20 :     constexpr FixedSpan(FixedSpan<U, M> const & other) : mDataBuf(other.data())
     318              :     {
     319              :         static_assert(M >= N, "Passed-in FixedSpan is smaller than we are");
     320           20 :     }
     321              : 
     322        85586 :     constexpr pointer data() const { return mDataBuf; }
     323           78 :     constexpr pointer begin() const { return mDataBuf; }
     324           78 :     constexpr pointer end() const { return mDataBuf + N; }
     325              : 
     326              :     // The size of a FixedSpan is always N. There is intentially no empty() method.
     327        81112 :     static constexpr size_t size() { return N; }
     328              : 
     329              :     // Element accessors, matching the std::span API.
     330              :     // VerifyOrDie cannot be used inside a constexpr function, because it uses
     331              :     // "static" on some platforms (e.g. when CHIP_PW_TOKENIZER_LOGGING is true)
     332              :     // and that's not allowed in constexpr functions.
     333           10 :     reference operator[](size_t index) const
     334              :     {
     335           10 :         VerifyOrDie(index < N);
     336           10 :         return data()[index];
     337              :     }
     338            1 :     reference front() const { return (*this)[0]; }
     339            1 :     reference back() const { return (*this)[size() - 1]; }
     340              : 
     341         3099 :     bool data_equal(const Span<const T> & other) const
     342              :     {
     343         3099 :         return (N == other.size() && memcmp(data(), other.data(), N * sizeof(T)) == 0);
     344              :     }
     345              : 
     346              :     // operator== explicitly not implemented on FixedSpan, because its meaning
     347              :     // (equality of data, or pointing to the same buffer and same length) is
     348              :     // ambiguous.  Use data_equal if testing for equality of data.
     349              :     template <typename U>
     350              :     bool operator==(const Span<U> & other) const = delete;
     351              :     template <typename U, size_t M>
     352              :     bool operator==(const FixedSpan<U, M> & other) const = delete;
     353              : 
     354              : private:
     355              :     pointer mDataBuf;
     356              : };
     357              : 
     358              : // Template deduction guides for FixedSpan
     359              : template <typename T, size_t N>
     360              : FixedSpan(T (&)[N]) -> FixedSpan<T, N>;
     361              : template <typename T, size_t N>
     362              : FixedSpan(std::array<T, N> &) -> FixedSpan<T, N>;
     363              : template <typename T, size_t N>
     364              : FixedSpan(const std::array<T, N> &) -> FixedSpan<const T, N>;
     365              : 
     366              : template <class T>
     367              : template <class U, size_t N, typename>
     368         3983 : constexpr Span<T>::Span(const FixedSpan<U, N> & other) : mDataBuf(other.data()), mDataLen(other.size())
     369         3983 : {}
     370              : 
     371              : template <typename T>
     372              : [[deprecated("Use !empty()")]] inline bool IsSpanUsable(const Span<T> & span)
     373              : {
     374              :     return !span.empty();
     375              : }
     376              : 
     377              : template <typename T, size_t N>
     378              : [[deprecated("FixedSpan is always usable / non-empty if N > 0")]] inline bool IsSpanUsable(const FixedSpan<T, N> & span)
     379              : {
     380              :     return N > 0;
     381              : }
     382              : 
     383              : using ByteSpan        = Span<const uint8_t>;
     384              : using MutableByteSpan = Span<uint8_t>;
     385              : template <size_t N>
     386              : using FixedByteSpan = FixedSpan<const uint8_t, N>;
     387              : template <size_t N>
     388              : using MutableFixedByteSpan = FixedSpan<uint8_t, N>;
     389              : 
     390              : using CharSpan        = Span<const char>;
     391              : using MutableCharSpan = Span<char>;
     392              : 
     393         4440 : inline CHIP_ERROR CopySpanToMutableSpan(ByteSpan span_to_copy, MutableByteSpan & out_buf)
     394              : {
     395         4440 :     VerifyOrReturnError(out_buf.size() >= span_to_copy.size(), CHIP_ERROR_BUFFER_TOO_SMALL);
     396              : 
     397              :     // There is no guarantee that span_to_copy and out_buf don't overlap, so use memmove()
     398         4439 :     memmove(out_buf.data(), span_to_copy.data(), span_to_copy.size());
     399         4439 :     out_buf.reduce_size(span_to_copy.size());
     400              : 
     401         4439 :     return CHIP_NO_ERROR;
     402              : }
     403              : 
     404           26 : inline CHIP_ERROR CopyCharSpanToMutableCharSpan(CharSpan cspan_to_copy, MutableCharSpan & out_buf)
     405              : {
     406           26 :     VerifyOrReturnError(out_buf.size() >= cspan_to_copy.size(), CHIP_ERROR_BUFFER_TOO_SMALL);
     407              : 
     408              :     // There is no guarantee that cspan_to_copy and out_buf don't overlap, so use memmove()
     409           23 :     memmove(out_buf.data(), cspan_to_copy.data(), cspan_to_copy.size());
     410           23 :     out_buf.reduce_size(cspan_to_copy.size());
     411              : 
     412           23 :     return CHIP_NO_ERROR;
     413              : }
     414              : 
     415              : /**
     416              :  * Copies a CharSpan into a MutableCharSpan.
     417              :  * If the span_to_copy does not fit in out_span, span_to_copy is truncated to fit in out_span.
     418              :  * @param span_to_copy The CharSpan to copy.
     419              :  * @param out_span The MutableCharSpan in which span_to_copy is to be copied.
     420              :  */
     421          241 : inline void CopyCharSpanToMutableCharSpanWithTruncation(CharSpan span_to_copy, MutableCharSpan & out_span)
     422              : {
     423          241 :     size_t size_to_copy = std::min(span_to_copy.size(), out_span.size());
     424              : 
     425              :     // There is no guarantee that span_to_copy and out_buf don't overlap, so use memmove()
     426          241 :     memmove(out_span.data(), span_to_copy.data(), size_to_copy);
     427          241 :     out_span.reduce_size(size_to_copy);
     428          241 : }
     429              : 
     430              : } // namespace chip
        

Generated by: LCOV version 2.0-1