blob: fb2bd0707fe390142e61723b6a220ce41d89497e [file]
/*
*
* Copyright (c) 2026 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 <app-common/zap-generated/attribute-type.h>
#include <app/util/attribute-metadata.h>
#include <app/util/attribute-storage.h>
#include <app/util/mock/Constants.h>
#include <app/util/mock/Functions.h>
#include <app/util/mock/MockNodeConfig.h>
#include <lib/support/Span.h>
#include <pw_unit_test/framework.h>
namespace {
using namespace chip;
using namespace chip::app;
const uint8_t sTestShortStringDefault[] = "\x05Hello";
const uint8_t sTestLongStringDefault[] = "\x0b\x00Hello World";
const uint8_t sTestOctetStringDefault[] = "\x04\x01\x02\x03\x04";
const uint8_t sTestNullShortStringDefault[] = "\xFF";
const uint8_t sTestNullLongStringDefault[] = "\xFF\xFF";
EmberAfAttributeMinMaxValue sTestMinMaxInt16 = {
.defaultValue = EmberAfDefaultAttributeValue(static_cast<uint16_t>(50)),
.minValue = EmberAfDefaultAttributeValue(static_cast<uint16_t>(10)),
.maxValue = EmberAfDefaultAttributeValue(static_cast<uint16_t>(100)),
};
// clang-format off
DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(sTestDynamicAttrs)
DECLARE_DYNAMIC_ATTRIBUTE(0x0001, INT8U, 1, MATTER_ATTRIBUTE_FLAG_NO_DEFAULT_VALUE),
DECLARE_DYNAMIC_ATTRIBUTE_WITH_SCALAR_DEFAULT(0x0002, INT8U, 1, 0, 42),
DECLARE_DYNAMIC_ATTRIBUTE_WITH_SCALAR_DEFAULT(0x0003, INT16U, 2, 0, 0x1234),
DECLARE_DYNAMIC_ATTRIBUTE_WITH_SCALAR_DEFAULT(0x0004, INT32U, 4, 0, 0x12345678),
DECLARE_DYNAMIC_ATTRIBUTE_WITH_SCALAR_DEFAULT(0x0005, BOOLEAN, 1, 0, true),
DECLARE_DYNAMIC_ATTRIBUTE_WITH_SCALAR_DEFAULT(0x0006, INT16S, 2, 0, -100),
DECLARE_DYNAMIC_ATTRIBUTE_WITH_STRING_DEFAULT(0x0007, CHAR_STRING, 16, 0, sTestShortStringDefault),
DECLARE_DYNAMIC_ATTRIBUTE_WITH_STRING_DEFAULT(0x0008, LONG_CHAR_STRING, 64, 0, sTestLongStringDefault),
DECLARE_DYNAMIC_ATTRIBUTE_WITH_STRING_DEFAULT(0x0009, OCTET_STRING, 16, 0, sTestOctetStringDefault),
DECLARE_DYNAMIC_ATTRIBUTE_WITH_MIN_MAX_DEFAULT(0x000A, INT16U, 2, 0, &sTestMinMaxInt16),
DECLARE_DYNAMIC_ATTRIBUTE_WITH_STRING_DEFAULT(0x000B, CHAR_STRING, 16, MATTER_ATTRIBUTE_FLAG_NULLABLE, sTestNullShortStringDefault),
DECLARE_DYNAMIC_ATTRIBUTE_WITH_STRING_DEFAULT(0x000C, LONG_CHAR_STRING, 64, MATTER_ATTRIBUTE_FLAG_NULLABLE, sTestNullLongStringDefault),
DECLARE_DYNAMIC_ATTRIBUTE_WITH_SCALAR_DEFAULT(0x000D, INT8U, 1, MATTER_ATTRIBUTE_FLAG_NULLABLE, 0xFF),
DECLARE_DYNAMIC_ATTRIBUTE_WITH_SCALAR_DEFAULT(0x000E, INT8U, 1, MATTER_ATTRIBUTE_FLAG_NULLABLE, 10),
DECLARE_DYNAMIC_ATTRIBUTE(0x000F, INT16S, 2, MATTER_ATTRIBUTE_FLAG_NULLABLE | MATTER_ATTRIBUTE_FLAG_NO_DEFAULT_VALUE),
DECLARE_DYNAMIC_ATTRIBUTE_WITH_MIN_MAX_DEFAULT(0x0010, INT16U, 2, MATTER_ATTRIBUTE_FLAG_NO_DEFAULT_VALUE, &sTestMinMaxInt16),
DECLARE_DYNAMIC_ATTRIBUTE_LIST_END_WITH_REVISION(3);
// clang-format on
TEST(TestDynamicAttributeMetadata, EmptyDefault)
{
AttributeDefaultValue val;
EXPECT_TRUE(sTestDynamicAttrs[0].HasEmptyDefault());
EXPECT_EQ(emberAfGetAttributeDefaultValue(sTestDynamicAttrs[0], val), Protocols::InteractionModel::Status::NotFound);
EXPECT_TRUE(val.rawData.empty());
EXPECT_EQ(val.As<uint8_t>(), 0);
// Nullable attribute with empty default -> Null
EXPECT_TRUE(sTestDynamicAttrs[14].HasEmptyDefault());
EXPECT_EQ(emberAfGetAttributeDefaultValue(sTestDynamicAttrs[14], val), Protocols::InteractionModel::Status::NotFound);
EXPECT_TRUE(val.rawData.empty());
EXPECT_TRUE(val.AsNullable<int16_t>().IsNull());
// Min/max attribute with no default flag -> HasEmptyDefault is true, rawData is empty
EXPECT_TRUE(sTestDynamicAttrs[15].HasEmptyDefault());
EXPECT_EQ(emberAfGetAttributeDefaultValue(sTestDynamicAttrs[15], val), Protocols::InteractionModel::Status::NotFound);
EXPECT_TRUE(val.rawData.empty());
}
TEST(TestDynamicAttributeMetadata, ScalarDefaults)
{
AttributeDefaultValue val;
// INT8U: 42
EXPECT_EQ(emberAfGetAttributeDefaultValue(sTestDynamicAttrs[1], val), Protocols::InteractionModel::Status::Success);
EXPECT_EQ(val.As<uint8_t>(), 42);
// INT16U: 0x1234
EXPECT_EQ(emberAfGetAttributeDefaultValue(sTestDynamicAttrs[2], val), Protocols::InteractionModel::Status::Success);
EXPECT_EQ(val.As<uint16_t>(), 0x1234);
// INT32U: 0x12345678
EXPECT_EQ(emberAfGetAttributeDefaultValue(sTestDynamicAttrs[3], val), Protocols::InteractionModel::Status::Success);
EXPECT_EQ(val.As<uint32_t>(), 0x12345678u);
// BOOLEAN: true
EXPECT_EQ(emberAfGetAttributeDefaultValue(sTestDynamicAttrs[4], val), Protocols::InteractionModel::Status::Success);
EXPECT_TRUE(val.As<bool>());
// INT16S: -100
EXPECT_EQ(emberAfGetAttributeDefaultValue(sTestDynamicAttrs[5], val), Protocols::InteractionModel::Status::Success);
EXPECT_EQ(val.As<int16_t>(), -100);
}
TEST(TestDynamicAttributeMetadata, StringDefaults)
{
AttributeDefaultValue val;
// Short string
EXPECT_EQ(emberAfGetAttributeDefaultValue(sTestDynamicAttrs[6], val), Protocols::InteractionModel::Status::Success);
EXPECT_TRUE(val.ToCharSpan().data_equal("Hello"_span));
// Long string
EXPECT_EQ(emberAfGetAttributeDefaultValue(sTestDynamicAttrs[7], val), Protocols::InteractionModel::Status::Success);
EXPECT_TRUE(val.ToCharSpan().data_equal("Hello World"_span));
// Octet string
EXPECT_EQ(emberAfGetAttributeDefaultValue(sTestDynamicAttrs[8], val), Protocols::InteractionModel::Status::Success);
const uint8_t expectedBytes[] = { 1, 2, 3, 4 };
EXPECT_TRUE(val.ToByteSpan().data_equal(ByteSpan(expectedBytes, sizeof(expectedBytes))));
}
TEST(TestDynamicAttributeMetadata, MinMaxDefault)
{
AttributeDefaultValue val;
EXPECT_EQ(emberAfGetAttributeDefaultValue(sTestDynamicAttrs[9], val), Protocols::InteractionModel::Status::Success);
EXPECT_EQ(val.As<uint16_t>(), 50);
EXPECT_FALSE(sTestDynamicAttrs[9].HasEmptyDefault());
// Min/max attribute with null pointer does not crash and returns Status::NotFound with empty rawData
EmberAfAttributeMetadata nullMinMaxAttr = DECLARE_DYNAMIC_ATTRIBUTE_WITH_MIN_MAX_DEFAULT(
0x000A, INT16U, 2, 0, static_cast<const EmberAfAttributeMinMaxValue *>(nullptr));
EXPECT_FALSE(nullMinMaxAttr.HasEmptyDefault());
EXPECT_EQ(emberAfGetAttributeDefaultValue(nullMinMaxAttr, val), Protocols::InteractionModel::Status::NotFound);
EXPECT_TRUE(val.rawData.empty());
}
TEST(TestDynamicAttributeMetadata, NullableDefaults)
{
AttributeDefaultValue val;
// Null short string
EXPECT_EQ(emberAfGetAttributeDefaultValue(sTestDynamicAttrs[10], val), Protocols::InteractionModel::Status::Success);
auto nullableShort = val.ToNullableCharSpan();
EXPECT_TRUE(nullableShort.IsNull());
// Null long string
EXPECT_EQ(emberAfGetAttributeDefaultValue(sTestDynamicAttrs[11], val), Protocols::InteractionModel::Status::Success);
auto nullableLong = val.ToNullableCharSpan();
EXPECT_TRUE(nullableLong.IsNull());
// Null scalar
EXPECT_EQ(emberAfGetAttributeDefaultValue(sTestDynamicAttrs[12], val), Protocols::InteractionModel::Status::Success);
auto nullableU8Null = val.AsNullable<uint8_t>();
EXPECT_TRUE(nullableU8Null.IsNull());
// Non-null nullable scalar
EXPECT_EQ(emberAfGetAttributeDefaultValue(sTestDynamicAttrs[13], val), Protocols::InteractionModel::Status::Success);
auto nullableU8Val = val.AsNullable<uint8_t>();
EXPECT_FALSE(nullableU8Val.IsNull());
EXPECT_EQ(nullableU8Val.Value(), 10);
}
TEST(TestDynamicAttributeMetadata, EmptyRawDataSemantics)
{
AttributeDefaultValue val;
val.rawData = ByteSpan();
// Non-nullable scalars: 0-fill
EXPECT_EQ(val.As<uint8_t>(), 0);
EXPECT_EQ(val.As<uint16_t>(), 0);
EXPECT_EQ(val.As<uint32_t>(), 0u);
EXPECT_EQ(val.As<int16_t>(), 0);
EXPECT_FALSE(val.As<bool>());
// Nullable scalars: Null
EXPECT_TRUE(val.AsNullable<uint8_t>().IsNull());
EXPECT_TRUE(val.AsNullable<uint16_t>().IsNull());
EXPECT_TRUE(val.AsNullable<uint32_t>().IsNull());
EXPECT_TRUE(val.AsNullable<int16_t>().IsNull());
EXPECT_TRUE(val.AsNullable<bool>().IsNull());
// Non-nullable strings: empty span
val.type = ZCL_CHAR_STRING_ATTRIBUTE_TYPE;
EXPECT_TRUE(val.ToCharSpan().empty());
val.type = ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE;
EXPECT_TRUE(val.ToCharSpan().empty());
val.type = ZCL_OCTET_STRING_ATTRIBUTE_TYPE;
EXPECT_TRUE(val.ToByteSpan().empty());
val.type = ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE;
EXPECT_TRUE(val.ToByteSpan().empty());
// Nullable strings: Null
val.type = ZCL_CHAR_STRING_ATTRIBUTE_TYPE;
EXPECT_TRUE(val.ToNullableCharSpan().IsNull());
val.type = ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE;
EXPECT_TRUE(val.ToNullableCharSpan().IsNull());
val.type = ZCL_OCTET_STRING_ATTRIBUTE_TYPE;
EXPECT_TRUE(val.ToNullableByteSpan().IsNull());
val.type = ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE;
EXPECT_TRUE(val.ToNullableByteSpan().IsNull());
}
TEST(TestDynamicAttributeMetadata, ClusterRevision)
{
AttributeDefaultValue val;
EXPECT_EQ(emberAfGetAttributeDefaultValue(sTestDynamicAttrs[16], val), Protocols::InteractionModel::Status::Success);
EXPECT_EQ(sTestDynamicAttrs[16].attributeId, 0xFFFDu);
EXPECT_EQ(val.As<uint16_t>(), 3);
}
TEST(TestDynamicAttributeMetadata, EndpointLevelLookup)
{
constexpr EndpointId kTestEndpointId = 1;
constexpr ClusterId kTestClusterId = 0xFFF1'0001;
Testing::MockNodeConfig nodeConfig({
Testing::MockEndpointConfig(kTestEndpointId,
{
Testing::MockClusterConfig(kTestClusterId,
{
Testing::MockAttributeConfig(0x0001, sTestDynamicAttrs[0]),
Testing::MockAttributeConfig(0x0002, sTestDynamicAttrs[1]),
Testing::MockAttributeConfig(0x0007, sTestDynamicAttrs[6]),
}),
}),
});
Testing::SetMockNodeConfig(nodeConfig);
AttributeDefaultValue val;
EXPECT_EQ(emberAfGetAttributeDefaultValue(kTestEndpointId, kTestClusterId, 0x0001, val),
Protocols::InteractionModel::Status::NotFound);
EXPECT_TRUE(val.rawData.empty());
EXPECT_EQ(emberAfGetAttributeDefaultValue(kTestEndpointId, kTestClusterId, 0x0002, val),
Protocols::InteractionModel::Status::Success);
EXPECT_EQ(val.As<uint8_t>(), 42);
EXPECT_EQ(emberAfGetAttributeDefaultValue(kTestEndpointId, kTestClusterId, 0x0007, val),
Protocols::InteractionModel::Status::Success);
EXPECT_TRUE(val.ToCharSpan().data_equal("Hello"_span));
EXPECT_EQ(emberAfGetAttributeDefaultValue(kTestEndpointId, kTestClusterId, 0x9999, val),
Protocols::InteractionModel::Status::UnsupportedAttribute);
// Endpoint 99 does not exist, so the cluster cannot be found.
EXPECT_EQ(emberAfGetAttributeDefaultValue(99, kTestClusterId, 0x0002, val),
Protocols::InteractionModel::Status::UnsupportedCluster);
Testing::ResetMockNodeConfig();
}
} // namespace