blob: a6f63c7c59f33ef0ee06ac41914ba506afa2fd20 [file] [log] [blame]
/*
*
* Copyright (c) 2021 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 <array>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <type_traits>
#include <lib/core/Unchecked.h>
#include <lib/support/CodeUtils.h>
namespace chip {
template <class T, size_t N>
class FixedSpan;
/**
* @brief A wrapper class for holding objects and its length, without the ownership of it.
* We can use C++20 std::span once we support it, the data() and size() come from C++20 std::span.
*/
template <class T>
class Span
{
public:
using pointer = T *;
using reference = T &;
constexpr Span() : mDataBuf(nullptr), mDataLen(0) {}
// Note: VerifyOrDie cannot be used inside a constexpr function, because it uses
// "static" on some platforms (e.g. when CHIP_PW_TOKENIZER_LOGGING is true)
// and that's not allowed in constexpr functions.
Span(pointer databuf, size_t datalen) : mDataBuf(databuf), mDataLen(datalen)
{
VerifyOrDie(databuf != nullptr || datalen == 0); // not constexpr on some platforms
}
// A Span can only point to null if it is empty (size == 0). The default constructor
// should be used to construct empty Spans. All other cases involving null are invalid.
Span(std::nullptr_t null, size_t size) = delete;
// Creates a Span view of a plain array.
//
// Note that this constructor template explicitly excludes `const char[]`, see below.
template <
class U, size_t N,
std::enable_if_t<!std::is_same_v<U, const char> && sizeof(U) == sizeof(T) && std::is_convertible_v<U *, T *>, bool> = true>
constexpr explicit Span(U (&databuf)[N]) : mDataBuf(databuf), mDataLen(N)
{}
// Explicitly disallow the creation of a CharSpan from a `const char[]` to prevent the
// creation of spans from string literals that incorrectly include the trailing '\0' byte.
// If CharSpan("Hi!") were allowed, it would be a span of length 4, not 3 as intended.
//
// To create a CharSpan literal, use the `_span` operator instead, e.g. "Hi!"_span.
template <
class U, size_t N,
std::enable_if_t<std::is_same_v<U, const char> && 1 == sizeof(T) && std::is_convertible_v<const char *, T *>, bool> = true>
constexpr explicit Span(U (&databuf)[N]) = delete;
// Creates a (potentially mutable) Span view of an std::array
template <class U, size_t N, typename = std::enable_if_t<sizeof(U) == sizeof(T) && std::is_convertible<U *, T *>::value>>
constexpr Span(std::array<U, N> & arr) : mDataBuf(arr.data()), mDataLen(N)
{}
template <class U, size_t N, typename = std::enable_if_t<sizeof(U) == sizeof(T) && std::is_convertible<U *, T *>::value>>
constexpr Span(std::array<U, N> && arr) = delete; // would be a view of an rvalue
// Creates a Span view of an std::array
template <class U, size_t N, typename = std::enable_if_t<sizeof(U) == sizeof(T) && std::is_convertible<const U *, T *>::value>>
constexpr Span(const std::array<U, N> & arr) : mDataBuf(arr.data()), mDataLen(N)
{}
template <size_t N>
constexpr Span & operator=(T (&databuf)[N])
{
mDataBuf = databuf;
mDataLen = N;
return (*this);
}
// Allow implicit construction from a Span over a type that matches our
// type's size, if a pointer to the other type can be treated as a pointer
// to our type (e.g. other type is same as ours, or is a same-size
// subclass). The size check is really important to make sure we don't get
// confused about where our object boundaries are.
template <class U, typename = std::enable_if_t<sizeof(U) == sizeof(T) && std::is_convertible<U *, T *>::value>>
constexpr Span(const Span<U> & other) : Span(other.data(), other.size())
{}
// Allow implicit construction from a FixedSpan over a type that matches our
// type's size, if a pointer to the other type can be treated as a pointer
// to our type (e.g. other type is same as ours, or is a same-size
// subclass). The size check is really important to make sure we don't get
// confused about where our object boundaries are.
template <class U, size_t N, typename = std::enable_if_t<sizeof(U) == sizeof(T) && std::is_convertible<U *, T *>::value>>
constexpr inline Span(const FixedSpan<U, N> & other);
constexpr pointer data() const { return mDataBuf; }
constexpr size_t size() const { return mDataLen; }
constexpr bool empty() const { return size() == 0; }
constexpr pointer begin() const { return data(); }
constexpr pointer end() const { return data() + size(); }
// Element accessors, matching the std::span API.
reference operator[](size_t index) const
{
VerifyOrDie(index < size()); // not constexpr on some platforms
return data()[index];
}
reference front() const { return (*this)[0]; }
reference back() const { return (*this)[size() - 1]; }
bool data_equal(const Span<const T> & other) const
{
return (size() == other.size()) && (empty() || (memcmp(data(), other.data(), size() * sizeof(T)) == 0));
}
Span SubSpan(size_t offset, size_t length) const
{
VerifyOrDie(offset <= mDataLen);
VerifyOrDie(length <= mDataLen - offset);
return Span(mDataBuf + offset, length);
}
Span SubSpan(size_t offset) const
{
VerifyOrDie(offset <= mDataLen);
return Span(mDataBuf + offset, mDataLen - offset);
}
// Allow reducing the size of a span.
void reduce_size(size_t new_size)
{
VerifyOrDie(new_size <= size());
mDataLen = new_size;
}
// Allow creating ByteSpans and CharSpans from ZCL octet strings, so we
// don't have to reinvent it various places.
template <class U,
typename = std::enable_if_t<std::is_same<uint8_t, std::remove_const_t<U>>::value &&
(std::is_same<const uint8_t, T>::value || std::is_same<const char, T>::value)>>
static Span fromZclString(U * bytes)
{
size_t length = bytes[0];
// Treat 0xFF (aka "null string") as zero-length.
if (length == 0xFF)
{
length = 0;
}
// Need reinterpret_cast if we're a CharSpan.
return Span(reinterpret_cast<T *>(&bytes[1]), length);
}
// Creates a CharSpan from a null-terminated C character string.
//
// Note that for string literals, the user-defined `_span` string
// literal operator should be used instead, e.g. `"Hello"_span`.
template <class U, typename = std::enable_if_t<std::is_same<T, const U>::value && std::is_same<const char, T>::value>>
static Span fromCharString(U * chars)
{
return Span(chars, strlen(chars));
}
// operator== explicitly not implemented on Span, because its meaning
// (equality of data, or pointing to the same buffer and same length) is
// ambiguous. Use data_equal if testing for equality of data.
template <typename U>
bool operator==(const Span<U> & other) const = delete;
// Creates a Span without checking whether databuf is a null pointer.
//
// Note: The normal (checked) constructor should be used for general use;
// this overload exists for special use cases where databuf is guaranteed
// to be valid (not null) and a constexpr constructor is required.
//
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61648 prevents making
// operator""_span a friend (and this constructor private).
constexpr Span(UncheckedType tag, pointer databuf, size_t datalen) : mDataBuf(databuf), mDataLen(datalen) {}
private:
pointer mDataBuf;
size_t mDataLen;
};
inline namespace literals {
inline constexpr Span<const char> operator"" _span(const char * literal, size_t size)
{
return Span<const char>(Unchecked, literal, size);
}
} // namespace literals
namespace detail {
// To make FixedSpan (specifically various FixedByteSpan types) default constructible
// without creating a weird "empty() == true but size() != 0" state, we need an
// appropriate sized array of zeroes. With a naive definition like
// template <class T, size_t N> constexpr T kZero[N] {};
// we would end up with separate zero arrays for each size, and might also accidentally
// increase the read-only data size of the binary by a large amount. Instead, we define
// a per-type limit for the zero array, FixedSpan won't be default constructible for
// T / N combinations that exceed the limit. The default limit is 0.
template <class T>
struct zero_limit : std::integral_constant<size_t, 0>
{
};
// FixedByteSpan types up to N=65 currently need to be default-constructible.
template <>
struct zero_limit<uint8_t> : std::integral_constant<size_t, 65>
{
};
template <class T>
inline constexpr T kZeroes[zero_limit<T>::value]{};
template <class T, size_t N>
constexpr T const * shared_zeroes()
{
static_assert(N <= zero_limit<typename std::remove_const<T>::type>::value, "N exceeds zero_limit<T>");
return kZeroes<typename std::remove_const<T>::type>;
}
} // namespace detail
/**
* Similar to a Span but with a fixed size.
*/
template <class T, size_t N>
class FixedSpan
{
public:
using pointer = T *;
using reference = T &;
// Creates a FixedSpan pointing to a sequence of zeroes.
constexpr FixedSpan() : mDataBuf(detail::shared_zeroes<T, N>()) {}
// We want to allow construction from things that look like T*, but we want
// to make construction from an array use the constructor that asserts the
// array is big enough. This requires that both constructors be templates
// (because otherwise the non-template would be favored by overload
// resolution, since due to decay to pointer it matches just as well as the
// template).
//
// To do that we have a template constructor enabled only when the type
// passed to it is a pointer type, and that pointer is to a type that
// matches T's size and can convert to T*.
template <class U,
typename = std::enable_if_t<std::is_pointer<U>::value && sizeof(std::remove_pointer_t<U>) == sizeof(T) &&
std::is_convertible<U, T *>::value>>
explicit FixedSpan(U databuf) : mDataBuf(databuf)
{
VerifyOrDie(databuf != nullptr || N == 0); // not constexpr on some platforms
}
// FixedSpan does not support an empty / null state.
FixedSpan(std::nullptr_t null) = delete;
template <class U, size_t M, typename = std::enable_if_t<sizeof(U) == sizeof(T) && std::is_convertible<U *, T *>::value>>
constexpr explicit FixedSpan(U (&databuf)[M]) : mDataBuf(databuf)
{
static_assert(M >= N, "Passed-in buffer too small for FixedSpan");
}
// Creates a (potentially mutable) FixedSpan view of an std::array
template <class U, typename = std::enable_if_t<sizeof(U) == sizeof(T) && std::is_convertible<U *, T *>::value>>
constexpr FixedSpan(std::array<U, N> & arr) : mDataBuf(arr.data())
{}
template <class U, typename = std::enable_if_t<sizeof(U) == sizeof(T) && std::is_convertible<U *, T *>::value>>
constexpr FixedSpan(std::array<U, N> && arr) = delete; // would be a view of an rvalue
// Creates a FixedSpan view of an std::array
template <class U, typename = std::enable_if_t<sizeof(U) == sizeof(T) && std::is_convertible<const U *, T *>::value>>
constexpr FixedSpan(const std::array<U, N> & arr) : mDataBuf(arr.data())
{}
// Allow implicit construction from a FixedSpan of sufficient size over a
// type that has the same size as ours, as long as the pointers are convertible.
template <class U, size_t M, typename = std::enable_if_t<sizeof(U) == sizeof(T) && std::is_convertible<U *, T *>::value>>
constexpr FixedSpan(FixedSpan<U, M> const & other) : mDataBuf(other.data())
{
static_assert(M >= N, "Passed-in FixedSpan is smaller than we are");
}
constexpr pointer data() const { return mDataBuf; }
constexpr pointer begin() const { return mDataBuf; }
constexpr pointer end() const { return mDataBuf + N; }
// The size of a FixedSpan is always N. There is intentially no empty() method.
static constexpr size_t size() { return N; }
// Element accessors, matching the std::span API.
// VerifyOrDie cannot be used inside a constexpr function, because it uses
// "static" on some platforms (e.g. when CHIP_PW_TOKENIZER_LOGGING is true)
// and that's not allowed in constexpr functions.
reference operator[](size_t index) const
{
VerifyOrDie(index < N);
return data()[index];
}
reference front() const { return (*this)[0]; }
reference back() const { return (*this)[size() - 1]; }
bool data_equal(const Span<const T> & other) const
{
return (N == other.size() && memcmp(data(), other.data(), N * sizeof(T)) == 0);
}
// operator== explicitly not implemented on FixedSpan, because its meaning
// (equality of data, or pointing to the same buffer and same length) is
// ambiguous. Use data_equal if testing for equality of data.
template <typename U>
bool operator==(const Span<U> & other) const = delete;
template <typename U, size_t M>
bool operator==(const FixedSpan<U, M> & other) const = delete;
private:
pointer mDataBuf;
};
template <class T>
template <class U, size_t N, typename>
constexpr Span<T>::Span(const FixedSpan<U, N> & other) : mDataBuf(other.data()), mDataLen(other.size())
{}
template <typename T>
[[deprecated("Use !empty()")]] inline bool IsSpanUsable(const Span<T> & span)
{
return !span.empty();
}
template <typename T, size_t N>
[[deprecated("FixedSpan is always usable / non-empty if N > 0")]] inline bool IsSpanUsable(const FixedSpan<T, N> & span)
{
return N > 0;
}
using ByteSpan = Span<const uint8_t>;
using MutableByteSpan = Span<uint8_t>;
template <size_t N>
using FixedByteSpan = FixedSpan<const uint8_t, N>;
using CharSpan = Span<const char>;
using MutableCharSpan = Span<char>;
inline CHIP_ERROR CopySpanToMutableSpan(ByteSpan span_to_copy, MutableByteSpan & out_buf)
{
VerifyOrReturnError(out_buf.size() >= span_to_copy.size(), CHIP_ERROR_BUFFER_TOO_SMALL);
memcpy(out_buf.data(), span_to_copy.data(), span_to_copy.size());
out_buf.reduce_size(span_to_copy.size());
return CHIP_NO_ERROR;
}
inline CHIP_ERROR CopyCharSpanToMutableCharSpan(CharSpan cspan_to_copy, MutableCharSpan & out_buf)
{
VerifyOrReturnError(out_buf.size() >= cspan_to_copy.size(), CHIP_ERROR_BUFFER_TOO_SMALL);
memcpy(out_buf.data(), cspan_to_copy.data(), cspan_to_copy.size());
out_buf.reduce_size(cspan_to_copy.size());
return CHIP_NO_ERROR;
}
} // namespace chip