| /* |
| * |
| * 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 |