From 10797686a1abf6c09e5c05a73d9afdf5503f88cb Mon Sep 17 00:00:00 2001 From: yunhanw-google Date: Fri, 11 Feb 2022 00:50:59 -0800 Subject: [PATCH] Add DataVersionFilter for read/subscribe (#14711) * Add DataVersionFilter support for read/subscribe --- .../commands/clusters/ReportCommand.h | 25 +- .../logging/DataModelLogger-src.zapt | 4 +- src/app/AttributeCache.cpp | 5 +- src/app/AttributeCache.h | 3 +- src/app/BufferedReadCallback.cpp | 7 +- src/app/BufferedReadCallback.h | 4 +- src/app/ClusterInfo.h | 3 + src/app/ConcreteAttributePath.h | 9 +- src/app/DataVersionFilter.h | 45 +++ src/app/InteractionModelEngine.h | 5 + src/app/MessageDef/ReadRequestMessage.cpp | 2 +- src/app/ReadClient.cpp | 65 +++- src/app/ReadClient.h | 23 +- src/app/ReadHandler.cpp | 53 +++- src/app/ReadHandler.h | 3 + src/app/ReadPrepareParams.h | 7 + src/app/reporting/Engine.cpp | 25 ++ src/app/reporting/Engine.h | 7 + src/app/tests/TestAttributeCache.cpp | 5 +- src/app/tests/TestBufferedReadCallback.cpp | 20 +- src/app/tests/TestMessageDef.cpp | 2 +- src/app/tests/TestReadInteraction.cpp | 284 +++++++++++++++++- .../tests/integration/chip_im_initiator.cpp | 9 +- .../tests/integration/chip_im_responder.cpp | 5 + .../util/ember-compatibility-functions.cpp | 19 +- src/controller/CHIPCluster.h | 25 +- src/controller/CHIPDeviceController.cpp | 1 - src/controller/ReadInteraction.h | 44 +-- src/controller/TypedReadCallback.h | 30 +- src/controller/java/AndroidCallbacks.cpp | 2 +- src/controller/java/AndroidCallbacks.h | 2 +- src/controller/python/chip/ChipDeviceCtrl.py | 25 +- .../python/chip/clusters/Attribute.py | 78 ++++- .../python/chip/clusters/attribute.cpp | 45 ++- .../python/chip/interaction_model/__init__.py | 4 +- .../python/chip/interaction_model/delegate.py | 7 + .../python/test/test_scripts/base.py | 8 +- .../test/test_scripts/cluster_objects.py | 88 ++++-- .../test_scripts/network_commissioning.py | 15 +- src/controller/tests/TestReadChunking.cpp | 4 +- .../tests/data_model/TestCommands.cpp | 14 - src/controller/tests/data_model/TestRead.cpp | 266 ++++++++++------ src/controller/tests/data_model/TestWrite.cpp | 29 -- src/darwin/Framework/CHIP/CHIPDevice.mm | 5 +- src/lib/core/CHIPError.cpp | 3 - src/lib/core/CHIPError.h | 8 - src/lib/core/DataModelTypes.h | 2 +- src/lib/core/tests/TestCHIPErrorStr.cpp | 1 - src/platform/CYW30739/args.gni | 2 + .../cluster/logging/DataModelLogger.cpp | 5 +- 50 files changed, 1041 insertions(+), 311 deletions(-) create mode 100644 src/app/DataVersionFilter.h diff --git a/examples/chip-tool/commands/clusters/ReportCommand.h b/examples/chip-tool/commands/clusters/ReportCommand.h index 1f5822dc4d08b1..fc41b31ade25fc 100644 --- a/examples/chip-tool/commands/clusters/ReportCommand.h +++ b/examples/chip-tool/commands/clusters/ReportCommand.h @@ -34,7 +34,7 @@ class ReportCommand : public ModelCommand, public chip::app::ReadClient::Callbac virtual void OnEventSubscription(){}; /////////// ReadClient Callback Interface ///////// - void OnAttributeData(const chip::app::ConcreteDataAttributePath & path, chip::DataVersion aVersion, chip::TLV::TLVReader * data, + void OnAttributeData(const chip::app::ConcreteDataAttributePath & path, chip::TLV::TLVReader * data, const chip::app::StatusIB & status) override { CHIP_ERROR error = status.ToChipError(); @@ -108,7 +108,8 @@ class ReportCommand : public ModelCommand, public chip::app::ReadClient::Callbac protected: CHIP_ERROR ReportAttribute(ChipDevice * device, chip::EndpointId endpointId, chip::ClusterId clusterId, chip::AttributeId attributeId, chip::app::ReadClient::InteractionType interactionType, - uint16_t minInterval = 0, uint16_t maxInterval = 0) + uint16_t minInterval = 0, uint16_t maxInterval = 0, + const chip::Optional & aDataVersion = chip::NullOptional) { chip::app::AttributePathParams attributePathParams[1]; attributePathParams[0].mEndpointId = endpointId; @@ -121,6 +122,13 @@ class ReportCommand : public ModelCommand, public chip::app::ReadClient::Callbac params.mpAttributePathParamsList = attributePathParams; params.mAttributePathParamsListSize = 1; + chip::Optional dataVersionFilter; + if (aDataVersion.HasValue()) + { + params.mpDataVersionFilterList = &dataVersionFilter.Emplace(endpointId, clusterId, aDataVersion.Value()); + params.mDataVersionFilterListSize = 1; + } + if (interactionType == chip::app::ReadClient::InteractionType::Subscribe) { params.mMinIntervalFloorSeconds = minInterval; @@ -169,6 +177,7 @@ class ReadAttribute : public ReportCommand { AddArgument("cluster-id", 0, UINT32_MAX, &mClusterId); AddArgument("attribute-id", 0, UINT32_MAX, &mAttributeId); + AddArgument("data-version", 0, UINT32_MAX, &mDataVersion); ReportCommand::AddArguments(); } @@ -176,6 +185,7 @@ class ReadAttribute : public ReportCommand ReportCommand("read-by-id", credsIssuerConfig), mClusterId(clusterId) { AddArgument("attribute-id", 0, UINT32_MAX, &mAttributeId); + AddArgument("data-version", 0, UINT32_MAX, &mDataVersion); ReportCommand::AddArguments(); } @@ -185,6 +195,7 @@ class ReadAttribute : public ReportCommand mClusterId(clusterId), mAttributeId(attributeId) { AddArgument("attr-name", attributeName); + AddArgument("data-version", 0, UINT32_MAX, &mDataVersion); ReportCommand::AddArguments(); } @@ -195,12 +206,13 @@ class ReadAttribute : public ReportCommand ChipLogProgress(chipTool, "Sending ReadAttribute to cluster " ChipLogFormatMEI " on endpoint %" PRIu16, ChipLogValueMEI(mClusterId), endpointId); return ReportCommand::ReportAttribute(device, endpointId, mClusterId, mAttributeId, - chip::app::ReadClient::InteractionType::Read); + chip::app::ReadClient::InteractionType::Read, 0, 0, mDataVersion); } private: chip::ClusterId mClusterId; chip::AttributeId mAttributeId; + chip::Optional mDataVersion; }; class SubscribeAttribute : public ReportCommand @@ -212,6 +224,7 @@ class SubscribeAttribute : public ReportCommand AddArgument("attribute-id", 0, UINT32_MAX, &mAttributeId); AddArgument("min-interval", 0, UINT16_MAX, &mMinInterval); AddArgument("max-interval", 0, UINT16_MAX, &mMaxInterval); + AddArgument("data-version", 0, UINT32_MAX, &mDataVersion); AddArgument("wait", 0, 1, &mWait); ReportCommand::AddArguments(); } @@ -222,6 +235,7 @@ class SubscribeAttribute : public ReportCommand AddArgument("attribute-id", 0, UINT32_MAX, &mAttributeId); AddArgument("min-interval", 0, UINT16_MAX, &mMinInterval); AddArgument("max-interval", 0, UINT16_MAX, &mMaxInterval); + AddArgument("data-version", 0, UINT32_MAX, &mDataVersion); AddArgument("wait", 0, 1, &mWait); ReportCommand::AddArguments(); } @@ -234,6 +248,7 @@ class SubscribeAttribute : public ReportCommand AddArgument("attr-name", attributeName); AddArgument("min-interval", 0, UINT16_MAX, &mMinInterval); AddArgument("max-interval", 0, UINT16_MAX, &mMaxInterval); + AddArgument("data-version", 0, UINT32_MAX, &mDataVersion); AddArgument("wait", 0, 1, &mWait); ReportCommand::AddArguments(); } @@ -245,7 +260,8 @@ class SubscribeAttribute : public ReportCommand ChipLogProgress(chipTool, "Sending SubscribeAttribute to cluster " ChipLogFormatMEI " on endpoint %" PRIu16, ChipLogValueMEI(mClusterId), endpointId); return ReportCommand::ReportAttribute(device, endpointId, mClusterId, mAttributeId, - chip::app::ReadClient::InteractionType::Subscribe, mMinInterval, mMaxInterval); + chip::app::ReadClient::InteractionType::Subscribe, mMinInterval, mMaxInterval, + mDataVersion); } chip::System::Clock::Timeout GetWaitDuration() const override @@ -267,6 +283,7 @@ class SubscribeAttribute : public ReportCommand uint16_t mMinInterval; uint16_t mMaxInterval; + chip::Optional mDataVersion; bool mWait; }; diff --git a/examples/chip-tool/templates/logging/DataModelLogger-src.zapt b/examples/chip-tool/templates/logging/DataModelLogger-src.zapt index 45a458e852d9bc..c91a6aadad3a1e 100644 --- a/examples/chip-tool/templates/logging/DataModelLogger-src.zapt +++ b/examples/chip-tool/templates/logging/DataModelLogger-src.zapt @@ -62,8 +62,8 @@ CHIP_ERROR DataModelLogger::LogValue(const char * label, size_t indent, const {{ CHIP_ERROR DataModelLogger::LogAttribute(const chip::app::ConcreteDataAttributePath & path, chip::TLV::TLVReader * data) { - ChipLogProgress(chipTool, "Endpoint: %" PRIu16 " Cluster: " ChipLogFormatMEI " Attribute " ChipLogFormatMEI, path.mEndpointId, - ChipLogValueMEI(path.mClusterId), ChipLogValueMEI(path.mAttributeId)); + ChipLogProgress(chipTool, "Endpoint: %" PRIu16 " Cluster: " ChipLogFormatMEI " Attribute " ChipLogFormatMEI "DataVersion: %" PRIu32, path.mEndpointId, + ChipLogValueMEI(path.mClusterId), ChipLogValueMEI(path.mAttributeId), path.mDataVersion); switch (path.mClusterId) { diff --git a/src/app/AttributeCache.cpp b/src/app/AttributeCache.cpp index 35a9b70bdba1ef..349a8563636144 100644 --- a/src/app/AttributeCache.cpp +++ b/src/app/AttributeCache.cpp @@ -100,8 +100,7 @@ void AttributeCache::OnReportEnd() mCallback.OnReportEnd(); } -void AttributeCache::OnAttributeData(const ConcreteDataAttributePath & aPath, DataVersion aVersion, TLV::TLVReader * apData, - const StatusIB & aStatus) +void AttributeCache::OnAttributeData(const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, const StatusIB & aStatus) { // // Since the cache itself is a ReadClient::Callback, it may be incorrectly passed in directly when registering with the @@ -121,7 +120,7 @@ void AttributeCache::OnAttributeData(const ConcreteDataAttributePath & aPath, Da // // Forward the call through. // - mCallback.OnAttributeData(aPath, aVersion, apData, aStatus); + mCallback.OnAttributeData(aPath, apData, aStatus); } CHIP_ERROR AttributeCache::Get(const ConcreteAttributePath & path, TLV::TLVReader & reader) diff --git a/src/app/AttributeCache.h b/src/app/AttributeCache.h index 1e682d29527c93..907dbedf63520d 100644 --- a/src/app/AttributeCache.h +++ b/src/app/AttributeCache.h @@ -342,8 +342,7 @@ class AttributeCache : protected ReadClient::Callback // void OnReportBegin() override; void OnReportEnd() override; - void OnAttributeData(const ConcreteDataAttributePath & aPath, DataVersion aVersion, TLV::TLVReader * apData, - const StatusIB & aStatus) override; + void OnAttributeData(const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, const StatusIB & aStatus) override; void OnError(CHIP_ERROR aError) override { return mCallback.OnError(aError); } void OnEventData(const EventHeader & aEventHeader, TLV::TLVReader * apData, const StatusIB * apStatus) override diff --git a/src/app/BufferedReadCallback.cpp b/src/app/BufferedReadCallback.cpp index b2686910972058..e1188a4c268ebb 100644 --- a/src/app/BufferedReadCallback.cpp +++ b/src/app/BufferedReadCallback.cpp @@ -215,7 +215,7 @@ CHIP_ERROR BufferedReadCallback::DispatchBufferedData(const ConcreteAttributePat // ReturnErrorOnFailure(reader.Next()); - mCallback.OnAttributeData(mBufferedPath, mDataVersion, &reader, statusIB); + mCallback.OnAttributeData(mBufferedPath, &reader, statusIB); // // Clear out our buffered contents to free up allocated buffers, and reset the buffered path. @@ -225,7 +225,7 @@ CHIP_ERROR BufferedReadCallback::DispatchBufferedData(const ConcreteAttributePat return CHIP_NO_ERROR; } -void BufferedReadCallback::OnAttributeData(const ConcreteDataAttributePath & aPath, DataVersion aVersion, TLV::TLVReader * apData, +void BufferedReadCallback::OnAttributeData(const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, const StatusIB & aStatus) { CHIP_ERROR err; @@ -246,14 +246,13 @@ void BufferedReadCallback::OnAttributeData(const ConcreteDataAttributePath & aPa } else { - mCallback.OnAttributeData(aPath, aVersion, apData, aStatus); + mCallback.OnAttributeData(aPath, apData, aStatus); } // // Update our latched buffered path. // mBufferedPath = aPath; - mDataVersion = aVersion; exit: if (err != CHIP_NO_ERROR) diff --git a/src/app/BufferedReadCallback.h b/src/app/BufferedReadCallback.h index ce9d959e9fdbfb..5f88ebdaa12c14 100644 --- a/src/app/BufferedReadCallback.h +++ b/src/app/BufferedReadCallback.h @@ -69,8 +69,7 @@ class BufferedReadCallback : public ReadClient::Callback // void OnReportBegin() override; void OnReportEnd() override; - void OnAttributeData(const ConcreteDataAttributePath & aPath, DataVersion aVersion, TLV::TLVReader * apData, - const StatusIB & aStatus) override; + void OnAttributeData(const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, const StatusIB & aStatus) override; void OnError(CHIP_ERROR aError) override { return mCallback.OnError(aError); } void OnEventData(const EventHeader & aEventHeader, TLV::TLVReader * apData, const StatusIB * apStatus) override { @@ -94,7 +93,6 @@ class BufferedReadCallback : public ReadClient::Callback * */ CHIP_ERROR BufferListItem(TLV::TLVReader & reader); - DataVersion mDataVersion; ConcreteDataAttributePath mBufferedPath; std::vector mBufferedList; Callback & mCallback; diff --git a/src/app/ClusterInfo.h b/src/app/ClusterInfo.h index 55e7d7ec403823..e8fdc95bf9fc73 100644 --- a/src/app/ClusterInfo.h +++ b/src/app/ClusterInfo.h @@ -79,6 +79,8 @@ struct ClusterInfo // For event, an event id can only be interpreted if the cluster id is known. bool IsValidEventPath() const { return !(HasWildcardClusterId() && !HasWildcardEventId()); } + bool IsValidDataVersionFilter() const { return !HasWildcardEndpointId() && !HasWildcardClusterId() && mDataVersion.HasValue(); } + inline bool HasWildcardNodeId() const { return mNodeId == kUndefinedNodeId; } inline bool HasWildcardEndpointId() const { return mEndpointId == kInvalidEndpointId; } inline bool HasWildcardClusterId() const { return mClusterId == kInvalidClusterId; } @@ -100,6 +102,7 @@ struct ClusterInfo EventId mEventId = kInvalidEventId; // uint32 ListIndex mListIndex = kInvalidListIndex; // uint16 EndpointId mEndpointId = kInvalidEndpointId; // uint16 + Optional mDataVersion; // uint32 }; } // namespace app } // namespace chip diff --git a/src/app/ConcreteAttributePath.h b/src/app/ConcreteAttributePath.h index 97e29bc190766d..486c5dcb8a0188 100644 --- a/src/app/ConcreteAttributePath.h +++ b/src/app/ConcreteAttributePath.h @@ -111,6 +111,10 @@ struct ConcreteDataAttributePath : public ConcreteAttributePath ConcreteAttributePath(aEndpointId, aClusterId, aAttributeId) {} + ConcreteDataAttributePath(EndpointId aEndpointId, ClusterId aClusterId, AttributeId aAttributeId, DataVersion aDataVersion) : + ConcreteAttributePath(aEndpointId, aClusterId, aAttributeId), mDataVersion(aDataVersion) + {} + ConcreteDataAttributePath(EndpointId aEndpointId, ClusterId aClusterId, AttributeId aAttributeId, ListOperation aListOp, uint16_t aListIndex) : ConcreteAttributePath(aEndpointId, aClusterId, aAttributeId) @@ -126,8 +130,9 @@ struct ConcreteDataAttributePath : public ConcreteAttributePath // This index is only valid if `mListOp` is set to a list item operation, i.e // ReplaceItem, DeleteItem or AppendItem. Otherwise, it is to be ignored. // - uint16_t mListIndex = 0; - ListOperation mListOp = ListOperation::NotList; + uint16_t mListIndex = 0; + ListOperation mListOp = ListOperation::NotList; + DataVersion mDataVersion = 0; }; } // namespace app diff --git a/src/app/DataVersionFilter.h b/src/app/DataVersionFilter.h new file mode 100644 index 00000000000000..ba93de61689693 --- /dev/null +++ b/src/app/DataVersionFilter.h @@ -0,0 +1,45 @@ +/* + * + * Copyright (c) 2022 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 + +#include + +namespace chip { +namespace app { +struct DataVersionFilter +{ + DataVersionFilter(EndpointId aEndpointId, ClusterId aClusterId, DataVersion aDataVersion) : + mEndpointId(aEndpointId), mClusterId(aClusterId), mDataVersion(aDataVersion) + {} + + DataVersionFilter() {} + + bool IsValidDataVersionFilter() + { + return (mEndpointId != kInvalidEndpointId) && (mClusterId != kInvalidClusterId) && (mDataVersion.HasValue()); + } + + EndpointId mEndpointId = kInvalidEndpointId; + ClusterId mClusterId = kInvalidClusterId; + Optional mDataVersion; +}; +} // namespace app +} // namespace chip diff --git a/src/app/InteractionModelEngine.h b/src/app/InteractionModelEngine.h index 78b936b45e8b01..6a7aa18b09404e 100644 --- a/src/app/InteractionModelEngine.h +++ b/src/app/InteractionModelEngine.h @@ -340,5 +340,10 @@ CHIP_ERROR ReadSingleClusterData(const Access::SubjectDescriptor & aSubjectDescr CHIP_ERROR WriteSingleClusterData(const Access::SubjectDescriptor & aSubjectDescriptor, const ConcreteDataAttributePath & aAttributePath, TLV::TLVReader & aReader, WriteHandler * apWriteHandler); + +/** + * Check if the given cluster has the given DataVersion. + */ +bool IsClusterDataVersionEqual(EndpointId aEndpointId, ClusterId aClusterId, DataVersion aRequiredVersion); } // namespace app } // namespace chip diff --git a/src/app/MessageDef/ReadRequestMessage.cpp b/src/app/MessageDef/ReadRequestMessage.cpp index 97478821551ebc..97d2a2abb88105 100644 --- a/src/app/MessageDef/ReadRequestMessage.cpp +++ b/src/app/MessageDef/ReadRequestMessage.cpp @@ -59,7 +59,7 @@ CHIP_ERROR ReadRequestMessage::Parser::CheckSchemaValidity() const break; case to_underlying(Tag::kDataVersionFilters): // check if this tag has appeared before - VerifyOrReturnError(!(tagPresenceMask & (1 << to_underlying(Tag::kEventFilters))), CHIP_ERROR_INVALID_TLV_TAG); + VerifyOrReturnError(!(tagPresenceMask & (1 << to_underlying(Tag::kDataVersionFilters))), CHIP_ERROR_INVALID_TLV_TAG); tagPresenceMask |= (1 << to_underlying(Tag::kDataVersionFilters)); { DataVersionFilterIBs::Parser dataVersionFilters; diff --git a/src/app/ReadClient.cpp b/src/app/ReadClient.cpp index 7c0047b9cad3ac..e88aa7c810a0f3 100644 --- a/src/app/ReadClient.cpp +++ b/src/app/ReadClient.cpp @@ -222,6 +222,14 @@ CHIP_ERROR ReadClient::SendReadRequest(ReadPrepareParams & aReadPrepareParams) ReturnErrorOnFailure(err = request.GetError()); ReturnErrorOnFailure(GenerateAttributePathList(attributePathListBuilder, aReadPrepareParams.mpAttributePathParamsList, aReadPrepareParams.mAttributePathParamsListSize)); + if (aReadPrepareParams.mDataVersionFilterListSize != 0 && aReadPrepareParams.mpDataVersionFilterList != nullptr) + { + DataVersionFilterIBs::Builder & dataVersionFilterListBuilder = request.CreateDataVersionFilters(); + ReturnErrorOnFailure(request.GetError()); + ReturnErrorOnFailure(GenerateDataVersionFilterList(dataVersionFilterListBuilder, + aReadPrepareParams.mpDataVersionFilterList, + aReadPrepareParams.mDataVersionFilterListSize)); + } } if (aReadPrepareParams.mEventPathParamsListSize != 0 && aReadPrepareParams.mpEventPathParamsList != nullptr) @@ -296,6 +304,28 @@ CHIP_ERROR ReadClient::GenerateAttributePathList(AttributePathIBs::Builder & aAt return aAttributePathIBsBuilder.GetError(); } +CHIP_ERROR ReadClient::GenerateDataVersionFilterList(DataVersionFilterIBs::Builder & aDataVersionFilterIBsBuilder, + DataVersionFilter * apDataVersionFilterList, size_t aDataVersionFilterListSize) +{ + for (size_t index = 0; index < aDataVersionFilterListSize; index++) + { + VerifyOrReturnError(apDataVersionFilterList[index].IsValidDataVersionFilter(), CHIP_ERROR_INVALID_ARGUMENT); + DataVersionFilterIB::Builder & filter = aDataVersionFilterIBsBuilder.CreateDataVersionFilter(); + ReturnErrorOnFailure(aDataVersionFilterIBsBuilder.GetError()); + ClusterPathIB::Builder & path = filter.CreatePath(); + ReturnErrorOnFailure(filter.GetError()); + ReturnErrorOnFailure(path.Endpoint(apDataVersionFilterList[index].mEndpointId) + .Cluster(apDataVersionFilterList[index].mClusterId) + .EndOfClusterPathIB() + .GetError()); + VerifyOrReturnError(apDataVersionFilterList[index].mDataVersion.HasValue(), CHIP_ERROR_INVALID_ARGUMENT); + ReturnErrorOnFailure( + filter.DataVersion(apDataVersionFilterList[index].mDataVersion.Value()).EndOfDataVersionFilterIB().GetError()); + } + + return aDataVersionFilterIBsBuilder.EndOfDataVersionFilterIBs().GetError(); +} + CHIP_ERROR ReadClient::OnMessageReceived(Messaging::ExchangeContext * apExchangeContext, const PayloadHeader & aPayloadHeader, System::PacketBufferHandle && aPayload) { @@ -554,8 +584,7 @@ CHIP_ERROR ReadClient::ProcessAttributeReportIBs(TLV::TLVReader & aAttributeRepo TLV::TLVReader reader = aAttributeReportIBsReader; ReturnErrorOnFailure(report.Init(reader)); - DataVersion version = kUndefinedDataVersion; - err = report.GetAttributeStatus(&status); + err = report.GetAttributeStatus(&status); if (CHIP_NO_ERROR == err) { StatusIB::Parser errorStatus; @@ -563,14 +592,19 @@ CHIP_ERROR ReadClient::ProcessAttributeReportIBs(TLV::TLVReader & aAttributeRepo ReturnErrorOnFailure(ProcessAttributePath(path, attributePath)); ReturnErrorOnFailure(status.GetErrorStatus(&errorStatus)); ReturnErrorOnFailure(errorStatus.DecodeStatusIB(statusIB)); - mpCallback.OnAttributeData(attributePath, version, nullptr, statusIB); + mpCallback.OnAttributeData(attributePath, nullptr, statusIB); } else if (CHIP_END_OF_TLV == err) { ReturnErrorOnFailure(report.GetAttributeData(&data)); ReturnErrorOnFailure(data.GetPath(&path)); ReturnErrorOnFailure(ProcessAttributePath(path, attributePath)); - ReturnErrorOnFailure(data.GetDataVersion(&version)); + ReturnErrorOnFailure(data.GetDataVersion(&attributePath.mDataVersion)); + if (mReadPrepareParams.mResubscribePolicy != nullptr) + { + UpdateDataVersionFilters(attributePath); + } + ReturnErrorOnFailure(data.GetData(&dataReader)); // The element in an array may be another array -- so we should only set the list operation when we are handling the @@ -580,7 +614,7 @@ CHIP_ERROR ReadClient::ProcessAttributeReportIBs(TLV::TLVReader & aAttributeRepo attributePath.mListOp = ConcreteDataAttributePath::ListOperation::ReplaceAll; } - mpCallback.OnAttributeData(attributePath, version, &dataReader, statusIB); + mpCallback.OnAttributeData(attributePath, &dataReader, statusIB); } } @@ -749,6 +783,14 @@ CHIP_ERROR ReadClient::SendSubscribeRequest(ReadPrepareParams & aReadPreparePara ReturnErrorOnFailure(err = attributePathListBuilder.GetError()); ReturnErrorOnFailure(GenerateAttributePathList(attributePathListBuilder, aReadPrepareParams.mpAttributePathParamsList, aReadPrepareParams.mAttributePathParamsListSize)); + if (aReadPrepareParams.mDataVersionFilterListSize != 0 && aReadPrepareParams.mpDataVersionFilterList != nullptr) + { + DataVersionFilterIBs::Builder & dataVersionFilterListBuilder = request.CreateDataVersionFilters(); + ReturnErrorOnFailure(request.GetError()); + ReturnErrorOnFailure(GenerateDataVersionFilterList(dataVersionFilterListBuilder, + aReadPrepareParams.mpDataVersionFilterList, + aReadPrepareParams.mDataVersionFilterListSize)); + } } if (aReadPrepareParams.mEventPathParamsListSize != 0 && aReadPrepareParams.mpEventPathParamsList != nullptr) @@ -775,7 +817,6 @@ CHIP_ERROR ReadClient::SendSubscribeRequest(ReadPrepareParams & aReadPreparePara request.IsFabricFiltered(aReadPrepareParams.mIsFabricFiltered).EndOfSubscribeRequestMessage(); ReturnErrorOnFailure(err = request.GetError()); - ReturnErrorOnFailure(writer.Finalize(&msgBuf)); mpExchangeCtx = mpExchangeMgr->NewContext(aReadPrepareParams.mSessionHolder.Get(), this); @@ -831,5 +872,17 @@ bool ReadClient::ResubscribeIfNeeded() return true; } +void ReadClient::UpdateDataVersionFilters(const ConcreteDataAttributePath & aPath) +{ + for (size_t index = 0; index < mReadPrepareParams.mDataVersionFilterListSize; index++) + { + if (mReadPrepareParams.mpDataVersionFilterList[index].mEndpointId == aPath.mEndpointId && + mReadPrepareParams.mpDataVersionFilterList[index].mClusterId == aPath.mClusterId) + { + // Now we know the current version for this cluster is aPath.mDataVersion. + mReadPrepareParams.mpDataVersionFilterList[index].mDataVersion.SetValue(aPath.mDataVersion); + } + } +} } // namespace app } // namespace chip diff --git a/src/app/ReadClient.h b/src/app/ReadClient.h index 24f731e82c6ec0..c8f30f359c5350 100644 --- a/src/app/ReadClient.h +++ b/src/app/ReadClient.h @@ -110,14 +110,11 @@ class ReadClient : public Messaging::ExchangeDelegate * receives an OnDone call to destroy the object. * * @param[in] aPath The attribute path field in report response. - * @param[in] aVersion The data version for cluster in report response. * @param[in] apData The attribute data of the given path, will be a nullptr if status is not Success. * @param[in] aStatus Attribute-specific status, containing an InteractionModel::Status code as well as an * optional cluster-specific status code. */ - virtual void OnAttributeData(const ConcreteDataAttributePath & aPath, DataVersion aVersion, TLV::TLVReader * apData, - const StatusIB & aStatus) - {} + virtual void OnAttributeData(const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, const StatusIB & aStatus) {} /** * OnSubscriptionEstablished will be called when a subscription is established for the given subscription transaction. @@ -250,13 +247,13 @@ class ReadClient : public Messaging::ExchangeDelegate // used. // // The application has to know to - // a) allocate a ReadPrepareParams object that will have fields mpEventPathParamsList and mpAttributePathParamsList with - // lifetimes as long as the ReadClient itself and b) free those up later in the call to OnDeallocatePaths. Note: At a given - // time in the system, you can either have a single subscription with re-sub enabled that that has mKeepSubscriptions = false, - // OR, multiple subs with re-sub enabled with mKeepSubscriptions = true. You shall not have a mix of both simultaneously. - // If SendAutoResubscribeRequest is called at all, it guarantees that it will call OnDeallocatePaths when OnDone is called. - // SendAutoResubscribeRequest is the only case that calls OnDeallocatePaths, since that's the only case when the consumer moved - // a ReadParams into the client. + // a) allocate a ReadPrepareParams object that will have fields mpEventPathParamsList and mpAttributePathParamsList and + // mpDataVersionFilterList with lifetimes as long as the ReadClient itself and b) free those up later in the call to + // OnDeallocatePaths. Note: At a given time in the system, you can either have a single subscription with re-sub enabled that + // that has mKeepSubscriptions = false, OR, multiple subs with re-sub enabled with mKeepSubscriptions = true. You shall not have + // a mix of both simultaneously. If SendAutoResubscribeRequest is called at all, it guarantees that it will call + // OnDeallocatePaths when OnDone is called. SendAutoResubscribeRequest is the only case that calls OnDeallocatePaths, since + // that's the only case when the consumer moved a ReadParams into the client. CHIP_ERROR SendAutoResubscribeRequest(ReadPrepareParams && aReadPrepareParams); private: @@ -293,6 +290,8 @@ class ReadClient : public Messaging::ExchangeDelegate size_t aEventPathParamsListSize); CHIP_ERROR GenerateAttributePathList(AttributePathIBs::Builder & aAttributePathIBsBuilder, AttributePathParams * apAttributePathParamsList, size_t aAttributePathParamsListSize); + CHIP_ERROR GenerateDataVersionFilterList(DataVersionFilterIBs::Builder & aDataVersionFilterIBsBuilder, + DataVersionFilter * apDataVersionFilterList, size_t aDataVersionFilterListSize); CHIP_ERROR ProcessAttributeReportIBs(TLV::TLVReader & aAttributeDataIBsReader); CHIP_ERROR ProcessEventReportIBs(TLV::TLVReader & aEventReportIBsReader); @@ -310,7 +309,7 @@ class ReadClient : public Messaging::ExchangeDelegate // Specialized request-sending functions. CHIP_ERROR SendReadRequest(ReadPrepareParams & aReadPrepareParams); CHIP_ERROR SendSubscribeRequest(ReadPrepareParams & aSubscribePrepareParams); - + void UpdateDataVersionFilters(const ConcreteDataAttributePath & aPath); static void OnResubscribeTimerCallback(System::Layer * apSystemLayer, void * apAppState); /* diff --git a/src/app/ReadHandler.cpp b/src/app/ReadHandler.cpp index 0f49aaa1b7cde8..9d3eeeda85ea0c 100644 --- a/src/app/ReadHandler.cpp +++ b/src/app/ReadHandler.cpp @@ -98,6 +98,7 @@ ReadHandler::~ReadHandler() InteractionModelEngine::GetInstance()->ReleaseClusterInfoList(mpAttributeClusterInfoList); InteractionModelEngine::GetInstance()->ReleaseClusterInfoList(mpEventClusterInfoList); + InteractionModelEngine::GetInstance()->ReleaseClusterInfoList(mpDataVersionFilterList); } void ReadHandler::Close() @@ -296,7 +297,6 @@ CHIP_ERROR ReadHandler::ProcessReadRequest(System::PacketBufferHandle && aPayloa #if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK ReturnErrorOnFailure(readRequestParser.CheckSchemaValidity()); #endif - err = readRequestParser.GetAttributeRequests(&attributePathListParser); if (err == CHIP_END_OF_TLV) { @@ -305,6 +305,16 @@ CHIP_ERROR ReadHandler::ProcessReadRequest(System::PacketBufferHandle && aPayloa else if (err == CHIP_NO_ERROR) { ReturnErrorOnFailure(ProcessAttributePathList(attributePathListParser)); + DataVersionFilterIBs::Parser dataVersionFilterListParser; + err = readRequestParser.GetDataVersionFilters(&dataVersionFilterListParser); + if (err == CHIP_END_OF_TLV) + { + err = CHIP_NO_ERROR; + } + else if (err == CHIP_NO_ERROR) + { + ReturnErrorOnFailure(ProcessDataVersionFilterList(dataVersionFilterListParser)); + } } ReturnErrorOnFailure(err); err = readRequestParser.GetEventRequests(&eventPathListParser); @@ -356,7 +366,6 @@ CHIP_ERROR ReadHandler::ProcessAttributePathList(AttributePathIBs::Parser & aAtt while (CHIP_NO_ERROR == (err = reader.Next())) { VerifyOrExit(TLV::AnonymousTag() == reader.GetTag(), err = CHIP_ERROR_INVALID_TLV_TAG); - VerifyOrExit(TLV::kTLVType_List == reader.GetType(), err = CHIP_ERROR_WRONG_TLV_TYPE); ClusterInfo clusterInfo; AttributePathIB::Parser path; err = path.Init(reader); @@ -421,6 +430,36 @@ CHIP_ERROR ReadHandler::ProcessAttributePathList(AttributePathIBs::Parser & aAtt return err; } +CHIP_ERROR ReadHandler::ProcessDataVersionFilterList(DataVersionFilterIBs::Parser & aDataVersionFilterListParser) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + TLV::TLVReader reader; + aDataVersionFilterListParser.GetReader(&reader); + + while (CHIP_NO_ERROR == (err = reader.Next())) + { + VerifyOrReturnError(TLV::AnonymousTag() == reader.GetTag(), CHIP_ERROR_INVALID_TLV_TAG); + ClusterInfo clusterInfo; + ClusterPathIB::Parser path; + DataVersionFilterIB::Parser filter; + ReturnErrorOnFailure(filter.Init(reader)); + DataVersion version = 0; + ReturnErrorOnFailure(filter.GetDataVersion(&version)); + clusterInfo.mDataVersion.SetValue(version); + ReturnErrorOnFailure(filter.GetPath(&path)); + ReturnErrorOnFailure(path.GetEndpoint(&(clusterInfo.mEndpointId))); + ReturnErrorOnFailure(path.GetCluster(&(clusterInfo.mClusterId))); + VerifyOrReturnError(clusterInfo.IsValidDataVersionFilter(), CHIP_ERROR_IM_MALFORMED_DATA_VERSION_FILTER_IB); + ReturnErrorOnFailure(InteractionModelEngine::GetInstance()->PushFront(mpDataVersionFilterList, clusterInfo)); + } + + if (CHIP_END_OF_TLV == err) + { + err = CHIP_NO_ERROR; + } + return err; +} + CHIP_ERROR ReadHandler::ProcessEventPaths(EventPathIBs::Parser & aEventPathsParser) { CHIP_ERROR err = CHIP_NO_ERROR; @@ -594,6 +633,16 @@ CHIP_ERROR ReadHandler::ProcessSubscribeRequest(System::PacketBufferHandle && aP else if (err == CHIP_NO_ERROR) { ReturnErrorOnFailure(ProcessAttributePathList(attributePathListParser)); + DataVersionFilterIBs::Parser dataVersionFilterListParser; + err = subscribeRequestParser.GetDataVersionFilters(&dataVersionFilterListParser); + if (err == CHIP_END_OF_TLV) + { + err = CHIP_NO_ERROR; + } + else if (err == CHIP_NO_ERROR) + { + ReturnErrorOnFailure(ProcessDataVersionFilterList(dataVersionFilterListParser)); + } } ReturnErrorOnFailure(err); diff --git a/src/app/ReadHandler.h b/src/app/ReadHandler.h index aae661fdd10ff2..7f64082af33f3c 100644 --- a/src/app/ReadHandler.h +++ b/src/app/ReadHandler.h @@ -132,8 +132,10 @@ class ReadHandler : public Messaging::ExchangeDelegate bool IsGeneratingReports() const { return mState == HandlerState::GeneratingReports; } bool IsAwaitingReportResponse() const { return mState == HandlerState::AwaitingReportResponse; } + CHIP_ERROR ProcessDataVersionFilterList(DataVersionFilterIBs::Parser & aDataVersionFilterListParser); ClusterInfo * GetAttributeClusterInfolist() { return mpAttributeClusterInfoList; } ClusterInfo * GetEventClusterInfolist() { return mpEventClusterInfoList; } + ClusterInfo * GetDataVersionFilterlist() const { return mpDataVersionFilterList; } EventNumber & GetEventMin() { return mEventMin; } PriorityLevel GetCurrentPriority() { return mCurrentPriority; } @@ -240,6 +242,7 @@ class ReadHandler : public Messaging::ExchangeDelegate HandlerState mState = HandlerState::Idle; ClusterInfo * mpAttributeClusterInfoList = nullptr; ClusterInfo * mpEventClusterInfoList = nullptr; + ClusterInfo * mpDataVersionFilterList = nullptr; PriorityLevel mCurrentPriority = PriorityLevel::Invalid; diff --git a/src/app/ReadPrepareParams.h b/src/app/ReadPrepareParams.h index 5fc760d5c3cd45..45caaa5161328d 100644 --- a/src/app/ReadPrepareParams.h +++ b/src/app/ReadPrepareParams.h @@ -19,6 +19,7 @@ #pragma once #include +#include #include #include #include @@ -45,6 +46,8 @@ struct ReadPrepareParams size_t mEventPathParamsListSize = 0; AttributePathParams * mpAttributePathParamsList = nullptr; size_t mAttributePathParamsListSize = 0; + DataVersionFilter * mpDataVersionFilterList = nullptr; + size_t mDataVersionFilterListSize = 0; EventNumber mEventNumber = 0; System::Clock::Timeout mTimeout = kImMessageTimeout; uint16_t mMinIntervalFloorSeconds = 0; @@ -62,6 +65,8 @@ struct ReadPrepareParams mEventPathParamsListSize = other.mEventPathParamsListSize; mpAttributePathParamsList = other.mpAttributePathParamsList; mAttributePathParamsListSize = other.mAttributePathParamsListSize; + mpDataVersionFilterList = other.mpDataVersionFilterList; + mDataVersionFilterListSize = other.mDataVersionFilterListSize; mEventNumber = other.mEventNumber; mMinIntervalFloorSeconds = other.mMinIntervalFloorSeconds; mMaxIntervalCeilingSeconds = other.mMaxIntervalCeilingSeconds; @@ -85,6 +90,8 @@ struct ReadPrepareParams mEventPathParamsListSize = other.mEventPathParamsListSize; mpAttributePathParamsList = other.mpAttributePathParamsList; mAttributePathParamsListSize = other.mAttributePathParamsListSize; + mpDataVersionFilterList = other.mpDataVersionFilterList; + mDataVersionFilterListSize = other.mDataVersionFilterListSize; mEventNumber = other.mEventNumber; mMinIntervalFloorSeconds = other.mMinIntervalFloorSeconds; mMaxIntervalCeilingSeconds = other.mMaxIntervalCeilingSeconds; diff --git a/src/app/reporting/Engine.cpp b/src/app/reporting/Engine.cpp index 244e896aef229f..5cdd5a8a77cd8b 100644 --- a/src/app/reporting/Engine.cpp +++ b/src/app/reporting/Engine.cpp @@ -47,6 +47,24 @@ void Engine::Shutdown() mGlobalDirtySet.ReleaseAll(); } +bool Engine::IsClusterDataVersionMatch(ClusterInfo * aDataVersionFilterList, const ConcreteReadAttributePath & aPath) +{ + bool existPathMatch = false; + bool existVersionMismatch = false; + for (auto filter = aDataVersionFilterList; filter != nullptr; filter = filter->mpNext) + { + if (aPath.mEndpointId == filter->mEndpointId && aPath.mClusterId == filter->mClusterId) + { + existPathMatch = true; + if (!IsClusterDataVersionEqual(filter->mEndpointId, filter->mClusterId, filter->mDataVersion.Value())) + { + existVersionMismatch = true; + } + } + } + return existPathMatch && !existVersionMismatch; +} + CHIP_ERROR Engine::RetrieveClusterData(const SubjectDescriptor & aSubjectDescriptor, bool aIsFabricFiltered, AttributeReportIBs::Builder & aAttributeReportIBs, const ConcreteReadAttributePath & aPath, @@ -111,6 +129,13 @@ CHIP_ERROR Engine::BuildSingleReportDataAttributeReportIBs(ReportDataMessage::Bu continue; } } + else + { + if (IsClusterDataVersionMatch(apReadHandler->GetDataVersionFilterlist(), readPath)) + { + continue; + } + } // If we are processing a read request, or the initial report of a subscription, just regard all paths as dirty paths. TLV::TLVWriter attributeBackup; diff --git a/src/app/reporting/Engine.h b/src/app/reporting/Engine.h index 1fa762b51236be..b8f430bd8c6663 100644 --- a/src/app/reporting/Engine.h +++ b/src/app/reporting/Engine.h @@ -118,6 +118,13 @@ class Engine const ConcreteReadAttributePath & aClusterInfo, AttributeValueEncoder::AttributeEncodeState * apEncoderState); + // If version match, it means don't send, if version mismatch, it means send. + // If client sends the same path with multiple data versions, client will get the data back per the spec, because at least one + // of those will fail to match. This function should return false if either nothing in the list matches the given + // endpoint+cluster in the path or there is an entry in the list that matches the endpoint+cluster in the path but does not + // match the current data version of that cluster. + bool IsClusterDataVersionMatch(ClusterInfo * aDataVersionFilterList, const ConcreteReadAttributePath & aPath); + /** * Check all active subscription, if the subscription has no paths that intersect with global dirty set, * it would clear dirty flag for that subscription diff --git a/src/app/tests/TestAttributeCache.cpp b/src/app/tests/TestAttributeCache.cpp index f5d6048a5e43b5..6df0a7b0a18a55 100644 --- a/src/app/tests/TestAttributeCache.cpp +++ b/src/app/tests/TestAttributeCache.cpp @@ -126,7 +126,6 @@ void DataSeriesGenerator::Generate() System::PacketBufferTLVReader reader; ReadClient::Callback * callback = mReadCallback; StatusIB status; - DataVersion version = kUndefinedDataVersion; callback->OnReportBegin(); uint8_t index = 0; @@ -198,13 +197,13 @@ void DataSeriesGenerator::Generate() writer.Finalize(&handle); reader.Init(std::move(handle)); NL_TEST_ASSERT(gSuite, reader.Next() == CHIP_NO_ERROR); - callback->OnAttributeData(path, version, &reader, status); + callback->OnAttributeData(path, &reader, status); } else { ChipLogProgress(DataManagement, "\t -- Generating Status"); status.mStatus = Protocols::InteractionModel::Status::Failure; - callback->OnAttributeData(path, version, nullptr, status); + callback->OnAttributeData(path, nullptr, status); } index++; diff --git a/src/app/tests/TestBufferedReadCallback.cpp b/src/app/tests/TestBufferedReadCallback.cpp index d53e36f38d8771..82f45c6ca7bad9 100644 --- a/src/app/tests/TestBufferedReadCallback.cpp +++ b/src/app/tests/TestBufferedReadCallback.cpp @@ -80,8 +80,7 @@ class DataSeriesValidator : public BufferedReadCallback::Callback void OnReportBegin() override; void OnReportEnd() override; - void OnAttributeData(const ConcreteDataAttributePath & aPath, DataVersion aVersion, TLV::TLVReader * apData, - const StatusIB & aStatus) override; + void OnAttributeData(const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, const StatusIB & aStatus) override; void OnDone() override {} std::vector mInstructionList; @@ -95,7 +94,7 @@ void DataSeriesValidator::OnReportBegin() void DataSeriesValidator::OnReportEnd() {} -void DataSeriesValidator::OnAttributeData(const ConcreteDataAttributePath & aPath, DataVersion aVersion, TLV::TLVReader * apData, +void DataSeriesValidator::OnAttributeData(const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, const StatusIB & aStatus) { uint32_t expectedListLength; @@ -302,7 +301,6 @@ void DataSeriesGenerator::Generate() ReadClient::Callback * callback = &mReadCallback; StatusIB status; bool hasData; - DataVersion version = kUndefinedDataVersion; callback->OnReportBegin(); @@ -403,7 +401,7 @@ void DataSeriesGenerator::Generate() path.mListOp = ConcreteDataAttributePath::ListOperation::ReplaceAll; status.mStatus = Protocols::InteractionModel::Status::Failure; hasData = false; - callback->OnAttributeData(path, version, &reader, status); + callback->OnAttributeData(path, &reader, status); break; } @@ -414,7 +412,7 @@ void DataSeriesGenerator::Generate() path.mListOp = ConcreteDataAttributePath::ListOperation::ReplaceAll; status.mStatus = Protocols::InteractionModel::Status::Failure; hasData = false; - callback->OnAttributeData(path, version, &reader, status); + callback->OnAttributeData(path, &reader, status); break; } @@ -432,7 +430,7 @@ void DataSeriesGenerator::Generate() writer.Finalize(&handle); reader.Init(std::move(handle)); NL_TEST_ASSERT(gSuite, reader.Next() == CHIP_NO_ERROR); - callback->OnAttributeData(path, version, &reader, status); + callback->OnAttributeData(path, &reader, status); } ChipLogProgress(DataManagement, "\t -- Generating C0..C512"); @@ -455,7 +453,7 @@ void DataSeriesGenerator::Generate() writer.Finalize(&handle); reader.Init(std::move(handle)); NL_TEST_ASSERT(gSuite, reader.Next() == CHIP_NO_ERROR); - callback->OnAttributeData(path, version, &reader, status); + callback->OnAttributeData(path, &reader, status); } break; @@ -475,7 +473,7 @@ void DataSeriesGenerator::Generate() writer.Finalize(&handle); reader.Init(std::move(handle)); NL_TEST_ASSERT(gSuite, reader.Next() == CHIP_NO_ERROR); - callback->OnAttributeData(path, version, &reader, status); + callback->OnAttributeData(path, &reader, status); } ChipLogProgress(DataManagement, "\t -- Generating D0..D512"); @@ -494,7 +492,7 @@ void DataSeriesGenerator::Generate() writer.Finalize(&handle); reader.Init(std::move(handle)); NL_TEST_ASSERT(gSuite, reader.Next() == CHIP_NO_ERROR); - callback->OnAttributeData(path, version, &reader, status); + callback->OnAttributeData(path, &reader, status); } break; @@ -509,7 +507,7 @@ void DataSeriesGenerator::Generate() writer.Finalize(&handle); reader.Init(std::move(handle)); NL_TEST_ASSERT(gSuite, reader.Next() == CHIP_NO_ERROR); - callback->OnAttributeData(path, version, &reader, status); + callback->OnAttributeData(path, &reader, status); } index++; diff --git a/src/app/tests/TestMessageDef.cpp b/src/app/tests/TestMessageDef.cpp index 37dead643545b4..95c3cd52f075e2 100644 --- a/src/app/tests/TestMessageDef.cpp +++ b/src/app/tests/TestMessageDef.cpp @@ -652,7 +652,7 @@ void ParseAttributeDataIB(nlTestSuite * apSuite, AttributeDataIB::Parser & aAttr { CHIP_ERROR err = CHIP_NO_ERROR; AttributePathIB::Parser attributePathParser; - chip::DataVersion version = chip::kUndefinedDataVersion; + chip::DataVersion version = 0; #if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK err = aAttributeDataIBParser.CheckSchemaValidity(); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); diff --git a/src/app/tests/TestReadInteraction.cpp b/src/app/tests/TestReadInteraction.cpp index 941f1801a28afb..f5d4130cd3680d 100644 --- a/src/app/tests/TestReadInteraction.cpp +++ b/src/app/tests/TestReadInteraction.cpp @@ -49,13 +49,16 @@ uint8_t gDebugEventBuffer[128]; uint8_t gInfoEventBuffer[128]; uint8_t gCritEventBuffer[128]; chip::app::CircularEventBuffer gCircularEventBuffer[3]; -chip::ClusterId kTestClusterId = 6; -chip::ClusterId kInvalidTestClusterId = 7; -chip::EndpointId kTestEndpointId = 1; -chip::EventId kTestEventIdDebug = 1; -chip::EventId kTestEventIdCritical = 2; -uint8_t kTestFieldValue1 = 1; -chip::TLV::Tag kTestEventTag = chip::TLV::ContextTag(1); +chip::ClusterId kTestClusterId = 6; +chip::ClusterId kInvalidTestClusterId = 7; +chip::EndpointId kTestEndpointId = 1; +chip::EventId kTestEventIdDebug = 1; +chip::EventId kTestEventIdCritical = 2; +uint8_t kTestFieldValue1 = 1; +chip::TLV::Tag kTestEventTag = chip::TLV::ContextTag(1); +chip::EndpointId kInvalidTestEndpointId = 3; +chip::DataVersion kTestDataVersion1 = 3; +chip::DataVersion kTestDataVersion2 = 5; class TestContext : public chip::Test::AppContext { @@ -143,8 +146,8 @@ class MockInteractionModelApp : public chip::app::ReadClient::Callback mGotEventResponse = true; } - void OnAttributeData(const chip::app::ConcreteDataAttributePath & aPath, chip::DataVersion aVersion, - chip::TLV::TLVReader * apData, const chip::app::StatusIB & status) override + void OnAttributeData(const chip::app::ConcreteDataAttributePath & aPath, chip::TLV::TLVReader * apData, + const chip::app::StatusIB & status) override { if (status.mStatus == chip::Protocols::InteractionModel::Status::Success) { @@ -168,6 +171,11 @@ class MockInteractionModelApp : public chip::app::ReadClient::Callback { delete[] aReadPrepareParams.mpEventPathParamsList; } + + if (aReadPrepareParams.mpDataVersionFilterList != nullptr) + { + delete[] aReadPrepareParams.mpDataVersionFilterList; + } } int mNumDataElementIndex = 0; @@ -230,6 +238,18 @@ CHIP_ERROR ReadSingleClusterData(const Access::SubjectDescriptor & aSubjectDescr return AttributeValueEncoder(aAttributeReports, 0, aPath, 0).Encode(kTestFieldValue1); } +bool IsClusterDataVersionEqual(EndpointId aEndpointId, ClusterId aClusterId, DataVersion aRequiredVersion) +{ + if (kTestDataVersion1 == aRequiredVersion) + { + return true; + } + else + { + return false; + } +} + class TestReadInteraction { public: @@ -243,6 +263,10 @@ class TestReadInteraction static void TestReadHandlerInvalidAttributePath(nlTestSuite * apSuite, void * apContext); static void TestProcessSubscribeRequest(nlTestSuite * apSuite, void * apContext); static void TestReadRoundtrip(nlTestSuite * apSuite, void * apContext); + static void TestReadRoundtripWithDataVersionFilter(nlTestSuite * apSuite, void * apContext); + static void TestReadRoundtripWithNoMatchPathDataVersionFilter(nlTestSuite * apSuite, void * apContext); + static void TestReadRoundtripWithMultiSamePathDifferentDataVersionFilter(nlTestSuite * apSuite, void * apContext); + static void TestReadRoundtripWithSameDifferentPathsDataVersionFilter(nlTestSuite * apSuite, void * apContext); static void TestReadWildcard(nlTestSuite * apSuite, void * apContext); static void TestReadChunking(nlTestSuite * apSuite, void * apContext); static void TestSetDirtyBetweenChunks(nlTestSuite * apSuite, void * apContext); @@ -728,6 +752,244 @@ void TestReadInteraction::TestReadRoundtrip(nlTestSuite * apSuite, void * apCont NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); } +void TestReadInteraction::TestReadRoundtripWithDataVersionFilter(nlTestSuite * apSuite, void * apContext) +{ + TestContext & ctx = *static_cast(apContext); + CHIP_ERROR err = CHIP_NO_ERROR; + + Messaging::ReliableMessageMgr * rm = ctx.GetExchangeManager().GetReliableMessageMgr(); + // Shouldn't have anything in the retransmit table when starting the test. + NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); + + GenerateEvents(apSuite, apContext); + + MockInteractionModelApp delegate; + auto * engine = chip::app::InteractionModelEngine::GetInstance(); + err = engine->Init(&ctx.GetExchangeManager()); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); + + chip::app::AttributePathParams attributePathParams[2]; + attributePathParams[0].mEndpointId = kTestEndpointId; + attributePathParams[0].mClusterId = kTestClusterId; + attributePathParams[0].mAttributeId = 1; + + attributePathParams[1].mEndpointId = kTestEndpointId; + attributePathParams[1].mClusterId = kTestClusterId; + attributePathParams[1].mAttributeId = 2; + attributePathParams[1].mListIndex = 1; + + chip::app::DataVersionFilter dataVersionFilters[1]; + dataVersionFilters[0].mEndpointId = kTestEndpointId; + dataVersionFilters[0].mClusterId = kTestClusterId; + dataVersionFilters[0].mDataVersion.SetValue(kTestDataVersion1); + + ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); + readPrepareParams.mpAttributePathParamsList = attributePathParams; + readPrepareParams.mAttributePathParamsListSize = 2; + readPrepareParams.mpDataVersionFilterList = dataVersionFilters; + readPrepareParams.mDataVersionFilterListSize = 1; + + { + app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), delegate, + chip::app::ReadClient::InteractionType::Read); + + err = readClient.SendRequest(readPrepareParams); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + InteractionModelEngine::GetInstance()->GetReportingEngine().Run(); + NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 0); + + delegate.mNumAttributeResponse = 0; + } + + NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadClients() == 0); + engine->Shutdown(); + NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); +} + +void TestReadInteraction::TestReadRoundtripWithNoMatchPathDataVersionFilter(nlTestSuite * apSuite, void * apContext) +{ + TestContext & ctx = *static_cast(apContext); + CHIP_ERROR err = CHIP_NO_ERROR; + + Messaging::ReliableMessageMgr * rm = ctx.GetExchangeManager().GetReliableMessageMgr(); + // Shouldn't have anything in the retransmit table when starting the test. + NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); + + GenerateEvents(apSuite, apContext); + + MockInteractionModelApp delegate; + auto * engine = chip::app::InteractionModelEngine::GetInstance(); + err = engine->Init(&ctx.GetExchangeManager()); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + chip::app::AttributePathParams attributePathParams[2]; + attributePathParams[0].mEndpointId = kTestEndpointId; + attributePathParams[0].mClusterId = kTestClusterId; + attributePathParams[0].mAttributeId = 1; + + attributePathParams[1].mEndpointId = kTestEndpointId; + attributePathParams[1].mClusterId = kTestClusterId; + attributePathParams[1].mAttributeId = 2; + attributePathParams[1].mListIndex = 1; + + chip::app::DataVersionFilter dataVersionFilters[2]; + dataVersionFilters[0].mEndpointId = kTestEndpointId; + dataVersionFilters[0].mClusterId = kInvalidTestClusterId; + dataVersionFilters[0].mDataVersion.SetValue(kTestDataVersion1); + + dataVersionFilters[1].mEndpointId = kInvalidTestEndpointId; + dataVersionFilters[1].mClusterId = kTestClusterId; + dataVersionFilters[1].mDataVersion.SetValue(kTestDataVersion2); + + ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); + readPrepareParams.mpAttributePathParamsList = attributePathParams; + readPrepareParams.mAttributePathParamsListSize = 2; + readPrepareParams.mpDataVersionFilterList = dataVersionFilters; + readPrepareParams.mDataVersionFilterListSize = 2; + + { + app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), delegate, + chip::app::ReadClient::InteractionType::Read); + + err = readClient.SendRequest(readPrepareParams); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + InteractionModelEngine::GetInstance()->GetReportingEngine().Run(); + NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 2); + NL_TEST_ASSERT(apSuite, !delegate.mReadError); + + delegate.mNumAttributeResponse = 0; + } + + NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadClients() == 0); + engine->Shutdown(); + NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); +} + +void TestReadInteraction::TestReadRoundtripWithMultiSamePathDifferentDataVersionFilter(nlTestSuite * apSuite, void * apContext) +{ + TestContext & ctx = *static_cast(apContext); + CHIP_ERROR err = CHIP_NO_ERROR; + + Messaging::ReliableMessageMgr * rm = ctx.GetExchangeManager().GetReliableMessageMgr(); + // Shouldn't have anything in the retransmit table when starting the test. + NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); + + GenerateEvents(apSuite, apContext); + + MockInteractionModelApp delegate; + auto * engine = chip::app::InteractionModelEngine::GetInstance(); + err = engine->Init(&ctx.GetExchangeManager()); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); + + chip::app::AttributePathParams attributePathParams[2]; + attributePathParams[0].mEndpointId = kTestEndpointId; + attributePathParams[0].mClusterId = kTestClusterId; + attributePathParams[0].mAttributeId = 1; + + attributePathParams[1].mEndpointId = kTestEndpointId; + attributePathParams[1].mClusterId = kTestClusterId; + attributePathParams[1].mAttributeId = 2; + attributePathParams[1].mListIndex = 1; + + chip::app::DataVersionFilter dataVersionFilters[2]; + dataVersionFilters[0].mEndpointId = kTestEndpointId; + dataVersionFilters[0].mClusterId = kTestClusterId; + dataVersionFilters[0].mDataVersion.SetValue(kTestDataVersion1); + + dataVersionFilters[1].mEndpointId = kTestEndpointId; + dataVersionFilters[1].mClusterId = kTestClusterId; + dataVersionFilters[1].mDataVersion.SetValue(kTestDataVersion2); + + ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); + readPrepareParams.mpAttributePathParamsList = attributePathParams; + readPrepareParams.mAttributePathParamsListSize = 2; + readPrepareParams.mpDataVersionFilterList = dataVersionFilters; + readPrepareParams.mDataVersionFilterListSize = 2; + + { + app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), delegate, + chip::app::ReadClient::InteractionType::Read); + + err = readClient.SendRequest(readPrepareParams); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + InteractionModelEngine::GetInstance()->GetReportingEngine().Run(); + NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 2); + NL_TEST_ASSERT(apSuite, !delegate.mReadError); + + delegate.mNumAttributeResponse = 0; + } + + NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadClients() == 0); + engine->Shutdown(); + NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); +} + +void TestReadInteraction::TestReadRoundtripWithSameDifferentPathsDataVersionFilter(nlTestSuite * apSuite, void * apContext) +{ + TestContext & ctx = *static_cast(apContext); + CHIP_ERROR err = CHIP_NO_ERROR; + + Messaging::ReliableMessageMgr * rm = ctx.GetExchangeManager().GetReliableMessageMgr(); + // Shouldn't have anything in the retransmit table when starting the test. + NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); + + GenerateEvents(apSuite, apContext); + + MockInteractionModelApp delegate; + auto * engine = chip::app::InteractionModelEngine::GetInstance(); + err = engine->Init(&ctx.GetExchangeManager()); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); + + chip::app::AttributePathParams attributePathParams[2]; + attributePathParams[0].mEndpointId = kTestEndpointId; + attributePathParams[0].mClusterId = kTestClusterId; + attributePathParams[0].mAttributeId = 1; + + attributePathParams[1].mEndpointId = kTestEndpointId; + attributePathParams[1].mClusterId = kTestClusterId; + attributePathParams[1].mAttributeId = 2; + attributePathParams[1].mListIndex = 1; + + chip::app::DataVersionFilter dataVersionFilters[2]; + dataVersionFilters[0].mEndpointId = kTestEndpointId; + dataVersionFilters[0].mClusterId = kTestClusterId; + dataVersionFilters[0].mDataVersion.SetValue(kTestDataVersion1); + + dataVersionFilters[1].mEndpointId = kInvalidTestEndpointId; + dataVersionFilters[1].mClusterId = kTestClusterId; + dataVersionFilters[1].mDataVersion.SetValue(kTestDataVersion2); + + ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); + readPrepareParams.mpAttributePathParamsList = attributePathParams; + readPrepareParams.mAttributePathParamsListSize = 2; + readPrepareParams.mpDataVersionFilterList = dataVersionFilters; + readPrepareParams.mDataVersionFilterListSize = 2; + + { + app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), delegate, + chip::app::ReadClient::InteractionType::Read); + + err = readClient.SendRequest(readPrepareParams); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + InteractionModelEngine::GetInstance()->GetReportingEngine().Run(); + NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 0); + NL_TEST_ASSERT(apSuite, !delegate.mReadError); + + delegate.mNumAttributeResponse = 0; + } + + NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadClients() == 0); + engine->Shutdown(); + NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); +} + void TestReadInteraction::TestReadWildcard(nlTestSuite * apSuite, void * apContext) { TestContext & ctx = *static_cast(apContext); @@ -1497,6 +1759,10 @@ namespace { const nlTest sTests[] = { NL_TEST_DEF("TestReadRoundtrip", chip::app::TestReadInteraction::TestReadRoundtrip), + NL_TEST_DEF("TestReadRoundtripWithDataVersionFilter", chip::app::TestReadInteraction::TestReadRoundtripWithDataVersionFilter), + NL_TEST_DEF("TestReadRoundtripWithNoMatchPathDataVersionFilter", chip::app::TestReadInteraction::TestReadRoundtripWithNoMatchPathDataVersionFilter), + NL_TEST_DEF("TestReadRoundtripWithMultiSamePathDifferentDataVersionFilter", chip::app::TestReadInteraction::TestReadRoundtripWithMultiSamePathDifferentDataVersionFilter), + NL_TEST_DEF("TestReadRoundtripWithSameDifferentPathsDataVersionFilter", chip::app::TestReadInteraction::TestReadRoundtripWithSameDifferentPathsDataVersionFilter), NL_TEST_DEF("TestReadWildcard", chip::app::TestReadInteraction::TestReadWildcard), NL_TEST_DEF("TestReadChunking", chip::app::TestReadInteraction::TestReadChunking), NL_TEST_DEF("TestSetDirtyBetweenChunks", chip::app::TestReadInteraction::TestSetDirtyBetweenChunks), diff --git a/src/app/tests/integration/chip_im_initiator.cpp b/src/app/tests/integration/chip_im_initiator.cpp index 56c0cadd5caca4..616470055caccd 100644 --- a/src/app/tests/integration/chip_im_initiator.cpp +++ b/src/app/tests/integration/chip_im_initiator.cpp @@ -145,8 +145,8 @@ class MockInteractionModelApp : public ::chip::app::CommandSender::Callback, } } } - void OnAttributeData(const chip::app::ConcreteDataAttributePath & aPath, chip::DataVersion aVersion, - chip::TLV::TLVReader * aData, const chip::app::StatusIB & status) override + void OnAttributeData(const chip::app::ConcreteDataAttributePath & aPath, chip::TLV::TLVReader * aData, + const chip::app::StatusIB & status) override {} void OnError(CHIP_ERROR aError) override { printf("ReadError with err %" CHIP_ERROR_FORMAT, aError.Format()); } @@ -667,6 +667,11 @@ CHIP_ERROR WriteSingleClusterData(const Access::SubjectDescriptor & aSubjectDesc } return CHIP_NO_ERROR; } + +bool IsClusterDataVersionEqual(EndpointId aEndpointId, ClusterId aClusterId, DataVersion aRequiredVersion) +{ + return true; +} } // namespace app } // namespace chip diff --git a/src/app/tests/integration/chip_im_responder.cpp b/src/app/tests/integration/chip_im_responder.cpp index 481dbae7c46bc1..2dbce00ef57cb3 100644 --- a/src/app/tests/integration/chip_im_responder.cpp +++ b/src/app/tests/integration/chip_im_responder.cpp @@ -129,6 +129,11 @@ CHIP_ERROR WriteSingleClusterData(const Access::SubjectDescriptor & aSubjectDesc err = apWriteHandler->AddStatus(attributePath, Protocols::InteractionModel::Status::Success); return err; } + +bool IsClusterDataVersionEqual(EndpointId aEndpointId, ClusterId aClusterId, DataVersion aRequiredVersion) +{ + return true; +} } // namespace app } // namespace chip diff --git a/src/app/util/ember-compatibility-functions.cpp b/src/app/util/ember-compatibility-functions.cpp index f7bc0ab2872bdd..a62e03cadcfe87 100644 --- a/src/app/util/ember-compatibility-functions.cpp +++ b/src/app/util/ember-compatibility-functions.cpp @@ -403,7 +403,7 @@ CHIP_ERROR ReadViaAccessInterface(FabricIndex aAccessingFabricIndex, bool aIsFab { AttributeValueEncoder::AttributeEncodeState state = (aEncoderState == nullptr ? AttributeValueEncoder::AttributeEncodeState() : *aEncoderState); - DataVersion version = kUndefinedDataVersion; + DataVersion version = 0; ReturnErrorOnFailure(ReadClusterDataVersion(aPath.mEndpointId, aPath.mClusterId, version)); AttributeValueEncoder valueEncoder(aAttributeReports, aAccessingFabricIndex, aPath, version, aIsFabricFiltered, state); CHIP_ERROR err = aAccessInterface->Read(aPath, valueEncoder); @@ -536,7 +536,7 @@ CHIP_ERROR ReadSingleClusterData(const SubjectDescriptor & aSubjectDescriptor, b AttributeDataIB::Builder & attributeDataIBBuilder = attributeReport.CreateAttributeData(); ReturnErrorOnFailure(attributeDataIBBuilder.GetError()); - DataVersion version = kUndefinedDataVersion; + DataVersion version = 0; ReturnErrorOnFailure(ReadClusterDataVersion(aPath.mEndpointId, aPath.mClusterId, version)); attributeDataIBBuilder.DataVersion(version); ReturnErrorOnFailure(attributeDataIBBuilder.GetError()); @@ -980,6 +980,21 @@ CHIP_ERROR WriteSingleClusterData(const SubjectDescriptor & aSubjectDescriptor, return apWriteHandler->AddStatus(aPath, status); } +bool IsClusterDataVersionEqual(EndpointId aEndpointId, ClusterId aClusterId, DataVersion aRequiredVersion) +{ + DataVersion * version = emberAfDataVersionStorage(aEndpointId, aClusterId); + if (version == nullptr) + { + ChipLogError(DataManagement, "Endpoint %" PRIx16 ", Cluster " ChipLogFormatMEI " not found in IsClusterDataVersionEqual!", + aEndpointId, ChipLogValueMEI(aClusterId)); + return false; + } + else + { + return (*(version)) == aRequiredVersion; + } +} + } // namespace app } // namespace chip diff --git a/src/controller/CHIPCluster.h b/src/controller/CHIPCluster.h index 47ab6ce4521bde..942f2870b9b121 100644 --- a/src/controller/CHIPCluster.h +++ b/src/controller/CHIPCluster.h @@ -187,15 +187,16 @@ class DLL_EXPORT ClusterBase */ template CHIP_ERROR ReadAttribute(void * context, ReadResponseSuccessCallback successCb, - ReadResponseFailureCallback failureCb) + ReadResponseFailureCallback failureCb, const Optional & aDataVersion = NullOptional) { return ReadAttribute( - context, AttributeInfo::GetClusterId(), AttributeInfo::GetAttributeId(), successCb, failureCb); + context, AttributeInfo::GetClusterId(), AttributeInfo::GetAttributeId(), successCb, failureCb, aDataVersion); } template CHIP_ERROR ReadAttribute(void * context, ClusterId clusterId, AttributeId attributeId, - ReadResponseSuccessCallback successCb, ReadResponseFailureCallback failureCb) + ReadResponseSuccessCallback successCb, ReadResponseFailureCallback failureCb, + const Optional & aDataVersion = NullOptional) { VerifyOrReturnError(mDevice != nullptr, CHIP_ERROR_INCORRECT_STATE); @@ -214,7 +215,8 @@ class DLL_EXPORT ClusterBase }; return Controller::ReadAttribute(mDevice->GetExchangeManager(), mDevice->GetSecureSession().Value(), - mEndpoint, clusterId, attributeId, onSuccessCb, onFailureCb); + mEndpoint, clusterId, attributeId, onSuccessCb, onFailureCb, true, + aDataVersion); } /** @@ -225,18 +227,20 @@ class DLL_EXPORT ClusterBase CHIP_ERROR SubscribeAttribute(void * context, ReadResponseSuccessCallback reportCb, ReadResponseFailureCallback failureCb, uint16_t minIntervalFloorSeconds, uint16_t maxIntervalCeilingSeconds, - SubscriptionEstablishedCallback subscriptionEstablishedCb = nullptr) + SubscriptionEstablishedCallback subscriptionEstablishedCb = nullptr, + const Optional & aDataVersion = NullOptional) { return SubscribeAttribute( context, AttributeInfo::GetClusterId(), AttributeInfo::GetAttributeId(), reportCb, failureCb, minIntervalFloorSeconds, - maxIntervalCeilingSeconds, subscriptionEstablishedCb); + maxIntervalCeilingSeconds, subscriptionEstablishedCb, aDataVersion); } template CHIP_ERROR SubscribeAttribute(void * context, ClusterId clusterId, AttributeId attributeId, ReadResponseSuccessCallback reportCb, ReadResponseFailureCallback failureCb, uint16_t minIntervalFloorSeconds, uint16_t maxIntervalCeilingSeconds, - SubscriptionEstablishedCallback subscriptionEstablishedCb = nullptr) + SubscriptionEstablishedCallback subscriptionEstablishedCb = nullptr, + const Optional & aDataVersion = NullOptional) { VerifyOrReturnError(mDevice != nullptr, CHIP_ERROR_INCORRECT_STATE); @@ -261,9 +265,10 @@ class DLL_EXPORT ClusterBase } }; - return Controller::SubscribeAttribute( - mDevice->GetExchangeManager(), mDevice->GetSecureSession().Value(), mEndpoint, clusterId, attributeId, onReportCb, - onFailureCb, minIntervalFloorSeconds, maxIntervalCeilingSeconds, onSubscriptionEstablishedCb); + return Controller::SubscribeAttribute(mDevice->GetExchangeManager(), mDevice->GetSecureSession().Value(), + mEndpoint, clusterId, attributeId, onReportCb, onFailureCb, + minIntervalFloorSeconds, maxIntervalCeilingSeconds, + onSubscriptionEstablishedCb, true, false, aDataVersion); } /** diff --git a/src/controller/CHIPDeviceController.cpp b/src/controller/CHIPDeviceController.cpp index 32422268c55df2..47add2e1a4aa16 100644 --- a/src/controller/CHIPDeviceController.cpp +++ b/src/controller/CHIPDeviceController.cpp @@ -483,7 +483,6 @@ CHIP_ERROR DeviceController::OpenCommissioningWindowWithCallback(NodeId deviceId constexpr EndpointId kBasicClusterEndpoint = 0; chip::Controller::BasicCluster cluster; cluster.Associate(device, kBasicClusterEndpoint); - return cluster.ReadAttribute(this, OnVIDReadResponse, OnVIDPIDReadFailureResponse); } diff --git a/src/controller/ReadInteraction.h b/src/controller/ReadInteraction.h index 57101cf2efa353..9296f02d7f850a 100644 --- a/src/controller/ReadInteraction.h +++ b/src/controller/ReadInteraction.h @@ -43,7 +43,8 @@ struct ReportAttributeParams : public app::ReadPrepareParams template CHIP_ERROR ReportAttribute(Messaging::ExchangeManager * exchangeMgr, EndpointId endpointId, ClusterId clusterId, - AttributeId attributeId, ReportAttributeParams && readParams) + AttributeId attributeId, ReportAttributeParams && readParams, + const Optional & aDataVersion = NullOptional) { app::InteractionModelEngine * engine = app::InteractionModelEngine::GetInstance(); CHIP_ERROR err = CHIP_NO_ERROR; @@ -52,7 +53,14 @@ CHIP_ERROR ReportAttribute(Messaging::ExchangeManager * exchangeMgr, EndpointId VerifyOrReturnError(readPaths != nullptr, CHIP_ERROR_NO_MEMORY); readParams.mpAttributePathParamsList = readPaths.get(); readParams.mAttributePathParamsListSize = 1; - + chip::Platform::UniquePtr dataVersionFilters; + if (aDataVersion.HasValue()) + { + dataVersionFilters = Platform::MakeUnique(endpointId, clusterId, aDataVersion.Value()); + VerifyOrReturnError(dataVersionFilters != nullptr, CHIP_ERROR_NO_MEMORY); + readParams.mpDataVersionFilterList = dataVersionFilters.get(); + readParams.mDataVersionFilterListSize = 1; + } auto onDone = [](TypedReadAttributeCallback * callback) { chip::Platform::Delete(callback); }; auto callback = chip::Platform::MakeUnique>( @@ -66,6 +74,8 @@ CHIP_ERROR ReportAttribute(Messaging::ExchangeManager * exchangeMgr, EndpointId if (readClient->IsSubscriptionType()) { readPaths.release(); + dataVersionFilters.release(); + err = readClient->SendAutoResubscribeRequest(std::move(readParams)); ReturnErrorOnFailure(err); } @@ -97,13 +107,13 @@ CHIP_ERROR ReadAttribute(Messaging::ExchangeManager * exchangeMgr, const Session ClusterId clusterId, AttributeId attributeId, typename TypedReadAttributeCallback::OnSuccessCallbackType onSuccessCb, typename TypedReadAttributeCallback::OnErrorCallbackType onErrorCb, - bool fabricFiltered = true) + bool fabricFiltered = true, const Optional & aDataVersion = NullOptional) { detail::ReportAttributeParams params(sessionHandle); params.mOnReportCb = onSuccessCb; params.mOnErrorCb = onErrorCb; params.mIsFabricFiltered = fabricFiltered; - return detail::ReportAttribute(exchangeMgr, endpointId, clusterId, attributeId, std::move(params)); + return detail::ReportAttribute(exchangeMgr, endpointId, clusterId, attributeId, std::move(params), aDataVersion); } /* @@ -120,23 +130,23 @@ CHIP_ERROR ReadAttribute(Messaging::ExchangeManager * exchangeMgr, const SessionHandle & sessionHandle, EndpointId endpointId, typename TypedReadAttributeCallback::OnSuccessCallbackType onSuccessCb, typename TypedReadAttributeCallback::OnErrorCallbackType onErrorCb, - bool fabricFiltered = true) + bool fabricFiltered = true, const Optional & aDataVersion = NullOptional) { return ReadAttribute( exchangeMgr, sessionHandle, endpointId, AttributeTypeInfo::GetClusterId(), AttributeTypeInfo::GetAttributeId(), onSuccessCb, - onErrorCb, fabricFiltered); + onErrorCb, fabricFiltered, aDataVersion); } // Helper for SubscribeAttribute to reduce the amount of code generated. template -CHIP_ERROR SubscribeAttribute(Messaging::ExchangeManager * exchangeMgr, const SessionHandle & sessionHandle, EndpointId endpointId, - ClusterId clusterId, AttributeId attributeId, - typename TypedReadAttributeCallback::OnSuccessCallbackType onReportCb, - typename TypedReadAttributeCallback::OnErrorCallbackType onErrorCb, - uint16_t minIntervalFloorSeconds, uint16_t maxIntervalCeilingSeconds, - typename TypedReadAttributeCallback::OnSubscriptionEstablishedCallbackType - onSubscriptionEstablishedCb = nullptr, - bool fabricFiltered = true, bool keepPreviousSubscriptions = false) +CHIP_ERROR SubscribeAttribute( + Messaging::ExchangeManager * exchangeMgr, const SessionHandle & sessionHandle, EndpointId endpointId, ClusterId clusterId, + AttributeId attributeId, typename TypedReadAttributeCallback::OnSuccessCallbackType onReportCb, + typename TypedReadAttributeCallback::OnErrorCallbackType onErrorCb, uint16_t minIntervalFloorSeconds, + uint16_t maxIntervalCeilingSeconds, + typename TypedReadAttributeCallback::OnSubscriptionEstablishedCallbackType onSubscriptionEstablishedCb = + nullptr, + bool fabricFiltered = true, bool keepPreviousSubscriptions = false, const Optional & aDataVersion = NullOptional) { detail::ReportAttributeParams params(sessionHandle); params.mOnReportCb = onReportCb; @@ -147,7 +157,7 @@ CHIP_ERROR SubscribeAttribute(Messaging::ExchangeManager * exchangeMgr, const Se params.mKeepSubscriptions = keepPreviousSubscriptions; params.mReportType = app::ReadClient::InteractionType::Subscribe; params.mIsFabricFiltered = fabricFiltered; - return detail::ReportAttribute(exchangeMgr, endpointId, clusterId, attributeId, std::move(params)); + return detail::ReportAttribute(exchangeMgr, endpointId, clusterId, attributeId, std::move(params), aDataVersion); } /* @@ -163,12 +173,12 @@ CHIP_ERROR SubscribeAttribute( uint16_t aMinIntervalFloorSeconds, uint16_t aMaxIntervalCeilingSeconds, typename TypedReadAttributeCallback::OnSubscriptionEstablishedCallbackType onSubscriptionEstablishedCb = nullptr, - bool fabricFiltered = true, bool keepPreviousSubscriptions = false) + bool fabricFiltered = true, bool keepPreviousSubscriptions = false, const Optional & aDataVersion = NullOptional) { return SubscribeAttribute( exchangeMgr, sessionHandle, endpointId, AttributeTypeInfo::GetClusterId(), AttributeTypeInfo::GetAttributeId(), onReportCb, onErrorCb, aMinIntervalFloorSeconds, aMaxIntervalCeilingSeconds, onSubscriptionEstablishedCb, fabricFiltered, - keepPreviousSubscriptions); + keepPreviousSubscriptions, aDataVersion); } namespace detail { diff --git a/src/controller/TypedReadCallback.h b/src/controller/TypedReadCallback.h index a842f6def9dbe6..12b420091e93f2 100644 --- a/src/controller/TypedReadCallback.h +++ b/src/controller/TypedReadCallback.h @@ -49,9 +49,9 @@ class TypedReadAttributeCallback final : public app::ReadClient::Callback { public: using OnSuccessCallbackType = - std::function; - using OnErrorCallbackType = std::function; - using OnDoneCallbackType = std::function; + std::function; + using OnErrorCallbackType = std::function; + using OnDoneCallbackType = std::function; using OnSubscriptionEstablishedCallbackType = std::function; TypedReadAttributeCallback(ClusterId aClusterId, AttributeId aAttributeId, OnSuccessCallbackType aOnSuccess, @@ -67,7 +67,7 @@ class TypedReadAttributeCallback final : public app::ReadClient::Callback void AdoptReadClient(Platform::UniquePtr aReadClient) { mReadClient = std::move(aReadClient); } private: - void OnAttributeData(const app::ConcreteDataAttributePath & aPath, DataVersion aVersion, TLV::TLVReader * apData, + void OnAttributeData(const app::ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, const app::StatusIB & aStatus) override { CHIP_ERROR err = CHIP_NO_ERROR; @@ -108,13 +108,12 @@ class TypedReadAttributeCallback final : public app::ReadClient::Callback void OnDeallocatePaths(chip::app::ReadPrepareParams && aReadPrepareParams) override { - if (aReadPrepareParams.mpAttributePathParamsList != nullptr) - { - for (size_t i = 0; i < aReadPrepareParams.mAttributePathParamsListSize; i++) - { - chip::Platform::Delete(&aReadPrepareParams.mpAttributePathParamsList[i]); - } - } + VerifyOrDie(aReadPrepareParams.mAttributePathParamsListSize == 1 && + aReadPrepareParams.mpAttributePathParamsList != nullptr); + chip::Platform::Delete(aReadPrepareParams.mpAttributePathParamsList); + + VerifyOrDie(aReadPrepareParams.mDataVersionFilterListSize == 1 && aReadPrepareParams.mpDataVersionFilterList != nullptr); + chip::Platform::Delete(aReadPrepareParams.mpDataVersionFilterList); } ClusterId mClusterId; @@ -172,13 +171,8 @@ class TypedReadEventCallback final : public app::ReadClient::Callback void OnDeallocatePaths(chip::app::ReadPrepareParams && aReadPrepareParams) override { - if (aReadPrepareParams.mpEventPathParamsList != nullptr) - { - for (size_t i = 0; i < aReadPrepareParams.mEventPathParamsListSize; i++) - { - chip::Platform::Delete(&aReadPrepareParams.mpEventPathParamsList[i]); - } - } + VerifyOrDie(aReadPrepareParams.mEventPathParamsListSize == 1 && aReadPrepareParams.mpEventPathParamsList != nullptr); + chip::Platform::Delete(aReadPrepareParams.mpEventPathParamsList); } void OnSubscriptionEstablished(uint64_t aSubscriptionId) override diff --git a/src/controller/java/AndroidCallbacks.cpp b/src/controller/java/AndroidCallbacks.cpp index cf31fae38c02d6..cb73e9b027c480 100644 --- a/src/controller/java/AndroidCallbacks.cpp +++ b/src/controller/java/AndroidCallbacks.cpp @@ -176,7 +176,7 @@ void ReportCallback::OnReportEnd() env->CallVoidMethod(mReportCallbackRef, onReportMethod, map); } -void ReportCallback::OnAttributeData(const app::ConcreteDataAttributePath & aPath, DataVersion aVersion, TLV::TLVReader * apData, +void ReportCallback::OnAttributeData(const app::ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, const app::StatusIB & aStatus) { CHIP_ERROR err = CHIP_NO_ERROR; diff --git a/src/controller/java/AndroidCallbacks.h b/src/controller/java/AndroidCallbacks.h index f8ebed8773dd67..3935afb8cb72bc 100644 --- a/src/controller/java/AndroidCallbacks.h +++ b/src/controller/java/AndroidCallbacks.h @@ -52,7 +52,7 @@ struct ReportCallback : public app::ReadClient::Callback void OnReportEnd() override; - void OnAttributeData(const app::ConcreteDataAttributePath & aPath, DataVersion aVersion, TLV::TLVReader * apData, + void OnAttributeData(const app::ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, const app::StatusIB & aStatus) override; void OnError(CHIP_ERROR aError) override; diff --git a/src/controller/python/chip/ChipDeviceCtrl.py b/src/controller/python/chip/ChipDeviceCtrl.py index 517debac1c01b4..152b76dcbae3d8 100644 --- a/src/controller/python/chip/ChipDeviceCtrl.py +++ b/src/controller/python/chip/ChipDeviceCtrl.py @@ -581,7 +581,7 @@ async def ReadAttribute(self, nodeid: int, attributes: typing.List[typing.Union[ typing.Tuple[int, typing.Type[ClusterObjects.Cluster]], # Concrete path typing.Tuple[int, typing.Type[ClusterObjects.ClusterAttributeDescriptor]] - ]], returnClusterObject: bool = False, reportInterval: typing.Tuple[int, int] = None, fabricFiltered: bool = True): + ]], dataVersionFilters: typing.List[typing.Tuple[int, typing.Type[ClusterObjects.Cluster], int]] = None, returnClusterObject: bool = False, reportInterval: typing.Tuple[int, int] = None, fabricFiltered: bool = True): ''' Read a list of attributes from a target node @@ -614,6 +614,7 @@ async def ReadAttribute(self, nodeid: int, attributes: typing.List[typing.Union[ device = self.GetConnectedDeviceSync(nodeid) attrs = [] + filters = [] for v in attributes: endpoint = None cluster = None @@ -641,8 +642,24 @@ async def ReadAttribute(self, nodeid: int, attributes: typing.List[typing.Union[ raise ValueError("Unsupported Attribute Path") attrs.append(ClusterAttribute.AttributePath( EndpointId=endpoint, Cluster=cluster, Attribute=attribute)) + if dataVersionFilters != None: + for v in dataVersionFilters: + endpoint = None + cluster = None + + # endpoint + (cluster) attribute / endpoint + cluster + endpoint = v[0] + if issubclass(v[1], ClusterObjects.Cluster): + cluster = v[1] + else: + raise ValueError("Unsupported Cluster Path") + dataVersion = v[2] + filters.append(ClusterAttribute.DataVersionFilter( + EndpointId=endpoint, Cluster=cluster, DataVersion=dataVersion)) + else: + filters = None res = self._ChipStack.Call( - lambda: ClusterAttribute.ReadAttributes(future, eventLoop, device, self, attrs, returnClusterObject, ClusterAttribute.SubscriptionParameters(reportInterval[0], reportInterval[1]) if reportInterval else None, fabricFiltered=fabricFiltered)) + lambda: ClusterAttribute.ReadAttributes(future, eventLoop, device, self, attrs, filters, returnClusterObject, ClusterAttribute.SubscriptionParameters(reportInterval[0], reportInterval[1]) if reportInterval else None, fabricFiltered=fabricFiltered)) if res != 0: raise self._ChipStack.ErrorToException(res) return await future @@ -755,7 +772,7 @@ def ZCLReadAttribute(self, cluster, attribute, nodeid, endpoint, groupid, blocki nodeid, [(endpoint, attributeType)])) path = ClusterAttribute.AttributePath( EndpointId=endpoint, Attribute=attributeType) - return im.AttributeReadResult(path=im.AttributePath(nodeId=nodeid, endpointId=path.EndpointId, clusterId=path.ClusterId, attributeId=path.AttributeId), status=0, value=result[endpoint][clusterType][attributeType]) + return im.AttributeReadResult(path=im.AttributePath(nodeId=nodeid, endpointId=path.EndpointId, clusterId=path.ClusterId, attributeId=path.AttributeId), status=0, value=result[0][endpoint][clusterType][attributeType]) def ZCLWriteAttribute(self, cluster: str, attribute: str, nodeid, endpoint, groupid, value, blocking=True): req = None @@ -775,7 +792,7 @@ def ZCLSubscribeAttribute(self, cluster, attribute, nodeid, endpoint, minInterva req = eval(f"GeneratedObjects.{cluster}.Attributes.{attribute}") except: raise UnknownAttribute(cluster, attribute) - return asyncio.run(self.ReadAttribute(nodeid, [(endpoint, req)], False, reportInterval=(minInterval, maxInterval))) + return asyncio.run(self.ReadAttribute(nodeid, [(endpoint, req)], None, False, reportInterval=(minInterval, maxInterval))) def ZCLCommandList(self): self.CheckIsActive() diff --git a/src/controller/python/chip/clusters/Attribute.py b/src/controller/python/chip/clusters/Attribute.py index 9620f1ba6af532..f7f0aceecbbf10 100644 --- a/src/controller/python/chip/clusters/Attribute.py +++ b/src/controller/python/chip/clusters/Attribute.py @@ -83,6 +83,31 @@ def __hash__(self): return str(self).__hash__() +@dataclass +class DataVersionFilter: + EndpointId: int = None + ClusterId: int = None + DataVersion: int = None + + def __init__(self, EndpointId: int = None, Cluster=None, ClusterId=None, DataVersion=None): + self.EndpointId = EndpointId + if Cluster is not None: + # Wildcard read for a specific cluster + if (ClusterId is not None): + raise Warning( + "Attribute, ClusterId and AttributeId is ignored when Cluster is specified") + self.ClusterId = Cluster.id + return + self.ClusterId = ClusterId + self.DataVersion = DataVersion + + def __str__(self) -> str: + return f"{self.EndpointId}/{self.ClusterId}/{self.DataVersion}" + + def __hash__(self): + return str(self).__hash__() + + @dataclass class TypedAttributePath: ''' Encapsulates an attribute path that has strongly typed references to cluster and attribute @@ -317,29 +342,43 @@ class AttributeCache: default_factory=lambda: {}) attributeCache: Dict[int, List[Cluster]] = field( default_factory=lambda: {}) + versionList: Dict[int, Dict[int, Dict[int, int]]] = field( + default_factory=lambda: {}) - def UpdateTLV(self, path: AttributePath, dataVersion: int, data: Union[bytes, ValueDecodeFailure]): + def UpdateTLV(self, path: AttributePath, dataVersion: int, data: Union[bytes, ValueDecodeFailure]): ''' Store data in TLV since that makes it easiest to eventually convert to either the cluster or attribute view representations (see below in UpdateCachedData). ''' if (path.EndpointId not in self.attributeTLVCache): self.attributeTLVCache[path.EndpointId] = {} + if (path.EndpointId not in self.versionList): + self.versionList[path.EndpointId] = {} + endpointCache = self.attributeTLVCache[path.EndpointId] + endpoint = self.versionList[path.EndpointId] if (path.ClusterId not in endpointCache): endpointCache[path.ClusterId] = {} + if (path.ClusterId not in endpoint): + endpoint[path.ClusterId] = {} + clusterCache = endpointCache[path.ClusterId] + cluster = endpoint[path.ClusterId] if (path.AttributeId not in clusterCache): clusterCache[path.AttributeId] = None + if (path.AttributeId not in cluster): + cluster[path.AttributeId] = None + clusterCache[path.AttributeId] = data + cluster[path.AttributeId] = dataVersion def UpdateCachedData(self): ''' This converts the raw TLV data into a cluster object format. Two formats are available: - 1. Attribute-View (returnClusterObject=False): Dict[EndpointId, Dict[ClusterObjectType, Dict[AttributeObjectType, AttributeValue]]] + 1. Attribute-View (returnClusterObject=False): Dict[EndpointId, Dict[ClusterObjectType, Dict[AttributeObjectType, Dict[AttributeValue, DataVersion]]]] 2. Cluster-View (returnClusterObject=True): Dict[EndpointId, Dict[ClusterObjectType, ClusterValue]] In the attribute-view, only attributes that match the original path criteria are present in the dictionary. The attribute values can @@ -639,12 +678,14 @@ def _handleDone(self): if (self._transactionType == TransactionType.READ_EVENTS): self._future.set_result(self._events) else: - self._future.set_result(self._cache.attributeCache) + self._future.set_result( + (self._cache.attributeCache, self._cache.versionList)) def handleDone(self): self._event_loop.call_soon_threadsafe(self._handleDone) def handleReportBegin(self): + self._cache.versionList.clear() pass def handleReportEnd(self): @@ -804,11 +845,12 @@ def WriteAttributes(future: Future, eventLoop, device, attributes: List[Attribut ) -def ReadAttributes(future: Future, eventLoop, device, devCtrl, attributes: List[AttributePath], returnClusterObject: bool = True, subscriptionParameters: SubscriptionParameters = None, fabricFiltered: bool = True) -> int: +def ReadAttributes(future: Future, eventLoop, device, devCtrl, attributes: List[AttributePath], dataVersionFilters: List[DataVersionFilter] = None, returnClusterObject: bool = True, subscriptionParameters: SubscriptionParameters = None, fabricFiltered: bool = True) -> int: handle = chip.native.GetLibraryHandle() transaction = AsyncReadTransaction( future, eventLoop, devCtrl, TransactionType.READ_ATTRIBUTES, returnClusterObject) + dataVersionFilterLength = 0 readargs = [] for attr in attributes: path = chip.interaction_model.AttributePathIBstruct.parse( @@ -822,6 +864,30 @@ def ReadAttributes(future: Future, eventLoop, device, devCtrl, attributes: List[ path = chip.interaction_model.AttributePathIBstruct.build(path) readargs.append(ctypes.c_char_p(path)) + if dataVersionFilters is not None: + dataVersionFilterLength = len(dataVersionFilters) + for f in dataVersionFilters: + filter = chip.interaction_model.DataVersionFilterIBstruct.parse( + b'\xff' * chip.interaction_model.DataVersionFilterIBstruct.sizeof()) + if f.EndpointId is not None: + filter.EndpointId = f.EndpointId + else: + raise ValueError( + f"DataVersionFilter must provide EndpointId.") + if f.ClusterId is not None: + filter.ClusterId = f.ClusterId + else: + raise ValueError( + f"DataVersionFilter must provide ClusterId.") + if f.DataVersion is not None: + filter.DataVersion = f.DataVersion + else: + raise ValueError( + f"DataVersionFilter must provide DataVersion.") + filter = chip.interaction_model.DataVersionFilterIBstruct.build( + filter) + readargs.append(ctypes.c_char_p(filter)) + ctypes.pythonapi.Py_IncRef(ctypes.py_object(transaction)) minInterval = 0 maxInterval = 0 @@ -844,7 +910,9 @@ def ReadAttributes(future: Future, eventLoop, device, devCtrl, attributes: List[ ctypes.byref(readCallbackObj), device, ctypes.c_char_p(params), - ctypes.c_size_t(len(attributes)), *readargs) + ctypes.c_size_t(len(attributes)), + ctypes.c_size_t(len(attributes) + dataVersionFilterLength), + *readargs) transaction.SetClientObjPointers(readClientObj, readCallbackObj) diff --git a/src/controller/python/chip/clusters/attribute.cpp b/src/controller/python/chip/clusters/attribute.cpp index 2fd4195cee5f3d..02bdba67ad600a 100644 --- a/src/controller/python/chip/clusters/attribute.cpp +++ b/src/controller/python/chip/clusters/attribute.cpp @@ -42,6 +42,7 @@ struct __attribute__((packed)) AttributePath chip::EndpointId endpointId; chip::ClusterId clusterId; chip::AttributeId attributeId; + chip::DataVersion dataVersion; }; struct __attribute__((packed)) EventPath @@ -51,6 +52,13 @@ struct __attribute__((packed)) EventPath chip::EventId eventId; }; +struct __attribute__((packed)) DataVersionFilter +{ + chip::EndpointId endpointId; + chip::ClusterId clusterId; + chip::DataVersion dataVersion; +}; + using OnReadAttributeDataCallback = void (*)(PyObject * appContext, chip::DataVersion version, chip::EndpointId endpointId, chip::ClusterId clusterId, chip::AttributeId attributeId, std::underlying_type_t imstatus, uint8_t * data, @@ -79,8 +87,7 @@ class ReadClientCallback : public ReadClient::Callback app::BufferedReadCallback * GetBufferedReadCallback() { return &mBufferedReadCallback; } - void OnAttributeData(const ConcreteDataAttributePath & aPath, DataVersion aVersion, TLV::TLVReader * apData, - const StatusIB & aStatus) override + void OnAttributeData(const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, const StatusIB & aStatus) override { // // We shouldn't be getting list item operations in the provided path since that should be handled by the buffered read @@ -108,7 +115,7 @@ class ReadClientCallback : public ReadClient::Callback size = writer.GetLengthWritten(); } - gOnReadAttributeDataCallback(mAppContext, aVersion, aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId, + gOnReadAttributeDataCallback(mAppContext, aPath.mDataVersion, aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId, to_underlying(aStatus.mStatus), buffer.get(), size); } @@ -164,6 +171,11 @@ class ReadClientCallback : public ReadClient::Callback { delete[] aReadPrepareParams.mpEventPathParamsList; } + + if (aReadPrepareParams.mpDataVersionFilterList != nullptr) + { + delete[] aReadPrepareParams.mpDataVersionFilterList; + } } void OnReportEnd() override { gOnReportEndCallback(mAppContext); } @@ -200,7 +212,7 @@ chip::ChipError::StorageType pychip_WriteClient_WriteAttributes(void * appContex uint16_t timedWriteTimeoutMs, size_t n, ...); chip::ChipError::StorageType pychip_ReadClient_ReadAttributes(void * appContext, ReadClient ** pReadClient, ReadClientCallback ** pCallback, DeviceProxy * device, - uint8_t * readParamsBuf, size_t n, ...); + uint8_t * readParamsBuf, size_t n, size_t total, ...); } using OnWriteResponseCallback = void (*)(PyObject * appContext, chip::EndpointId endpointId, chip::ClusterId clusterId, @@ -328,7 +340,7 @@ void pychip_ReadClient_Abort(ReadClient * apReadClient, ReadClientCallback * apC chip::ChipError::StorageType pychip_ReadClient_ReadAttributes(void * appContext, ReadClient ** pReadClient, ReadClientCallback ** pCallback, DeviceProxy * device, - uint8_t * readParamsBuf, size_t n, ...) + uint8_t * readParamsBuf, size_t n, size_t total, ...) { CHIP_ERROR err = CHIP_NO_ERROR; PyReadAttributeParams pyParams = {}; @@ -337,10 +349,12 @@ chip::ChipError::StorageType pychip_ReadClient_ReadAttributes(void * appContext, std::unique_ptr callback = std::make_unique(appContext); + size_t m = total - n; va_list args; - va_start(args, n); + va_start(args, total); std::unique_ptr readPaths(new AttributePathParams[n]); + std::unique_ptr dataVersionFilters(new chip::app::DataVersionFilter[m]); std::unique_ptr readClient; { @@ -355,6 +369,16 @@ chip::ChipError::StorageType pychip_ReadClient_ReadAttributes(void * appContext, } } + for (size_t j = 0; j < m; j++) + { + void * filter = va_arg(args, void *); + + python::DataVersionFilter filterObj; + memcpy(&filterObj, filter, sizeof(python::DataVersionFilter)); + + dataVersionFilters[j] = chip::app::DataVersionFilter(filterObj.endpointId, filterObj.clusterId, filterObj.dataVersion); + } + Optional session = device->GetSecureSession(); VerifyOrExit(session.HasValue(), err = CHIP_ERROR_NOT_CONNECTED); @@ -366,7 +390,14 @@ chip::ChipError::StorageType pychip_ReadClient_ReadAttributes(void * appContext, ReadPrepareParams params(session.Value()); params.mpAttributePathParamsList = readPaths.get(); params.mAttributePathParamsListSize = n; - params.mIsFabricFiltered = pyParams.isFabricFiltered; + if (m != 0) + { + params.mpDataVersionFilterList = dataVersionFilters.get(); + params.mDataVersionFilterListSize = m; + } + + params.mIsFabricFiltered = pyParams.isFabricFiltered; + if (pyParams.isSubscription) { params.mMinIntervalFloorSeconds = pyParams.minInterval; diff --git a/src/controller/python/chip/interaction_model/__init__.py b/src/controller/python/chip/interaction_model/__init__.py index 2bd904226112ff..79e94646e08b63 100644 --- a/src/controller/python/chip/interaction_model/__init__.py +++ b/src/controller/python/chip/interaction_model/__init__.py @@ -22,7 +22,7 @@ """Provides Python APIs for CHIP.""" import enum -from .delegate import AttributePath, AttributePathIBstruct, EventPath, EventPathIBstruct +from .delegate import AttributePath, AttributePathIBstruct, EventPath, EventPathIBstruct, DataVersionFilterIBstruct from chip.exceptions import ChipStackException @@ -53,7 +53,7 @@ class Status(enum.IntEnum): UnsupportedRead = 0x8f Deprecated90 = 0x90 Deprecated91 = 0x91 - Reserved92 = 0x92 + DataVersionMismatch = 0x92 Deprecated93 = 0x93 Timeout = 0x94 Reserved95 = 0x95 diff --git a/src/controller/python/chip/interaction_model/delegate.py b/src/controller/python/chip/interaction_model/delegate.py index 6ad6bd5a5785d4..dbbd818f98fd4b 100644 --- a/src/controller/python/chip/interaction_model/delegate.py +++ b/src/controller/python/chip/interaction_model/delegate.py @@ -51,6 +51,7 @@ "EndpointId" / Int16ul, "ClusterId" / Int32ul, "AttributeId" / Int32ul, + "DataVersion" / Int32ul, ) # EventPath should not contain padding @@ -60,6 +61,12 @@ "EventId" / Int32ul, ) +DataVersionFilterIBstruct = Struct( + "EndpointId" / Int16ul, + "ClusterId" / Int32ul, + "DataVersion" / Int32ul, +) + @dataclass class AttributePath: diff --git a/src/controller/python/test/test_scripts/base.py b/src/controller/python/test/test_scripts/base.py index 4114fafcba5c98..a33fe9b879e636 100644 --- a/src/controller/python/test/test_scripts/base.py +++ b/src/controller/python/test/test_scripts/base.py @@ -190,8 +190,8 @@ async def TestMultiFabric(self, ip: str, setuppin: int, nodeid: int): data2 = await devCtrl2.ReadAttribute(nodeid, [(Clusters.OperationalCredentials.Attributes.NOCs)], fabricFiltered=False) # Read out noclist from each fabric, and each should contain two NOCs. - nocList1 = data1[0][Clusters.OperationalCredentials][Clusters.OperationalCredentials.Attributes.NOCs] - nocList2 = data2[0][Clusters.OperationalCredentials][Clusters.OperationalCredentials.Attributes.NOCs] + nocList1 = data1[0][0][Clusters.OperationalCredentials][Clusters.OperationalCredentials.Attributes.NOCs] + nocList2 = data2[0][0][Clusters.OperationalCredentials][Clusters.OperationalCredentials.Attributes.NOCs] if (len(nocList1) != 2 or len(nocList2) != 2): self.logger.error("Got back invalid nocList") @@ -201,8 +201,8 @@ async def TestMultiFabric(self, ip: str, setuppin: int, nodeid: int): data2 = await devCtrl2.ReadAttribute(nodeid, [(Clusters.OperationalCredentials.Attributes.CurrentFabricIndex)], fabricFiltered=False) # Read out current fabric from each fabric, and both should be different. - currentFabric1 = data1[0][Clusters.OperationalCredentials][Clusters.OperationalCredentials.Attributes.CurrentFabricIndex] - currentFabric2 = data2[0][Clusters.OperationalCredentials][Clusters.OperationalCredentials.Attributes.CurrentFabricIndex] + currentFabric1 = data1[0][0][Clusters.OperationalCredentials][Clusters.OperationalCredentials.Attributes.CurrentFabricIndex] + currentFabric2 = data2[0][0][Clusters.OperationalCredentials][Clusters.OperationalCredentials.Attributes.CurrentFabricIndex] if (currentFabric1 == currentFabric2): self.logger.error( "Got back fabric indices that match for two different fabrics!") diff --git a/src/controller/python/test/test_scripts/cluster_objects.py b/src/controller/python/test/test_scripts/cluster_objects.py index 02c17615e0bf0c..32ee18cf588eaa 100644 --- a/src/controller/python/test/test_scripts/cluster_objects.py +++ b/src/controller/python/test/test_scripts/cluster_objects.py @@ -39,10 +39,11 @@ def _IgnoreAttributeDecodeFailure(path): def VerifyDecodeSuccess(values): - for endpoint in values: - for cluster in values[endpoint]: - for attribute in values[endpoint][cluster]: - v = values[endpoint][cluster][attribute] + print(f"{values}") + for endpoint in values[0]: + for cluster in values[0][endpoint]: + for attribute in values[0][endpoint][cluster]: + v = values[0][endpoint][cluster][attribute] print(f"EP{endpoint}/{attribute} = {v}") if (isinstance(v, ValueDecodeFailure)): if _IgnoreAttributeDecodeFailure((endpoint, cluster, attribute)): @@ -50,7 +51,20 @@ def VerifyDecodeSuccess(values): f"Ignoring attribute decode failure for path {endpoint}/{attribute}") else: raise AssertionError( - f"Cannot decode value for path {k}, got error: '{str(v.Data.Reason)}', raw TLV data: '{v.Data.TLVValue}'") + f"Cannot decode value for path {endpoint}/{attribute}, got error: '{str(v.Reason)}', raw TLV data: '{v.TLVValue}'") + + for endpoint in values[1]: + for cluster in values[1][endpoint]: + for attribute in values[1][endpoint][cluster]: + v = values[1][endpoint][cluster][attribute] + print(f"EP{endpoint}/{attribute} version = {v}") + if (isinstance(v, ValueDecodeFailure)): + if _IgnoreAttributeDecodeFailure((endpoint, cluster, attribute)): + print( + f"Ignoring attribute version decode failure for path {endpoint}/{attribute}") + else: + raise AssertionError( + f"Cannot decode value for path {endpoint}/{attribute}, got error: '{str(v.Reason)}', raw TLV data: '{v.TLVValue}'") def _AssumeEventsDecodeSuccess(values): @@ -107,9 +121,9 @@ async def SendWriteRequest(cls, devCtrl): res = await devCtrl.WriteAttribute(nodeid=NODE_ID, attributes=[ (0, Clusters.Basic.Attributes.NodeLabel( - "Test")), + "Test"), 0), (0, Clusters.Basic.Attributes.Location( - "A loooong string")) + "A loooong string"), 0) ]) expectedRes = [ AttributeStatus(Path=AttributePath(EndpointId=0, ClusterId=40, @@ -181,9 +195,9 @@ async def TestReadAttributeRequests(cls, devCtrl): (0, Clusters.Basic.Attributes.HardwareVersion), ] res = await devCtrl.ReadAttribute(nodeid=NODE_ID, attributes=req) - if ((0 not in res) or (Clusters.Basic not in res[0]) or (len(res[0][Clusters.Basic]) != 3)): + if ((0 not in res[0]) or (Clusters.Basic not in res[0][0]) or (len(res[0][0][Clusters.Basic]) != 3)): raise AssertionError( - f"Got back {len(res)} data items instead of 3") + f"Got back {len(res[0])} data items instead of 3") VerifyDecodeSuccess(res) logger.info("2: Reading Ex Cx A*") @@ -226,23 +240,23 @@ async def TestReadAttributeRequests(cls, devCtrl): logger.info("7: Reading Chunked List") res = await devCtrl.ReadAttribute(nodeid=NODE_ID, attributes=[(1, Clusters.TestCluster.Attributes.ListLongOctetString)]) - if res[1][Clusters.TestCluster][Clusters.TestCluster.Attributes.ListLongOctetString] != [b'0123456789abcdef' * 32] * 4: + if res[0][1][Clusters.TestCluster][Clusters.TestCluster.Attributes.ListLongOctetString] != [b'0123456789abcdef' * 32] * 4: raise AssertionError("Unexpected read result") logger.info("*: Getting current fabric index") res = await devCtrl.ReadAttribute(nodeid=NODE_ID, attributes=[(0, Clusters.OperationalCredentials.Attributes.CurrentFabricIndex)]) - fabricIndex = res[0][Clusters.OperationalCredentials][Clusters.OperationalCredentials.Attributes.CurrentFabricIndex] + fabricIndex = res[0][0][Clusters.OperationalCredentials][Clusters.OperationalCredentials.Attributes.CurrentFabricIndex] logger.info("8: Read without fabric filter") res = await devCtrl.ReadAttribute(nodeid=NODE_ID, attributes=[(1, Clusters.TestCluster.Attributes.ListFabricScoped)], fabricFiltered=False) - if len(res[1][Clusters.TestCluster][Clusters.TestCluster.Attributes.ListFabricScoped]) <= 1: + if len(res[0][1][Clusters.TestCluster][Clusters.TestCluster.Attributes.ListFabricScoped]) <= 1: raise AssertionError("Expect more elements in the response") logger.info("9: Read with fabric filter") res = await devCtrl.ReadAttribute(nodeid=NODE_ID, attributes=[(1, Clusters.TestCluster.Attributes.ListFabricScoped)], fabricFiltered=True) - if len(res[1][Clusters.TestCluster][Clusters.TestCluster.Attributes.ListFabricScoped]) != 1: + if len(res[0][1][Clusters.TestCluster][Clusters.TestCluster.Attributes.ListFabricScoped]) != 1: raise AssertionError("Expect exact one element in the response") - if res[1][Clusters.TestCluster][Clusters.TestCluster.Attributes.ListFabricScoped][0].fabricIndex != fabricIndex: + if res[0][1][Clusters.TestCluster][Clusters.TestCluster.Attributes.ListFabricScoped][0].fabricIndex != fabricIndex: raise AssertionError( "Expect the fabric index matches the one current reading") @@ -307,7 +321,7 @@ async def TestTimedRequest(cls, devCtrl): await devCtrl.WriteAttribute(nodeid=NODE_ID, attributes=[ (1, Clusters.TestCluster.Attributes.TimedWriteBoolean( - True)), + True), 0), ], timedRequestTimeoutMs=1000) @@ -325,7 +339,7 @@ async def TestTimedRequest(cls, devCtrl): await devCtrl.WriteAttribute(nodeid=NODE_ID, attributes=[ (1, Clusters.TestCluster.Attributes.TimedWriteBoolean( - True)), + True), 0), ], timedRequestTimeoutMs=10) raise AssertionError("Timeout expected!") @@ -347,12 +361,51 @@ async def TestTimedRequest(cls, devCtrl): await devCtrl.WriteAttribute(nodeid=NODE_ID, attributes=[ (1, Clusters.TestCluster.Attributes.TimedWriteBoolean( - True)), + True), 0), ]) raise AssertionError("The write request should be rejected.") except ValueError: pass + @classmethod + async def TestReadWriteAttributeRequestsWithVersion(cls, devCtrl): + logger.info("TestReadWriteAttributeRequestsWithVersion") + req = [ + (0, Clusters.Basic.Attributes.VendorName) + ] + res = await devCtrl.ReadAttribute(nodeid=NODE_ID, attributes=req) + VerifyDecodeSuccess(res) + data_version = res[1][0][40][1] + + res = await devCtrl.WriteAttribute(nodeid=NODE_ID, + attributes=[ + (0, Clusters.Basic.Attributes.NodeLabel( + "Test"), 0) + ]) + expectedRes = [ + AttributeStatus(Path=AttributePath(EndpointId=0, ClusterId=40, + AttributeId=5), Status=chip.interaction_model.Status.Success), + ] + + if res != expectedRes: + for i in range(len(res)): + if res[i] != expectedRes[i]: + logger.error( + f"Item {i} is not expected, expect {expectedRes[i]} got {res[i]}") + raise AssertionError("Write returned unexpected result.") + + req = [ + (0, Clusters.Basic.Attributes.VendorName), + ] + res = await devCtrl.ReadAttribute(nodeid=NODE_ID, attributes=req, dataVersionFilters=[(0, Clusters.Basic, data_version)]) + VerifyDecodeSuccess(res) + new_data_version = res[1][0][40][1] + if (data_version + 1) != new_data_version: + raise AssertionError("Version mistmatch happens.") + + res = await devCtrl.ReadAttribute(nodeid=NODE_ID, attributes=req, dataVersionFilters=[(0, Clusters.Basic, new_data_version)]) + VerifyDecodeSuccess(res) + @classmethod async def RunTest(cls, devCtrl): try: @@ -361,6 +414,7 @@ async def RunTest(cls, devCtrl): await cls.RoundTripTestWithBadEndpoint(devCtrl) await cls.SendCommandWithResponse(devCtrl) await cls.TestReadEventRequests(devCtrl, 1) + await cls.TestReadWriteAttributeRequestsWithVersion(devCtrl) await cls.TestReadAttributeRequests(devCtrl) await cls.TestSubscribeAttribute(devCtrl) # Note: Write will change some attribute values, always put it after read tests diff --git a/src/controller/python/test/test_scripts/network_commissioning.py b/src/controller/python/test/test_scripts/network_commissioning.py index a76236a7b6da66..523fe580eb4c90 100644 --- a/src/controller/python/test/test_scripts/network_commissioning.py +++ b/src/controller/python/test/test_scripts/network_commissioning.py @@ -72,7 +72,7 @@ async def test_wifi(self, endpointId): (endpointId, Clusters.NetworkCommissioning.Attributes.FeatureMap)], returnClusterObject=True) self.log_interface_basic_info( - res[endpointId][Clusters.NetworkCommissioning]) + res[0][endpointId][Clusters.NetworkCommissioning]) logger.info(f"Finished getting basic information of the endpoint") # Scan networks @@ -87,7 +87,7 @@ async def test_wifi(self, endpointId): # Remove existing network logger.info(f"Check network list") res = await self._devCtrl.ReadAttribute(nodeid=self._nodeid, attributes=[(endpointId, Clusters.NetworkCommissioning.Attributes.Networks)], returnClusterObject=True) - networkList = res[endpointId][Clusters.NetworkCommissioning].networks + networkList = res[0][endpointId][Clusters.NetworkCommissioning].networks logger.info(f"Got network list: {networkList}") if len(networkList) != 0: logger.info(f"Removing existing network") @@ -110,7 +110,7 @@ async def test_wifi(self, endpointId): logger.info(f"Check network list") res = await self._devCtrl.ReadAttribute(nodeid=self._nodeid, attributes=[(endpointId, Clusters.NetworkCommissioning.Attributes.Networks)], returnClusterObject=True) - networkList = res[endpointId][Clusters.NetworkCommissioning].networks + networkList = res[0][endpointId][Clusters.NetworkCommissioning].networks logger.info(f"Got network list: {networkList}") if len(networkList) != 1: raise AssertionError( @@ -133,7 +133,7 @@ async def test_wifi(self, endpointId): logger.info(f"Check network is connected") res = await self._devCtrl.ReadAttribute(nodeid=self._nodeid, attributes=[(endpointId, Clusters.NetworkCommissioning.Attributes.Networks)], returnClusterObject=True) - networkList = res[endpointId][Clusters.NetworkCommissioning].networks + networkList = res[0][endpointId][Clusters.NetworkCommissioning].networks logger.info(f"Got network list: {networkList}") if len(networkList) != 1: raise AssertionError( @@ -156,7 +156,7 @@ async def test_thread(self, endpointId): (endpointId, Clusters.NetworkCommissioning.Attributes.FeatureMap)], returnClusterObject=True) self.log_interface_basic_info( - res[endpointId][Clusters.NetworkCommissioning]) + res[0][endpointId][Clusters.NetworkCommissioning]) logger.info(f"Finished getting basic information of the endpoint") # Scan networks @@ -171,7 +171,7 @@ async def test_thread(self, endpointId): # Remove existing network logger.info(f"Check network list") res = await self._devCtrl.ReadAttribute(nodeid=self._nodeid, attributes=[(endpointId, Clusters.NetworkCommissioning.Attributes.Networks)], returnClusterObject=True) - networkList = res[endpointId][Clusters.NetworkCommissioning].networks + networkList = res[0][endpointId][Clusters.NetworkCommissioning].networks logger.info(f"Got network list: {networkList}") if len(networkList) != 0: logger.info(f"Removing existing network") @@ -194,7 +194,7 @@ async def test_thread(self, endpointId): logger.info(f"Check network list") res = await self._devCtrl.ReadAttribute(nodeid=self._nodeid, attributes=[(endpointId, Clusters.NetworkCommissioning.Attributes.Networks)], returnClusterObject=True) - networkList = res[endpointId][Clusters.NetworkCommissioning].networks + networkList = res[0][endpointId][Clusters.NetworkCommissioning].networks logger.info(f"Got network list: {networkList}") if len(networkList) != 1: raise AssertionError( @@ -233,6 +233,7 @@ async def run(self): try: endpoints = await self._devCtrl.ReadAttribute(nodeid=self._nodeid, attributes=[(Clusters.NetworkCommissioning.Attributes.FeatureMap)], returnClusterObject=True) logger.info(endpoints) + endpoints = endpoints[0] for endpoint, obj in endpoints.items(): clus = obj[Clusters.NetworkCommissioning] if clus.featureMap == WIFI_NETWORK_FEATURE_MAP: diff --git a/src/controller/tests/TestReadChunking.cpp b/src/controller/tests/TestReadChunking.cpp index d704e16fb94f5e..9baab882aa9cbd 100644 --- a/src/controller/tests/TestReadChunking.cpp +++ b/src/controller/tests/TestReadChunking.cpp @@ -96,7 +96,7 @@ class TestReadCallback : public app::ReadClient::Callback { public: TestReadCallback() : mBufferedCallback(*this) {} - void OnAttributeData(const app::ConcreteDataAttributePath & aPath, DataVersion aVersion, TLV::TLVReader * apData, + void OnAttributeData(const app::ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, const app::StatusIB & aStatus) override; void OnDone() override; @@ -108,7 +108,7 @@ class TestReadCallback : public app::ReadClient::Callback app::BufferedReadCallback mBufferedCallback; }; -void TestReadCallback::OnAttributeData(const app::ConcreteDataAttributePath & aPath, DataVersion aVersion, TLV::TLVReader * apData, +void TestReadCallback::OnAttributeData(const app::ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, const app::StatusIB & aStatus) { if (aPath.mAttributeId != kTestListAttribute) diff --git a/src/controller/tests/data_model/TestCommands.cpp b/src/controller/tests/data_model/TestCommands.cpp index aedc62f28527d2..393c1918f67b50 100644 --- a/src/controller/tests/data_model/TestCommands.cpp +++ b/src/controller/tests/data_model/TestCommands.cpp @@ -148,20 +148,6 @@ InteractionModel::Status ServerClusterCommandExists(const ConcreteCommandPath & return Status::Success; } - -CHIP_ERROR ReadSingleClusterData(const Access::SubjectDescriptor & aSubjectDescriptor, bool aIsFabricFiltered, - const ConcreteReadAttributePath & aPath, AttributeReportIBs::Builder & aAttributeReports, - AttributeValueEncoder::AttributeEncodeState * apEncoderState) -{ - return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; -} - -CHIP_ERROR WriteSingleClusterData(const Access::SubjectDescriptor & aSubjectDescriptor, const ConcreteDataAttributePath & aPath, - TLV::TLVReader & aReader, WriteHandler * aWriteHandler) -{ - return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; -} - } // namespace app } // namespace chip diff --git a/src/controller/tests/data_model/TestRead.cpp b/src/controller/tests/data_model/TestRead.cpp index dcf98a40e31758..2451eda01410cb 100644 --- a/src/controller/tests/data_model/TestRead.cpp +++ b/src/controller/tests/data_model/TestRead.cpp @@ -36,6 +36,7 @@ using namespace chip::Protocols; namespace { constexpr EndpointId kTestEndpointId = 1; +constexpr DataVersion kDataVersion = 5; enum ResponseDirective { @@ -49,34 +50,10 @@ ResponseDirective responseDirective; namespace chip { namespace app { - -void DispatchSingleClusterCommand(const ConcreteCommandPath & aCommandPath, chip::TLV::TLVReader & aReader, - CommandHandler * apCommandObj) -{} - -InteractionModel::Status ServerClusterCommandExists(const ConcreteCommandPath & aCommandPath) -{ - // Mock cluster catalog, only support commands on one cluster on one endpoint. - using InteractionModel::Status; - - if (aCommandPath.mEndpointId != kTestEndpointId) - { - return Status::UnsupportedEndpoint; - } - - if (aCommandPath.mClusterId != TestCluster::Id) - { - return Status::UnsupportedCluster; - } - - return Status::Success; -} - CHIP_ERROR ReadSingleClusterData(const Access::SubjectDescriptor & aSubjectDescriptor, bool aIsFabricFiltered, const ConcreteReadAttributePath & aPath, AttributeReportIBs::Builder & aAttributeReports, AttributeValueEncoder::AttributeEncodeState * apEncoderState) { - if (responseDirective == kSendDataResponse) { if (aPath.mClusterId == app::Clusters::TestCluster::Id && @@ -88,7 +65,7 @@ CHIP_ERROR ReadSingleClusterData(const Access::SubjectDescriptor & aSubjectDescr aIsFabricFiltered, state); return valueEncoder.EncodeList([aSubjectDescriptor](const auto & encoder) -> CHIP_ERROR { - chip::app::Clusters::TestCluster::Structs::TestFabricScoped::Type val; + app::Clusters::TestCluster::Structs::TestFabricScoped::Type val; val.fabricIndex = aSubjectDescriptor.fabricIndex; ReturnErrorOnFailure(encoder.Encode(val)); val.fabricIndex = (val.fabricIndex == 1) ? 2 : 1; @@ -114,7 +91,8 @@ CHIP_ERROR ReadSingleClusterData(const Access::SubjectDescriptor & aSubjectDescr i++; } - attributeData.DataVersion(0); + attributeData.DataVersion(kDataVersion); + ReturnErrorOnFailure(attributeData.GetError()); AttributePathIB::Builder & attributePath = attributeData.CreatePath(); attributePath.Endpoint(aPath.mEndpointId) .Cluster(aPath.mClusterId) @@ -123,7 +101,7 @@ CHIP_ERROR ReadSingleClusterData(const Access::SubjectDescriptor & aSubjectDescr ReturnErrorOnFailure(attributePath.GetError()); ReturnErrorOnFailure(DataModel::Encode(*(attributeData.GetWriter()), - chip::TLV::ContextTag(chip::to_underlying(AttributeDataIB::Tag::kData)), value)); + TLV::ContextTag(to_underlying(AttributeDataIB::Tag::kData)), value)); ReturnErrorOnFailure(attributeData.EndOfAttributeDataIB().GetError()); return attributeReport.EndOfAttributeReportIB().GetError(); } @@ -148,12 +126,17 @@ CHIP_ERROR ReadSingleClusterData(const Access::SubjectDescriptor & aSubjectDescr return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; } -CHIP_ERROR WriteSingleClusterData(const Access::SubjectDescriptor & aSubjectDescriptor, const ConcreteDataAttributePath & aPath, - TLV::TLVReader & aReader, WriteHandler * aWriteHandler) +bool IsClusterDataVersionEqual(EndpointId aEndpointId, ClusterId aClusterId, DataVersion aRequiredDataVersion) { - return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; + if (aRequiredDataVersion == kDataVersion) + { + return true; + } + else + { + return false; + } } - } // namespace app } // namespace chip @@ -165,12 +148,14 @@ class TestReadInteraction TestReadInteraction() {} static void TestReadAttributeResponse(nlTestSuite * apSuite, void * apContext); + static void TestReadDataVersionFilter(nlTestSuite * apSuite, void * apContext); static void TestReadAttributeError(nlTestSuite * apSuite, void * apContext); static void TestReadAttributeTimeout(nlTestSuite * apSuite, void * apContext); static void TestReadEventResponse(nlTestSuite * apSuite, void * apContext); static void TestReadFabricScopedWithoutFabricFilter(nlTestSuite * apSuite, void * apContext); static void TestReadFabricScopedWithFabricFilter(nlTestSuite * apSuite, void * apContext); static void TestReadHandler_MultipleSubscriptions(nlTestSuite * apSuite, void * apContext); + static void TestReadHandler_MultipleSubscriptionsWithDataVersionFilter(nlTestSuite * apSuite, void * apContext); static void TestReadHandlerResourceExhaustion_MultipleSubscriptions(nlTestSuite * apSuite, void * apContext); static void TestReadHandlerResourceExhaustion_MultipleReads(nlTestSuite * apSuite, void * apContext); @@ -187,9 +172,10 @@ void TestReadInteraction::TestReadAttributeResponse(nlTestSuite * apSuite, void // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. - auto onSuccessCb = [apSuite, &onSuccessCbInvoked](const app::ConcreteAttributePath & attributePath, const auto & dataResponse) { + auto onSuccessCb = [apSuite, &onSuccessCbInvoked](const app::ConcreteDataAttributePath & attributePath, + const auto & dataResponse) { uint8_t i = 0; - + NL_TEST_ASSERT(apSuite, attributePath.mDataVersion == kDataVersion); auto iter = dataResponse.begin(); while (iter.Next()) { @@ -197,29 +183,72 @@ void TestReadInteraction::TestReadAttributeResponse(nlTestSuite * apSuite, void NL_TEST_ASSERT(apSuite, item.fabricIndex == i); i++; } - NL_TEST_ASSERT(apSuite, i == 4); NL_TEST_ASSERT(apSuite, iter.GetStatus() == CHIP_NO_ERROR); - onSuccessCbInvoked = true; }; // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. - auto onFailureCb = [&onFailureCbInvoked](const app::ConcreteAttributePath * attributePath, CHIP_ERROR aError) { + auto onFailureCb = [&onFailureCbInvoked](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) { onFailureCbInvoked = true; }; - chip::Controller::ReadAttribute( - &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb); + Controller::ReadAttribute(&ctx.GetExchangeManager(), sessionHandle, + kTestEndpointId, onSuccessCb, onFailureCb); ctx.DrainAndServiceIO(); - chip::app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run(); + app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run(); ctx.DrainAndServiceIO(); NL_TEST_ASSERT(apSuite, onSuccessCbInvoked && !onFailureCbInvoked); - NL_TEST_ASSERT(apSuite, chip::app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0); - NL_TEST_ASSERT(apSuite, chip::app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0); + NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0); + NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0); + NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); +} + +void TestReadInteraction::TestReadDataVersionFilter(nlTestSuite * apSuite, void * apContext) +{ + TestContext & ctx = *static_cast(apContext); + auto sessionHandle = ctx.GetSessionBobToAlice(); + bool onSuccessCbInvoked = false, onFailureCbInvoked = false; + + responseDirective = kSendDataResponse; + + // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's + // not safe to do so. + auto onSuccessCb = [apSuite, &onSuccessCbInvoked](const app::ConcreteDataAttributePath & attributePath, + const auto & dataResponse) { + uint8_t i = 0; + auto iter = dataResponse.begin(); + while (iter.Next()) + { + auto & item = iter.GetValue(); + NL_TEST_ASSERT(apSuite, item.fabricIndex == i); + i++; + } + NL_TEST_ASSERT(apSuite, i == 4); + NL_TEST_ASSERT(apSuite, iter.GetStatus() == CHIP_NO_ERROR); + onSuccessCbInvoked = true; + }; + + // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's + // not safe to do so. + auto onFailureCb = [&onFailureCbInvoked](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) { + onFailureCbInvoked = true; + }; + + Optional dataVersion(kDataVersion); + Controller::ReadAttribute( + &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, true, dataVersion); + + ctx.DrainAndServiceIO(); + app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run(); + ctx.DrainAndServiceIO(); + + NL_TEST_ASSERT(apSuite, !onSuccessCbInvoked && !onFailureCbInvoked); + NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0); + NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0); NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); } @@ -243,16 +272,16 @@ void TestReadInteraction::TestReadEventResponse(nlTestSuite * apSuite, void * ap onFailureCbInvoked = true; }; - chip::Controller::ReadEvent(&ctx.GetExchangeManager(), sessionHandle, - kTestEndpointId, onSuccessCb, onFailureCb); + Controller::ReadEvent(&ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, + onSuccessCb, onFailureCb); ctx.DrainAndServiceIO(); - chip::app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run(); + app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run(); ctx.DrainAndServiceIO(); NL_TEST_ASSERT(apSuite, !onFailureCbInvoked); - NL_TEST_ASSERT(apSuite, chip::app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0); - NL_TEST_ASSERT(apSuite, chip::app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0); + NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0); + NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0); NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); } @@ -266,27 +295,27 @@ void TestReadInteraction::TestReadAttributeError(nlTestSuite * apSuite, void * a // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. - auto onSuccessCb = [&onSuccessCbInvoked](const app::ConcreteAttributePath & attributePath, const auto & dataResponse) { + auto onSuccessCb = [&onSuccessCbInvoked](const app::ConcreteDataAttributePath & attributePath, const auto & dataResponse) { onSuccessCbInvoked = true; }; // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. - auto onFailureCb = [&onFailureCbInvoked, apSuite](const app::ConcreteAttributePath * attributePath, CHIP_ERROR aError) { + auto onFailureCb = [&onFailureCbInvoked, apSuite](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) { NL_TEST_ASSERT(apSuite, aError.IsIMStatus() && app::StatusIB(aError).mStatus == Protocols::InteractionModel::Status::Busy); onFailureCbInvoked = true; }; - chip::Controller::ReadAttribute( - &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb); + Controller::ReadAttribute(&ctx.GetExchangeManager(), sessionHandle, + kTestEndpointId, onSuccessCb, onFailureCb); ctx.DrainAndServiceIO(); - chip::app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run(); + app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run(); ctx.DrainAndServiceIO(); NL_TEST_ASSERT(apSuite, !onSuccessCbInvoked && onFailureCbInvoked); - NL_TEST_ASSERT(apSuite, chip::app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0); - NL_TEST_ASSERT(apSuite, chip::app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0); + NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0); + NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0); NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); } @@ -300,19 +329,19 @@ void TestReadInteraction::TestReadAttributeTimeout(nlTestSuite * apSuite, void * // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. - auto onSuccessCb = [&onSuccessCbInvoked](const app::ConcreteAttributePath & attributePath, const auto & dataResponse) { + auto onSuccessCb = [&onSuccessCbInvoked](const app::ConcreteDataAttributePath & attributePath, const auto & dataResponse) { onSuccessCbInvoked = true; }; // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. - auto onFailureCb = [&onFailureCbInvoked, apSuite](const app::ConcreteAttributePath * attributePath, CHIP_ERROR aError) { + auto onFailureCb = [&onFailureCbInvoked, apSuite](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) { NL_TEST_ASSERT(apSuite, aError == CHIP_ERROR_TIMEOUT); onFailureCbInvoked = true; }; - chip::Controller::ReadAttribute( - &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb); + Controller::ReadAttribute(&ctx.GetExchangeManager(), sessionHandle, + kTestEndpointId, onSuccessCb, onFailureCb); ctx.DrainAndServiceIO(); @@ -330,12 +359,12 @@ void TestReadInteraction::TestReadAttributeTimeout(nlTestSuite * apSuite, void * // NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 1); ctx.DrainAndServiceIO(); - chip::app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run(); + app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run(); ctx.DrainAndServiceIO(); ctx.ExpireSessionAliceToBob(); - NL_TEST_ASSERT(apSuite, chip::app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0); + NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0); // // Let's put back the sessions so that the next tests (which assume a valid initialized set of sessions) @@ -361,13 +390,13 @@ void TestReadInteraction::TestReadHandler_MultipleSubscriptions(nlTestSuite * ap // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. - auto onSuccessCb = [&numSuccessCalls](const app::ConcreteAttributePath & attributePath, const auto & dataResponse) { + auto onSuccessCb = [&numSuccessCalls](const app::ConcreteDataAttributePath & attributePath, const auto & dataResponse) { numSuccessCalls++; }; // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. - auto onFailureCb = [&apSuite](const app::ConcreteAttributePath * attributePath, CHIP_ERROR aError) { + auto onFailureCb = [&apSuite](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) { // // We shouldn't be encountering any failures in this test. // @@ -384,7 +413,7 @@ void TestReadInteraction::TestReadHandler_MultipleSubscriptions(nlTestSuite * ap for (int i = 0; i < (CHIP_IM_MAX_NUM_READ_HANDLER + 1); i++) { NL_TEST_ASSERT(apSuite, - chip::Controller::SubscribeAttribute( + Controller::SubscribeAttribute( &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 10, onSubscriptionEstablishedCb, false, true) == CHIP_NO_ERROR); } @@ -396,7 +425,71 @@ void TestReadInteraction::TestReadHandler_MultipleSubscriptions(nlTestSuite * ap for (int i = 0; i < 10 && (numSubscriptionEstablishedCalls != (CHIP_IM_MAX_NUM_READ_HANDLER + 1)); i++) { ctx.DrainAndServiceIO(); - chip::app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run(); + app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run(); + ctx.DrainAndServiceIO(); + } + + NL_TEST_ASSERT(apSuite, numSuccessCalls == (CHIP_IM_MAX_NUM_READ_HANDLER + 1)); + NL_TEST_ASSERT(apSuite, numSubscriptionEstablishedCalls == (CHIP_IM_MAX_NUM_READ_HANDLER + 1)); + + app::InteractionModelEngine::GetInstance()->ShutdownActiveReads(); + + // + // TODO: Figure out why I cannot enable this line below. + // + // NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); +} + +void TestReadInteraction::TestReadHandler_MultipleSubscriptionsWithDataVersionFilter(nlTestSuite * apSuite, void * apContext) +{ + TestContext & ctx = *static_cast(apContext); + auto sessionHandle = ctx.GetSessionBobToAlice(); + uint32_t numSuccessCalls = 0; + uint32_t numSubscriptionEstablishedCalls = 0; + + responseDirective = kSendDataResponse; + + // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's + // not safe to do so. + auto onSuccessCb = [apSuite, &numSuccessCalls](const app::ConcreteDataAttributePath & attributePath, + const auto & dataResponse) { + NL_TEST_ASSERT(apSuite, attributePath.mDataVersion == kDataVersion); + numSuccessCalls++; + }; + + // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's + // not safe to do so. + auto onFailureCb = [&apSuite](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) { + // + // We shouldn't be encountering any failures in this test. + // + NL_TEST_ASSERT(apSuite, false); + }; + + auto onSubscriptionEstablishedCb = [&numSubscriptionEstablishedCalls]() { numSubscriptionEstablishedCalls++; }; + + // + // Try to issue parallel subscriptions that will exceed the value for CHIP_IM_MAX_NUM_READ_HANDLER. + // If heap allocation is correctly setup, this should result in it successfully servicing more than the number + // present in that define. + // + chip::Optional dataVersion(1); + for (int i = 0; i < (CHIP_IM_MAX_NUM_READ_HANDLER + 1); i++) + { + NL_TEST_ASSERT(apSuite, + Controller::SubscribeAttribute( + &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 10, + onSubscriptionEstablishedCb, false, true, dataVersion) == CHIP_NO_ERROR); + } + + // + // It may take a couple of service calls since we may hit the limit of CHIP_IM_MAX_REPORTS_IN_FLIGHT + // reports. + // + for (int i = 0; i < 10 && (numSubscriptionEstablishedCalls != (CHIP_IM_MAX_NUM_READ_HANDLER + 1)); i++) + { + ctx.DrainAndServiceIO(); + app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run(); ctx.DrainAndServiceIO(); } @@ -423,13 +516,13 @@ void TestReadInteraction::TestReadHandlerResourceExhaustion_MultipleSubscription // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. - auto onSuccessCb = [&numSuccessCalls](const app::ConcreteAttributePath & attributePath, const auto & dataResponse) { + auto onSuccessCb = [&numSuccessCalls](const app::ConcreteDataAttributePath & attributePath, const auto & dataResponse) { numSuccessCalls++; }; // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. - auto onFailureCb = [&apSuite, &numFailureCalls](const app::ConcreteAttributePath * attributePath, CHIP_ERROR aError) { + auto onFailureCb = [&apSuite, &numFailureCalls](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) { numFailureCalls++; NL_TEST_ASSERT(apSuite, aError == CHIP_IM_GLOBAL_STATUS(ResourceExhausted)); @@ -443,14 +536,13 @@ void TestReadInteraction::TestReadHandlerResourceExhaustion_MultipleSubscription // since the second subscription below should fail correctly. // app::InteractionModelEngine::GetInstance()->SetHandlerCapacity(2); - NL_TEST_ASSERT(apSuite, - chip::Controller::SubscribeAttribute( + Controller::SubscribeAttribute( &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 10, onSubscriptionEstablishedCb, false, true) == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, - chip::Controller::SubscribeAttribute( + Controller::SubscribeAttribute( &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 10, onSubscriptionEstablishedCb, false, true) == CHIP_NO_ERROR); @@ -461,7 +553,7 @@ void TestReadInteraction::TestReadHandlerResourceExhaustion_MultipleSubscription for (int i = 0; i < 10; i++) { ctx.DrainAndServiceIO(); - chip::app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run(); + app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run(); ctx.DrainAndServiceIO(); } @@ -490,13 +582,13 @@ void TestReadInteraction::TestReadHandlerResourceExhaustion_MultipleReads(nlTest // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. - auto onSuccessCb = [&numSuccessCalls](const app::ConcreteAttributePath & attributePath, const auto & dataResponse) { + auto onSuccessCb = [&numSuccessCalls](const app::ConcreteDataAttributePath & attributePath, const auto & dataResponse) { numSuccessCalls++; }; // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. - auto onFailureCb = [&apSuite, &numFailureCalls](const app::ConcreteAttributePath * attributePath, CHIP_ERROR aError) { + auto onFailureCb = [&apSuite, &numFailureCalls](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) { numFailureCalls++; NL_TEST_ASSERT(apSuite, aError == CHIP_IM_GLOBAL_STATUS(ResourceExhausted)); @@ -506,7 +598,7 @@ void TestReadInteraction::TestReadHandlerResourceExhaustion_MultipleReads(nlTest app::InteractionModelEngine::GetInstance()->SetHandlerCapacity(0); NL_TEST_ASSERT(apSuite, - chip::Controller::ReadAttribute( + Controller::ReadAttribute( &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb) == CHIP_NO_ERROR); // @@ -516,7 +608,7 @@ void TestReadInteraction::TestReadHandlerResourceExhaustion_MultipleReads(nlTest for (int i = 0; i < 10; i++) { ctx.DrainAndServiceIO(); - chip::app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run(); + app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run(); ctx.DrainAndServiceIO(); } @@ -552,7 +644,8 @@ void TestReadInteraction::TestReadFabricScopedWithoutFabricFilter(nlTestSuite * // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. - auto onSuccessCb = [apSuite, &onSuccessCbInvoked](const app::ConcreteAttributePath & attributePath, const auto & dataResponse) { + auto onSuccessCb = [apSuite, &onSuccessCbInvoked](const app::ConcreteDataAttributePath & attributePath, + const auto & dataResponse) { size_t len = 0; NL_TEST_ASSERT(apSuite, dataResponse.ComputeSize(&len) == CHIP_NO_ERROR); @@ -563,20 +656,20 @@ void TestReadInteraction::TestReadFabricScopedWithoutFabricFilter(nlTestSuite * // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. - auto onFailureCb = [&onFailureCbInvoked](const app::ConcreteAttributePath * attributePath, CHIP_ERROR aError) { + auto onFailureCb = [&onFailureCbInvoked](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) { onFailureCbInvoked = true; }; - chip::Controller::ReadAttribute( + Controller::ReadAttribute( &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, false /* fabric filtered */); ctx.DrainAndServiceIO(); - chip::app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run(); + app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run(); ctx.DrainAndServiceIO(); NL_TEST_ASSERT(apSuite, onSuccessCbInvoked && !onFailureCbInvoked); - NL_TEST_ASSERT(apSuite, chip::app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0); - NL_TEST_ASSERT(apSuite, chip::app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0); + NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0); + NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0); NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); } @@ -600,7 +693,8 @@ void TestReadInteraction::TestReadFabricScopedWithFabricFilter(nlTestSuite * apS // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. - auto onSuccessCb = [apSuite, &onSuccessCbInvoked](const app::ConcreteAttributePath & attributePath, const auto & dataResponse) { + auto onSuccessCb = [apSuite, &onSuccessCbInvoked](const app::ConcreteDataAttributePath & attributePath, + const auto & dataResponse) { size_t len = 0; NL_TEST_ASSERT(apSuite, dataResponse.ComputeSize(&len) == CHIP_NO_ERROR); @@ -620,20 +714,20 @@ void TestReadInteraction::TestReadFabricScopedWithFabricFilter(nlTestSuite * apS // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. - auto onFailureCb = [&onFailureCbInvoked](const app::ConcreteAttributePath * attributePath, CHIP_ERROR aError) { + auto onFailureCb = [&onFailureCbInvoked](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) { onFailureCbInvoked = true; }; - chip::Controller::ReadAttribute( + Controller::ReadAttribute( &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, true /* fabric filtered */); ctx.DrainAndServiceIO(); - chip::app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run(); + app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run(); ctx.DrainAndServiceIO(); NL_TEST_ASSERT(apSuite, onSuccessCbInvoked && !onFailureCbInvoked); - NL_TEST_ASSERT(apSuite, chip::app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0); - NL_TEST_ASSERT(apSuite, chip::app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0); + NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0); + NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0); NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); } @@ -641,12 +735,14 @@ void TestReadInteraction::TestReadFabricScopedWithFabricFilter(nlTestSuite * apS const nlTest sTests[] = { NL_TEST_DEF("TestReadAttributeResponse", TestReadInteraction::TestReadAttributeResponse), + NL_TEST_DEF("TestReadDataVersionFilter", TestReadInteraction::TestReadDataVersionFilter), NL_TEST_DEF("TestReadEventResponse", TestReadInteraction::TestReadEventResponse), NL_TEST_DEF("TestReadAttributeError", TestReadInteraction::TestReadAttributeError), NL_TEST_DEF("TestReadFabricScopedWithoutFabricFilter", TestReadInteraction::TestReadFabricScopedWithoutFabricFilter), NL_TEST_DEF("TestReadFabricScopedWithFabricFilter", TestReadInteraction::TestReadFabricScopedWithFabricFilter), NL_TEST_DEF("TestReadAttributeTimeout", TestReadInteraction::TestReadAttributeTimeout), NL_TEST_DEF("TestReadHandler_MultipleSubscriptions", TestReadInteraction::TestReadHandler_MultipleSubscriptions), + NL_TEST_DEF("TestReadHandler_MultipleSubscriptionsWithDataVersionFilter", TestReadInteraction::TestReadHandler_MultipleSubscriptionsWithDataVersionFilter), NL_TEST_DEF("TestReadHandlerResourceExhaustion_MultipleSubscriptions", TestReadInteraction::TestReadHandlerResourceExhaustion_MultipleSubscriptions), NL_TEST_DEF("TestReadHandlerResourceExhaustion_MultipleReads", TestReadInteraction::TestReadHandlerResourceExhaustion_MultipleReads), NL_TEST_SENTINEL() diff --git a/src/controller/tests/data_model/TestWrite.cpp b/src/controller/tests/data_model/TestWrite.cpp index e97feaec9ec7b7..c13f5473a8438c 100644 --- a/src/controller/tests/data_model/TestWrite.cpp +++ b/src/controller/tests/data_model/TestWrite.cpp @@ -53,35 +53,6 @@ ResponseDirective responseDirective; namespace chip { namespace app { -void DispatchSingleClusterCommand(const ConcreteCommandPath & aCommandPath, chip::TLV::TLVReader & aReader, - CommandHandler * apCommandObj) -{} - -InteractionModel::Status ServerClusterCommandExists(const ConcreteCommandPath & aCommandPath) -{ - // Mock cluster catalog, only support commands on one cluster on one endpoint. - using InteractionModel::Status; - - if (aCommandPath.mEndpointId != kTestEndpointId) - { - return Status::UnsupportedEndpoint; - } - - if (aCommandPath.mClusterId != TestCluster::Id) - { - return Status::UnsupportedCluster; - } - - return Status::Success; -} - -CHIP_ERROR ReadSingleClusterData(const Access::SubjectDescriptor & aSubjectDescriptor, bool aIsFabricFiltered, - const ConcreteReadAttributePath & aPath, AttributeReportIBs::Builder & aAttributeReports, - AttributeValueEncoder::AttributeEncodeState * apEncoderState) -{ - return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; -} - CHIP_ERROR WriteSingleClusterData(const Access::SubjectDescriptor & aSubjectDescriptor, const ConcreteDataAttributePath & aPath, TLV::TLVReader & aReader, WriteHandler * aWriteHandler) { diff --git a/src/darwin/Framework/CHIP/CHIPDevice.mm b/src/darwin/Framework/CHIP/CHIPDevice.mm index d75bd399e998bb..6dd9f6e4068a9f 100644 --- a/src/darwin/Framework/CHIP/CHIPDevice.mm +++ b/src/darwin/Framework/CHIP/CHIPDevice.mm @@ -96,8 +96,7 @@ - (instancetype)initWithDevice:(chip::DeviceProxy *)device void OnReportEnd() override; - void OnAttributeData( - const ConcreteDataAttributePath & aPath, DataVersion aVersion, TLV::TLVReader * apData, const StatusIB & aStatus) override; + void OnAttributeData(const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, const StatusIB & aStatus) override; void OnError(CHIP_ERROR aError) override; @@ -215,7 +214,7 @@ - (instancetype)initWithPath:(const ConcreteDataAttributePath &)path value:(null } void SubscriptionCallback::OnAttributeData( - const ConcreteDataAttributePath & aPath, DataVersion aVersion, TLV::TLVReader * apData, const StatusIB & aStatus) + const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, const StatusIB & aStatus) { if (aPath.IsListItemOperation()) { ReportError(CHIP_ERROR_INCORRECT_STATE); diff --git a/src/lib/core/CHIPError.cpp b/src/lib/core/CHIPError.cpp index ea22ad36e8c71e..f3dd0284467673 100644 --- a/src/lib/core/CHIPError.cpp +++ b/src/lib/core/CHIPError.cpp @@ -545,9 +545,6 @@ bool FormatCHIPError(char * buf, uint16_t bufSize, CHIP_ERROR err) case CHIP_ERROR_INCOMPATIBLE_SCHEMA_VERSION.AsInteger(): desc = "Incompatible data schema version"; break; - case CHIP_ERROR_MISMATCH_UPDATE_REQUIRED_VERSION.AsInteger(): - desc = "Update Required Version mismatch"; - break; case CHIP_ERROR_ACCESS_DENIED.AsInteger(): desc = "The CHIP message is not granted access"; break; diff --git a/src/lib/core/CHIPError.h b/src/lib/core/CHIPError.h index c5eba86087b7e5..7420d851e48467 100644 --- a/src/lib/core/CHIPError.h +++ b/src/lib/core/CHIPError.h @@ -1894,14 +1894,6 @@ using CHIP_ERROR = ::chip::ChipError; */ #define CHIP_ERROR_INCOMPATIBLE_SCHEMA_VERSION CHIP_CORE_ERROR(0xa3) -/** - * @def CHIP_ERROR_MISMATCH_UPDATE_REQUIRED_VERSION - * - * @brief - * Encountered a mismatch between update required version and current version - */ -#define CHIP_ERROR_MISMATCH_UPDATE_REQUIRED_VERSION CHIP_CORE_ERROR(0xa4) - /** * @def CHIP_ERROR_ACCESS_DENIED * diff --git a/src/lib/core/DataModelTypes.h b/src/lib/core/DataModelTypes.h index d931944e1926f1..3dccb05932e64a 100644 --- a/src/lib/core/DataModelTypes.h +++ b/src/lib/core/DataModelTypes.h @@ -48,7 +48,7 @@ constexpr FabricIndex kUndefinedFabricIndex = 0; constexpr EndpointId kInvalidEndpointId = 0xFFFF; constexpr EndpointId kRootEndpointId = 0; constexpr ListIndex kInvalidListIndex = 0xFFFF; // List index is a uint16 thus 0xFFFF is a invalid list index. -constexpr DataVersion kUndefinedDataVersion = 0; + // These are MEIs, 0xFFFF is not a valid manufacturer code, // thus 0xFFFF'FFFF is not a valid MEI. static constexpr ClusterId kInvalidClusterId = 0xFFFF'FFFF; diff --git a/src/lib/core/tests/TestCHIPErrorStr.cpp b/src/lib/core/tests/TestCHIPErrorStr.cpp index 32a71fff60dd25..1f8e5e78fba931 100644 --- a/src/lib/core/tests/TestCHIPErrorStr.cpp +++ b/src/lib/core/tests/TestCHIPErrorStr.cpp @@ -210,7 +210,6 @@ static const CHIP_ERROR kTestElements[] = CHIP_ERROR_PROFILE_STRING_CONTEXT_ALREADY_REGISTERED, CHIP_ERROR_PROFILE_STRING_CONTEXT_NOT_REGISTERED, CHIP_ERROR_INCOMPATIBLE_SCHEMA_VERSION, - CHIP_ERROR_MISMATCH_UPDATE_REQUIRED_VERSION, CHIP_ERROR_ACCESS_DENIED, CHIP_ERROR_UNKNOWN_RESOURCE_ID, CHIP_ERROR_VERSION_MISMATCH, diff --git a/src/platform/CYW30739/args.gni b/src/platform/CYW30739/args.gni index a49440e56219ee..43f6ec41ac2fc4 100644 --- a/src/platform/CYW30739/args.gni +++ b/src/platform/CYW30739/args.gni @@ -29,6 +29,8 @@ chip_crypto = "mbedtls" chip_device_platform = "cyw30739" chip_mdns = "platform" +# Trying to fit into the available flash by disabling optional logging for now +chip_detail_logging = false chip_enable_openthread = true lwip_platform = "cyw30739" diff --git a/zzz_generated/chip-tool/zap-generated/cluster/logging/DataModelLogger.cpp b/zzz_generated/chip-tool/zap-generated/cluster/logging/DataModelLogger.cpp index 3247d56af00129..d2203d405eaa26 100644 --- a/zzz_generated/chip-tool/zap-generated/cluster/logging/DataModelLogger.cpp +++ b/zzz_generated/chip-tool/zap-generated/cluster/logging/DataModelLogger.cpp @@ -4120,8 +4120,9 @@ CHIP_ERROR DataModelLogger::LogValue(const char * label, size_t indent, CHIP_ERROR DataModelLogger::LogAttribute(const chip::app::ConcreteDataAttributePath & path, chip::TLV::TLVReader * data) { - ChipLogProgress(chipTool, "Endpoint: %" PRIu16 " Cluster: " ChipLogFormatMEI " Attribute " ChipLogFormatMEI, path.mEndpointId, - ChipLogValueMEI(path.mClusterId), ChipLogValueMEI(path.mAttributeId)); + ChipLogProgress(chipTool, + "Endpoint: %" PRIu16 " Cluster: " ChipLogFormatMEI " Attribute " ChipLogFormatMEI "DataVersion: %" PRIu32, + path.mEndpointId, ChipLogValueMEI(path.mClusterId), ChipLogValueMEI(path.mAttributeId), path.mDataVersion); switch (path.mClusterId) {