From 1379624f49c129209667323b8a0fb94d75734424 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Thu, 24 Oct 2024 14:49:12 -0400 Subject: [PATCH] Use custom ember-buffer decode on codegen data model Read as well. (#36229) * Use EmberAttributeDataBuffer for codegen provider _Read * Fix comments * Restyled by clang-format --------- Co-authored-by: Andrei Litvin Co-authored-by: Restyled.io --- .../CodegenDataModelProvider_Read.cpp | 175 +----- .../EmberAttributeDataBuffer.cpp | 250 +++++++- .../EmberAttributeDataBuffer.h | 31 +- .../tests/TestEmberAttributeDataBuffer.cpp | 542 +++++++++++++++++- 4 files changed, 822 insertions(+), 176 deletions(-) diff --git a/src/app/codegen-data-model-provider/CodegenDataModelProvider_Read.cpp b/src/app/codegen-data-model-provider/CodegenDataModelProvider_Read.cpp index 6ce5d282f89d30..ea35356391d63d 100644 --- a/src/app/codegen-data-model-provider/CodegenDataModelProvider_Read.cpp +++ b/src/app/codegen-data-model-provider/CodegenDataModelProvider_Read.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -41,6 +42,7 @@ #include #include #include +#include #include @@ -85,173 +87,6 @@ std::optional TryReadViaAccessInterface(const ConcreteAttributePath return encoder.TriedEncode() ? std::make_optional(CHIP_NO_ERROR) : std::nullopt; } -/// Metadata of what a ember/pascal short string means (prepended by a u8 length) -struct ShortPascalString -{ - using LengthType = uint8_t; - static constexpr LengthType kNullLength = 0xFF; - - static size_t GetLength(ByteSpan buffer) - { - VerifyOrDie(buffer.size() >= 1); - // NOTE: we do NOT use emberAfStringLength from ember-strings.h because that will result in 0 - // length for null sizes (i.e. 0xFF is translated to 0 and we do not want that here) - return buffer[0]; - } -}; - -/// Metadata of what a ember/pascal LONG string means (prepended by a u16 length) -struct LongPascalString -{ - using LengthType = uint16_t; - static constexpr LengthType kNullLength = 0xFFFF; - - static size_t GetLength(ByteSpan buffer) - { - // NOTE: we do NOT use emberAfLongStringLength from ember-strings.h because that will result in 0 - // length for null sizes (i.e. 0xFFFF is translated to 0 and we do not want that here) - VerifyOrDie(buffer.size() >= 2); - const uint8_t * data = buffer.data(); - return Encoding::LittleEndian::Read16(data); - } -}; - -// ember assumptions ... should just work -static_assert(sizeof(ShortPascalString::LengthType) == 1); -static_assert(sizeof(LongPascalString::LengthType) == 2); - -/// Given a ByteSpan containing data from ember, interpret it -/// as a span of type OUT (i.e. ByteSpan or CharSpan) given a ENCODING -/// where ENCODING is Short or Long pascal strings. -template -std::optional ExtractEmberString(ByteSpan data) -{ - constexpr size_t kLengthTypeSize = sizeof(typename ENCODING::LengthType); - VerifyOrDie(kLengthTypeSize <= data.size()); - auto len = ENCODING::GetLength(data); - - if (len == ENCODING::kNullLength) - { - return std::nullopt; - } - - VerifyOrDie(len + sizeof(len) <= data.size()); - return std::make_optional(reinterpret_cast(data.data() + kLengthTypeSize), len); -} - -/// Encode a value inside `encoder` -/// -/// The value encoded will be of type T (e.g. CharSpan or ByteSpan) and it will be decoded -/// via the given ENCODING (i.e. ShortPascalString or LongPascalString) -/// -/// isNullable defines if the value of NULL is allowed to be encoded. -template -CHIP_ERROR EncodeStringLike(ByteSpan data, bool isNullable, AttributeValueEncoder & encoder) -{ - std::optional value = ExtractEmberString(data); - if (!value.has_value()) - { - if (isNullable) - { - return encoder.EncodeNull(); - } - return CHIP_ERROR_INCORRECT_STATE; - } - - // encode value as-is - return encoder.Encode(*value); -} - -/// Encodes a numeric data value of type T from the given ember-encoded buffer `data`. -/// -/// isNullable defines if the value of NULL is allowed to be encoded. -template -CHIP_ERROR EncodeFromSpan(ByteSpan data, bool isNullable, AttributeValueEncoder & encoder) -{ - typename NumericAttributeTraits::StorageType value; - - VerifyOrReturnError(data.size() >= sizeof(value), CHIP_ERROR_INVALID_ARGUMENT); - memcpy(&value, data.data(), sizeof(value)); - - if (isNullable && NumericAttributeTraits::IsNullValue(value)) - { - return encoder.EncodeNull(); - } - - if (!NumericAttributeTraits::CanRepresentValue(isNullable, value)) - { - return CHIP_ERROR_INCORRECT_STATE; - } - - return encoder.Encode(NumericAttributeTraits::StorageToWorking(value)); -} - -/// Converts raw ember data from `data` into the encoder -/// -/// Uses the attribute `metadata` to determine how the data is encoded into `data` and -/// write a suitable value into `encoder`. -CHIP_ERROR EncodeEmberValue(ByteSpan data, const EmberAfAttributeMetadata * metadata, AttributeValueEncoder & encoder) -{ - VerifyOrReturnError(metadata != nullptr, CHIP_ERROR_INVALID_ARGUMENT); - - const bool isNullable = metadata->IsNullable(); - - switch (AttributeBaseType(metadata->attributeType)) - { - case ZCL_NO_DATA_ATTRIBUTE_TYPE: // No data - return encoder.EncodeNull(); - case ZCL_BOOLEAN_ATTRIBUTE_TYPE: // Boolean - return EncodeFromSpan(data, isNullable, encoder); - case ZCL_INT8U_ATTRIBUTE_TYPE: // Unsigned 8-bit integer - return EncodeFromSpan(data, isNullable, encoder); - case ZCL_INT16U_ATTRIBUTE_TYPE: // Unsigned 16-bit integer - return EncodeFromSpan(data, isNullable, encoder); - case ZCL_INT24U_ATTRIBUTE_TYPE: // Unsigned 24-bit integer - return EncodeFromSpan>(data, isNullable, encoder); - case ZCL_INT32U_ATTRIBUTE_TYPE: // Unsigned 32-bit integer - return EncodeFromSpan(data, isNullable, encoder); - case ZCL_INT40U_ATTRIBUTE_TYPE: // Unsigned 40-bit integer - return EncodeFromSpan>(data, isNullable, encoder); - case ZCL_INT48U_ATTRIBUTE_TYPE: // Unsigned 48-bit integer - return EncodeFromSpan>(data, isNullable, encoder); - case ZCL_INT56U_ATTRIBUTE_TYPE: // Unsigned 56-bit integer - return EncodeFromSpan>(data, isNullable, encoder); - case ZCL_INT64U_ATTRIBUTE_TYPE: // Unsigned 64-bit integer - return EncodeFromSpan(data, isNullable, encoder); - case ZCL_INT8S_ATTRIBUTE_TYPE: // Signed 8-bit integer - return EncodeFromSpan(data, isNullable, encoder); - case ZCL_INT16S_ATTRIBUTE_TYPE: // Signed 16-bit integer - return EncodeFromSpan(data, isNullable, encoder); - case ZCL_INT24S_ATTRIBUTE_TYPE: // Signed 24-bit integer - return EncodeFromSpan>(data, isNullable, encoder); - case ZCL_INT32S_ATTRIBUTE_TYPE: // Signed 32-bit integer - return EncodeFromSpan(data, isNullable, encoder); - case ZCL_INT40S_ATTRIBUTE_TYPE: // Signed 40-bit integer - return EncodeFromSpan>(data, isNullable, encoder); - case ZCL_INT48S_ATTRIBUTE_TYPE: // Signed 48-bit integer - return EncodeFromSpan>(data, isNullable, encoder); - case ZCL_INT56S_ATTRIBUTE_TYPE: // Signed 56-bit integer - return EncodeFromSpan>(data, isNullable, encoder); - case ZCL_INT64S_ATTRIBUTE_TYPE: // Signed 64-bit integer - return EncodeFromSpan(data, isNullable, encoder); - case ZCL_SINGLE_ATTRIBUTE_TYPE: // 32-bit float - return EncodeFromSpan(data, isNullable, encoder); - case ZCL_DOUBLE_ATTRIBUTE_TYPE: // 64-bit float - return EncodeFromSpan(data, isNullable, encoder); - case ZCL_CHAR_STRING_ATTRIBUTE_TYPE: // Char string - return EncodeStringLike(data, isNullable, encoder); - case ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE: - return EncodeStringLike(data, isNullable, encoder); - case ZCL_OCTET_STRING_ATTRIBUTE_TYPE: // Octet string - return EncodeStringLike(data, isNullable, encoder); - case ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE: - return EncodeStringLike(data, isNullable, encoder); - default: - ChipLogError(DataManagement, "Attribute type 0x%x not handled", static_cast(metadata->attributeType)); - return CHIP_IM_GLOBAL_STATUS(Failure); - } -} - } // namespace /// separated-out ReadAttribute implementation (given existing complexity) @@ -343,7 +178,11 @@ DataModel::ActionReturnStatus CodegenDataModelProvider::ReadAttribute(const Data return CHIP_ERROR_IM_GLOBAL_STATUS_VALUE(status); } - return EncodeEmberValue(gEmberAttributeIOBufferSpan, attributeMetadata, encoder); + VerifyOrReturnError(attributeMetadata != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + + MutableByteSpan data = gEmberAttributeIOBufferSpan; + Ember::EmberAttributeDataBuffer emberData(attributeMetadata, data); + return encoder.Encode(emberData); } } // namespace app diff --git a/src/app/codegen-data-model-provider/EmberAttributeDataBuffer.cpp b/src/app/codegen-data-model-provider/EmberAttributeDataBuffer.cpp index ab8891657fde50..2c764241ebf3d8 100644 --- a/src/app/codegen-data-model-provider/EmberAttributeDataBuffer.cpp +++ b/src/app/codegen-data-model-provider/EmberAttributeDataBuffer.cpp @@ -17,14 +17,18 @@ #include #include +#include #include #include #include +#include #include +#include #include #include #include +#include #include namespace chip { @@ -43,7 +47,7 @@ constexpr uint32_t MaxLength(EmberAttributeDataBuffer::PascalStringType s) { return std::numeric_limits::max() - 1; } - // EmberAttributeBuffer::PascalStringType::kLong: + // EmberAttributeDataBuffer::PascalStringType::kLong: return std::numeric_limits::max() - 1; } @@ -116,6 +120,55 @@ constexpr SignedDecodeInfo GetSignedDecodeInfo(EmberAfAttributeType type) chipDie(); } +/// Encodes the string of type stringType pointed to by `reader` into the TLV `writer`. +/// Then encoded string will be at tag `tag` and of type `tlvType` +CHIP_ERROR EncodeString(EmberAttributeDataBuffer::PascalStringType stringType, TLV::TLVType tlvType, TLV::TLVWriter & writer, + TLV::Tag tag, EmberAttributeDataBuffer::EndianReader & reader, bool nullable) +{ + unsigned stringLen; + if (stringType == EmberAttributeDataBuffer::PascalStringType::kShort) + { + uint8_t len; + if (!reader.Read8(&len).IsSuccess()) + { + return reader.StatusCode(); + } + if (len == NumericAttributeTraits::kNullValue) + { + VerifyOrReturnError(nullable, CHIP_ERROR_INVALID_ARGUMENT); + return writer.PutNull(tag); + } + stringLen = len; + } + else + { + uint16_t len; + if (!reader.Read16(&len).IsSuccess()) + { + return reader.StatusCode(); + } + if (len == NumericAttributeTraits::kNullValue) + { + VerifyOrReturnError(nullable, CHIP_ERROR_INVALID_ARGUMENT); + return writer.PutNull(tag); + } + stringLen = len; + } + + const uint8_t * data; + if (!reader.ZeroCopyProcessBytes(stringLen, &data).IsSuccess()) + { + return reader.StatusCode(); + } + + if (tlvType == TLV::kTLVType_UTF8String) + { + return writer.PutString(tag, reinterpret_cast(data), stringLen); + } + + return writer.PutBytes(tag, data, stringLen); +} + } // namespace CHIP_ERROR EmberAttributeDataBuffer::DecodeUnsignedInteger(chip::TLV::TLVReader & reader, EndianWriter & writer) @@ -191,6 +244,7 @@ CHIP_ERROR EmberAttributeDataBuffer::DecodeAsString(chip::TLV::TLVReader & reade writer.Put16(NumericAttributeTraits::kNullValue); break; } + return CHIP_NO_ERROR; } @@ -321,6 +375,200 @@ CHIP_ERROR EmberAttributeDataBuffer::Decode(chip::TLV::TLVReader & reader) return CHIP_NO_ERROR; } +CHIP_ERROR EmberAttributeDataBuffer::EncodeInteger(chip::TLV::TLVWriter & writer, TLV::Tag tag, EndianReader & reader) const +{ + // Encodes an integer by first reading as raw bytes and then + // bitshift-convert + // + // This optimizes code size rather than readability at this point. + + uint8_t raw_bytes[8]; + + bool isSigned = (mAttributeType == ZCL_INT8S_ATTRIBUTE_TYPE) // + || (mAttributeType == ZCL_INT16S_ATTRIBUTE_TYPE) // + || (mAttributeType == ZCL_INT24S_ATTRIBUTE_TYPE) // + || (mAttributeType == ZCL_INT32S_ATTRIBUTE_TYPE) // + || (mAttributeType == ZCL_INT40S_ATTRIBUTE_TYPE) // + || (mAttributeType == ZCL_INT48S_ATTRIBUTE_TYPE) // + || (mAttributeType == ZCL_INT56S_ATTRIBUTE_TYPE) // + || (mAttributeType == ZCL_INT64S_ATTRIBUTE_TYPE); + + unsigned byteCount; + uint64_t nullValue; + + if (isSigned) + { + const SignedDecodeInfo info = GetSignedDecodeInfo(mAttributeType); + byteCount = info.byteCount; + nullValue = static_cast(info.minValue); // just a bit cast for easy compare + } + else + { + const UnsignedDecodeInfo info = GetUnsignedDecodeInfo(mAttributeType); + byteCount = info.byteCount; + nullValue = info.maxValue; + } + + VerifyOrDie(sizeof(raw_bytes) >= byteCount); + if (!reader.ReadBytes(raw_bytes, byteCount).IsSuccess()) + { + return reader.StatusCode(); + } + + // At this point, RAW_VALUE contains the actual value, need to make it "real" + union + { + int64_t int_value; + uint64_t uint_value; + } value; + + value.uint_value = 0; + +#if CHIP_CONFIG_BIG_ENDIAN_TARGET + bool isNegative = isSigned && (raw_bytes[0] >= 0x80); + if (isNegative) + { + value.int_value = -1; + } + for (int i = 0; i < static_cast(byteCount); i++) + { +#else + bool isNegative = isSigned && (raw_bytes[byteCount - 1] >= 0x80); + if (isNegative) + { + value.int_value = -1; + } + for (int i = static_cast(byteCount) - 1; i >= 0; i--) + { +#endif + value.uint_value <<= 8; + value.uint_value = (value.uint_value & ~0xFFULL) | raw_bytes[i]; + } + + if (mIsNullable && (value.uint_value == nullValue)) + { + // MaxValue is used for NULL setting + return writer.PutNull(tag); + } + + switch (mAttributeType) + { + case ZCL_INT8U_ATTRIBUTE_TYPE: // Unsigned 8-bit integer + return writer.Put(tag, static_cast(value.uint_value)); + case ZCL_INT16U_ATTRIBUTE_TYPE: // Unsigned 16-bit integer + return writer.Put(tag, static_cast(value.uint_value)); + case ZCL_INT24U_ATTRIBUTE_TYPE: // Unsigned 24-bit integer + case ZCL_INT32U_ATTRIBUTE_TYPE: // Unsigned 32-bit integer + return writer.Put(tag, static_cast(value.uint_value)); + case ZCL_INT40U_ATTRIBUTE_TYPE: // Unsigned 40-bit integer + case ZCL_INT48U_ATTRIBUTE_TYPE: // Unsigned 48-bit integer + case ZCL_INT56U_ATTRIBUTE_TYPE: // Signed 56-bit integer + case ZCL_INT64U_ATTRIBUTE_TYPE: // Signed 64-bit integer + return writer.Put(tag, static_cast(value.uint_value)); + case ZCL_INT8S_ATTRIBUTE_TYPE: // Signed 8-bit integer + return writer.Put(tag, static_cast(value.int_value)); + case ZCL_INT16S_ATTRIBUTE_TYPE: // Signed 16-bit integer + return writer.Put(tag, static_cast(value.int_value)); + case ZCL_INT24S_ATTRIBUTE_TYPE: // Signed 24-bit integer + case ZCL_INT32S_ATTRIBUTE_TYPE: // Signed 32-bit integer + return writer.Put(tag, static_cast(value.int_value)); + default: + return writer.Put(tag, static_cast(value.int_value)); + } +} + +CHIP_ERROR EmberAttributeDataBuffer::Encode(chip::TLV::TLVWriter & writer, TLV::Tag tag) const +{ + EndianReader endianReader(mDataBuffer.data(), mDataBuffer.size()); + + switch (mAttributeType) + { + case ZCL_NO_DATA_ATTRIBUTE_TYPE: // No data + return writer.PutNull(tag); + case ZCL_BOOLEAN_ATTRIBUTE_TYPE: { // Boolean + uint8_t value; + if (!endianReader.Read8(&value).IsSuccess()) + { + return endianReader.StatusCode(); + } + switch (value) + { + case 0: + case 1: + return writer.PutBoolean(tag, value != 0); + case 0xFF: + return writer.PutNull(tag); + default: + // Unknown types + return CHIP_ERROR_INCORRECT_STATE; + } + } + case ZCL_INT8U_ATTRIBUTE_TYPE: // Unsigned 8-bit integer + case ZCL_INT16U_ATTRIBUTE_TYPE: // Unsigned 16-bit integer + case ZCL_INT24U_ATTRIBUTE_TYPE: // Unsigned 24-bit integer + case ZCL_INT32U_ATTRIBUTE_TYPE: // Unsigned 32-bit integer + case ZCL_INT40U_ATTRIBUTE_TYPE: // Unsigned 40-bit integer + case ZCL_INT48U_ATTRIBUTE_TYPE: // Unsigned 48-bit integer + case ZCL_INT56U_ATTRIBUTE_TYPE: // Unsigned 56-bit integer + case ZCL_INT64U_ATTRIBUTE_TYPE: // Unsigned 64-bit integer + case ZCL_INT8S_ATTRIBUTE_TYPE: // Signed 8-bit integer + case ZCL_INT16S_ATTRIBUTE_TYPE: // Signed 16-bit integer + case ZCL_INT24S_ATTRIBUTE_TYPE: // Signed 24-bit integer + case ZCL_INT32S_ATTRIBUTE_TYPE: // Signed 32-bit integer + case ZCL_INT40S_ATTRIBUTE_TYPE: // Signed 40-bit integer + case ZCL_INT48S_ATTRIBUTE_TYPE: // Signed 48-bit integer + case ZCL_INT56S_ATTRIBUTE_TYPE: // Signed 56-bit integer + case ZCL_INT64S_ATTRIBUTE_TYPE: // Signed 64-bit integer + return EncodeInteger(writer, tag, endianReader); + case ZCL_SINGLE_ATTRIBUTE_TYPE: { // 32-bit float + union + { + uint8_t raw[sizeof(float)]; + float value; + } value; + + if (!endianReader.ReadBytes(value.raw, sizeof(value)).IsSuccess()) + { + return endianReader.StatusCode(); + } + if (NumericAttributeTraits::IsNullValue(value.value)) + { + return writer.PutNull(tag); + } + return writer.Put(tag, value.value); + } + case ZCL_DOUBLE_ATTRIBUTE_TYPE: { // 64-bit float + union + { + uint8_t raw[sizeof(double)]; + double value; + } value; + + if (!endianReader.ReadBytes(value.raw, sizeof(value)).IsSuccess()) + { + return endianReader.StatusCode(); + } + if (NumericAttributeTraits::IsNullValue(value.value)) + { + return writer.PutNull(tag); + } + return writer.Put(tag, value.value); + } + + case ZCL_CHAR_STRING_ATTRIBUTE_TYPE: // Char string + return EncodeString(PascalStringType::kShort, TLV::kTLVType_UTF8String, writer, tag, endianReader, mIsNullable); + case ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE: + return EncodeString(PascalStringType::kLong, TLV::kTLVType_UTF8String, writer, tag, endianReader, mIsNullable); + case ZCL_OCTET_STRING_ATTRIBUTE_TYPE: // Octet string + return EncodeString(PascalStringType::kShort, TLV::kTLVType_ByteString, writer, tag, endianReader, mIsNullable); + case ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE: + return EncodeString(PascalStringType::kLong, TLV::kTLVType_ByteString, writer, tag, endianReader, mIsNullable); + default: + ChipLogError(DataManagement, "Attribute type 0x%x not handled", static_cast(mAttributeType)); + return CHIP_IM_GLOBAL_STATUS(Failure); + } +} + } // namespace Ember } // namespace app } // namespace chip diff --git a/src/app/codegen-data-model-provider/EmberAttributeDataBuffer.h b/src/app/codegen-data-model-provider/EmberAttributeDataBuffer.h index a4f24c78c0476b..c3d7acfcafb72b 100644 --- a/src/app/codegen-data-model-provider/EmberAttributeDataBuffer.h +++ b/src/app/codegen-data-model-provider/EmberAttributeDataBuffer.h @@ -16,10 +16,12 @@ */ #pragma once +#include "lib/core/TLVWriter.h" #include #include #include #include +#include #include #include @@ -39,6 +41,14 @@ namespace Ember { class EmberAttributeDataBuffer { public: +#if CHIP_CONFIG_BIG_ENDIAN_TARGET + using EndianWriter = Encoding::BigEndian::BufferWriter; + using EndianReader = Encoding::BigEndian::Reader; +#else + using EndianWriter = Encoding::LittleEndian::BufferWriter; + using EndianReader = Encoding::LittleEndian::Reader; +#endif + enum class PascalStringType { kShort, @@ -59,12 +69,14 @@ class EmberAttributeDataBuffer /// modified by this call. CHIP_ERROR Decode(chip::TLV::TLVReader & reader); + /// Writes the data encoded in the underlying buffer into the given `writer` + /// + /// The data in the internal data buffer is assumed to be already formatted correctly + /// HOWEVER the size inside it will not be fully considered (i.e. encoding will use + /// the data encoding line integer or string sizes and NOT the databuffer max size) + CHIP_ERROR Encode(chip::TLV::TLVWriter & writer, TLV::Tag tag) const; + private: -#if CHIP_CONFIG_BIG_ENDIAN_TARGET - using EndianWriter = Encoding::BigEndian::BufferWriter; -#else - using EndianWriter = Encoding::LittleEndian::BufferWriter; -#endif /// Decodes the UNSIGNED integer stored in `reader` and places its content into `writer` /// Takes into account internal mIsNullable. CHIP_ERROR DecodeUnsignedInteger(chip::TLV::TLVReader & reader, EndianWriter & writer); @@ -73,6 +85,10 @@ class EmberAttributeDataBuffer /// Takes into account internal mIsNullable. CHIP_ERROR DecodeSignedInteger(chip::TLV::TLVReader & reader, EndianWriter & writer); + /// Encodes the UNSIGNED integer into `writer`. + /// Takes into account internal mIsNullable. + CHIP_ERROR EncodeInteger(chip::TLV::TLVWriter & writer, TLV::Tag tag, EndianReader & reader) const; + /// Decodes the string/byte string contained in `reader` and stores it into `writer`. /// String is encoded using a pascal-prefix of size `stringType`. /// Takes into account internal mIsNullable. @@ -96,6 +112,11 @@ inline CHIP_ERROR Decode(TLV::TLVReader & reader, Ember::EmberAttributeDataBuffe return buffer.Decode(reader); } +inline CHIP_ERROR Encode(TLV::TLVWriter & writer, TLV::Tag tag, Ember::EmberAttributeDataBuffer & buffer) +{ + return buffer.Encode(writer, tag); +} + } // namespace DataModel } // namespace app } // namespace chip diff --git a/src/app/codegen-data-model-provider/tests/TestEmberAttributeDataBuffer.cpp b/src/app/codegen-data-model-provider/tests/TestEmberAttributeDataBuffer.cpp index 7d46360029c529..dc83773454d1ff 100644 --- a/src/app/codegen-data-model-provider/tests/TestEmberAttributeDataBuffer.cpp +++ b/src/app/codegen-data-model-provider/tests/TestEmberAttributeDataBuffer.cpp @@ -101,6 +101,56 @@ class EncodeResult std::optional mResult; }; +template +bool IsEqual(const T & a, const T & b) +{ + return a == b; +} + +template <> +bool IsEqual(const ByteSpan & a, const ByteSpan & b) +{ + return a.data_equal(b); +} + +template <> +bool IsEqual(const CharSpan & a, const CharSpan & b) +{ + return a.data_equal(b); +} + +template +bool IsEqual(const std::optional & a, const std::optional & b) +{ + if (a.has_value() != b.has_value()) + { + return false; + } + + if (!a.has_value()) + { + return true; + } + + return IsEqual(*a, *b); +} + +template +bool IsEqual(const DataModel::Nullable & a, const DataModel::Nullable & b) +{ + if (a.IsNull() != b.IsNull()) + { + return false; + } + + if (a.IsNull()) + { + return true; + } + + return IsEqual(a.Value(), b.Value()); +} + /// Validates that an encoded value in ember takes a specific format template class EncodeTester @@ -122,7 +172,6 @@ class EncodeTester CHIP_ERROR err = buffer.Decode(reader); if (err != CHIP_NO_ERROR) { - ChipLogError(Test, "Decoding failed: %" CHIP_ERROR_FORMAT, err.Format()); return err; } @@ -142,6 +191,42 @@ class EncodeTester return EncodeResult::Ok(); } + template + EncodeResult TryDecode(const T & value, const uint8_t (&arr)[N]) + { + // Write data to TLV + { + uint8_t mutableBuffer[N]; + memcpy(mutableBuffer, arr, N); + + MutableByteSpan data_span(mutableBuffer); + Ember::EmberAttributeDataBuffer buffer(mMetaData, data_span); + + TLV::TLVWriter writer; + writer.Init(mEmberAttributeDataBuffer, sizeof(mEmberAttributeDataBuffer)); + ReturnErrorOnFailure(buffer.Encode(writer, TLV::AnonymousTag())); + ReturnErrorOnFailure(writer.Finalize()); + } + + // Data was written in TLV. Take it back out + + TLV::TLVReader reader; + reader.Init(mEmberAttributeDataBuffer, sizeof(mEmberAttributeDataBuffer)); + + ReturnErrorOnFailure(reader.Next()); + + T encodedValue; + ReturnErrorOnFailure(DataModel::Decode(reader, encodedValue)); + + if (!IsEqual(encodedValue, value)) + { + ChipLogError(Test, "Encode mismatch: different data"); + return CHIP_ERROR_INTERNAL; + } + + return EncodeResult::Ok(); + } + private: const EmberAfAttributeMetadata * mMetaData; uint8_t mEmberAttributeDataBuffer[kMaxSize]; @@ -570,7 +655,7 @@ TEST(TestEmberAttributeBuffer, TestEncodeStrings) } } -TEST(TestEmberAttributeBuffer, TestFailures) +TEST(TestEmberAttributeBuffer, TestEncodeFailures) { { // attribute type that is not handled @@ -581,11 +666,38 @@ TEST(TestEmberAttributeBuffer, TestFailures) { // Insufficient space EncodeTester<3> tester(CreateFakeMeta(ZCL_CHAR_STRING_ATTRIBUTE_TYPE, true /* nullable */)); + + // Empty is ok EXPECT_TRUE(tester.TryEncode(""_span, { 0 }).IsSuccess()); + + // Short strings (with and without count) is wrong. EXPECT_EQ(tester.TryEncode("test"_span, { 0 }), CHIP_ERROR_NO_MEMORY); + EXPECT_EQ(tester.TryEncode("foo"_span, { 3, 'f', 'o' }), CHIP_ERROR_NO_MEMORY); + EXPECT_TRUE(tester.TryEncode>(DataModel::NullNullable, { 0xFF }).IsSuccess()); } + { + // Insufficient space + EncodeTester<3> tester(CreateFakeMeta(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE, true /* nullable */)); + + // Empty is ok + EXPECT_TRUE(tester.TryEncode(""_span, { 0, 0 }).IsSuccess()); + + // Short strings (with and without count) is wrong. + EXPECT_EQ(tester.TryEncode("test"_span, { 0 }), CHIP_ERROR_NO_MEMORY); + EXPECT_EQ(tester.TryEncode("foo"_span, { 0, 3, 'f', 'o' }), CHIP_ERROR_NO_MEMORY); + EXPECT_EQ(tester.TryEncode("test"_span, { 0xFF }), CHIP_ERROR_NO_MEMORY); + + EXPECT_TRUE(tester.TryEncode>(DataModel::NullNullable, { 0xFF, 0xFF }).IsSuccess()); + } + + { + // Insufficient space even for length + EncodeTester<1> tester(CreateFakeMeta(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_EQ(tester.TryEncode(""_span, { 0 }), CHIP_ERROR_NO_MEMORY); + } + // bad type casts { EncodeTester tester(CreateFakeMeta(ZCL_CHAR_STRING_ATTRIBUTE_TYPE, false /* nullable */)); @@ -596,3 +708,429 @@ TEST(TestEmberAttributeBuffer, TestFailures) EXPECT_EQ(tester.TryEncode(true, { 0 }), CHIP_ERROR_WRONG_TLV_TYPE); } } + +TEST(TestEmberAttributeBuffer, TestNoData) +{ + EncodeTester tester(CreateFakeMeta(ZCL_NO_DATA_ATTRIBUTE_TYPE, true /* nullable */)); + + // support a always-null type + EXPECT_TRUE(tester.TryDecode>(DataModel::NullNullable, { 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode>(DataModel::NullNullable, { 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode>(DataModel::NullNullable, { 0 }).IsSuccess()); +} + +TEST(TestEmberAttributeBuffer, TestDecodeFailures) +{ + { + // attribute type that is not handled + EncodeTester tester(CreateFakeMeta(ZCL_UNKNOWN_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_EQ(tester.TryDecode>(DataModel::NullNullable, { 0 }), CHIP_IM_GLOBAL_STATUS(Failure)); + } + + { + // Insufficient input + EncodeTester<3> tester(CreateFakeMeta(ZCL_CHAR_STRING_ATTRIBUTE_TYPE, true /* nullable */)); + + EXPECT_EQ(tester.TryDecode("test"_span, { 10 }), CHIP_ERROR_BUFFER_TOO_SMALL); + EXPECT_EQ(tester.TryDecode("foo"_span, { 3, 'f', 'o' }), CHIP_ERROR_BUFFER_TOO_SMALL); + } + + { + // Insufficient data buffer - should never happen, but test that we will error out + EncodeTester tester(CreateFakeMeta(ZCL_INT32U_ATTRIBUTE_TYPE, false /* nullable */)); + EXPECT_EQ(tester.TryDecode(123, { 1, 2, 3 }), CHIP_ERROR_BUFFER_TOO_SMALL); + } + + { + // Insufficient data buffer - should never happen, but test that we will error out + EncodeTester tester(CreateFakeMeta(ZCL_SINGLE_ATTRIBUTE_TYPE, false /* nullable */)); + EXPECT_EQ(tester.TryDecode(1.5f, { 1, 2, 3 }), CHIP_ERROR_BUFFER_TOO_SMALL); + } + + { + // Insufficient data buffer - should never happen, but test that we will error out + EncodeTester tester(CreateFakeMeta(ZCL_DOUBLE_ATTRIBUTE_TYPE, false /* nullable */)); + EXPECT_EQ(tester.TryDecode(1.5, { 1, 2, 3 }), CHIP_ERROR_BUFFER_TOO_SMALL); + } + + { + // Bad boolean data + EncodeTester tester(CreateFakeMeta(ZCL_BOOLEAN_ATTRIBUTE_TYPE, false /* nullable */)); + EXPECT_EQ(tester.TryDecode(true, { 123 }), CHIP_ERROR_INCORRECT_STATE); + } +} + +TEST(TestEmberAttributeBuffer, TestDecodeSignedTypes) +{ + { + EncodeTester tester(CreateFakeMeta(ZCL_INT8S_ATTRIBUTE_TYPE, false /* nullable */)); + + EXPECT_TRUE(tester.TryDecode(0, { 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(123, { 123 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(127, { 127 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(-10, { 0xF6 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(-128, { 0x80 }).IsSuccess()); + + // longer data is ok + EXPECT_TRUE(tester.TryDecode(-128, { 0x80, 1, 2, 3, 4 }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_INT8S_ATTRIBUTE_TYPE, true /* nullable */)); + + EXPECT_TRUE(tester.TryDecode(0, { 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(123, { 123 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(127, { 127 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(-10, { 0xF6 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(-127, { 0x81 }).IsSuccess()); + + // NULL can be decoded + EXPECT_TRUE(tester.TryDecode>(DataModel::NullNullable, { 0x80 }).IsSuccess()); + + // decoding as nullable proceeds as normal + EXPECT_TRUE(tester.TryDecode>(-127, { 0x81 }).IsSuccess()); + } + + { + + EncodeTester tester(CreateFakeMeta(ZCL_INT16S_ATTRIBUTE_TYPE, false /* nullable */)); + + EXPECT_TRUE(tester.TryDecode(0, { 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(123, { 123, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(127, { 127, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(-10, { 0xF6, 0xFF }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(-128, { 0x80, 0xFF }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(-1234, { 0x2E, 0xFB }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(std::numeric_limits::min(), { 0x0, 0x80 }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_INT16S_ATTRIBUTE_TYPE, true /* nullable */)); + + EXPECT_TRUE(tester.TryDecode(0, { 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(123, { 123, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(127, { 127, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(-10, { 0xF6, 0xFF }).IsSuccess()); + + // NULL decoding + EXPECT_TRUE(tester.TryDecode>(DataModel::NullNullable, { 0x00, 0x80 }).IsSuccess()); + } + + // Odd size integers + { + EncodeTester tester(CreateFakeMeta(ZCL_INT24S_ATTRIBUTE_TYPE, false /* nullable */)); + + EXPECT_TRUE(tester.TryDecode(0, { 0, 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(0x123456, { 0x56, 0x34, 0x12 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(-1, { 0xFF, 0xFF, 0xFF }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(-10, { 0xF6, 0xFF, 0xFF }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(-1234, { 0x2E, 0xFB, 0xFF }).IsSuccess()); + } + { + EncodeTester tester(CreateFakeMeta(ZCL_INT24S_ATTRIBUTE_TYPE, true /* nullable */)); + + EXPECT_TRUE(tester.TryDecode(0, { 0, 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(0x123456, { 0x56, 0x34, 0x12 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(-1, { 0xFF, 0xFF, 0xFF }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(-10, { 0xF6, 0xFF, 0xFF }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(-1234, { 0x2E, 0xFB, 0xFF }).IsSuccess()); + + EXPECT_TRUE(tester.TryDecode>(DataModel::NullNullable, { 0x00, 0x00, 0x80 }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_INT40S_ATTRIBUTE_TYPE, true /* nullable */)); + + // NOTE: to generate encoded values, you an use commands like: + // + // python -c 'import struct; print(", ".join(["0x%X" % v for v in struct.pack("(0, { 0, 0, 0, 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(0x123456, { 0x56, 0x34, 0x12, 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(-1234, { 0x2E, 0xFB, 0xFF, 0xFF, 0xFF }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(-123456789, { 0xeb, 0x32, 0xa4, 0xf8, 0xFF }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(-12345678910, { 0xc2, 0xe3, 0x23, 0x20, 0xfd }).IsSuccess()); + + EXPECT_TRUE( + tester.TryDecode>(DataModel::NullNullable, { 0x00, 0x00, 0x00, 0x00, 0x80 }).IsSuccess()); + } + + // Double-check tests, not as exhaustive, to cover all other unsigned values and get + // more test line coverage + { + EncodeTester tester(CreateFakeMeta(ZCL_INT32S_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryDecode(-1234, { 0x2E, 0xFB, 0xFF, 0xFF }).IsSuccess()); + } + { + EncodeTester tester(CreateFakeMeta(ZCL_INT48S_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryDecode(-1234, { 0x2E, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF }).IsSuccess()); + } + { + EncodeTester tester(CreateFakeMeta(ZCL_INT56S_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryDecode(-1234, { 0x2E, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_INT64S_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryDecode(-1234, { 0x2E, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }).IsSuccess()); + + // min/max ranges too + EXPECT_TRUE( + tester.TryDecode(std::numeric_limits::min() + 1, { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80 }) + .IsSuccess()); + EXPECT_TRUE( + tester.TryDecode(std::numeric_limits::max(), { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F }) + .IsSuccess()); + + EXPECT_TRUE(tester + .TryDecode>(DataModel::NullNullable, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80 }) + .IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_INT64S_ATTRIBUTE_TYPE, false /* nullable */)); + EXPECT_TRUE(tester.TryDecode(-1234, { 0x2E, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }).IsSuccess()); + + EXPECT_TRUE( + tester.TryDecode(std::numeric_limits::min(), { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80 }) + .IsSuccess()); + EXPECT_TRUE( + tester.TryDecode(std::numeric_limits::min() + 1, { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80 }) + .IsSuccess()); + EXPECT_TRUE( + tester.TryDecode(std::numeric_limits::max(), { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F }) + .IsSuccess()); + } +} + +TEST(TestEmberAttributeBuffer, TestDecodeUnsignedTypes) +{ + { + EncodeTester tester(CreateFakeMeta(ZCL_INT8U_ATTRIBUTE_TYPE, false /* nullable */)); + + EXPECT_TRUE(tester.TryDecode(0, { 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(123, { 123 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(0xFD, { 0xFD }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(255, { 0xFF }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_INT8U_ATTRIBUTE_TYPE, true /* nullable */)); + + EXPECT_TRUE(tester.TryDecode(0, { 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(123, { 123 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(0xFD, { 0xFD }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode>(DataModel::NullNullable, { 0xFF }).IsSuccess()); + + // NULL decoding should work + EXPECT_TRUE(tester.TryDecode>(DataModel::NullNullable, { 0xFF }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_INT16U_ATTRIBUTE_TYPE, false /* nullable */)); + + EXPECT_TRUE(tester.TryDecode(0, { 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(123, { 123, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(0xFD, { 0xFD, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(255, { 0xFF, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(0xABCD, { 0xCD, 0xAB }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(0xFFFF, { 0xFF, 0xFF }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_INT16U_ATTRIBUTE_TYPE, true /* nullable */)); + + EXPECT_TRUE(tester.TryDecode(0, { 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(123, { 123, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(0xFD, { 0xFD, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(255, { 0xFF, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(0xABCD, { 0xCD, 0xAB }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode>(DataModel::NullNullable, { 0xFF, 0xFF }).IsSuccess()); + + // NULL SUPPORT + EXPECT_TRUE(tester.TryDecode>(DataModel::NullNullable, { 0xFF, 0xFF }).IsSuccess()); + } + { + EncodeTester tester(CreateFakeMeta(ZCL_INT64U_ATTRIBUTE_TYPE, true /* nullable */)); + + EXPECT_TRUE(tester.TryDecode(0, { 0, 0, 0, 0, 0, 0, 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(0x1234567, { 0x67, 0x45, 0x23, 0x01, 0, 0, 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(0xAABBCCDDEEFF1122, { 0x22, 0x11, 0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA }).IsSuccess()); + EXPECT_TRUE( + tester.TryDecode(std::numeric_limits::max() - 1, { 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }) + .IsSuccess()); + + EXPECT_TRUE(tester + .TryDecode>(DataModel::NullNullable, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }) + .IsSuccess()); + + EXPECT_TRUE(tester + .TryDecode>(DataModel::NullNullable, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }) + .IsSuccess()); + } + { + EncodeTester tester(CreateFakeMeta(ZCL_INT64U_ATTRIBUTE_TYPE, false /* nullable */)); + + // we should be able to encode the maximum value + EXPECT_TRUE( + tester.TryDecode(std::numeric_limits::max(), { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }) + .IsSuccess()); + } + + /// Odd sized integers + { + EncodeTester tester(CreateFakeMeta(ZCL_INT24U_ATTRIBUTE_TYPE, false /* nullable */)); + EXPECT_TRUE(tester.TryDecode(0, { 0, 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(0x123456, { 0x56, 0x34, 0x12 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(0xFFFFFF, { 0xFF, 0xFF, 0xFF }).IsSuccess()); + } + { + EncodeTester tester(CreateFakeMeta(ZCL_INT24U_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryDecode(0, { 0, 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(0x123456, { 0x56, 0x34, 0x12 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode>(DataModel::NullNullable, { 0xFF, 0xFF, 0xFF }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode>(0x1234, { 0x34, 0x12, 0x00 }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_INT40U_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryDecode(0, { 0, 0, 0, 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(0x123456, { 0x56, 0x34, 0x12, 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(0x123456FFFF, { 0xFF, 0xFF, 0x56, 0x34, 0x12 }).IsSuccess()); + EXPECT_TRUE( + tester.TryDecode>(DataModel::NullNullable, { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }).IsSuccess()); + } + + // Double-check tests, not as exhaustive, to cover all other unsigned values and get + // more test line coverage + { + EncodeTester tester(CreateFakeMeta(ZCL_INT32U_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryDecode(0x1234, { 0x34, 0x12, 0, 0 }).IsSuccess()); + } + { + EncodeTester tester(CreateFakeMeta(ZCL_INT48U_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryDecode(0x1234, { 0x34, 0x12, 0, 0, 0, 0 }).IsSuccess()); + } + { + EncodeTester tester(CreateFakeMeta(ZCL_INT56U_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryDecode(0x1234, { 0x34, 0x12, 0, 0, 0, 0, 0 }).IsSuccess()); + } +} + +TEST(TestEmberAttributeBuffer, TestDecodeStrings) +{ + { + EncodeTester tester(CreateFakeMeta(ZCL_CHAR_STRING_ATTRIBUTE_TYPE, false /* nullable */)); + EXPECT_TRUE(tester.TryDecode(""_span, { 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode("test"_span, { 4, 't', 'e', 's', 't' }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode("foo"_span, { 3, 'f', 'o', 'o' }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_CHAR_STRING_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryDecode(""_span, { 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode("test"_span, { 4, 't', 'e', 's', 't' }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode>(DataModel::NullNullable, { 0xFF }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE, false /* nullable */)); + EXPECT_TRUE(tester.TryDecode(""_span, { 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode("test"_span, { 4, 0, 't', 'e', 's', 't' }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode("foo"_span, { 3, 0, 'f', 'o', 'o' }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryDecode("test"_span, { 4, 0, 't', 'e', 's', 't' }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode>(DataModel::NullNullable, { 0xFF, 0xFF }).IsSuccess()); + } + + const uint8_t kOctetData[] = { 1, 2, 3 }; + + // Binary data + { + EncodeTester tester(CreateFakeMeta(ZCL_OCTET_STRING_ATTRIBUTE_TYPE, false /* nullable */)); + EXPECT_TRUE(tester.TryDecode(ByteSpan({}), { 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(ByteSpan(kOctetData), { 3, 1, 2, 3 }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_OCTET_STRING_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryDecode(ByteSpan({}), { 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(ByteSpan(kOctetData), { 3, 1, 2, 3 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode>(DataModel::NullNullable, { 0xFF }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE, false /* nullable */)); + EXPECT_TRUE(tester.TryDecode(ByteSpan({}), { 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(ByteSpan(kOctetData), { 3, 0, 1, 2, 3 }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryDecode(ByteSpan({}), { 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(ByteSpan(kOctetData), { 3, 0, 1, 2, 3 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode>(DataModel::NullNullable, { 0xFF, 0xFF }).IsSuccess()); + } +} + +TEST(TestEmberAttributeBuffer, TestDecodeBool) +{ + { + EncodeTester tester(CreateFakeMeta(ZCL_BOOLEAN_ATTRIBUTE_TYPE, false /* nullable */)); + + EXPECT_TRUE(tester.TryDecode(true, { 1 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(false, { 0 }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_BOOLEAN_ATTRIBUTE_TYPE, true /* nullable */)); + + EXPECT_TRUE(tester.TryDecode>(true, { 1 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode>(false, { 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode>(DataModel::NullNullable, { 0xFF }).IsSuccess()); + } +} + +TEST(TestEmberAttributeBuffer, TestDecodeFloatingPoint) +{ + // NOTE: to generate encoded values, you an use commands like: + // + // python -c 'import struct; print(", ".join(["0x%X" % v for v in struct.pack("(123.55f, { 0x9A, 0x19, 0xF7, 0x42 }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_SINGLE_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryDecode(123.55f, { 0x9A, 0x19, 0xF7, 0x42 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode>(DataModel::NullNullable, { 0, 0, 0xC0, 0x7F }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_DOUBLE_ATTRIBUTE_TYPE, false /* nullable */)); + EXPECT_TRUE(tester.TryDecode(123.55, { 0x33, 0x33, 0x33, 0x33, 0x33, 0xE3, 0x5E, 0x40 }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_DOUBLE_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryDecode(123.55, { 0x33, 0x33, 0x33, 0x33, 0x33, 0xE3, 0x5E, 0x40 }).IsSuccess()); + EXPECT_TRUE( + tester.TryDecode>(123.55, { 0x33, 0x33, 0x33, 0x33, 0x33, 0xE3, 0x5E, 0x40 }).IsSuccess()); + EXPECT_TRUE( + tester.TryDecode>(DataModel::NullNullable, { 0, 0, 0, 0, 0, 0, 0xF8, 0x7F }).IsSuccess()); + } +}