diff --git a/src/app/AttributeAccessInterface.h b/src/app/AttributeAccessInterface.h index 15627a40fead13..af9f575ccb24a3 100644 --- a/src/app/AttributeAccessInterface.h +++ b/src/app/AttributeAccessInterface.h @@ -127,6 +127,27 @@ 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 write. + * @param [in] aTLVReader A pointer to a TLVReader, which should point to the beginning + * of this AttributeDataElement to write. + * @param [out] aDataWrite whether we actually tried to write data. If + * this function returns success and aDataWrite is + * false, 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, TLV::TLVReader & aReader, bool * aDataWrite) + { + *aDataWrite = false; + + return CHIP_NO_ERROR; + } + /** * Mechanism for keeping track of a chain of AttributeAccessInterfaces. */ diff --git a/src/app/clusters/test-cluster-server/test-cluster-server.cpp b/src/app/clusters/test-cluster-server/test-cluster-server.cpp index 689969833e948d..4f8ce816a7100e 100644 --- a/src/app/clusters/test-cluster-server/test-cluster-server.cpp +++ b/src/app/clusters/test-cluster-server/test-cluster-server.cpp @@ -44,6 +44,9 @@ using namespace chip::app::Clusters::TestCluster::Attributes; constexpr const char * kErrorStr = "Test Cluster: List Octet cluster (0x%02x) Error setting '%s' attribute: 0x%02x"; #endif // CHIP_CLUSTER_CONFIG_ENABLE_COMPLEX_ATTRIBUTE_READ +constexpr uint8_t kAttributeListLength = 4; +constexpr uint8_t kAttributeEntryLength = 6; + namespace { class TestAttrAccess : public AttributeAccessInterface @@ -53,15 +56,24 @@ class TestAttrAccess : public AttributeAccessInterface TestAttrAccess() : AttributeAccessInterface(Optional::Missing(), TestCluster::Id) {} CHIP_ERROR Read(const ConcreteAttributePath & aPath, AttributeValueEncoder & aEncoder) override; + CHIP_ERROR Write(const ConcreteAttributePath & aPath, TLV::TLVReader & aReader, bool * aDataWrite) override; private: CHIP_ERROR ReadListInt8uAttribute(AttributeValueEncoder & aEncoder); + CHIP_ERROR WriteListInt8uAttribute(TLV::TLVReader & aReader); CHIP_ERROR ReadListOctetStringAttribute(AttributeValueEncoder & aEncoder); + CHIP_ERROR WriteListOctetStringAttribute(TLV::TLVReader & aReader); CHIP_ERROR ReadListStructOctetStringAttribute(AttributeValueEncoder & aEncoder); + CHIP_ERROR WriteListStructOctetStringAttribute(TLV::TLVReader & aReader); CHIP_ERROR ReadListNullablesAndOptionalsStructAttribute(AttributeValueEncoder & aEncoder); + CHIP_ERROR WrtieListNullablesAndOptionalsStructAttribute(TLV::TLVReader & aReader); }; TestAttrAccess gAttrAccess; +uint8_t gListUint8Data[kAttributeListLength]; +char gListOctetStringData[kAttributeListLength][kAttributeEntryLength]; +char gListOperationalCert[kAttributeListLength][kAttributeEntryLength]; +Structs::TestListStructOctet::Type listStructOctetStringData[kAttributeListLength]; CHIP_ERROR TestAttrAccess::Read(const ConcreteAttributePath & aPath, AttributeValueEncoder & aEncoder) { @@ -87,6 +99,33 @@ CHIP_ERROR TestAttrAccess::Read(const ConcreteAttributePath & aPath, AttributeVa return CHIP_NO_ERROR; } +CHIP_ERROR TestAttrAccess::Write(const ConcreteAttributePath & aPath, TLV::TLVReader & aReader, bool * aDataWrite) +{ + *aDataWrite = true; + + switch (aPath.mAttributeId) + { + case ListInt8u::Id: { + return WriteListInt8uAttribute(aReader); + } + case ListOctetString::Id: { + return WriteListOctetStringAttribute(aReader); + } + case ListStructOctetString::Id: { + return WriteListStructOctetStringAttribute(aReader); + } + case ListNullablesAndOptionalsStruct::Id: { + return WrtieListNullablesAndOptionalsStructAttribute(aReader); + } + default: { + *aDataWrite = false; + break; + } + } + + return CHIP_NO_ERROR; +} + #if !CHIP_CLUSTER_CONFIG_ENABLE_COMPLEX_ATTRIBUTE_READ EmberAfStatus writeAttribute(EndpointId endpoint, AttributeId attributeId, uint8_t * buffer, int32_t index = -1) { @@ -113,14 +152,13 @@ EmberAfStatus writeTestListInt8uAttribute(EndpointId endpoint) EmberAfStatus status = EMBER_ZCL_STATUS_SUCCESS; AttributeId attributeId = ZCL_LIST_ATTRIBUTE_ID; - uint16_t attributeCount = 4; - for (uint8_t index = 0; index < attributeCount; index++) + for (uint8_t index = 0; index < kAttributeListLength; index++) { status = writeAttribute(endpoint, attributeId, (uint8_t *) &index, index); VerifyOrReturnError(status == EMBER_ZCL_STATUS_SUCCESS, status); } - status = writeAttribute(endpoint, attributeId, (uint8_t *) &attributeCount); + status = writeAttribute(endpoint, attributeId, (uint8_t *) &kAttributeListLength); VerifyOrReturnError(status == EMBER_ZCL_STATUS_SUCCESS, status); return status; } @@ -129,26 +167,46 @@ EmberAfStatus writeTestListInt8uAttribute(EndpointId endpoint) CHIP_ERROR TestAttrAccess::ReadListInt8uAttribute(AttributeValueEncoder & aEncoder) { return aEncoder.EncodeList([](const TagBoundEncoder & encoder) -> CHIP_ERROR { - constexpr uint16_t attributeCount = 4; - for (uint8_t index = 0; index < attributeCount; index++) + for (uint8_t index = 0; index < kAttributeListLength; index++) { - ReturnErrorOnFailure(encoder.Encode(index)); + ReturnErrorOnFailure(encoder.Encode(gListUint8Data[index])); } return CHIP_NO_ERROR; }); } +CHIP_ERROR TestAttrAccess::WriteListInt8uAttribute(TLV::TLVReader & aReader) +{ + DataModel::DecodableList list; + + ReturnErrorOnFailure(DataModel::Decode(aReader, list)); + + uint8_t index = 0; + auto iter = list.begin(); + while (iter.Next()) + { + auto & entry = iter.GetValue(); + gListUint8Data[index++] = entry; + } + + if (iter.GetStatus() != CHIP_NO_ERROR) + { + return CHIP_ERROR_INVALID_DATA_LIST; + } + + return CHIP_NO_ERROR; +} + #if !CHIP_CLUSTER_CONFIG_ENABLE_COMPLEX_ATTRIBUTE_READ EmberAfStatus writeTestListOctetAttribute(EndpointId endpoint) { EmberAfStatus status = EMBER_ZCL_STATUS_SUCCESS; AttributeId attributeId = ZCL_LIST_OCTET_STRING_ATTRIBUTE_ID; - uint16_t attributeCount = 4; - char data[6] = { 'T', 'e', 's', 't', 'N', '\0' }; - ByteSpan span = ByteSpan(Uint8::from_char(data), strlen(data)); + char data[6] = { 'T', 'e', 's', 't', 'N', '\0' }; + ByteSpan span = ByteSpan(Uint8::from_char(data), strlen(data)); - for (uint8_t index = 0; index < attributeCount; index++) + for (uint8_t index = 0; index < kAttributeListLength; index++) { sprintf(data + strlen(data) - 1, "%d", index); @@ -156,7 +214,7 @@ EmberAfStatus writeTestListOctetAttribute(EndpointId endpoint) VerifyOrReturnError(status == EMBER_ZCL_STATUS_SUCCESS, status); } - status = writeAttribute(endpoint, attributeId, (uint8_t *) &attributeCount); + status = writeAttribute(endpoint, attributeId, (uint8_t *) &kAttributeListLength); VerifyOrReturnError(status == EMBER_ZCL_STATUS_SUCCESS, status); return status; } @@ -165,30 +223,45 @@ EmberAfStatus writeTestListOctetAttribute(EndpointId endpoint) 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(Uint8::from_char(gListOctetStringData[index]), strlen(gListOctetStringData[index])); ReturnErrorOnFailure(encoder.Encode(span)); } return CHIP_NO_ERROR; }); } +CHIP_ERROR TestAttrAccess::WriteListOctetStringAttribute(TLV::TLVReader & aReader) +{ + DataModel::DecodableList list; + + ReturnErrorOnFailure(DataModel::Decode(aReader, 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); + Platform::CopyString(gListOctetStringData[index], sizeof(gListOctetStringData[index]), entry); + index++; + } + + return iter.GetStatus(); +} + #if !CHIP_CLUSTER_CONFIG_ENABLE_COMPLEX_ATTRIBUTE_READ EmberAfStatus writeTestListStructOctetAttribute(EndpointId endpoint) { EmberAfStatus status = EMBER_ZCL_STATUS_SUCCESS; AttributeId attributeId = ZCL_LIST_STRUCT_OCTET_STRING_ATTRIBUTE_ID; - uint16_t attributeCount = 4; - char data[6] = { 'T', 'e', 's', 't', 'N', '\0' }; - ByteSpan span = ByteSpan(Uint8::from_char(data), strlen(data)); + char data[6] = { 'T', 'e', 's', 't', 'N', '\0' }; + ByteSpan span = ByteSpan(Uint8::from_char(data), strlen(data)); - for (uint8_t index = 0; index < attributeCount; index++) + for (uint8_t index = 0; index < kAttributeListLength; index++) { sprintf(data + strlen(data) - 1, "%d", index); @@ -200,7 +273,7 @@ EmberAfStatus writeTestListStructOctetAttribute(EndpointId endpoint) VerifyOrReturnError(status == EMBER_ZCL_STATUS_SUCCESS, status); } - status = writeAttribute(endpoint, attributeId, (uint8_t *) &attributeCount); + status = writeAttribute(endpoint, attributeId, (uint8_t *) &kAttributeListLength); VerifyOrReturnError(status == EMBER_ZCL_STATUS_SUCCESS, status); return status; } @@ -209,23 +282,46 @@ EmberAfStatus writeTestListStructOctetAttribute(EndpointId endpoint) 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(TLV::TLVReader & aReader) +{ + DataModel::DecodableList list; + + ReturnErrorOnFailure(DataModel::Decode(aReader, 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); + Platform::CopyString(gListOperationalCert[index], sizeof(gListOperationalCert[index]), entry.operationalCert); + listStructOctetStringData[index].fabricIndex = entry.fabricIndex; + listStructOctetStringData[index].operationalCert = + ByteSpan(Uint8::from_char(gListOperationalCert[index]), strlen(gListOperationalCert[index])); + index++; + } + + 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 { @@ -237,6 +333,11 @@ CHIP_ERROR TestAttrAccess::ReadListNullablesAndOptionalsStructAttribute(Attribut }); } +CHIP_ERROR TestAttrAccess::WrtieListNullablesAndOptionalsStructAttribute(TLV::TLVReader & aReader) +{ + // TODO Add yaml test case for NullablesAndOptionalsStruct list + return CHIP_NO_ERROR; +} } // namespace bool emberAfTestClusterClusterTestCallback(app::CommandHandler *, const app::ConcreteCommandPath & commandPath, diff --git a/src/app/tests/suites/TestCluster.yaml b/src/app/tests/suites/TestCluster.yaml index cc14d5e8f7ec3e..8e58b8ce14dff0 100644 --- a/src/app/tests/suites/TestCluster.yaml +++ b/src/app/tests/suites/TestCluster.yaml @@ -720,36 +720,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" diff --git a/src/app/tests/suites/TestClusterComplexTypes.yaml b/src/app/tests/suites/TestClusterComplexTypes.yaml index 8241a66258ba69..244b56dae38330 100644 --- a/src/app/tests/suites/TestClusterComplexTypes.yaml +++ b/src/app/tests/suites/TestClusterComplexTypes.yaml @@ -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." diff --git a/src/app/util/ember-compatibility-functions.cpp b/src/app/util/ember-compatibility-functions.cpp index 450fe340d4f5c0..fd14249ded01a5 100644 --- a/src/app/util/ember-compatibility-functions.cpp +++ b/src/app/util/ember-compatibility-functions.cpp @@ -503,6 +503,22 @@ 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) + { + bool dataWrite; + ConcreteAttributePath path(aClusterInfo.mEndpointId, aClusterInfo.mClusterId, aClusterInfo.mAttributeId); + + ReturnErrorOnFailure(attrOverride->Write(path, aReader, &dataWrite)); + + if (dataWrite) + { + return apWriteHandler->AddStatus(attributePathParams, Protocols::InteractionModel::Status::Success); + } + } + auto imCode = WriteSingleClusterDataInternal(aClusterInfo, aReader, apWriteHandler); return apWriteHandler->AddStatus(attributePathParams, imCode); }