Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[IM] Fabric filtered read #12710

Merged
merged 2 commits into from
Dec 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions src/app/AttributeAccessInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@
#include <app/MessageDef/AttributeReportIBs.h>
#include <app/data-model/Decode.h>
#include <app/data-model/Encode.h>
#include <app/data-model/FabricScoped.h>
#include <app/data-model/List.h> // So we can encode lists
#include <app/data-model/TagBoundEncoder.h>
#include <app/util/basic-types.h>
#include <lib/core/CHIPTLV.h>
#include <lib/core/Optional.h>
#include <lib/support/logging/CHIPLogging.h>

/**
* Callback class that clusters can implement in order to interpose custom
Expand Down Expand Up @@ -97,10 +99,18 @@ class AttributeValueEncoder
public:
ListEncodeHelper(AttributeValueEncoder & encoder) : mAttributeValueEncoder(encoder) {}

template <typename... Ts>
CHIP_ERROR Encode(Ts &&... aArgs) const
template <typename T, std::enable_if_t<DataModel::IsFabricScoped<T>::value, bool> = true>
CHIP_ERROR Encode(T && aArg) const
{
return mAttributeValueEncoder.EncodeListItem(std::forward<Ts>(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<T>(aArg));
}

template <typename T, std::enable_if_t<!DataModel::IsFabricScoped<T>::value, bool> = true>
CHIP_ERROR Encode(T && aArg) const
{
return mAttributeValueEncoder.EncodeListItem(std::forward<T>(aArg));
}

private:
Expand Down
50 changes: 50 additions & 0 deletions src/app/data-model/FabricScoped.h
Original file line number Diff line number Diff line change
@@ -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 <lib/support/TypeTraits.h>
#include <type_traits>

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 <typename T>
class IsFabricScoped
{
private:
template <typename Tp>
static auto TestHasMatchesFabricIndex(int) -> TemplatedTrueType<decltype(&Tp::MatchesFabricIndex)>;

template <typename Tp>
static auto TestHasMatchesFabricIndex(long) -> std::false_type;

public:
static constexpr bool value = decltype(TestHasMatchesFabricIndex<std::decay_t<T>>(0))::value;
};

} // namespace DataModel
} // namespace app
} // namespace chip
82 changes: 70 additions & 12 deletions src/app/tests/TestAttributeValueEncoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
*
*/

#include <app-common/zap-generated/cluster-objects.h>
#include <app/AttributeAccessInterface.h>
#include <app/MessageDef/AttributeDataIB.h>
#include <lib/support/CodeUtils.h>
Expand All @@ -32,21 +33,24 @@ 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
constexpr EndpointId kRandomEndpointId = 0x55;
constexpr ClusterId kRandomClusterId = 0xaa;
constexpr AttributeId kRandomAttributeId = 0xcc;
constexpr DataVersion kRandomDataVersion = 0x99;
constexpr FabricIndex kTestFabricIndex = 1;

template <size_t N>
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);
{
Expand Down Expand Up @@ -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[] = {
erjiaqing marked this conversation as resolved.
Show resolved Hide resolved
// 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;
Expand All @@ -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();
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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()
Expand Down
5 changes: 5 additions & 0 deletions src/app/zap-templates/templates/app/cluster-objects.zapt
Original file line number Diff line number Diff line change
Expand Up @@ -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}}
Expand Down
25 changes: 25 additions & 0 deletions src/lib/support/TypeTraits.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,29 @@ constexpr std::underlying_type_t<T> to_underlying(T e)
return static_cast<std::underlying_type_t<T>>(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 <typename T>
* class IsMagic
* {
* private:
* template <typename Tp>
* static auto TestHasMagic(int) -> TemplatedTrueType<decltype(&Tp::Magic)>;
*
* template <typename Tp>
* static auto TestHasMagic(long) -> std::false_type;
*
* public:
* static constexpr bool value = decltype(TestHasMagic<std::decay_t<T>>(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 <typename T>
using TemplatedTrueType = std::true_type;

} // namespace chip

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.