blob: 61c375b85041f38a58bb8f68bd515c16069d33c4 [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 <cstdint>
#include <cstdlib>
#include <string.h>
#include <type_traits>
#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) {}
constexpr Span(pointer databuf, size_t datalen) : mDataBuf(databuf), mDataLen(datalen) {}
template <class U, size_t N, typename = std::enable_if_t<sizeof(U) == sizeof(T) && std::is_convertible<U *, T *>::value>>
constexpr explicit Span(U (&databuf)[N]) : Span(databuf, 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) : 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.
// 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 < size());
return data()[index];
}
reference front() const { return (*this)[0]; }
reference back() const { return (*this)[size() - 1]; }
template <class U, typename = std::enable_if_t<std::is_same<std::remove_const_t<T>, std::remove_const_t<U>>::value>>
bool data_equal(const Span<U> & other) const
{
return (size() == other.size()) && (empty() || (memcmp(data(), other.data(), size() * sizeof(T)) == 0));
}
template <class U, size_t N, typename = std::enable_if_t<std::is_same<std::remove_const_t<T>, std::remove_const_t<U>>::value>>
inline bool data_equal(const FixedSpan<U, N> & other) const;
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);
}
// Allow creating CharSpans from a character string.
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;
private:
pointer mDataBuf;
size_t mDataLen;
};
template <class T, size_t N>
class FixedSpan
{
public:
using pointer = T *;
using reference = T &;
constexpr FixedSpan() : mDataBuf(nullptr) {}
// 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>>
constexpr explicit FixedSpan(U databuf) : mDataBuf(databuf)
{}
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");
}
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())
{}
// 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 size_t size() const { return N; }
constexpr bool empty() const { return data() == nullptr; }
constexpr pointer begin() const { return mDataBuf; }
constexpr pointer end() const { return mDataBuf + 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 < size());
return data()[index];
}
reference front() const { return (*this)[0]; }
reference back() const { return (*this)[size() - 1]; }
// Allow data_equal for spans that are over the same type up to const-ness.
template <class U, typename = std::enable_if_t<std::is_same<std::remove_const_t<T>, std::remove_const_t<U>>::value>>
bool data_equal(const FixedSpan<U, N> & other) const
{
return (empty() && other.empty()) ||
(!empty() && !other.empty() && (memcmp(data(), other.data(), size() * sizeof(T)) == 0));
}
template <class U, typename = std::enable_if_t<std::is_same<std::remove_const_t<T>, std::remove_const_t<U>>::value>>
bool data_equal(const Span<U> & other) const
{
return (size() == other.size() && (empty() || (memcmp(data(), other.data(), size() * 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) : Span(other.data(), other.size())
{}
template <class T>
template <class U, size_t N, typename>
inline bool Span<T>::data_equal(const FixedSpan<U, N> & other) const
{
return (size() == other.size()) && (empty() || (memcmp(data(), other.data(), size() * sizeof(T)) == 0));
}
/**
* @brief Returns true if the `span` could be used to access some data,
* false otherwise.
* @param[in] span The Span to validate.
*/
template <typename T>
inline bool IsSpanUsable(const Span<T> & span)
{
return (span.data() != nullptr) && (span.size() > 0);
}
/**
* @brief Returns true if the `span` could be used to access some data,
* false otherwise.
* @param[in] span The FixedSpan to validate.
*/
template <typename T, size_t N>
inline bool IsSpanUsable(const FixedSpan<T, N> & span)
{
return (span.data() != nullptr);
}
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(IsSpanUsable(span_to_copy), CHIP_ERROR_INVALID_ARGUMENT);
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(IsSpanUsable(cspan_to_copy), CHIP_ERROR_INVALID_ARGUMENT);
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