| #pragma once |
| |
| #include <app/AttributeAccessInterface.h> |
| #include <app/data-model/Decode.h> |
| #include <app/data-model/Encode.h> |
| #include <app/data-model/Nullable.h> |
| |
| #include <iterator> |
| #include <vector> |
| |
| namespace chip { |
| namespace app { |
| namespace DataModel { |
| |
| // Container requirements. |
| // To encode and decode as a list the following must be present |
| // void clear(); |
| // size_type size(); |
| // T& back(); |
| // void emplace_back(); |
| // iterator_type begin(); |
| // iterator_type end(); |
| // iterator_type must conform to LegacyForwardIterator |
| // The contained type must be default-constructible |
| template <typename X> |
| class IsList |
| { |
| typedef char yes; |
| typedef long no; |
| |
| template <typename U> |
| static yes test(decltype(&U::size)); |
| template <typename U> |
| static no test(...); |
| |
| public: |
| static constexpr bool value = sizeof(test<X>(0)) == sizeof(yes); |
| }; |
| |
| template <typename X> |
| struct IsOptionalOrNullable |
| { |
| static constexpr bool value = false; |
| }; |
| template <typename X> |
| struct IsOptionalOrNullable<Optional<X>> |
| { |
| static constexpr bool value = true; |
| }; |
| template <typename X> |
| struct IsOptionalOrNullable<Nullable<X>> |
| { |
| static constexpr bool value = true; |
| }; |
| |
| static_assert(IsList<std::vector<unsigned char>>::value, "Vector of chars must be a list"); |
| |
| template <typename X, |
| std::enable_if_t<!IsOptionalOrNullable<std::decay_t<X>>::value && !IsList<std::decay_t<X>>::value, bool> = true> |
| CHIP_ERROR Encode(const ConcreteReadAttributePath &, AttributeValueEncoder & aEncoder, const X & x) |
| { |
| return aEncoder.Encode(x); |
| } |
| |
| /* |
| * @brief |
| * Lists that are string-like should be encoded as char/byte spans. |
| */ |
| template < |
| typename X, |
| std::enable_if_t<IsList<std::decay_t<X>>::value && sizeof(std::decay_t<typename X::pointer>) == sizeof(char), bool> = true> |
| CHIP_ERROR Encode(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder, const X & x) |
| { |
| return aEncoder.Encode(Span<std::decay_t<typename X::pointer>>(x.data(), x.size())); |
| } |
| |
| /* |
| * @brief |
| * |
| * If an item is requested from a list, encode just that single item, or the entire list otherwise. |
| * |
| * The object must satisfy the following constraints |
| * size() must return an integer |
| * begin() must return a type conforming to LegacyRandomAccessIterator |
| * |
| * This is const X& instead of X&& because it is "more specialized" and so this overload will |
| * be chosen if possible. |
| */ |
| template < |
| typename X, |
| std::enable_if_t<IsList<std::decay_t<X>>::value && (sizeof(std::decay_t<typename X::pointer>) > sizeof(char)), bool> = true> |
| CHIP_ERROR Encode(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder, const X & x) |
| { |
| if (aPath.mListIndex.HasValue()) |
| { |
| uint16_t index = aPath.mListIndex.Value(); |
| if (index >= x.size()) |
| return CHIP_ERROR_INVALID_ARGUMENT; |
| |
| auto it = x.begin(); |
| std::advance(it, index); |
| return aEncoder.Encode(*it); |
| } |
| return aEncoder.EncodeList([x](const auto & encoder) { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| for (auto & v : x) |
| { |
| err = encoder.Encode(v); |
| if (err != CHIP_NO_ERROR) |
| break; |
| } |
| return err; |
| }); |
| } |
| |
| /* |
| * @brief |
| * Set of overloaded encode methods that can be called from AttributeAccessInterface::Read |
| */ |
| template <typename X> |
| CHIP_ERROR Encode(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder, const Optional<X> & x) |
| { |
| if (x.HasValue()) |
| { |
| return Encode(aPath, aEncoder, x.Value()); |
| } |
| // If no value, just do nothing. |
| return CHIP_NO_ERROR; |
| } |
| |
| /* |
| * @brief |
| * |
| * Encodes a nullable value. |
| */ |
| template <typename X> |
| CHIP_ERROR Encode(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder, const Nullable<X> & x) |
| { |
| if (x.IsNull()) |
| { |
| return aEncoder.EncodeNull(); |
| } |
| |
| // Allow sending invalid values for nullables when |
| // CONFIG_BUILD_FOR_HOST_UNIT_TEST is true, so we can test how the other side |
| // responds. |
| #if !CONFIG_BUILD_FOR_HOST_UNIT_TEST |
| if (!x.HasValidValue()) |
| { |
| return CHIP_IM_GLOBAL_STATUS(ConstraintError); |
| } |
| #endif // !CONFIG_BUILD_FOR_HOST_UNIT_TEST |
| |
| // The -Wmaybe-uninitialized warning gets confused about the fact |
| // that x.mValue is always initialized if x.IsNull() is not |
| // true, so suppress it for our access to x.Value(). |
| #pragma GCC diagnostic push |
| #if !defined(__clang__) |
| #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" |
| #endif // !defined(__clang__) |
| return Encode(aPath, aEncoder, x.Value()); |
| #pragma GCC diagnostic pop |
| } |
| |
| template <typename X, |
| std::enable_if_t<!IsOptionalOrNullable<std::decay_t<X>>::value && !IsList<std::decay_t<X>>::value, bool> = true> |
| CHIP_ERROR Decode(const ConcreteDataAttributePath &, AttributeValueDecoder & aDecoder, X & x) |
| { |
| return aDecoder.Decode(x); |
| } |
| |
| /* |
| * @brief |
| * Lists that are string-like should be decoded as char/byte spans. |
| */ |
| template < |
| typename X, |
| std::enable_if_t<IsList<std::decay_t<X>>::value && sizeof(std::decay_t<typename X::pointer>) == sizeof(char), bool> = true> |
| CHIP_ERROR Decode(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder, X & x) |
| { |
| Span<std::decay_t<typename X::pointer>> span; |
| CHIP_ERROR err = aDecoder.Decode(span); |
| if (err == CHIP_NO_ERROR) |
| { |
| x = X(span.data(), span.size()); |
| } |
| return err; |
| } |
| |
| template < |
| typename X, |
| std::enable_if_t<IsList<std::decay_t<X>>::value && (sizeof(std::decay_t<typename X::pointer>) > sizeof(char)), bool> = true> |
| CHIP_ERROR Decode(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder, X & x) |
| { |
| switch (aPath.mListOp) |
| { |
| case ConcreteDataAttributePath::ListOperation::DeleteItem: |
| if (aPath.mListIndex >= x.size()) |
| { |
| return CHIP_ERROR_INVALID_LIST_LENGTH; |
| } |
| else |
| { |
| auto it = x.begin(); |
| std::advance(it, aPath.mListIndex); |
| x.erase(it); |
| return CHIP_NO_ERROR; |
| } |
| |
| case ConcreteDataAttributePath::ListOperation::ReplaceItem: |
| if (aPath.mListIndex >= x.size()) |
| { |
| return CHIP_ERROR_INVALID_LIST_LENGTH; |
| } |
| else |
| { |
| auto it = x.begin(); |
| std::advance(it, aPath.mListIndex); |
| return aDecoder.Decode(*it); |
| } |
| |
| case ConcreteDataAttributePath::ListOperation::ReplaceAll: |
| x.clear(); |
| // fallthrough |
| default: |
| x.emplace_back(); |
| return aDecoder.Decode(x.back()); |
| } |
| } |
| |
| /* |
| * @brief |
| * |
| * Decodes an optional value (struct field, command field, event field). |
| */ |
| template <typename X> |
| CHIP_ERROR Decode(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder, Optional<X> & x) |
| { |
| // If we are calling this, it means we found the right tag, so just decode |
| // the item. |
| return Decode(aPath, aDecoder, x.HasValue() ? x.Value() : x.Emplace()); |
| } |
| |
| /* |
| * @brief |
| * |
| * Decodes a nullable value. |
| */ |
| template <typename X> |
| CHIP_ERROR Decode(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder, Nullable<X> & x) |
| { |
| if (aDecoder.WillDecodeNull()) |
| { |
| x.SetNull(); |
| return CHIP_NO_ERROR; |
| } |
| |
| // We have a value; decode it. |
| ReturnErrorOnFailure(Decode(aPath, aDecoder, x.SetNonNull())); |
| if (!x.HasValidValue()) |
| { |
| return CHIP_IM_GLOBAL_STATUS(ConstraintError); |
| } |
| return CHIP_NO_ERROR; |
| } |
| |
| } // namespace DataModel |
| } // namespace app |
| } // namespace chip |