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
|