Skip to content

Commit

Permalink
[IM] Fabric filtered read (#12710)
Browse files Browse the repository at this point in the history
  • Loading branch information
erjiaqing authored and pull[bot] committed Apr 14, 2022
1 parent efc93b1 commit 3566497
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 15 deletions.
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[] = {
// 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.

0 comments on commit 3566497

Please sign in to comment.