diff --git a/src/app/AttributeAccessInterface.h b/src/app/AttributeAccessInterface.h index 9b58f379d40f67..259d2ad1d3c920 100644 --- a/src/app/AttributeAccessInterface.h +++ b/src/app/AttributeAccessInterface.h @@ -23,11 +23,13 @@ #include #include #include +#include #include // So we can encode lists #include #include #include #include +#include /** * Callback class that clusters can implement in order to interpose custom @@ -97,10 +99,18 @@ class AttributeValueEncoder public: ListEncodeHelper(AttributeValueEncoder & encoder) : mAttributeValueEncoder(encoder) {} - template - CHIP_ERROR Encode(Ts &&... aArgs) const + template ::value, bool> = true> + CHIP_ERROR Encode(T && aArg) const { - return mAttributeValueEncoder.EncodeListItem(std::forward(aArgs)...); + // If the fabric index does not match that present in the request, skip encoding this list item. + VerifyOrReturnError(aArg.MatchesFabricIndex(mAttributeValueEncoder.mAccessingFabricIndex), CHIP_NO_ERROR); + return mAttributeValueEncoder.EncodeListItem(std::forward(aArg)); + } + + template ::value, bool> = true> + CHIP_ERROR Encode(T && aArg) const + { + return mAttributeValueEncoder.EncodeListItem(std::forward(aArg)); } private: diff --git a/src/app/data-model/FabricScoped.h b/src/app/data-model/FabricScoped.h new file mode 100644 index 00000000000000..0297c54a3456e4 --- /dev/null +++ b/src/app/data-model/FabricScoped.h @@ -0,0 +1,50 @@ +/* + * + * Copyright (c) 2021 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. + */ + +#pragma once + +#include +#include + +namespace chip { +namespace app { +namespace DataModel { + +/* + * Check whether a cluster object struct is fabric scoped. + * A fabric scoped struct contains a field of "FabricIndex" type, however, we cannot tell the difference between that field and + * other uint8_t fields. Thus we add a MatchesFabricIndex member function for checking the fabric id. Here, IsFabricScoped check the + * presence of MatchesFabricIndex function. This template can be used with std::enable_if. + */ +template +class IsFabricScoped +{ +private: + template + static auto TestHasMatchesFabricIndex(int) -> TemplatedTrueType; + + template + static auto TestHasMatchesFabricIndex(long) -> std::false_type; + +public: + static constexpr bool value = decltype(TestHasMatchesFabricIndex>(0))::value; +}; + +} // namespace DataModel +} // namespace app +} // namespace chip diff --git a/src/app/tests/TestAttributeValueEncoder.cpp b/src/app/tests/TestAttributeValueEncoder.cpp index 9338d53577dae9..ae343b75dc92d7 100644 --- a/src/app/tests/TestAttributeValueEncoder.cpp +++ b/src/app/tests/TestAttributeValueEncoder.cpp @@ -22,6 +22,7 @@ * */ +#include #include #include #include @@ -32,6 +33,8 @@ using namespace chip; using namespace chip::app; using namespace chip::TLV; +// TODO: This unit tests contains hard code TLV data, they should be replaced with some decoding code to improve readability. + namespace { // These values are easier to be recognized in the encoded buffer @@ -39,14 +42,15 @@ constexpr EndpointId kRandomEndpointId = 0x55; constexpr ClusterId kRandomClusterId = 0xaa; constexpr AttributeId kRandomAttributeId = 0xcc; constexpr DataVersion kRandomDataVersion = 0x99; +constexpr FabricIndex kTestFabricIndex = 1; template struct LimitedTestSetup { - LimitedTestSetup(nlTestSuite * aSuite, + LimitedTestSetup(nlTestSuite * aSuite, const FabricIndex aFabricIndex = 0, const AttributeValueEncoder::AttributeEncodeState & aState = AttributeValueEncoder::AttributeEncodeState()) : - encoder(builder, 0, ConcreteAttributePath(kRandomEndpointId, kRandomClusterId, kRandomAttributeId), kRandomDataVersion, - aState) + encoder(builder, aFabricIndex, ConcreteAttributePath(kRandomEndpointId, kRandomClusterId, kRandomAttributeId), + kRandomDataVersion, aState) { writer.Init(buf); { @@ -238,6 +242,59 @@ void TestEncodeEmptyList(nlTestSuite * aSuite, void * aContext) VERIFY_BUFFER_STATE(aSuite, test, expected); } +void TestEncodeFabricScoped(nlTestSuite * aSuite, void * aContext) +{ + TestSetup test(aSuite, kTestFabricIndex); + Clusters::AccessControl::Structs::ExtensionEntry::Type items[3]; + items[0].fabricIndex = 0; + items[1].fabricIndex = 1; + items[2].fabricIndex = 2; + + // We tried to encode three items, however, the encoder should only put the item with matching fabric index into the final list. + CHIP_ERROR err = test.encoder.EncodeList([items](const auto & encoder) -> CHIP_ERROR { + for (size_t i = 0; i < 3; i++) + { + ReturnErrorOnFailure(encoder.Encode(items[i])); + } + return CHIP_NO_ERROR; + }); + NL_TEST_ASSERT(aSuite, err == CHIP_NO_ERROR); + const uint8_t expected[] = { + // clang-format off + 0x15, 0x36, 0x01, // Test overhead, Start Anonymous struct + Start 1 byte Tag Array + Tag (01) + 0x15, // Start anonymous struct + 0x35, 0x01, // Start 1 byte tag struct + Tag (01) + 0x24, 0x00, 0x99, // Tag (00) Value (1 byte uint) 0x99 (Attribute Version) + 0x37, 0x01, // Start 1 byte tag list + Tag (01) (Attribute Path) + 0x24, 0x02, 0x55, // Tag (02) Value (1 byte uint) 0x55 + 0x24, 0x03, 0xaa, // Tag (03) Value (1 byte uint) 0xaa + 0x24, 0x04, 0xcc, // Tag (04) Value (1 byte uint) 0xcc + 0x18, // End of container + // Intended empty array + 0x36, 0x02, // Start 1 byte tag array + Tag (02) (Attribute Value) + 0x18, // End of container + 0x18, // End of container + 0x18, // End of container + 0x15, // Start anonymous struct + 0x35, 0x01, // Start 1 byte tag struct + Tag (01) + 0x24, 0x00, 0x99, // Tag (00) Value (1 byte uint) 0x99 (Attribute Version) + 0x37, 0x01, // Start 1 byte tag list + Tag (01) (Attribute Path) + 0x24, 0x02, 0x55, // Tag (02) Value (1 byte uint) 0x55 + 0x24, 0x03, 0xaa, // Tag (03) Value (1 byte uint) 0xaa + 0x24, 0x04, 0xcc, // Tag (04) Value (1 byte uint) 0xcc + 0x34, 0x05, // Tag (05) Null + 0x18, // End of container (attribute path) + 0x35, 0x02, // Tag 02 (attribute data) + 0x24, 0x00, 0x01, // Tag 0, UINT8 Value 1 (fabric index) + 0x30, 0x01, 0x00, // Tag 1, OCTET_STRING length 0 (data) + 0x18, + 0x18, + 0x18, + // clang-format on + }; + VERIFY_BUFFER_STATE(aSuite, test, expected); +} + void TestEncodeListChunking(nlTestSuite * aSuite, void * aContext) { AttributeValueEncoder::AttributeEncodeState state; @@ -252,7 +309,8 @@ void TestEncodeListChunking(nlTestSuite * aSuite, void * aContext) }; { - LimitedTestSetup<60> test1(aSuite); + // Use 60 bytes buffer to force chunking. The kTestFabricIndex is not effective in this test. + LimitedTestSetup<60> test1(aSuite, kTestFabricIndex); CHIP_ERROR err = test1.encoder.EncodeList(listEncoder); NL_TEST_ASSERT(aSuite, err == CHIP_ERROR_NO_MEMORY || err == CHIP_ERROR_BUFFER_TOO_SMALL); state = test1.encoder.GetState(); @@ -291,7 +349,8 @@ void TestEncodeListChunking(nlTestSuite * aSuite, void * aContext) VERIFY_BUFFER_STATE(aSuite, test1, expected); } { - LimitedTestSetup<60> test2(aSuite, state); + // Use 60 bytes buffer to force chunking. The kTestFabricIndex is not effective in this test. + LimitedTestSetup<60> test2(aSuite, 0, state); CHIP_ERROR err = test2.encoder.EncodeList(listEncoder); NL_TEST_ASSERT(aSuite, err == CHIP_NO_ERROR); @@ -321,13 +380,12 @@ void TestEncodeListChunking(nlTestSuite * aSuite, void * aContext) } // anonymous namespace namespace { -const nlTest sTests[] = { NL_TEST_DEF("TestEncodeNothing", TestEncodeNothing), - NL_TEST_DEF("TestEncodeBool", TestEncodeBool), - NL_TEST_DEF("TestEncodeEmptyList", TestEncodeEmptyList), - NL_TEST_DEF("TestEncodeListOfBools1", TestEncodeListOfBools1), - NL_TEST_DEF("TestEncodeListOfBools2", TestEncodeListOfBools2), - NL_TEST_DEF("TestEncodeListChunking", TestEncodeListChunking), - NL_TEST_SENTINEL() }; +const nlTest sTests[] = { + NL_TEST_DEF("TestEncodeNothing", TestEncodeNothing), NL_TEST_DEF("TestEncodeBool", TestEncodeBool), + NL_TEST_DEF("TestEncodeEmptyList", TestEncodeEmptyList), NL_TEST_DEF("TestEncodeListOfBools1", TestEncodeListOfBools1), + NL_TEST_DEF("TestEncodeListOfBools2", TestEncodeListOfBools2), NL_TEST_DEF("TestEncodeListChunking", TestEncodeListChunking), + NL_TEST_DEF("TestEncodeFabricScoped", TestEncodeFabricScoped), NL_TEST_SENTINEL() +}; } int TestAttributeValueEncoder() diff --git a/src/app/zap-templates/templates/app/cluster-objects.zapt b/src/app/zap-templates/templates/app/cluster-objects.zapt index cebc068d344229..ac082a2c3f34d0 100644 --- a/src/app/zap-templates/templates/app/cluster-objects.zapt +++ b/src/app/zap-templates/templates/app/cluster-objects.zapt @@ -72,6 +72,11 @@ namespace {{asUpperCamelCase name}} { {{#unless struct_contains_array}} CHIP_ERROR Decode(TLV::TLVReader &reader); {{/unless}} + {{#if struct_is_fabric_scoped}} + bool MatchesFabricIndex(FabricIndex fabricIndex_) const { + return {{ asLowerCamelCase struct_fabric_idx_field }} == fabricIndex_; + } + {{/if}} }; {{#if struct_contains_array}} diff --git a/src/lib/support/TypeTraits.h b/src/lib/support/TypeTraits.h index 4eb344b2ce9fc7..4a0e01b6e968b7 100644 --- a/src/lib/support/TypeTraits.h +++ b/src/lib/support/TypeTraits.h @@ -38,4 +38,29 @@ constexpr std::underlying_type_t to_underlying(T e) return static_cast>(e); } +/** + * @brief This template is not designed to be used directly. A common pattern to check the presence of a member of a class is: + * + * template + * class IsMagic + * { + * private: + * template + * static auto TestHasMagic(int) -> TemplatedTrueType; + * + * template + * static auto TestHasMagic(long) -> std::false_type; + * + * public: + * static constexpr bool value = decltype(TestHasMagic>(0))::value; + * }; + * + * The compiler will try to match TestHasMagicFunction(int) first, if MagicFunction is a member function of T, the match succeed + * and HasMagicFunction is an alias of std::true_type. If MagicFunction is not a member function of T, the match of + * TestHasMagicFunction(int) will result in compile error, due to SFINAE, compiler will try the next candicate, which is + * TestHasMagicFunction(long), it will always success for all types, and HasMagicFunction becomes an alias of std::false_type. + */ +template +using TemplatedTrueType = std::true_type; + } // namespace chip diff --git a/zzz_generated/app-common/app-common/zap-generated/cluster-objects.h b/zzz_generated/app-common/app-common/zap-generated/cluster-objects.h index 416c6feb39f85f..dafd4ffab21669 100644 --- a/zzz_generated/app-common/app-common/zap-generated/cluster-objects.h +++ b/zzz_generated/app-common/app-common/zap-generated/cluster-objects.h @@ -6046,6 +6046,7 @@ struct Type DataModel::Nullable> targets; CHIP_ERROR Encode(TLV::TLVWriter & writer, TLV::Tag tag) const; + bool MatchesFabricIndex(FabricIndex fabricIndex_) const { return fabricIndex == fabricIndex_; } }; struct DecodableType @@ -6075,6 +6076,7 @@ struct Type CHIP_ERROR Encode(TLV::TLVWriter & writer, TLV::Tag tag) const; CHIP_ERROR Decode(TLV::TLVReader & reader); + bool MatchesFabricIndex(FabricIndex fabricIndex_) const { return fabricIndex == fabricIndex_; } }; using DecodableType = Type;