Skip to content

Commit 2d0eb94

Browse files
authored
Add a way to run custom cluster logic for attribute write (#11520)
* Add a way to run custom cluster logic for attribute write * Address review comments
1 parent 14eaa07 commit 2d0eb94

File tree

7 files changed

+675
-437
lines changed

7 files changed

+675
-437
lines changed

src/app/AttributeAccessInterface.h

+34
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
#include <app/ConcreteAttributePath.h>
2222
#include <app/MessageDef/AttributeDataIB.h>
23+
#include <app/data-model/Decode.h>
2324
#include <app/data-model/Encode.h>
2425
#include <app/data-model/List.h> // So we can encode lists
2526
#include <app/data-model/TagBoundEncoder.h>
@@ -101,6 +102,25 @@ class AttributeValueEncoder : protected TagBoundEncoder
101102
const FabricIndex mAccessingFabricIndex;
102103
};
103104

105+
class AttributeValueDecoder
106+
{
107+
public:
108+
AttributeValueDecoder(TLV::TLVReader & aReader) : mReader(aReader) {}
109+
110+
template <typename T>
111+
CHIP_ERROR Decode(T & aArg)
112+
{
113+
mTriedDecode = true;
114+
return DataModel::Decode(mReader, aArg);
115+
}
116+
117+
bool TriedDecode() const { return mTriedDecode; }
118+
119+
private:
120+
TLV::TLVReader & mReader;
121+
bool mTriedDecode = false;
122+
};
123+
104124
class AttributeAccessInterface
105125
{
106126
public:
@@ -127,6 +147,20 @@ class AttributeAccessInterface
127147
*/
128148
virtual CHIP_ERROR Read(const ConcreteAttributePath & aPath, AttributeValueEncoder & aEncoder) = 0;
129149

150+
/**
151+
* Callback for writing attributes.
152+
*
153+
* @param [in] aPath indicates which exact data is being written.
154+
* @param [in] aDecoder the AttributeValueDecoder to use for decoding the
155+
* data. If this function returns scucess and no attempt is
156+
* made to decode data using aDecoder, the
157+
* AttributeAccessInterface did not try to write any data. In
158+
* this case, normal attribute access will happen for the write.
159+
* This may involve writing to the attribute store or external
160+
* attribute callbacks.
161+
*/
162+
virtual CHIP_ERROR Write(const ConcreteAttributePath & aPath, AttributeValueDecoder & aDecoder) { return CHIP_NO_ERROR; }
163+
130164
/**
131165
* Mechanism for keeping track of a chain of AttributeAccessInterfaces.
132166
*/

src/app/clusters/test-cluster-server/test-cluster-server.cpp

+136-18
Original file line numberDiff line numberDiff line change
@@ -40,24 +40,51 @@ using namespace chip::app::Clusters::TestCluster;
4040
using namespace chip::app::Clusters::TestCluster::Commands;
4141
using namespace chip::app::Clusters::TestCluster::Attributes;
4242

43+
// The number of elements in the test attribute list
44+
constexpr uint8_t kAttributeListLength = 4;
45+
46+
// The maximum length of the test attribute list element in bytes
47+
constexpr uint8_t kAttributeEntryLength = 6;
48+
4349
namespace {
4450

51+
class OctetStringData
52+
{
53+
public:
54+
uint8_t * Data() { return mDataBuf; }
55+
size_t Length() const { return mDataLen; }
56+
void SetLength(size_t size) { mDataLen = size; }
57+
58+
private:
59+
uint8_t mDataBuf[kAttributeEntryLength];
60+
size_t mDataLen = 0;
61+
};
62+
4563
class TestAttrAccess : public AttributeAccessInterface
4664
{
4765
public:
4866
// Register for the Test Cluster cluster on all endpoints.
4967
TestAttrAccess() : AttributeAccessInterface(Optional<EndpointId>::Missing(), TestCluster::Id) {}
5068

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

5372
private:
5473
CHIP_ERROR ReadListInt8uAttribute(AttributeValueEncoder & aEncoder);
74+
CHIP_ERROR WriteListInt8uAttribute(AttributeValueDecoder & aDecoder);
5575
CHIP_ERROR ReadListOctetStringAttribute(AttributeValueEncoder & aEncoder);
76+
CHIP_ERROR WriteListOctetStringAttribute(AttributeValueDecoder & aDecoder);
5677
CHIP_ERROR ReadListStructOctetStringAttribute(AttributeValueEncoder & aEncoder);
78+
CHIP_ERROR WriteListStructOctetStringAttribute(AttributeValueDecoder & aDecoder);
5779
CHIP_ERROR ReadListNullablesAndOptionalsStructAttribute(AttributeValueEncoder & aEncoder);
80+
CHIP_ERROR WriteListNullablesAndOptionalsStructAttribute(AttributeValueDecoder & aDecoder);
5881
};
5982

6083
TestAttrAccess gAttrAccess;
84+
uint8_t gListUint8Data[kAttributeListLength];
85+
OctetStringData gListOctetStringData[kAttributeListLength];
86+
OctetStringData gListOperationalCert[kAttributeListLength];
87+
Structs::TestListStructOctet::Type listStructOctetStringData[kAttributeListLength];
6188

6289
CHIP_ERROR TestAttrAccess::Read(const ConcreteAttributePath & aPath, AttributeValueEncoder & aEncoder)
6390
{
@@ -83,54 +110,140 @@ CHIP_ERROR TestAttrAccess::Read(const ConcreteAttributePath & aPath, AttributeVa
83110
return CHIP_NO_ERROR;
84111
}
85112

113+
CHIP_ERROR TestAttrAccess::Write(const ConcreteAttributePath & aPath, AttributeValueDecoder & aDecoder)
114+
{
115+
switch (aPath.mAttributeId)
116+
{
117+
case ListInt8u::Id: {
118+
return WriteListInt8uAttribute(aDecoder);
119+
}
120+
case ListOctetString::Id: {
121+
return WriteListOctetStringAttribute(aDecoder);
122+
}
123+
case ListStructOctetString::Id: {
124+
return WriteListStructOctetStringAttribute(aDecoder);
125+
}
126+
case ListNullablesAndOptionalsStruct::Id: {
127+
return WriteListNullablesAndOptionalsStructAttribute(aDecoder);
128+
}
129+
default: {
130+
break;
131+
}
132+
}
133+
134+
return CHIP_NO_ERROR;
135+
}
136+
86137
CHIP_ERROR TestAttrAccess::ReadListInt8uAttribute(AttributeValueEncoder & aEncoder)
87138
{
88139
return aEncoder.EncodeList([](const TagBoundEncoder & encoder) -> CHIP_ERROR {
89-
constexpr uint8_t maxValue = 4;
90-
for (uint8_t value = 1; value <= maxValue; value++)
140+
for (uint8_t index = 0; index < kAttributeListLength; index++)
91141
{
92-
ReturnErrorOnFailure(encoder.Encode(value));
142+
ReturnErrorOnFailure(encoder.Encode(gListUint8Data[index]));
93143
}
94144
return CHIP_NO_ERROR;
95145
});
96146
}
97147

148+
CHIP_ERROR TestAttrAccess::WriteListInt8uAttribute(AttributeValueDecoder & aDecoder)
149+
{
150+
ListInt8u::TypeInfo::DecodableType list;
151+
152+
ReturnErrorOnFailure(aDecoder.Decode(list));
153+
154+
uint8_t index = 0;
155+
auto iter = list.begin();
156+
while (iter.Next())
157+
{
158+
auto & entry = iter.GetValue();
159+
160+
VerifyOrReturnError(index < kAttributeListLength, CHIP_ERROR_BUFFER_TOO_SMALL);
161+
gListUint8Data[index++] = entry;
162+
}
163+
164+
return iter.GetStatus();
165+
}
166+
98167
CHIP_ERROR TestAttrAccess::ReadListOctetStringAttribute(AttributeValueEncoder & aEncoder)
99168
{
100169
return aEncoder.EncodeList([](const TagBoundEncoder & encoder) -> CHIP_ERROR {
101-
constexpr uint16_t attributeCount = 4;
102-
char data[6] = { 'T', 'e', 's', 't', 'N', '\0' };
103-
104-
for (uint8_t index = 0; index < attributeCount; index++)
170+
for (uint8_t index = 0; index < kAttributeListLength; index++)
105171
{
106-
snprintf(data + strlen(data) - 1, 2, "%d", index);
107-
ByteSpan span(Uint8::from_char(data), strlen(data));
172+
ByteSpan span(gListOctetStringData[index].Data(), gListOctetStringData[index].Length());
108173
ReturnErrorOnFailure(encoder.Encode(span));
109174
}
110175
return CHIP_NO_ERROR;
111176
});
112177
}
113178

179+
CHIP_ERROR TestAttrAccess::WriteListOctetStringAttribute(AttributeValueDecoder & aDecoder)
180+
{
181+
ListOctetString::TypeInfo::DecodableType list;
182+
183+
ReturnErrorOnFailure(aDecoder.Decode(list));
184+
185+
uint8_t index = 0;
186+
auto iter = list.begin();
187+
while (iter.Next())
188+
{
189+
const auto & entry = iter.GetValue();
190+
191+
VerifyOrReturnError(index < kAttributeListLength, CHIP_ERROR_BUFFER_TOO_SMALL);
192+
VerifyOrReturnError(entry.size() <= kAttributeEntryLength, CHIP_ERROR_BUFFER_TOO_SMALL);
193+
memcpy(gListOctetStringData[index].Data(), entry.data(), entry.size());
194+
gListOctetStringData[index].SetLength(entry.size());
195+
index++;
196+
}
197+
198+
return iter.GetStatus();
199+
}
200+
114201
CHIP_ERROR TestAttrAccess::ReadListStructOctetStringAttribute(AttributeValueEncoder & aEncoder)
115202
{
116203
return aEncoder.EncodeList([](const TagBoundEncoder & encoder) -> CHIP_ERROR {
117-
constexpr uint16_t attributeCount = 4;
118-
char data[6] = { 'T', 'e', 's', 't', 'N', '\0' };
119-
120-
for (uint8_t index = 0; index < attributeCount; index++)
204+
for (uint8_t index = 0; index < kAttributeListLength; index++)
121205
{
122-
snprintf(data + strlen(data) - 1, 2, "%d", index);
123-
ByteSpan span(Uint8::from_char(data), strlen(data));
124-
125206
Structs::TestListStructOctet::Type structOctet;
126-
structOctet.fabricIndex = index;
127-
structOctet.operationalCert = span;
207+
structOctet.fabricIndex = listStructOctetStringData[index].fabricIndex;
208+
structOctet.operationalCert = listStructOctetStringData[index].operationalCert;
128209
ReturnErrorOnFailure(encoder.Encode(structOctet));
129210
}
211+
130212
return CHIP_NO_ERROR;
131213
});
132214
}
133215

216+
CHIP_ERROR TestAttrAccess::WriteListStructOctetStringAttribute(AttributeValueDecoder & aDecoder)
217+
{
218+
ListStructOctetString::TypeInfo::DecodableType list;
219+
220+
ReturnErrorOnFailure(aDecoder.Decode(list));
221+
222+
uint8_t index = 0;
223+
auto iter = list.begin();
224+
while (iter.Next())
225+
{
226+
const auto & entry = iter.GetValue();
227+
228+
VerifyOrReturnError(index < kAttributeListLength, CHIP_ERROR_BUFFER_TOO_SMALL);
229+
VerifyOrReturnError(entry.operationalCert.size() <= kAttributeEntryLength, CHIP_ERROR_BUFFER_TOO_SMALL);
230+
memcpy(gListOperationalCert[index].Data(), entry.operationalCert.data(), entry.operationalCert.size());
231+
gListOperationalCert[index].SetLength(entry.operationalCert.size());
232+
233+
listStructOctetStringData[index].fabricIndex = entry.fabricIndex;
234+
listStructOctetStringData[index].operationalCert =
235+
ByteSpan(gListOperationalCert[index].Data(), gListOperationalCert[index].Length());
236+
index++;
237+
}
238+
239+
if (iter.GetStatus() != CHIP_NO_ERROR)
240+
{
241+
return CHIP_ERROR_INVALID_DATA_LIST;
242+
}
243+
244+
return CHIP_NO_ERROR;
245+
}
246+
134247
CHIP_ERROR TestAttrAccess::ReadListNullablesAndOptionalsStructAttribute(AttributeValueEncoder & aEncoder)
135248
{
136249
return aEncoder.EncodeList([](const TagBoundEncoder & encoder) -> CHIP_ERROR {
@@ -142,6 +255,11 @@ CHIP_ERROR TestAttrAccess::ReadListNullablesAndOptionalsStructAttribute(Attribut
142255
});
143256
}
144257

258+
CHIP_ERROR TestAttrAccess::WriteListNullablesAndOptionalsStructAttribute(AttributeValueDecoder & aDecoder)
259+
{
260+
// TODO Add yaml test case for NullablesAndOptionalsStruct list
261+
return CHIP_NO_ERROR;
262+
}
145263
} // namespace
146264

147265
bool emberAfTestClusterClusterTestCallback(app::CommandHandler *, const app::ConcreteCommandPath & commandPath,

src/app/tests/suites/TestCluster.yaml

-30
Original file line numberDiff line numberDiff line change
@@ -732,36 +732,6 @@ tests:
732732
arguments:
733733
value: ""
734734

735-
# Tests for List attribute
736-
737-
- label: "Read attribute LIST"
738-
command: "readAttribute"
739-
attribute: "list_int8u"
740-
response:
741-
value: [1, 2, 3, 4]
742-
743-
# Tests for List Octet String attribute
744-
745-
- label: "Read attribute LIST_OCTET_STRING"
746-
command: "readAttribute"
747-
attribute: "list_octet_string"
748-
response:
749-
value: ["Test0", "Test1", "Test2", "Test3"]
750-
751-
# Tests for List Struct Octet String attribute
752-
753-
- label: "Read attribute LIST_STRUCT_OCTET_STRING"
754-
command: "readAttribute"
755-
attribute: "list_struct_octet_string"
756-
response:
757-
value:
758-
[
759-
{ fabricIndex: 0, operationalCert: "Test0" },
760-
{ fabricIndex: 1, operationalCert: "Test1" },
761-
{ fabricIndex: 2, operationalCert: "Test2" },
762-
{ fabricIndex: 3, operationalCert: "Test3" },
763-
]
764-
765735
# Tests for Epoch Microseconds
766736

767737
- label: "Read attribute EPOCH_US Default Value"

src/app/tests/suites/TestClusterComplexTypes.yaml

+49
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,55 @@ tests:
439439
- name: "value"
440440
value: false
441441

442+
- label:
443+
"Write attribute LIST With List of INT8U and none of them is set to 0"
444+
command: "writeAttribute"
445+
attribute: "list_int8u"
446+
arguments:
447+
value: [1, 2, 3, 4]
448+
449+
- label: "Read attribute LIST With List of INT8U"
450+
command: "readAttribute"
451+
attribute: "list_int8u"
452+
response:
453+
value: [1, 2, 3, 4]
454+
455+
- label: "Write attribute LIST With List of OCTET_STRING"
456+
command: "writeAttribute"
457+
attribute: "list_octet_string"
458+
arguments:
459+
value: ["Test0", "Test1", "Test2", "Test3"]
460+
461+
- label: "Read attribute LIST With List of OCTET_STRING"
462+
command: "readAttribute"
463+
attribute: "list_octet_string"
464+
response:
465+
value: ["Test0", "Test1", "Test2", "Test3"]
466+
467+
- label: "Write attribute LIST With List of LIST_STRUCT_OCTET_STRING"
468+
command: "writeAttribute"
469+
attribute: "list_struct_octet_string"
470+
arguments:
471+
value:
472+
[
473+
{ fabricIndex: 0, operationalCert: "Test0" },
474+
{ fabricIndex: 1, operationalCert: "Test1" },
475+
{ fabricIndex: 2, operationalCert: "Test2" },
476+
{ fabricIndex: 3, operationalCert: "Test3" },
477+
]
478+
479+
- label: "Read attribute LIST With List of LIST_STRUCT_OCTET_STRING"
480+
command: "readAttribute"
481+
attribute: "list_struct_octet_string"
482+
response:
483+
value:
484+
[
485+
{ fabricIndex: 0, operationalCert: "Test0" },
486+
{ fabricIndex: 1, operationalCert: "Test1" },
487+
{ fabricIndex: 2, operationalCert: "Test2" },
488+
{ fabricIndex: 3, operationalCert: "Test3" },
489+
]
490+
442491
# Tests for Nullables and Optionals
443492

444493
- label: "Send Test Command with optional arg set."

src/app/util/ember-compatibility-functions.cpp

+16
Original file line numberDiff line numberDiff line change
@@ -503,9 +503,25 @@ CHIP_ERROR WriteSingleClusterData(ClusterInfo & aClusterInfo, TLV::TLVReader & a
503503
attributePathParams.mClusterId = aClusterInfo.mClusterId;
504504
attributePathParams.mAttributeId = aClusterInfo.mAttributeId;
505505

506+
// TODO: Refactor WriteSingleClusterData and all dependent functions to take ConcreteAttributePath instead of ClusterInfo
507+
// as the input argument.
508+
AttributeAccessInterface * attrOverride = findAttributeAccessOverride(aClusterInfo.mEndpointId, aClusterInfo.mClusterId);
509+
if (attrOverride != nullptr)
510+
{
511+
ConcreteAttributePath path(aClusterInfo.mEndpointId, aClusterInfo.mClusterId, aClusterInfo.mAttributeId);
512+
AttributeValueDecoder valueDecoder(aReader);
513+
ReturnErrorOnFailure(attrOverride->Write(path, valueDecoder));
514+
515+
if (valueDecoder.TriedDecode())
516+
{
517+
return apWriteHandler->AddStatus(attributePathParams, Protocols::InteractionModel::Status::Success);
518+
}
519+
}
520+
506521
auto imCode = WriteSingleClusterDataInternal(aClusterInfo, aReader, apWriteHandler);
507522
return apWriteHandler->AddStatus(attributePathParams, imCode);
508523
}
524+
509525
} // namespace app
510526
} // namespace chip
511527

0 commit comments

Comments
 (0)