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 <andreilitvin@google.com>
Co-authored-by: Restyled.io <commits@restyled.io>
diff --git a/src/app/codegen-data-model-provider/CodegenDataModelProvider_Read.cpp b/src/app/codegen-data-model-provider/CodegenDataModelProvider_Read.cpp
index 6ce5d28..ea35356 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 <app/AttributeValueEncoder.h>
#include <app/GlobalAttributes.h>
#include <app/RequiredPrivilege.h>
+#include <app/codegen-data-model-provider/EmberAttributeDataBuffer.h>
#include <app/codegen-data-model-provider/EmberMetadata.h>
#include <app/data-model/FabricScoped.h>
#include <app/util/af-types.h>
@@ -41,6 +42,7 @@
#include <app/util/odd-sized-integers.h>
#include <lib/core/CHIPError.h>
#include <lib/support/CodeUtils.h>
+#include <lib/support/Span.h>
#include <zap-generated/endpoint_config.h>
@@ -85,173 +87,6 @@
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 <class OUT_TYPE, class ENCODING>
-std::optional<OUT_TYPE> 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<OUT_TYPE>(reinterpret_cast<typename OUT_TYPE::pointer>(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 <typename T, class ENCODING>
-CHIP_ERROR EncodeStringLike(ByteSpan data, bool isNullable, AttributeValueEncoder & encoder)
-{
- std::optional<T> value = ExtractEmberString<T, ENCODING>(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 <typename T>
-CHIP_ERROR EncodeFromSpan(ByteSpan data, bool isNullable, AttributeValueEncoder & encoder)
-{
- typename NumericAttributeTraits<T>::StorageType value;
-
- VerifyOrReturnError(data.size() >= sizeof(value), CHIP_ERROR_INVALID_ARGUMENT);
- memcpy(&value, data.data(), sizeof(value));
-
- if (isNullable && NumericAttributeTraits<T>::IsNullValue(value))
- {
- return encoder.EncodeNull();
- }
-
- if (!NumericAttributeTraits<T>::CanRepresentValue(isNullable, value))
- {
- return CHIP_ERROR_INCORRECT_STATE;
- }
-
- return encoder.Encode(NumericAttributeTraits<T>::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<bool>(data, isNullable, encoder);
- case ZCL_INT8U_ATTRIBUTE_TYPE: // Unsigned 8-bit integer
- return EncodeFromSpan<uint8_t>(data, isNullable, encoder);
- case ZCL_INT16U_ATTRIBUTE_TYPE: // Unsigned 16-bit integer
- return EncodeFromSpan<uint16_t>(data, isNullable, encoder);
- case ZCL_INT24U_ATTRIBUTE_TYPE: // Unsigned 24-bit integer
- return EncodeFromSpan<OddSizedInteger<3, false>>(data, isNullable, encoder);
- case ZCL_INT32U_ATTRIBUTE_TYPE: // Unsigned 32-bit integer
- return EncodeFromSpan<uint32_t>(data, isNullable, encoder);
- case ZCL_INT40U_ATTRIBUTE_TYPE: // Unsigned 40-bit integer
- return EncodeFromSpan<OddSizedInteger<5, false>>(data, isNullable, encoder);
- case ZCL_INT48U_ATTRIBUTE_TYPE: // Unsigned 48-bit integer
- return EncodeFromSpan<OddSizedInteger<6, false>>(data, isNullable, encoder);
- case ZCL_INT56U_ATTRIBUTE_TYPE: // Unsigned 56-bit integer
- return EncodeFromSpan<OddSizedInteger<7, false>>(data, isNullable, encoder);
- case ZCL_INT64U_ATTRIBUTE_TYPE: // Unsigned 64-bit integer
- return EncodeFromSpan<uint64_t>(data, isNullable, encoder);
- case ZCL_INT8S_ATTRIBUTE_TYPE: // Signed 8-bit integer
- return EncodeFromSpan<int8_t>(data, isNullable, encoder);
- case ZCL_INT16S_ATTRIBUTE_TYPE: // Signed 16-bit integer
- return EncodeFromSpan<int16_t>(data, isNullable, encoder);
- case ZCL_INT24S_ATTRIBUTE_TYPE: // Signed 24-bit integer
- return EncodeFromSpan<OddSizedInteger<3, true>>(data, isNullable, encoder);
- case ZCL_INT32S_ATTRIBUTE_TYPE: // Signed 32-bit integer
- return EncodeFromSpan<int32_t>(data, isNullable, encoder);
- case ZCL_INT40S_ATTRIBUTE_TYPE: // Signed 40-bit integer
- return EncodeFromSpan<OddSizedInteger<5, true>>(data, isNullable, encoder);
- case ZCL_INT48S_ATTRIBUTE_TYPE: // Signed 48-bit integer
- return EncodeFromSpan<OddSizedInteger<6, true>>(data, isNullable, encoder);
- case ZCL_INT56S_ATTRIBUTE_TYPE: // Signed 56-bit integer
- return EncodeFromSpan<OddSizedInteger<7, true>>(data, isNullable, encoder);
- case ZCL_INT64S_ATTRIBUTE_TYPE: // Signed 64-bit integer
- return EncodeFromSpan<int64_t>(data, isNullable, encoder);
- case ZCL_SINGLE_ATTRIBUTE_TYPE: // 32-bit float
- return EncodeFromSpan<float>(data, isNullable, encoder);
- case ZCL_DOUBLE_ATTRIBUTE_TYPE: // 64-bit float
- return EncodeFromSpan<double>(data, isNullable, encoder);
- case ZCL_CHAR_STRING_ATTRIBUTE_TYPE: // Char string
- return EncodeStringLike<CharSpan, ShortPascalString>(data, isNullable, encoder);
- case ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE:
- return EncodeStringLike<CharSpan, LongPascalString>(data, isNullable, encoder);
- case ZCL_OCTET_STRING_ATTRIBUTE_TYPE: // Octet string
- return EncodeStringLike<ByteSpan, ShortPascalString>(data, isNullable, encoder);
- case ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE:
- return EncodeStringLike<ByteSpan, LongPascalString>(data, isNullable, encoder);
- default:
- ChipLogError(DataManagement, "Attribute type 0x%x not handled", static_cast<int>(metadata->attributeType));
- return CHIP_IM_GLOBAL_STATUS(Failure);
- }
-}
-
} // namespace
/// separated-out ReadAttribute implementation (given existing complexity)
@@ -343,7 +178,11 @@
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 ab88916..2c76424 100644
--- a/src/app/codegen-data-model-provider/EmberAttributeDataBuffer.cpp
+++ b/src/app/codegen-data-model-provider/EmberAttributeDataBuffer.cpp
@@ -17,14 +17,18 @@
#include <app/codegen-data-model-provider/EmberAttributeDataBuffer.h>
#include <app-common/zap-generated/attribute-type.h>
+#include <app/util/attribute-metadata.h>
#include <app/util/attribute-storage-null-handling.h>
#include <app/util/odd-sized-integers.h>
#include <lib/core/CHIPError.h>
+#include <lib/core/TLVTags.h>
#include <lib/core/TLVTypes.h>
+#include <lib/core/TLVWriter.h>
#include <lib/support/CodeUtils.h>
#include <protocols/interaction_model/Constants.h>
#include <protocols/interaction_model/StatusCode.h>
+#include <cstdint>
#include <limits>
namespace chip {
@@ -43,7 +47,7 @@
{
return std::numeric_limits<uint8_t>::max() - 1;
}
- // EmberAttributeBuffer::PascalStringType::kLong:
+ // EmberAttributeDataBuffer::PascalStringType::kLong:
return std::numeric_limits<uint16_t>::max() - 1;
}
@@ -116,6 +120,55 @@
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<uint8_t>::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<uint16_t>::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<const char *>(data), stringLen);
+ }
+
+ return writer.PutBytes(tag, data, stringLen);
+}
+
} // namespace
CHIP_ERROR EmberAttributeDataBuffer::DecodeUnsignedInteger(chip::TLV::TLVReader & reader, EndianWriter & writer)
@@ -191,6 +244,7 @@
writer.Put16(NumericAttributeTraits<uint16_t>::kNullValue);
break;
}
+
return CHIP_NO_ERROR;
}
@@ -321,6 +375,200 @@
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<uint64_t>(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<int>(byteCount); i++)
+ {
+#else
+ bool isNegative = isSigned && (raw_bytes[byteCount - 1] >= 0x80);
+ if (isNegative)
+ {
+ value.int_value = -1;
+ }
+ for (int i = static_cast<int>(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<uint8_t>(value.uint_value));
+ case ZCL_INT16U_ATTRIBUTE_TYPE: // Unsigned 16-bit integer
+ return writer.Put(tag, static_cast<uint16_t>(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<uint32_t>(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<uint64_t>(value.uint_value));
+ case ZCL_INT8S_ATTRIBUTE_TYPE: // Signed 8-bit integer
+ return writer.Put(tag, static_cast<int8_t>(value.int_value));
+ case ZCL_INT16S_ATTRIBUTE_TYPE: // Signed 16-bit integer
+ return writer.Put(tag, static_cast<int16_t>(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<int32_t>(value.int_value));
+ default:
+ return writer.Put(tag, static_cast<int64_t>(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<float>::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<double>::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<int>(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 a4f24c7..c3d7acf 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 <app/util/attribute-metadata.h>
#include <app/util/ember-io-storage.h>
#include <lib/core/TLVReader.h>
#include <lib/core/TLVTypes.h>
+#include <lib/support/BufferReader.h>
#include <lib/support/BufferWriter.h>
#include <lib/support/Span.h>
@@ -39,6 +41,14 @@
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 @@
/// 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 @@
/// 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 @@
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 7d46360..dc83773 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 @@
std::optional<CHIP_ERROR> mResult;
};
+template <typename T>
+bool IsEqual(const T & a, const T & b)
+{
+ return a == b;
+}
+
+template <>
+bool IsEqual<ByteSpan>(const ByteSpan & a, const ByteSpan & b)
+{
+ return a.data_equal(b);
+}
+
+template <>
+bool IsEqual<CharSpan>(const CharSpan & a, const CharSpan & b)
+{
+ return a.data_equal(b);
+}
+
+template <typename T>
+bool IsEqual(const std::optional<T> & a, const std::optional<T> & b)
+{
+ if (a.has_value() != b.has_value())
+ {
+ return false;
+ }
+
+ if (!a.has_value())
+ {
+ return true;
+ }
+
+ return IsEqual(*a, *b);
+}
+
+template <typename T>
+bool IsEqual(const DataModel::Nullable<T> & a, const DataModel::Nullable<T> & 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 <size_t kMaxSize = 128>
class EncodeTester
@@ -122,7 +172,6 @@
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 @@
return EncodeResult::Ok();
}
+ template <typename T, size_t N>
+ 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, TestFailures)
+TEST(TestEmberAttributeBuffer, TestEncodeFailures)
{
{
// attribute type that is not handled
@@ -581,11 +666,38 @@
{
// Insufficient space
EncodeTester<3> tester(CreateFakeMeta(ZCL_CHAR_STRING_ATTRIBUTE_TYPE, true /* nullable */));
+
+ // Empty is ok
EXPECT_TRUE(tester.TryEncode<CharSpan>(""_span, { 0 }).IsSuccess());
+
+ // Short strings (with and without count) is wrong.
EXPECT_EQ(tester.TryEncode<CharSpan>("test"_span, { 0 }), CHIP_ERROR_NO_MEMORY);
+ EXPECT_EQ(tester.TryEncode<CharSpan>("foo"_span, { 3, 'f', 'o' }), CHIP_ERROR_NO_MEMORY);
+
EXPECT_TRUE(tester.TryEncode<DataModel::Nullable<CharSpan>>(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<CharSpan>(""_span, { 0, 0 }).IsSuccess());
+
+ // Short strings (with and without count) is wrong.
+ EXPECT_EQ(tester.TryEncode<CharSpan>("test"_span, { 0 }), CHIP_ERROR_NO_MEMORY);
+ EXPECT_EQ(tester.TryEncode<CharSpan>("foo"_span, { 0, 3, 'f', 'o' }), CHIP_ERROR_NO_MEMORY);
+ EXPECT_EQ(tester.TryEncode<CharSpan>("test"_span, { 0xFF }), CHIP_ERROR_NO_MEMORY);
+
+ EXPECT_TRUE(tester.TryEncode<DataModel::Nullable<CharSpan>>(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<CharSpan>(""_span, { 0 }), CHIP_ERROR_NO_MEMORY);
+ }
+
// bad type casts
{
EncodeTester tester(CreateFakeMeta(ZCL_CHAR_STRING_ATTRIBUTE_TYPE, false /* nullable */));
@@ -596,3 +708,429 @@
EXPECT_EQ(tester.TryEncode<bool>(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::Nullable<uint32_t>>(DataModel::NullNullable, { 0 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<DataModel::Nullable<float>>(DataModel::NullNullable, { 0 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<DataModel::Nullable<bool>>(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::Nullable<uint32_t>>(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<CharSpan>("test"_span, { 10 }), CHIP_ERROR_BUFFER_TOO_SMALL);
+ EXPECT_EQ(tester.TryDecode<CharSpan>("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<uint32_t>(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<float>(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<double>(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<bool>(true, { 123 }), CHIP_ERROR_INCORRECT_STATE);
+ }
+}
+
+TEST(TestEmberAttributeBuffer, TestDecodeSignedTypes)
+{
+ {
+ EncodeTester tester(CreateFakeMeta(ZCL_INT8S_ATTRIBUTE_TYPE, false /* nullable */));
+
+ EXPECT_TRUE(tester.TryDecode<int8_t>(0, { 0 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<int8_t>(123, { 123 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<int8_t>(127, { 127 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<int8_t>(-10, { 0xF6 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<int8_t>(-128, { 0x80 }).IsSuccess());
+
+ // longer data is ok
+ EXPECT_TRUE(tester.TryDecode<int8_t>(-128, { 0x80, 1, 2, 3, 4 }).IsSuccess());
+ }
+
+ {
+ EncodeTester tester(CreateFakeMeta(ZCL_INT8S_ATTRIBUTE_TYPE, true /* nullable */));
+
+ EXPECT_TRUE(tester.TryDecode<int8_t>(0, { 0 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<int8_t>(123, { 123 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<int8_t>(127, { 127 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<int8_t>(-10, { 0xF6 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<int8_t>(-127, { 0x81 }).IsSuccess());
+
+ // NULL can be decoded
+ EXPECT_TRUE(tester.TryDecode<DataModel::Nullable<int8_t>>(DataModel::NullNullable, { 0x80 }).IsSuccess());
+
+ // decoding as nullable proceeds as normal
+ EXPECT_TRUE(tester.TryDecode<DataModel::Nullable<int8_t>>(-127, { 0x81 }).IsSuccess());
+ }
+
+ {
+
+ EncodeTester tester(CreateFakeMeta(ZCL_INT16S_ATTRIBUTE_TYPE, false /* nullable */));
+
+ EXPECT_TRUE(tester.TryDecode<int16_t>(0, { 0, 0 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<int16_t>(123, { 123, 0 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<int16_t>(127, { 127, 0 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<int16_t>(-10, { 0xF6, 0xFF }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<int16_t>(-128, { 0x80, 0xFF }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<int16_t>(-1234, { 0x2E, 0xFB }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<int16_t>(std::numeric_limits<int16_t>::min(), { 0x0, 0x80 }).IsSuccess());
+ }
+
+ {
+ EncodeTester tester(CreateFakeMeta(ZCL_INT16S_ATTRIBUTE_TYPE, true /* nullable */));
+
+ EXPECT_TRUE(tester.TryDecode<int16_t>(0, { 0, 0 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<int16_t>(123, { 123, 0 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<int16_t>(127, { 127, 0 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<int16_t>(-10, { 0xF6, 0xFF }).IsSuccess());
+
+ // NULL decoding
+ EXPECT_TRUE(tester.TryDecode<DataModel::Nullable<int16_t>>(DataModel::NullNullable, { 0x00, 0x80 }).IsSuccess());
+ }
+
+ // Odd size integers
+ {
+ EncodeTester tester(CreateFakeMeta(ZCL_INT24S_ATTRIBUTE_TYPE, false /* nullable */));
+
+ EXPECT_TRUE(tester.TryDecode<int32_t>(0, { 0, 0, 0 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<int32_t>(0x123456, { 0x56, 0x34, 0x12 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<int32_t>(-1, { 0xFF, 0xFF, 0xFF }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<int32_t>(-10, { 0xF6, 0xFF, 0xFF }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<int32_t>(-1234, { 0x2E, 0xFB, 0xFF }).IsSuccess());
+ }
+ {
+ EncodeTester tester(CreateFakeMeta(ZCL_INT24S_ATTRIBUTE_TYPE, true /* nullable */));
+
+ EXPECT_TRUE(tester.TryDecode<int32_t>(0, { 0, 0, 0 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<int32_t>(0x123456, { 0x56, 0x34, 0x12 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<int32_t>(-1, { 0xFF, 0xFF, 0xFF }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<int32_t>(-10, { 0xF6, 0xFF, 0xFF }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<int32_t>(-1234, { 0x2E, 0xFB, 0xFF }).IsSuccess());
+
+ EXPECT_TRUE(tester.TryDecode<DataModel::Nullable<uint32_t>>(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("<q", -12345678910)]))'
+ //
+ // OUTPUT: 0xC2, 0xE3, 0x23, 0x20, 0xFD, 0xFF, 0xFF, 0xFF
+ //
+ EXPECT_TRUE(tester.TryDecode<int64_t>(0, { 0, 0, 0, 0, 0 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<int64_t>(0x123456, { 0x56, 0x34, 0x12, 0, 0 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<int64_t>(-1234, { 0x2E, 0xFB, 0xFF, 0xFF, 0xFF }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<int64_t>(-123456789, { 0xeb, 0x32, 0xa4, 0xf8, 0xFF }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<int64_t>(-12345678910, { 0xc2, 0xe3, 0x23, 0x20, 0xfd }).IsSuccess());
+
+ EXPECT_TRUE(
+ tester.TryDecode<DataModel::Nullable<uint64_t>>(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<int64_t>(-1234, { 0x2E, 0xFB, 0xFF, 0xFF }).IsSuccess());
+ }
+ {
+ EncodeTester tester(CreateFakeMeta(ZCL_INT48S_ATTRIBUTE_TYPE, true /* nullable */));
+ EXPECT_TRUE(tester.TryDecode<int64_t>(-1234, { 0x2E, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF }).IsSuccess());
+ }
+ {
+ EncodeTester tester(CreateFakeMeta(ZCL_INT56S_ATTRIBUTE_TYPE, true /* nullable */));
+ EXPECT_TRUE(tester.TryDecode<int64_t>(-1234, { 0x2E, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }).IsSuccess());
+ }
+
+ {
+ EncodeTester tester(CreateFakeMeta(ZCL_INT64S_ATTRIBUTE_TYPE, true /* nullable */));
+ EXPECT_TRUE(tester.TryDecode<int64_t>(-1234, { 0x2E, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }).IsSuccess());
+
+ // min/max ranges too
+ EXPECT_TRUE(
+ tester.TryDecode<int64_t>(std::numeric_limits<int64_t>::min() + 1, { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80 })
+ .IsSuccess());
+ EXPECT_TRUE(
+ tester.TryDecode<int64_t>(std::numeric_limits<int64_t>::max(), { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F })
+ .IsSuccess());
+
+ EXPECT_TRUE(tester
+ .TryDecode<DataModel::Nullable<int64_t>>(DataModel::NullNullable,
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80 })
+ .IsSuccess());
+ }
+
+ {
+ EncodeTester tester(CreateFakeMeta(ZCL_INT64S_ATTRIBUTE_TYPE, false /* nullable */));
+ EXPECT_TRUE(tester.TryDecode<int64_t>(-1234, { 0x2E, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }).IsSuccess());
+
+ EXPECT_TRUE(
+ tester.TryDecode<int64_t>(std::numeric_limits<int64_t>::min(), { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80 })
+ .IsSuccess());
+ EXPECT_TRUE(
+ tester.TryDecode<int64_t>(std::numeric_limits<int64_t>::min() + 1, { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80 })
+ .IsSuccess());
+ EXPECT_TRUE(
+ tester.TryDecode<int64_t>(std::numeric_limits<int64_t>::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<uint8_t>(0, { 0 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<uint8_t>(123, { 123 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<uint8_t>(0xFD, { 0xFD }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<uint8_t>(255, { 0xFF }).IsSuccess());
+ }
+
+ {
+ EncodeTester tester(CreateFakeMeta(ZCL_INT8U_ATTRIBUTE_TYPE, true /* nullable */));
+
+ EXPECT_TRUE(tester.TryDecode<uint8_t>(0, { 0 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<uint8_t>(123, { 123 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<uint8_t>(0xFD, { 0xFD }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<DataModel::Nullable<uint8_t>>(DataModel::NullNullable, { 0xFF }).IsSuccess());
+
+ // NULL decoding should work
+ EXPECT_TRUE(tester.TryDecode<DataModel::Nullable<uint8_t>>(DataModel::NullNullable, { 0xFF }).IsSuccess());
+ }
+
+ {
+ EncodeTester tester(CreateFakeMeta(ZCL_INT16U_ATTRIBUTE_TYPE, false /* nullable */));
+
+ EXPECT_TRUE(tester.TryDecode<uint16_t>(0, { 0, 0 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<uint16_t>(123, { 123, 0 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<uint16_t>(0xFD, { 0xFD, 0 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<uint16_t>(255, { 0xFF, 0 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<uint16_t>(0xABCD, { 0xCD, 0xAB }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<uint16_t>(0xFFFF, { 0xFF, 0xFF }).IsSuccess());
+ }
+
+ {
+ EncodeTester tester(CreateFakeMeta(ZCL_INT16U_ATTRIBUTE_TYPE, true /* nullable */));
+
+ EXPECT_TRUE(tester.TryDecode<uint16_t>(0, { 0, 0 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<uint16_t>(123, { 123, 0 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<uint16_t>(0xFD, { 0xFD, 0 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<uint16_t>(255, { 0xFF, 0 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<uint16_t>(0xABCD, { 0xCD, 0xAB }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<DataModel::Nullable<uint16_t>>(DataModel::NullNullable, { 0xFF, 0xFF }).IsSuccess());
+
+ // NULL SUPPORT
+ EXPECT_TRUE(tester.TryDecode<DataModel::Nullable<uint16_t>>(DataModel::NullNullable, { 0xFF, 0xFF }).IsSuccess());
+ }
+ {
+ EncodeTester tester(CreateFakeMeta(ZCL_INT64U_ATTRIBUTE_TYPE, true /* nullable */));
+
+ EXPECT_TRUE(tester.TryDecode<uint64_t>(0, { 0, 0, 0, 0, 0, 0, 0, 0 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<uint64_t>(0x1234567, { 0x67, 0x45, 0x23, 0x01, 0, 0, 0, 0 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<uint64_t>(0xAABBCCDDEEFF1122, { 0x22, 0x11, 0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA }).IsSuccess());
+ EXPECT_TRUE(
+ tester.TryDecode<uint64_t>(std::numeric_limits<uint64_t>::max() - 1, { 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF })
+ .IsSuccess());
+
+ EXPECT_TRUE(tester
+ .TryDecode<DataModel::Nullable<uint64_t>>(DataModel::NullNullable,
+ { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF })
+ .IsSuccess());
+
+ EXPECT_TRUE(tester
+ .TryDecode<DataModel::Nullable<uint64_t>>(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<uint64_t>(std::numeric_limits<uint64_t>::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<uint32_t>(0, { 0, 0, 0 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<uint32_t>(0x123456, { 0x56, 0x34, 0x12 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<uint32_t>(0xFFFFFF, { 0xFF, 0xFF, 0xFF }).IsSuccess());
+ }
+ {
+ EncodeTester tester(CreateFakeMeta(ZCL_INT24U_ATTRIBUTE_TYPE, true /* nullable */));
+ EXPECT_TRUE(tester.TryDecode<uint32_t>(0, { 0, 0, 0 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<uint32_t>(0x123456, { 0x56, 0x34, 0x12 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<DataModel::Nullable<uint32_t>>(DataModel::NullNullable, { 0xFF, 0xFF, 0xFF }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<DataModel::Nullable<uint32_t>>(0x1234, { 0x34, 0x12, 0x00 }).IsSuccess());
+ }
+
+ {
+ EncodeTester tester(CreateFakeMeta(ZCL_INT40U_ATTRIBUTE_TYPE, true /* nullable */));
+ EXPECT_TRUE(tester.TryDecode<uint64_t>(0, { 0, 0, 0, 0, 0 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<uint64_t>(0x123456, { 0x56, 0x34, 0x12, 0, 0 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<uint64_t>(0x123456FFFF, { 0xFF, 0xFF, 0x56, 0x34, 0x12 }).IsSuccess());
+ EXPECT_TRUE(
+ tester.TryDecode<DataModel::Nullable<uint64_t>>(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<uint64_t>(0x1234, { 0x34, 0x12, 0, 0 }).IsSuccess());
+ }
+ {
+ EncodeTester tester(CreateFakeMeta(ZCL_INT48U_ATTRIBUTE_TYPE, true /* nullable */));
+ EXPECT_TRUE(tester.TryDecode<uint64_t>(0x1234, { 0x34, 0x12, 0, 0, 0, 0 }).IsSuccess());
+ }
+ {
+ EncodeTester tester(CreateFakeMeta(ZCL_INT56U_ATTRIBUTE_TYPE, true /* nullable */));
+ EXPECT_TRUE(tester.TryDecode<uint64_t>(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<CharSpan>(""_span, { 0 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<CharSpan>("test"_span, { 4, 't', 'e', 's', 't' }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<CharSpan>("foo"_span, { 3, 'f', 'o', 'o' }).IsSuccess());
+ }
+
+ {
+ EncodeTester tester(CreateFakeMeta(ZCL_CHAR_STRING_ATTRIBUTE_TYPE, true /* nullable */));
+ EXPECT_TRUE(tester.TryDecode<CharSpan>(""_span, { 0 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<CharSpan>("test"_span, { 4, 't', 'e', 's', 't' }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<DataModel::Nullable<CharSpan>>(DataModel::NullNullable, { 0xFF }).IsSuccess());
+ }
+
+ {
+ EncodeTester tester(CreateFakeMeta(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE, false /* nullable */));
+ EXPECT_TRUE(tester.TryDecode<CharSpan>(""_span, { 0, 0 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<CharSpan>("test"_span, { 4, 0, 't', 'e', 's', 't' }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<CharSpan>("foo"_span, { 3, 0, 'f', 'o', 'o' }).IsSuccess());
+ }
+
+ {
+ EncodeTester tester(CreateFakeMeta(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE, true /* nullable */));
+ EXPECT_TRUE(tester.TryDecode<CharSpan>("test"_span, { 4, 0, 't', 'e', 's', 't' }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<DataModel::Nullable<CharSpan>>(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>(ByteSpan({}), { 0 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<ByteSpan>(ByteSpan(kOctetData), { 3, 1, 2, 3 }).IsSuccess());
+ }
+
+ {
+ EncodeTester tester(CreateFakeMeta(ZCL_OCTET_STRING_ATTRIBUTE_TYPE, true /* nullable */));
+ EXPECT_TRUE(tester.TryDecode<ByteSpan>(ByteSpan({}), { 0 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<ByteSpan>(ByteSpan(kOctetData), { 3, 1, 2, 3 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<DataModel::Nullable<ByteSpan>>(DataModel::NullNullable, { 0xFF }).IsSuccess());
+ }
+
+ {
+ EncodeTester tester(CreateFakeMeta(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE, false /* nullable */));
+ EXPECT_TRUE(tester.TryDecode<ByteSpan>(ByteSpan({}), { 0, 0 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<ByteSpan>(ByteSpan(kOctetData), { 3, 0, 1, 2, 3 }).IsSuccess());
+ }
+
+ {
+ EncodeTester tester(CreateFakeMeta(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE, true /* nullable */));
+ EXPECT_TRUE(tester.TryDecode<ByteSpan>(ByteSpan({}), { 0, 0 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<ByteSpan>(ByteSpan(kOctetData), { 3, 0, 1, 2, 3 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<DataModel::Nullable<ByteSpan>>(DataModel::NullNullable, { 0xFF, 0xFF }).IsSuccess());
+ }
+}
+
+TEST(TestEmberAttributeBuffer, TestDecodeBool)
+{
+ {
+ EncodeTester tester(CreateFakeMeta(ZCL_BOOLEAN_ATTRIBUTE_TYPE, false /* nullable */));
+
+ EXPECT_TRUE(tester.TryDecode<bool>(true, { 1 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<bool>(false, { 0 }).IsSuccess());
+ }
+
+ {
+ EncodeTester tester(CreateFakeMeta(ZCL_BOOLEAN_ATTRIBUTE_TYPE, true /* nullable */));
+
+ EXPECT_TRUE(tester.TryDecode<DataModel::Nullable<bool>>(true, { 1 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<DataModel::Nullable<bool>>(false, { 0 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<DataModel::Nullable<bool>>(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("<f", -123.55)]))'
+ // OUTPUT: 0x9A, 0x19, 0xF7, 0x42
+ //
+ // python -c 'import struct; print(", ".join(["0x%X" % v for v in struct.pack("<f", float("nan"))]))'
+ // OUTPUT: 0x00, 0x00, 0xC0, 0x7F
+ //
+ {
+ EncodeTester tester(CreateFakeMeta(ZCL_SINGLE_ATTRIBUTE_TYPE, false /* nullable */));
+ EXPECT_TRUE(tester.TryDecode<float>(123.55f, { 0x9A, 0x19, 0xF7, 0x42 }).IsSuccess());
+ }
+
+ {
+ EncodeTester tester(CreateFakeMeta(ZCL_SINGLE_ATTRIBUTE_TYPE, true /* nullable */));
+ EXPECT_TRUE(tester.TryDecode<float>(123.55f, { 0x9A, 0x19, 0xF7, 0x42 }).IsSuccess());
+ EXPECT_TRUE(tester.TryDecode<DataModel::Nullable<float>>(DataModel::NullNullable, { 0, 0, 0xC0, 0x7F }).IsSuccess());
+ }
+
+ {
+ EncodeTester tester(CreateFakeMeta(ZCL_DOUBLE_ATTRIBUTE_TYPE, false /* nullable */));
+ EXPECT_TRUE(tester.TryDecode<double>(123.55, { 0x33, 0x33, 0x33, 0x33, 0x33, 0xE3, 0x5E, 0x40 }).IsSuccess());
+ }
+
+ {
+ EncodeTester tester(CreateFakeMeta(ZCL_DOUBLE_ATTRIBUTE_TYPE, true /* nullable */));
+ EXPECT_TRUE(tester.TryDecode<double>(123.55, { 0x33, 0x33, 0x33, 0x33, 0x33, 0xE3, 0x5E, 0x40 }).IsSuccess());
+ EXPECT_TRUE(
+ tester.TryDecode<DataModel::Nullable<double>>(123.55, { 0x33, 0x33, 0x33, 0x33, 0x33, 0xE3, 0x5E, 0x40 }).IsSuccess());
+ EXPECT_TRUE(
+ tester.TryDecode<DataModel::Nullable<double>>(DataModel::NullNullable, { 0, 0, 0, 0, 0, 0, 0xF8, 0x7F }).IsSuccess());
+ }
+}