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

Add a way to run custom cluster logic for attribute write #11520

Merged
merged 2 commits into from
Nov 12, 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
34 changes: 34 additions & 0 deletions src/app/AttributeAccessInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

#include <app/ConcreteAttributePath.h>
#include <app/MessageDef/AttributeDataIB.h>
#include <app/data-model/Decode.h>
#include <app/data-model/Encode.h>
#include <app/data-model/List.h> // So we can encode lists
#include <app/data-model/TagBoundEncoder.h>
Expand Down Expand Up @@ -101,6 +102,25 @@ class AttributeValueEncoder : protected TagBoundEncoder
const FabricIndex mAccessingFabricIndex;
};

class AttributeValueDecoder
{
public:
AttributeValueDecoder(TLV::TLVReader & aReader) : mReader(aReader) {}

template <typename T>
CHIP_ERROR Decode(T & aArg)
{
mTriedDecode = true;
return DataModel::Decode(mReader, aArg);
}

bool TriedDecode() const { return mTriedDecode; }

private:
TLV::TLVReader & mReader;
bool mTriedDecode = false;
};

class AttributeAccessInterface
{
public:
Expand All @@ -127,6 +147,20 @@ class AttributeAccessInterface
*/
virtual CHIP_ERROR Read(const ConcreteAttributePath & aPath, AttributeValueEncoder & aEncoder) = 0;

/**
* Callback for writing attributes.
*
* @param [in] aPath indicates which exact data is being written.
* @param [in] aDecoder the AttributeValueDecoder to use for decoding the
* data. If this function returns scucess and no attempt is
* made to decode data using aDecoder, the
* AttributeAccessInterface did not try to write any data. In
* this case, normal attribute access will happen for the write.
* This may involve writing to the attribute store or external
* attribute callbacks.
*/
virtual CHIP_ERROR Write(const ConcreteAttributePath & aPath, AttributeValueDecoder & aDecoder) { return CHIP_NO_ERROR; }

/**
* Mechanism for keeping track of a chain of AttributeAccessInterfaces.
*/
Expand Down
154 changes: 136 additions & 18 deletions src/app/clusters/test-cluster-server/test-cluster-server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,24 +40,51 @@ using namespace chip::app::Clusters::TestCluster;
using namespace chip::app::Clusters::TestCluster::Commands;
using namespace chip::app::Clusters::TestCluster::Attributes;

// The number of elements in the test attribute list
constexpr uint8_t kAttributeListLength = 4;

// The maximum length of the test attribute list element in bytes
constexpr uint8_t kAttributeEntryLength = 6;

namespace {

class OctetStringData
{
public:
uint8_t * Data() { return mDataBuf; }
size_t Length() const { return mDataLen; }
void SetLength(size_t size) { mDataLen = size; }

private:
uint8_t mDataBuf[kAttributeEntryLength];
size_t mDataLen = 0;
};

class TestAttrAccess : public AttributeAccessInterface
{
public:
// Register for the Test Cluster cluster on all endpoints.
TestAttrAccess() : AttributeAccessInterface(Optional<EndpointId>::Missing(), TestCluster::Id) {}

CHIP_ERROR Read(const ConcreteAttributePath & aPath, AttributeValueEncoder & aEncoder) override;
CHIP_ERROR Write(const ConcreteAttributePath & aPath, AttributeValueDecoder & aDecoder) override;

private:
CHIP_ERROR ReadListInt8uAttribute(AttributeValueEncoder & aEncoder);
CHIP_ERROR WriteListInt8uAttribute(AttributeValueDecoder & aDecoder);
CHIP_ERROR ReadListOctetStringAttribute(AttributeValueEncoder & aEncoder);
CHIP_ERROR WriteListOctetStringAttribute(AttributeValueDecoder & aDecoder);
CHIP_ERROR ReadListStructOctetStringAttribute(AttributeValueEncoder & aEncoder);
CHIP_ERROR WriteListStructOctetStringAttribute(AttributeValueDecoder & aDecoder);
CHIP_ERROR ReadListNullablesAndOptionalsStructAttribute(AttributeValueEncoder & aEncoder);
CHIP_ERROR WriteListNullablesAndOptionalsStructAttribute(AttributeValueDecoder & aDecoder);
};

TestAttrAccess gAttrAccess;
uint8_t gListUint8Data[kAttributeListLength];
OctetStringData gListOctetStringData[kAttributeListLength];
OctetStringData gListOperationalCert[kAttributeListLength];
Structs::TestListStructOctet::Type listStructOctetStringData[kAttributeListLength];

CHIP_ERROR TestAttrAccess::Read(const ConcreteAttributePath & aPath, AttributeValueEncoder & aEncoder)
{
Expand All @@ -83,54 +110,140 @@ CHIP_ERROR TestAttrAccess::Read(const ConcreteAttributePath & aPath, AttributeVa
return CHIP_NO_ERROR;
}

CHIP_ERROR TestAttrAccess::Write(const ConcreteAttributePath & aPath, AttributeValueDecoder & aDecoder)
{
switch (aPath.mAttributeId)
{
case ListInt8u::Id: {
return WriteListInt8uAttribute(aDecoder);
}
case ListOctetString::Id: {
return WriteListOctetStringAttribute(aDecoder);
}
case ListStructOctetString::Id: {
return WriteListStructOctetStringAttribute(aDecoder);
}
case ListNullablesAndOptionalsStruct::Id: {
return WriteListNullablesAndOptionalsStructAttribute(aDecoder);
}
default: {
break;
}
}

return CHIP_NO_ERROR;
}

CHIP_ERROR TestAttrAccess::ReadListInt8uAttribute(AttributeValueEncoder & aEncoder)
{
return aEncoder.EncodeList([](const TagBoundEncoder & encoder) -> CHIP_ERROR {
constexpr uint8_t maxValue = 4;
for (uint8_t value = 1; value <= maxValue; value++)
for (uint8_t index = 0; index < kAttributeListLength; index++)
{
ReturnErrorOnFailure(encoder.Encode(value));
ReturnErrorOnFailure(encoder.Encode(gListUint8Data[index]));
}
return CHIP_NO_ERROR;
});
}

CHIP_ERROR TestAttrAccess::WriteListInt8uAttribute(AttributeValueDecoder & aDecoder)
{
ListInt8u::TypeInfo::DecodableType list;

ReturnErrorOnFailure(aDecoder.Decode(list));

uint8_t index = 0;
auto iter = list.begin();
while (iter.Next())
{
auto & entry = iter.GetValue();

VerifyOrReturnError(index < kAttributeListLength, CHIP_ERROR_BUFFER_TOO_SMALL);
gListUint8Data[index++] = entry;
yufengwangca marked this conversation as resolved.
Show resolved Hide resolved
}

return iter.GetStatus();
}

CHIP_ERROR TestAttrAccess::ReadListOctetStringAttribute(AttributeValueEncoder & aEncoder)
{
return aEncoder.EncodeList([](const TagBoundEncoder & encoder) -> CHIP_ERROR {
constexpr uint16_t attributeCount = 4;
char data[6] = { 'T', 'e', 's', 't', 'N', '\0' };

for (uint8_t index = 0; index < attributeCount; index++)
for (uint8_t index = 0; index < kAttributeListLength; index++)
{
snprintf(data + strlen(data) - 1, 2, "%d", index);
ByteSpan span(Uint8::from_char(data), strlen(data));
ByteSpan span(gListOctetStringData[index].Data(), gListOctetStringData[index].Length());
ReturnErrorOnFailure(encoder.Encode(span));
}
return CHIP_NO_ERROR;
});
}

CHIP_ERROR TestAttrAccess::WriteListOctetStringAttribute(AttributeValueDecoder & aDecoder)
{
ListOctetString::TypeInfo::DecodableType list;

ReturnErrorOnFailure(aDecoder.Decode(list));

uint8_t index = 0;
auto iter = list.begin();
while (iter.Next())
{
const auto & entry = iter.GetValue();

VerifyOrReturnError(index < kAttributeListLength, CHIP_ERROR_BUFFER_TOO_SMALL);
VerifyOrReturnError(entry.size() <= kAttributeEntryLength, CHIP_ERROR_BUFFER_TOO_SMALL);
memcpy(gListOctetStringData[index].Data(), entry.data(), entry.size());
gListOctetStringData[index].SetLength(entry.size());
index++;
}

return iter.GetStatus();
yufengwangca marked this conversation as resolved.
Show resolved Hide resolved
}

CHIP_ERROR TestAttrAccess::ReadListStructOctetStringAttribute(AttributeValueEncoder & aEncoder)
{
return aEncoder.EncodeList([](const TagBoundEncoder & encoder) -> CHIP_ERROR {
constexpr uint16_t attributeCount = 4;
char data[6] = { 'T', 'e', 's', 't', 'N', '\0' };

for (uint8_t index = 0; index < attributeCount; index++)
for (uint8_t index = 0; index < kAttributeListLength; index++)
{
snprintf(data + strlen(data) - 1, 2, "%d", index);
ByteSpan span(Uint8::from_char(data), strlen(data));

Structs::TestListStructOctet::Type structOctet;
structOctet.fabricIndex = index;
structOctet.operationalCert = span;
structOctet.fabricIndex = listStructOctetStringData[index].fabricIndex;
structOctet.operationalCert = listStructOctetStringData[index].operationalCert;
ReturnErrorOnFailure(encoder.Encode(structOctet));
}

return CHIP_NO_ERROR;
});
}

CHIP_ERROR TestAttrAccess::WriteListStructOctetStringAttribute(AttributeValueDecoder & aDecoder)
{
ListStructOctetString::TypeInfo::DecodableType list;

ReturnErrorOnFailure(aDecoder.Decode(list));

uint8_t index = 0;
auto iter = list.begin();
while (iter.Next())
{
const auto & entry = iter.GetValue();

VerifyOrReturnError(index < kAttributeListLength, CHIP_ERROR_BUFFER_TOO_SMALL);
VerifyOrReturnError(entry.operationalCert.size() <= kAttributeEntryLength, CHIP_ERROR_BUFFER_TOO_SMALL);
memcpy(gListOperationalCert[index].Data(), entry.operationalCert.data(), entry.operationalCert.size());
gListOperationalCert[index].SetLength(entry.operationalCert.size());

listStructOctetStringData[index].fabricIndex = entry.fabricIndex;
listStructOctetStringData[index].operationalCert =
ByteSpan(gListOperationalCert[index].Data(), gListOperationalCert[index].Length());
index++;
yufengwangca marked this conversation as resolved.
Show resolved Hide resolved
}

if (iter.GetStatus() != CHIP_NO_ERROR)
{
return CHIP_ERROR_INVALID_DATA_LIST;
}

return CHIP_NO_ERROR;
}

CHIP_ERROR TestAttrAccess::ReadListNullablesAndOptionalsStructAttribute(AttributeValueEncoder & aEncoder)
{
return aEncoder.EncodeList([](const TagBoundEncoder & encoder) -> CHIP_ERROR {
Expand All @@ -142,6 +255,11 @@ CHIP_ERROR TestAttrAccess::ReadListNullablesAndOptionalsStructAttribute(Attribut
});
}

CHIP_ERROR TestAttrAccess::WriteListNullablesAndOptionalsStructAttribute(AttributeValueDecoder & aDecoder)
{
// TODO Add yaml test case for NullablesAndOptionalsStruct list
yufengwangca marked this conversation as resolved.
Show resolved Hide resolved
return CHIP_NO_ERROR;
}
} // namespace

bool emberAfTestClusterClusterTestCallback(app::CommandHandler *, const app::ConcreteCommandPath & commandPath,
Expand Down
30 changes: 0 additions & 30 deletions src/app/tests/suites/TestCluster.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -732,36 +732,6 @@ tests:
arguments:
value: ""

# Tests for List attribute

- label: "Read attribute LIST"
command: "readAttribute"
attribute: "list_int8u"
response:
value: [1, 2, 3, 4]

# Tests for List Octet String attribute

- label: "Read attribute LIST_OCTET_STRING"
command: "readAttribute"
attribute: "list_octet_string"
response:
value: ["Test0", "Test1", "Test2", "Test3"]

# Tests for List Struct Octet String attribute

- label: "Read attribute LIST_STRUCT_OCTET_STRING"
command: "readAttribute"
attribute: "list_struct_octet_string"
response:
value:
[
{ fabricIndex: 0, operationalCert: "Test0" },
{ fabricIndex: 1, operationalCert: "Test1" },
{ fabricIndex: 2, operationalCert: "Test2" },
{ fabricIndex: 3, operationalCert: "Test3" },
]

# Tests for Epoch Microseconds

- label: "Read attribute EPOCH_US Default Value"
Expand Down
49 changes: 49 additions & 0 deletions src/app/tests/suites/TestClusterComplexTypes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,55 @@ tests:
- name: "value"
value: false

- label:
"Write attribute LIST With List of INT8U and none of them is set to 0"
command: "writeAttribute"
attribute: "list_int8u"
arguments:
value: [1, 2, 3, 4]

- label: "Read attribute LIST With List of INT8U"
command: "readAttribute"
attribute: "list_int8u"
response:
value: [1, 2, 3, 4]

- label: "Write attribute LIST With List of OCTET_STRING"
command: "writeAttribute"
attribute: "list_octet_string"
arguments:
value: ["Test0", "Test1", "Test2", "Test3"]

- label: "Read attribute LIST With List of OCTET_STRING"
command: "readAttribute"
attribute: "list_octet_string"
response:
value: ["Test0", "Test1", "Test2", "Test3"]

- label: "Write attribute LIST With List of LIST_STRUCT_OCTET_STRING"
command: "writeAttribute"
attribute: "list_struct_octet_string"
arguments:
value:
[
{ fabricIndex: 0, operationalCert: "Test0" },
{ fabricIndex: 1, operationalCert: "Test1" },
{ fabricIndex: 2, operationalCert: "Test2" },
{ fabricIndex: 3, operationalCert: "Test3" },
]

- label: "Read attribute LIST With List of LIST_STRUCT_OCTET_STRING"
command: "readAttribute"
attribute: "list_struct_octet_string"
response:
value:
[
{ fabricIndex: 0, operationalCert: "Test0" },
{ fabricIndex: 1, operationalCert: "Test1" },
{ fabricIndex: 2, operationalCert: "Test2" },
{ fabricIndex: 3, operationalCert: "Test3" },
]

# Tests for Nullables and Optionals

- label: "Send Test Command with optional arg set."
Expand Down
16 changes: 16 additions & 0 deletions src/app/util/ember-compatibility-functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -503,9 +503,25 @@ CHIP_ERROR WriteSingleClusterData(ClusterInfo & aClusterInfo, TLV::TLVReader & a
attributePathParams.mClusterId = aClusterInfo.mClusterId;
attributePathParams.mAttributeId = aClusterInfo.mAttributeId;

// TODO: Refactor WriteSingleClusterData and all dependent functions to take ConcreteAttributePath instead of ClusterInfo
// as the input argument.
AttributeAccessInterface * attrOverride = findAttributeAccessOverride(aClusterInfo.mEndpointId, aClusterInfo.mClusterId);
if (attrOverride != nullptr)
{
ConcreteAttributePath path(aClusterInfo.mEndpointId, aClusterInfo.mClusterId, aClusterInfo.mAttributeId);
AttributeValueDecoder valueDecoder(aReader);
ReturnErrorOnFailure(attrOverride->Write(path, valueDecoder));

if (valueDecoder.TriedDecode())
{
return apWriteHandler->AddStatus(attributePathParams, Protocols::InteractionModel::Status::Success);
yufengwangca marked this conversation as resolved.
Show resolved Hide resolved
}
}

auto imCode = WriteSingleClusterDataInternal(aClusterInfo, aReader, apWriteHandler);
mrjerryjohns marked this conversation as resolved.
Show resolved Hide resolved
return apWriteHandler->AddStatus(attributePathParams, imCode);
}

} // namespace app
} // namespace chip

Expand Down
Loading