blob: dc83773454d1ff8bad5fcec5cd68fb2e684c527b [file] [log] [blame]
/*
* Copyright (c) 2024 Project CHIP Authors
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <pw_unit_test/framework.h>
#include <app/codegen-data-model-provider/EmberAttributeDataBuffer.h>
#include <app-common/zap-generated/attribute-type.h>
#include <app/MessageDef/AttributeDataIB.h>
#include <app/data-model/Encode.h>
#include <app/data-model/Nullable.h>
#include <app/util/af-types.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/StringBuilderAdapters.h>
#include <lib/core/TLVReader.h>
#include <lib/core/TLVTags.h>
#include <lib/core/TLVTypes.h>
#include <lib/core/TLVWriter.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/Span.h>
#include <limits>
#include <optional>
using namespace chip;
using namespace chip::app;
namespace {
/// encodes a simple value in a TLV buffer
class TLVEncodedValue
{
public:
TLVEncodedValue() = default;
~TLVEncodedValue() = default;
template <typename T>
TLV::TLVReader EncodeValue(const T & value)
{
const auto kTag = TLV::ContextTag(AttributeDataIB::Tag::kData);
TLV::TLVWriter writer;
writer.Init(mBuffer, sizeof(mBuffer));
TLV::TLVType outer;
VerifyOrDie(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outer) == CHIP_NO_ERROR);
VerifyOrDie(DataModel::Encode(writer, kTag, value) == CHIP_NO_ERROR);
VerifyOrDie(writer.EndContainer(outer) == CHIP_NO_ERROR);
VerifyOrDie(writer.Finalize() == CHIP_NO_ERROR);
size_t fill = writer.GetLengthWritten();
TLV::TLVReader reader;
reader.Init(mBuffer, fill);
VerifyOrDie(reader.Next() == CHIP_NO_ERROR);
VerifyOrDie(reader.GetTag() == TLV::AnonymousTag());
VerifyOrDie(reader.EnterContainer(outer) == CHIP_NO_ERROR);
VerifyOrDie(reader.Next() == CHIP_NO_ERROR);
VerifyOrDie(reader.GetTag() == kTag);
return reader;
}
private:
static constexpr size_t kMaxSize = 128;
uint8_t mBuffer[kMaxSize];
};
class EncodeResult
{
public:
explicit EncodeResult() = default;
EncodeResult(CHIP_ERROR error) : mResult(error) { VerifyOrDie(error != CHIP_NO_ERROR); }
static EncodeResult Ok() { return EncodeResult(); }
bool IsSuccess() const { return !mResult.has_value(); }
bool operator==(const CHIP_ERROR & other) const { return mResult.has_value() && (*mResult == other); }
const std::optional<CHIP_ERROR> & Value() const { return mResult; }
private:
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
{
public:
EncodeTester(const EmberAfAttributeMetadata * meta) : mMetaData(meta) {}
~EncodeTester() = default;
template <typename T, size_t N>
EncodeResult TryEncode(const T & value, const uint8_t (&arr)[N])
{
ByteSpan expected(arr);
MutableByteSpan out_span(mEmberAttributeDataBuffer);
Ember::EmberAttributeDataBuffer buffer(mMetaData, out_span);
TLVEncodedValue tlvEncoded;
TLV::TLVReader reader = tlvEncoded.EncodeValue(value);
CHIP_ERROR err = buffer.Decode(reader);
if (err != CHIP_NO_ERROR)
{
return err;
}
if (expected.size() != out_span.size())
{
ChipLogError(Test, "Decode mismatch in size: expected %u, got %u", static_cast<unsigned>(expected.size()),
static_cast<unsigned>(out_span.size()));
return CHIP_ERROR_INTERNAL;
}
if (!expected.data_equal(out_span))
{
ChipLogError(Test, "Decode mismatch in content for %u bytes", static_cast<unsigned>(expected.size()));
return CHIP_ERROR_INTERNAL;
}
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];
};
const EmberAfAttributeMetadata * CreateFakeMeta(EmberAfAttributeType type, bool nullable)
{
static EmberAfAttributeMetadata meta = {
.defaultValue = EmberAfDefaultOrMinMaxAttributeValue(static_cast<uint8_t *>(nullptr)),
.attributeId = 0,
.size = 0, // likely not valid, however not used for tests
.attributeType = ZCL_UNKNOWN_ATTRIBUTE_TYPE,
.mask = 0,
};
meta.attributeType = type;
meta.mask = nullable ? ATTRIBUTE_MASK_NULLABLE : 0;
return &meta;
}
} // namespace
// All the tests below assume buffer ordering in little endian format
// Since currently all chip platforms in CI are little endian, we just kept tests
// as-is
static_assert(!CHIP_CONFIG_BIG_ENDIAN_TARGET);
TEST(TestEmberAttributeBuffer, TestEncodeUnsignedTypes)
{
{
EncodeTester tester(CreateFakeMeta(ZCL_INT8U_ATTRIBUTE_TYPE, false /* nullable */));
EXPECT_TRUE(tester.TryEncode<uint8_t>(0, { 0 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<uint8_t>(123, { 123 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<uint8_t>(0xFD, { 0xFD }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<uint8_t>(255, { 0xFF }).IsSuccess());
}
{
EncodeTester tester(CreateFakeMeta(ZCL_INT8U_ATTRIBUTE_TYPE, true /* nullable */));
EXPECT_TRUE(tester.TryEncode<uint8_t>(0, { 0 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<uint8_t>(123, { 123 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<uint8_t>(0xFD, { 0xFD }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<DataModel::Nullable<uint8_t>>(DataModel::NullNullable, { 0xFF }).IsSuccess());
// Not allowed to encode null-equivalent
EXPECT_EQ(tester.TryEncode<uint8_t>(0xFF, { 0xFF }), CHIP_IM_GLOBAL_STATUS(ConstraintError));
}
{
EncodeTester tester(CreateFakeMeta(ZCL_INT16U_ATTRIBUTE_TYPE, false /* nullable */));
EXPECT_TRUE(tester.TryEncode<uint16_t>(0, { 0, 0 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<uint16_t>(123, { 123, 0 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<uint16_t>(0xFD, { 0xFD, 0 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<uint16_t>(255, { 0xFF, 0 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<uint16_t>(0xABCD, { 0xCD, 0xAB }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<uint16_t>(0xFFFF, { 0xFF, 0xFF }).IsSuccess());
}
{
EncodeTester tester(CreateFakeMeta(ZCL_INT16U_ATTRIBUTE_TYPE, true /* nullable */));
EXPECT_TRUE(tester.TryEncode<uint16_t>(0, { 0, 0 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<uint16_t>(123, { 123, 0 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<uint16_t>(0xFD, { 0xFD, 0 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<uint16_t>(255, { 0xFF, 0 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<uint16_t>(0xABCD, { 0xCD, 0xAB }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<DataModel::Nullable<uint16_t>>(DataModel::NullNullable, { 0xFF, 0xFF }).IsSuccess());
// Not allowed to encode null-equivalent
EXPECT_EQ(tester.TryEncode<uint16_t>(0xFFFF, { 0xFF, 0xFF }), CHIP_IM_GLOBAL_STATUS(ConstraintError));
}
{
EncodeTester tester(CreateFakeMeta(ZCL_INT64U_ATTRIBUTE_TYPE, true /* nullable */));
EXPECT_TRUE(tester.TryEncode<uint64_t>(0, { 0, 0, 0, 0, 0, 0, 0, 0 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<uint64_t>(0x1234567, { 0x67, 0x45, 0x23, 0x01, 0, 0, 0, 0 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<uint64_t>(0xAABBCCDDEEFF1122, { 0x22, 0x11, 0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA }).IsSuccess());
EXPECT_TRUE(
tester.TryEncode<uint64_t>(std::numeric_limits<uint64_t>::max() - 1, { 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF })
.IsSuccess());
EXPECT_TRUE(tester
.TryEncode<DataModel::Nullable<uint64_t>>(DataModel::NullNullable,
{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF })
.IsSuccess());
EXPECT_EQ(
tester.TryEncode<uint64_t>(std::numeric_limits<uint64_t>::max(), { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }),
CHIP_IM_GLOBAL_STATUS(ConstraintError));
}
{
EncodeTester tester(CreateFakeMeta(ZCL_INT64U_ATTRIBUTE_TYPE, false /* nullable */));
// we should be able to encode the maximum value
EXPECT_TRUE(
tester.TryEncode<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.TryEncode<uint32_t>(0, { 0, 0, 0 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<uint32_t>(0x123456, { 0x56, 0x34, 0x12 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<uint32_t>(0xFFFFFF, { 0xFF, 0xFF, 0xFF }).IsSuccess());
// Out of range
EXPECT_EQ(tester.TryEncode<uint32_t>(0x1000000, { 0 }), CHIP_IM_GLOBAL_STATUS(ConstraintError));
EXPECT_EQ(tester.TryEncode<uint32_t>(0xFF000000, { 0 }), CHIP_IM_GLOBAL_STATUS(ConstraintError));
}
{
EncodeTester tester(CreateFakeMeta(ZCL_INT24U_ATTRIBUTE_TYPE, true /* nullable */));
EXPECT_TRUE(tester.TryEncode<uint32_t>(0, { 0, 0, 0 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<uint32_t>(0x123456, { 0x56, 0x34, 0x12 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<DataModel::Nullable<uint32_t>>(DataModel::NullNullable, { 0xFF, 0xFF, 0xFF }).IsSuccess());
// Out of range
EXPECT_EQ(tester.TryEncode<uint32_t>(0x1000000, { 0 }), CHIP_IM_GLOBAL_STATUS(ConstraintError));
// cannot encode null equivalent value
EXPECT_EQ(tester.TryEncode<uint32_t>(0xFFFFFF, { 0x56, 0x34, 0x12 }), CHIP_IM_GLOBAL_STATUS(ConstraintError));
}
{
EncodeTester tester(CreateFakeMeta(ZCL_INT40U_ATTRIBUTE_TYPE, true /* nullable */));
EXPECT_TRUE(tester.TryEncode<uint64_t>(0, { 0, 0, 0, 0, 0 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<uint64_t>(0x123456, { 0x56, 0x34, 0x12, 0, 0 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<uint64_t>(0x123456FFFF, { 0xFF, 0xFF, 0x56, 0x34, 0x12 }).IsSuccess());
EXPECT_TRUE(
tester.TryEncode<DataModel::Nullable<uint64_t>>(DataModel::NullNullable, { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }).IsSuccess());
// Out of range
EXPECT_EQ(tester.TryEncode<uint64_t>(0x10011001100, { 0 }), CHIP_IM_GLOBAL_STATUS(ConstraintError));
// cannot encode null equivalent value
EXPECT_EQ(tester.TryEncode<uint64_t>(0xFFFFFFFFFF, { 0 }), CHIP_IM_GLOBAL_STATUS(ConstraintError));
}
// 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.TryEncode<uint64_t>(0x1234, { 0x34, 0x12, 0, 0 }).IsSuccess());
}
{
EncodeTester tester(CreateFakeMeta(ZCL_INT48U_ATTRIBUTE_TYPE, true /* nullable */));
EXPECT_TRUE(tester.TryEncode<uint64_t>(0x1234, { 0x34, 0x12, 0, 0, 0, 0 }).IsSuccess());
}
{
EncodeTester tester(CreateFakeMeta(ZCL_INT56U_ATTRIBUTE_TYPE, true /* nullable */));
EXPECT_TRUE(tester.TryEncode<uint64_t>(0x1234, { 0x34, 0x12, 0, 0, 0, 0, 0 }).IsSuccess());
}
}
TEST(TestEmberAttributeBuffer, TestEncodeSignedTypes)
{
{
EncodeTester tester(CreateFakeMeta(ZCL_INT8S_ATTRIBUTE_TYPE, false /* nullable */));
EXPECT_TRUE(tester.TryEncode<int8_t>(0, { 0 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<int8_t>(123, { 123 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<int8_t>(127, { 127 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<int8_t>(-10, { 0xF6 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<int8_t>(-128, { 0x80 }).IsSuccess());
}
{
EncodeTester tester(CreateFakeMeta(ZCL_INT8S_ATTRIBUTE_TYPE, true /* nullable */));
EXPECT_TRUE(tester.TryEncode<int8_t>(0, { 0 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<int8_t>(123, { 123 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<int8_t>(127, { 127 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<int8_t>(-10, { 0xF6 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<int8_t>(-127, { 0x81 }).IsSuccess());
// NULL canot be encoded
EXPECT_EQ(tester.TryEncode<int8_t>(std::numeric_limits<int8_t>::min(), { 0x80 }), CHIP_IM_GLOBAL_STATUS(ConstraintError));
}
{
EncodeTester tester(CreateFakeMeta(ZCL_INT16S_ATTRIBUTE_TYPE, false /* nullable */));
EXPECT_TRUE(tester.TryEncode<int16_t>(0, { 0, 0 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<int16_t>(123, { 123, 0 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<int16_t>(127, { 127, 0 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<int16_t>(-10, { 0xF6, 0xFF }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<int16_t>(-128, { 0x80, 0xFF }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<int16_t>(-1234, { 0x2E, 0xFB }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<int16_t>(std::numeric_limits<int16_t>::min(), { 0x0, 0x80 }).IsSuccess());
}
{
EncodeTester tester(CreateFakeMeta(ZCL_INT16S_ATTRIBUTE_TYPE, true /* nullable */));
EXPECT_TRUE(tester.TryEncode<int16_t>(0, { 0, 0 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<int16_t>(123, { 123, 0 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<int16_t>(127, { 127, 0 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<int16_t>(-10, { 0xF6, 0xFF }).IsSuccess());
// NULL canot be encoded
EXPECT_EQ(tester.TryEncode<int16_t>(std::numeric_limits<int16_t>::min(), { 0x80 }), CHIP_IM_GLOBAL_STATUS(ConstraintError));
}
// Odd size integers
{
EncodeTester tester(CreateFakeMeta(ZCL_INT24S_ATTRIBUTE_TYPE, false /* nullable */));
EXPECT_TRUE(tester.TryEncode<int32_t>(0, { 0, 0, 0 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<int32_t>(0x123456, { 0x56, 0x34, 0x12 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<int32_t>(-1, { 0xFF, 0xFF, 0xFF }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<int32_t>(-10, { 0xF6, 0xFF, 0xFF }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<int32_t>(-1234, { 0x2E, 0xFB, 0xFF }).IsSuccess());
// Out of range
EXPECT_EQ(tester.TryEncode<int32_t>(0x1000000, { 0 }), CHIP_IM_GLOBAL_STATUS(ConstraintError));
EXPECT_EQ(tester.TryEncode<int32_t>(0x0F000000, { 0 }), CHIP_IM_GLOBAL_STATUS(ConstraintError));
EXPECT_EQ(tester.TryEncode<int32_t>(-0x1000000, { 0 }), CHIP_IM_GLOBAL_STATUS(ConstraintError));
}
{
EncodeTester tester(CreateFakeMeta(ZCL_INT24S_ATTRIBUTE_TYPE, true /* nullable */));
EXPECT_TRUE(tester.TryEncode<int32_t>(0, { 0, 0, 0 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<int32_t>(0x123456, { 0x56, 0x34, 0x12 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<int32_t>(-1, { 0xFF, 0xFF, 0xFF }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<int32_t>(-10, { 0xF6, 0xFF, 0xFF }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<int32_t>(-1234, { 0x2E, 0xFB, 0xFF }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<DataModel::Nullable<uint32_t>>(DataModel::NullNullable, { 0x00, 0x00, 0x80 }).IsSuccess());
// Out of range
EXPECT_EQ(tester.TryEncode<int32_t>(0x1000000, { 0 }), CHIP_IM_GLOBAL_STATUS(ConstraintError));
// cannot encode null equivalent value - this is the minimum negative value
// for 24-bit
EXPECT_EQ(tester.TryEncode<int32_t>(-(1 << 24) - 1, { 0x56, 0x34, 0x12 }), CHIP_IM_GLOBAL_STATUS(ConstraintError));
// Out of range for signed - these are unsigned values that are larger
EXPECT_EQ(tester.TryEncode<int32_t>(0xFFFFFF, { 0x56, 0x34, 0x12 }), CHIP_IM_GLOBAL_STATUS(ConstraintError));
EXPECT_EQ(tester.TryEncode<int32_t>(0x800000, { 0x56, 0x34, 0x12 }), CHIP_IM_GLOBAL_STATUS(ConstraintError));
}
{
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.TryEncode<int64_t>(0, { 0, 0, 0, 0, 0 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<int64_t>(0x123456, { 0x56, 0x34, 0x12, 0, 0 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<int64_t>(-1234, { 0x2E, 0xFB, 0xFF, 0xFF, 0xFF }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<int64_t>(-123456789, { 0xeb, 0x32, 0xa4, 0xf8, 0xFF }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<int64_t>(-12345678910, { 0xc2, 0xe3, 0x23, 0x20, 0xfd }).IsSuccess());
EXPECT_TRUE(
tester.TryEncode<DataModel::Nullable<uint64_t>>(DataModel::NullNullable, { 0x00, 0x00, 0x00, 0x00, 0x80 }).IsSuccess());
// Out of range
EXPECT_EQ(tester.TryEncode<int64_t>(0x10011001100, { 0 }), CHIP_IM_GLOBAL_STATUS(ConstraintError));
// cannot encode null equivalent value
EXPECT_EQ(tester.TryEncode<int64_t>(-(1LL << 40) - 1, { 0 }), CHIP_IM_GLOBAL_STATUS(ConstraintError));
// negative out of range
EXPECT_EQ(tester.TryEncode<int64_t>(-0x10000000000, { 0 }), CHIP_IM_GLOBAL_STATUS(ConstraintError));
}
// 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.TryEncode<int64_t>(-1234, { 0x2E, 0xFB, 0xFF, 0xFF }).IsSuccess());
}
{
EncodeTester tester(CreateFakeMeta(ZCL_INT48S_ATTRIBUTE_TYPE, true /* nullable */));
EXPECT_TRUE(tester.TryEncode<int64_t>(-1234, { 0x2E, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF }).IsSuccess());
}
{
EncodeTester tester(CreateFakeMeta(ZCL_INT56S_ATTRIBUTE_TYPE, true /* nullable */));
EXPECT_TRUE(tester.TryEncode<int64_t>(-1234, { 0x2E, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }).IsSuccess());
}
{
EncodeTester tester(CreateFakeMeta(ZCL_INT64S_ATTRIBUTE_TYPE, true /* nullable */));
EXPECT_TRUE(tester.TryEncode<int64_t>(-1234, { 0x2E, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }).IsSuccess());
// min/max ranges too
EXPECT_TRUE(
tester.TryEncode<int64_t>(std::numeric_limits<int64_t>::min() + 1, { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80 })
.IsSuccess());
EXPECT_TRUE(
tester.TryEncode<int64_t>(std::numeric_limits<int64_t>::max(), { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F })
.IsSuccess());
// Reserved value for NULL
EXPECT_EQ(
tester.TryEncode<int64_t>(std::numeric_limits<int64_t>::min(), { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80 }),
CHIP_IM_GLOBAL_STATUS(ConstraintError));
}
{
EncodeTester tester(CreateFakeMeta(ZCL_INT64S_ATTRIBUTE_TYPE, false /* nullable */));
EXPECT_TRUE(tester.TryEncode<int64_t>(-1234, { 0x2E, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }).IsSuccess());
EXPECT_TRUE(
tester.TryEncode<int64_t>(std::numeric_limits<int64_t>::min(), { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80 })
.IsSuccess());
EXPECT_TRUE(
tester.TryEncode<int64_t>(std::numeric_limits<int64_t>::min() + 1, { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80 })
.IsSuccess());
EXPECT_TRUE(
tester.TryEncode<int64_t>(std::numeric_limits<int64_t>::max(), { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F })
.IsSuccess());
}
}
TEST(TestEmberAttributeBuffer, TestEncodeBool)
{
{
EncodeTester tester(CreateFakeMeta(ZCL_BOOLEAN_ATTRIBUTE_TYPE, false /* nullable */));
EXPECT_TRUE(tester.TryEncode<bool>(true, { 1 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<bool>(false, { 0 }).IsSuccess());
}
{
EncodeTester tester(CreateFakeMeta(ZCL_BOOLEAN_ATTRIBUTE_TYPE, true /* nullable */));
EXPECT_TRUE(tester.TryEncode<bool>(true, { 1 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<bool>(false, { 0 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<DataModel::Nullable<bool>>(DataModel::NullNullable, { 0xFF }).IsSuccess());
}
}
TEST(TestEmberAttributeBuffer, TestEncodeFloatingPoint)
{
// 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.TryEncode<float>(123.55f, { 0x9A, 0x19, 0xF7, 0x42 }).IsSuccess());
}
{
EncodeTester tester(CreateFakeMeta(ZCL_SINGLE_ATTRIBUTE_TYPE, true /* nullable */));
EXPECT_TRUE(tester.TryEncode<float>(123.55f, { 0x9A, 0x19, 0xF7, 0x42 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<DataModel::Nullable<float>>(DataModel::NullNullable, { 0, 0, 0xC0, 0x7F }).IsSuccess());
}
{
EncodeTester tester(CreateFakeMeta(ZCL_DOUBLE_ATTRIBUTE_TYPE, false /* nullable */));
EXPECT_TRUE(tester.TryEncode<double>(123.55, { 0x33, 0x33, 0x33, 0x33, 0x33, 0xE3, 0x5E, 0x40 }).IsSuccess());
}
{
EncodeTester tester(CreateFakeMeta(ZCL_DOUBLE_ATTRIBUTE_TYPE, true /* nullable */));
EXPECT_TRUE(tester.TryEncode<double>(123.55, { 0x33, 0x33, 0x33, 0x33, 0x33, 0xE3, 0x5E, 0x40 }).IsSuccess());
EXPECT_TRUE(
tester.TryEncode<DataModel::Nullable<double>>(DataModel::NullNullable, { 0, 0, 0, 0, 0, 0, 0xF8, 0x7F }).IsSuccess());
}
}
TEST(TestEmberAttributeBuffer, TestEncodeStrings)
{
{
EncodeTester tester(CreateFakeMeta(ZCL_CHAR_STRING_ATTRIBUTE_TYPE, false /* nullable */));
EXPECT_TRUE(tester.TryEncode<CharSpan>(""_span, { 0 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<CharSpan>("test"_span, { 4, 't', 'e', 's', 't' }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<CharSpan>("foo"_span, { 3, 'f', 'o', 'o' }).IsSuccess());
}
{
EncodeTester tester(CreateFakeMeta(ZCL_CHAR_STRING_ATTRIBUTE_TYPE, true /* nullable */));
EXPECT_TRUE(tester.TryEncode<CharSpan>(""_span, { 0 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<CharSpan>("test"_span, { 4, 't', 'e', 's', 't' }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<DataModel::Nullable<CharSpan>>(DataModel::NullNullable, { 0xFF }).IsSuccess());
}
{
EncodeTester tester(CreateFakeMeta(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE, false /* nullable */));
EXPECT_TRUE(tester.TryEncode<CharSpan>(""_span, { 0, 0 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<CharSpan>("test"_span, { 4, 0, 't', 'e', 's', 't' }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<CharSpan>("foo"_span, { 3, 0, 'f', 'o', 'o' }).IsSuccess());
}
{
EncodeTester tester(CreateFakeMeta(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE, true /* nullable */));
EXPECT_TRUE(tester.TryEncode<CharSpan>("test"_span, { 4, 0, 't', 'e', 's', 't' }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<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.TryEncode<ByteSpan>(ByteSpan({}), { 0 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<ByteSpan>(ByteSpan(kOctetData), { 3, 1, 2, 3 }).IsSuccess());
}
{
EncodeTester tester(CreateFakeMeta(ZCL_OCTET_STRING_ATTRIBUTE_TYPE, true /* nullable */));
EXPECT_TRUE(tester.TryEncode<ByteSpan>(ByteSpan({}), { 0 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<ByteSpan>(ByteSpan(kOctetData), { 3, 1, 2, 3 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<DataModel::Nullable<ByteSpan>>(DataModel::NullNullable, { 0xFF }).IsSuccess());
}
{
EncodeTester tester(CreateFakeMeta(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE, false /* nullable */));
EXPECT_TRUE(tester.TryEncode<ByteSpan>(ByteSpan({}), { 0, 0 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<ByteSpan>(ByteSpan(kOctetData), { 3, 0, 1, 2, 3 }).IsSuccess());
}
{
EncodeTester tester(CreateFakeMeta(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE, true /* nullable */));
EXPECT_TRUE(tester.TryEncode<ByteSpan>(ByteSpan({}), { 0, 0 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<ByteSpan>(ByteSpan(kOctetData), { 3, 0, 1, 2, 3 }).IsSuccess());
EXPECT_TRUE(tester.TryEncode<DataModel::Nullable<ByteSpan>>(DataModel::NullNullable, { 0xFF, 0xFF }).IsSuccess());
}
}
TEST(TestEmberAttributeBuffer, TestEncodeFailures)
{
{
// attribute type that is not handled
EncodeTester tester(CreateFakeMeta(ZCL_UNKNOWN_ATTRIBUTE_TYPE, true /* nullable */));
EXPECT_EQ(tester.TryEncode<DataModel::Nullable<uint32_t>>(DataModel::NullNullable, { 0 }), CHIP_IM_GLOBAL_STATUS(Failure));
}
{
// 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 */));
EXPECT_EQ(tester.TryEncode<ByteSpan>(ByteSpan({}), { 0 }), CHIP_ERROR_WRONG_TLV_TYPE);
}
{
EncodeTester tester(CreateFakeMeta(ZCL_INT32U_ATTRIBUTE_TYPE, false /* nullable */));
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());
}
}