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());
+    }
+}