diff --git a/src/app/InteractionModelEngine.cpp b/src/app/InteractionModelEngine.cpp index e48a4ef4330b6d..6c32952135c902 100644 --- a/src/app/InteractionModelEngine.cpp +++ b/src/app/InteractionModelEngine.cpp @@ -30,11 +30,11 @@ #include "access/RequestPath.h" #include "access/SubjectDescriptor.h" #include +#include +#include #include #include -extern bool emberAfContainsAttribute(chip::EndpointId endpoint, chip::ClusterId clusterId, chip::AttributeId attributeId); - namespace chip { namespace app { @@ -351,10 +351,8 @@ CHIP_ERROR InteractionModelEngine::ParseAttributePaths(const Access::SubjectDesc aHasValidAttributePath = false; aRequestedAttributePathCount = 0; - while (CHIP_NO_ERROR == (err = pathReader.Next())) + while (CHIP_NO_ERROR == (err = pathReader.Next(TLV::AnonymousTag()))) { - VerifyOrReturnError(TLV::AnonymousTag() == pathReader.GetTag(), CHIP_ERROR_INVALID_TLV_TAG); - AttributePathIB::Parser path; // // We create an iterator to point to a single item object list that tracks the path we just parsed. @@ -413,6 +411,152 @@ CHIP_ERROR InteractionModelEngine::ParseAttributePaths(const Access::SubjectDesc return err; } +static bool CanAccess(const Access::SubjectDescriptor & aSubjectDescriptor, const ConcreteClusterPath & aPath, + Access::Privilege aNeededPrivilege) +{ + Access::RequestPath requestPath{ .cluster = aPath.mClusterId, .endpoint = aPath.mEndpointId }; + CHIP_ERROR err = Access::GetAccessControl().Check(aSubjectDescriptor, requestPath, aNeededPrivilege); + return (err == CHIP_NO_ERROR); +} + +static bool CanAccess(const Access::SubjectDescriptor & aSubjectDescriptor, const ConcreteEventPath & aPath) +{ + return CanAccess(aSubjectDescriptor, aPath, RequiredPrivilege::ForReadEvent(aPath)); +} + +/** + * Helper to handle wildcard events in the event path. + */ +static bool HasValidEventPathForEndpointAndCluster(EndpointId aEndpoint, const EmberAfCluster * aCluster, + const EventPathParams & aEventPath, + const Access::SubjectDescriptor & aSubjectDescriptor) +{ + if (aEventPath.HasWildcardEventId()) + { +#if CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE + for (decltype(aCluster->eventCount) idx = 0; idx > aCluster->eventCount; ++idx) + { + ConcreteEventPath path(aEndpoint, aCluster->clusterId, aCluster->eventList[idx]); + // If we get here, the path exists. We just have to do an ACL check for it. + bool isValid = CanAccess(aSubjectDescriptor, path); + if (isValid) + { + return true; + } + } + + return false; +#else + // We have no way to expand wildcards. Just assume that we would need + // View permissions for whatever events are involved. + ConcreteClusterPath clusterPath(aEndpoint, aCluster->clusterId); + return CanAccess(aSubjectDescriptor, clusterPath, Access::Privilege::kView); +#endif + } + + ConcreteEventPath path(aEndpoint, aCluster->clusterId, aEventPath.mEventId); + if (CheckEventSupportStatus(path) != Status::Success) + { + // Not an existing event path. + return false; + } + return CanAccess(aSubjectDescriptor, path); +} + +/** + * Helper to handle wildcard clusters in the event path. + */ +static bool HasValidEventPathForEndpoint(EndpointId aEndpoint, const EventPathParams & aEventPath, + const Access::SubjectDescriptor & aSubjectDescriptor) +{ + if (aEventPath.HasWildcardClusterId()) + { + auto * endpointType = emberAfFindEndpointType(aEndpoint); + if (endpointType == nullptr) + { + // Not going to have any valid paths in here. + return false; + } + + for (decltype(endpointType->clusterCount) idx = 0; idx < endpointType->clusterCount; ++idx) + { + bool hasValidPath = + HasValidEventPathForEndpointAndCluster(aEndpoint, &endpointType->cluster[idx], aEventPath, aSubjectDescriptor); + if (hasValidPath) + { + return true; + } + } + + return false; + } + + auto * cluster = emberAfFindServerCluster(aEndpoint, aEventPath.mClusterId); + if (cluster == nullptr) + { + // Nothing valid here. + return false; + } + return HasValidEventPathForEndpointAndCluster(aEndpoint, cluster, aEventPath, aSubjectDescriptor); +} + +CHIP_ERROR InteractionModelEngine::ParseEventPaths(const Access::SubjectDescriptor & aSubjectDescriptor, + EventPathIBs::Parser & aEventPathListParser, bool & aHasValidEventPath, + size_t & aRequestedEventPathCount) +{ + TLV::TLVReader pathReader; + aEventPathListParser.GetReader(&pathReader); + CHIP_ERROR err = CHIP_NO_ERROR; + + aHasValidEventPath = false; + aRequestedEventPathCount = 0; + + while (CHIP_NO_ERROR == (err = pathReader.Next(TLV::AnonymousTag()))) + { + EventPathIB::Parser path; + ReturnErrorOnFailure(path.Init(pathReader)); + + EventPathParams eventPath; + ReturnErrorOnFailure(path.ParsePath(eventPath)); + + ++aRequestedEventPathCount; + + if (aHasValidEventPath) + { + // Can skip all the rest of the checking. + continue; + } + + // The definition of "valid path" is "path exists and ACL allows + // access". We need to do some expansion of wildcards to handle that. + if (eventPath.HasWildcardEndpointId()) + { + for (uint16_t endpointIndex = 0; !aHasValidEventPath && endpointIndex < emberAfEndpointCount(); ++endpointIndex) + { + if (!emberAfEndpointIndexIsEnabled(endpointIndex)) + { + continue; + } + aHasValidEventPath = + HasValidEventPathForEndpoint(emberAfEndpointFromIndex(endpointIndex), eventPath, aSubjectDescriptor); + } + } + else + { + // No need to check whether the endpoint is enabled, because + // emberAfFindEndpointType returns null for disabled endpoints. + aHasValidEventPath = HasValidEventPathForEndpoint(eventPath.mEndpointId, eventPath, aSubjectDescriptor); + } + } + + if (err == CHIP_ERROR_END_OF_TLV) + { + err = CHIP_NO_ERROR; + } + + return err; +} + Protocols::InteractionModel::Status InteractionModelEngine::OnReadInitialRequest(Messaging::ExchangeContext * apExchangeContext, const PayloadHeader & aPayloadHeader, System::PacketBufferHandle && aPayload, @@ -472,6 +616,7 @@ Protocols::InteractionModel::Status InteractionModelEngine::OnReadInitialRequest size_t requestedEventPathCount = 0; AttributePathIBs::Parser attributePathListParser; bool hasValidAttributePath = false; + bool hasValidEventPath = false; CHIP_ERROR err = subscribeRequestParser.GetAttributeRequests(&attributePathListParser); if (err == CHIP_NO_ERROR) @@ -489,14 +634,16 @@ Protocols::InteractionModel::Status InteractionModelEngine::OnReadInitialRequest return Status::InvalidAction; } - EventPathIBs::Parser eventpathListParser; - err = subscribeRequestParser.GetEventRequests(&eventpathListParser); + EventPathIBs::Parser eventPathListParser; + err = subscribeRequestParser.GetEventRequests(&eventPathListParser); if (err == CHIP_NO_ERROR) { - TLV::TLVReader pathReader; - eventpathListParser.GetReader(&pathReader); - ReturnErrorCodeIf(TLV::Utilities::Count(pathReader, requestedEventPathCount, false) != CHIP_NO_ERROR, - Status::InvalidAction); + auto subjectDescriptor = apExchangeContext->GetSessionHandle()->AsSecureSession()->GetSubjectDescriptor(); + err = ParseEventPaths(subjectDescriptor, eventPathListParser, hasValidEventPath, requestedEventPathCount); + if (err != CHIP_NO_ERROR) + { + return Status::InvalidAction; + } } else if (err != CHIP_ERROR_END_OF_TLV) { @@ -512,12 +659,7 @@ Protocols::InteractionModel::Status InteractionModelEngine::OnReadInitialRequest return Status::InvalidAction; } - // - // TODO: We don't have an easy way to do a similar 'path expansion' for events to deduce - // access so for now, assume that the presence of any path in the event list means they - // might have some access to those events. - // - if (!hasValidAttributePath && requestedEventPathCount == 0) + if (!hasValidAttributePath && !hasValidEventPath) { ChipLogError(InteractionModel, "Subscription from [%u:" ChipLogFormatX64 "] has no access at all. Rejecting request.", diff --git a/src/app/InteractionModelEngine.h b/src/app/InteractionModelEngine.h index 5401511899e0d5..b7de62e16ff0be 100644 --- a/src/app/InteractionModelEngine.h +++ b/src/app/InteractionModelEngine.h @@ -46,6 +46,7 @@ #include #include #include +#include #include #include #include @@ -398,6 +399,19 @@ class InteractionModelEngine : public Messaging::UnsolicitedMessageHandler, AttributePathIBs::Parser & aAttributePathListParser, bool & aHasValidAttributePath, size_t & aRequestedAttributePathCount); + /** + * This parses the event path list to ensure it is well formed. If so, for each path in the list, it will expand to a list + * of concrete paths and walk each path to check if it has privileges to read that event. + * + * If there is AT LEAST one "existent path" (as the spec calls it) that has sufficient privilege, aHasValidEventPath + * will be set to true. Otherwise, it will be set to false. + * + * aRequestedEventPathCount will be updated to reflect the number of event paths in the request. + */ + static CHIP_ERROR ParseEventPaths(const Access::SubjectDescriptor & aSubjectDescriptor, + EventPathIBs::Parser & aEventPathListParser, bool & aHasValidEventPath, + size_t & aRequestedEventPathCount); + /** * Called when Interaction Model receives a Read Request message. Errors processing * the Read Request are handled entirely within this function. If the @@ -677,7 +691,12 @@ bool IsDeviceTypeOnEndpoint(DeviceTypeId deviceType, EndpointId endpoint); * * @retval The metadata of the attribute, will return null if the given attribute does not exists. */ -const EmberAfAttributeMetadata * GetAttributeMetadata(const ConcreteAttributePath & aConcreteClusterPath); +const EmberAfAttributeMetadata * GetAttributeMetadata(const ConcreteAttributePath & aPath); + +/** + * Returns the event support status for the given event, as an interaction model status. + */ +Protocols::InteractionModel::Status CheckEventSupportStatus(const ConcreteEventPath & aPath); } // namespace app } // namespace chip diff --git a/src/app/reporting/Engine.cpp b/src/app/reporting/Engine.cpp index 7761e24ffb1c75..0f9d592737ce79 100644 --- a/src/app/reporting/Engine.cpp +++ b/src/app/reporting/Engine.cpp @@ -295,6 +295,8 @@ CHIP_ERROR Engine::BuildSingleReportDataAttributeReportIBs(ReportDataMessage::Bu CHIP_ERROR Engine::CheckAccessDeniedEventPaths(TLV::TLVWriter & aWriter, bool & aHasEncodedData, ReadHandler * apReadHandler) { + using Protocols::InteractionModel::Status; + CHIP_ERROR err = CHIP_NO_ERROR; for (auto current = apReadHandler->mpEventPathList; current != nullptr;) { @@ -304,8 +306,21 @@ CHIP_ERROR Engine::CheckAccessDeniedEventPaths(TLV::TLVWriter & aWriter, bool & continue; } - Access::RequestPath requestPath{ .cluster = current->mValue.mClusterId, .endpoint = current->mValue.mEndpointId }; ConcreteEventPath path(current->mValue.mEndpointId, current->mValue.mClusterId, current->mValue.mEventId); + Status status = CheckEventSupportStatus(path); + if (status != Status::Success) + { + TLV::TLVWriter checkpoint = aWriter; + err = EventReportIB::ConstructEventStatusIB(aWriter, path, StatusIB(status)); + if (err != CHIP_NO_ERROR) + { + aWriter = checkpoint; + break; + } + aHasEncodedData = true; + } + + Access::RequestPath requestPath{ .cluster = current->mValue.mClusterId, .endpoint = current->mValue.mEndpointId }; Access::Privilege requestPrivilege = RequiredPrivilege::ForReadEvent(path); err = Access::GetAccessControl().Check(apReadHandler->GetSubjectDescriptor(), requestPath, requestPrivilege); @@ -316,8 +331,7 @@ CHIP_ERROR Engine::CheckAccessDeniedEventPaths(TLV::TLVWriter & aWriter, bool & else { TLV::TLVWriter checkpoint = aWriter; - err = EventReportIB::ConstructEventStatusIB(aWriter, path, - StatusIB(Protocols::InteractionModel::Status::UnsupportedAccess)); + err = EventReportIB::ConstructEventStatusIB(aWriter, path, StatusIB(Status::UnsupportedAccess)); if (err != CHIP_NO_ERROR) { aWriter = checkpoint; diff --git a/src/app/tests/TestAclAttribute.cpp b/src/app/tests/TestAclAttribute.cpp index 8d17a94cccff81..cd7d774ef3429a 100644 --- a/src/app/tests/TestAclAttribute.cpp +++ b/src/app/tests/TestAclAttribute.cpp @@ -19,6 +19,8 @@ #include "lib/support/CHIPMem.h" #include #include +#include +#include #include #include #include @@ -140,6 +142,16 @@ bool ConcreteAttributePathExists(const ConcreteAttributePath & aPath) return aPath.mClusterId != kTestDeniedClusterId1; } +Protocols::InteractionModel::Status CheckEventSupportStatus(const ConcreteEventPath & aPath) +{ + if (aPath.mClusterId == kTestDeniedClusterId1) + { + return Protocols::InteractionModel::Status::UnsupportedCluster; + } + + return Protocols::InteractionModel::Status::Success; +} + class TestAclAttribute { public: diff --git a/src/app/tests/TestReadInteraction.cpp b/src/app/tests/TestReadInteraction.cpp index 5a5073f1f04059..fe0d2a04e39731 100644 --- a/src/app/tests/TestReadInteraction.cpp +++ b/src/app/tests/TestReadInteraction.cpp @@ -54,10 +54,12 @@ uint8_t gInfoEventBuffer[128]; uint8_t gCritEventBuffer[128]; chip::app::CircularEventBuffer gCircularEventBuffer[3]; chip::ClusterId kTestClusterId = 6; +chip::ClusterId kTestEventClusterId = chip::Test::MockClusterId(1); chip::ClusterId kInvalidTestClusterId = 7; chip::EndpointId kTestEndpointId = 1; -chip::EventId kTestEventIdDebug = 1; -chip::EventId kTestEventIdCritical = 2; +chip::EndpointId kTestEventEndpointId = chip::Test::kMockEndpoint1; +chip::EventId kTestEventIdDebug = chip::Test::MockEventId(1); +chip::EventId kTestEventIdCritical = chip::Test::MockEventId(2); uint8_t kTestFieldValue1 = 1; chip::TLV::Tag kTestEventTag = chip::TLV::ContextTag(1); chip::EndpointId kInvalidTestEndpointId = 3; @@ -128,11 +130,11 @@ void GenerateEvents(nlTestSuite * apSuite, void * apContext) CHIP_ERROR err = CHIP_NO_ERROR; chip::EventNumber eid1, eid2; chip::app::EventOptions options1; - options1.mPath = { kTestEndpointId, kTestClusterId, kTestEventIdDebug }; + options1.mPath = { kTestEventEndpointId, kTestEventClusterId, kTestEventIdDebug }; options1.mPriority = chip::app::PriorityLevel::Info; chip::app::EventOptions options2; - options2.mPath = { kTestEndpointId, kTestClusterId, kTestEventIdCritical }; + options2.mPath = { kTestEventEndpointId, kTestEventClusterId, kTestEventIdCritical }; options2.mPriority = chip::app::PriorityLevel::Critical; TestEventGenerator testEventGenerator; chip::app::EventManagement & logMgmt = chip::app::EventManagement::GetInstance(); @@ -820,8 +822,8 @@ void TestReadInteraction::TestReadRoundtrip(nlTestSuite * apSuite, void * apCont NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); chip::app::EventPathParams eventPathParams[1]; - eventPathParams[0].mEndpointId = kTestEndpointId; - eventPathParams[0].mClusterId = kTestClusterId; + eventPathParams[0].mEndpointId = kTestEventEndpointId; + eventPathParams[0].mClusterId = kTestEventClusterId; chip::app::AttributePathParams attributePathParams[2]; attributePathParams[0].mEndpointId = kTestEndpointId; @@ -1498,12 +1500,12 @@ void TestReadInteraction::TestSubscribeRoundtrip(nlTestSuite * apSuite, void * a ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); chip::app::EventPathParams eventPathParams[2]; readPrepareParams.mpEventPathParamsList = eventPathParams; - readPrepareParams.mpEventPathParamsList[0].mEndpointId = kTestEndpointId; - readPrepareParams.mpEventPathParamsList[0].mClusterId = kTestClusterId; + readPrepareParams.mpEventPathParamsList[0].mEndpointId = kTestEventEndpointId; + readPrepareParams.mpEventPathParamsList[0].mClusterId = kTestEventClusterId; readPrepareParams.mpEventPathParamsList[0].mEventId = kTestEventIdDebug; - readPrepareParams.mpEventPathParamsList[1].mEndpointId = kTestEndpointId; - readPrepareParams.mpEventPathParamsList[1].mClusterId = kTestClusterId; + readPrepareParams.mpEventPathParamsList[1].mEndpointId = kTestEventEndpointId; + readPrepareParams.mpEventPathParamsList[1].mClusterId = kTestEventClusterId; readPrepareParams.mpEventPathParamsList[1].mEventId = kTestEventIdCritical; readPrepareParams.mEventPathParamsListSize = 2; @@ -1690,11 +1692,11 @@ void TestReadInteraction::TestSubscribeUrgentWildcardEvent(nlTestSuite * apSuite ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); chip::app::EventPathParams eventPathParams[2]; readPrepareParams.mpEventPathParamsList = eventPathParams; - readPrepareParams.mpEventPathParamsList[0].mEndpointId = kTestEndpointId; - readPrepareParams.mpEventPathParamsList[0].mClusterId = kTestClusterId; + readPrepareParams.mpEventPathParamsList[0].mEndpointId = kTestEventEndpointId; + readPrepareParams.mpEventPathParamsList[0].mClusterId = kTestEventClusterId; - readPrepareParams.mpEventPathParamsList[1].mEndpointId = kTestEndpointId; - readPrepareParams.mpEventPathParamsList[1].mClusterId = kTestClusterId; + readPrepareParams.mpEventPathParamsList[1].mEndpointId = kTestEventEndpointId; + readPrepareParams.mpEventPathParamsList[1].mClusterId = kTestEventClusterId; readPrepareParams.mpEventPathParamsList[1].mEventId = kTestEventIdCritical; readPrepareParams.mEventPathParamsListSize = 2; @@ -2340,12 +2342,12 @@ void TestReadInteraction::TestPostSubscribeRoundtripStatusReportTimeout(nlTestSu ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); chip::app::EventPathParams eventPathParams[2]; readPrepareParams.mpEventPathParamsList = eventPathParams; - readPrepareParams.mpEventPathParamsList[0].mEndpointId = kTestEndpointId; - readPrepareParams.mpEventPathParamsList[0].mClusterId = kTestClusterId; + readPrepareParams.mpEventPathParamsList[0].mEndpointId = kTestEventEndpointId; + readPrepareParams.mpEventPathParamsList[0].mClusterId = kTestEventClusterId; readPrepareParams.mpEventPathParamsList[0].mEventId = kTestEventIdDebug; - readPrepareParams.mpEventPathParamsList[1].mEndpointId = kTestEndpointId; - readPrepareParams.mpEventPathParamsList[1].mClusterId = kTestClusterId; + readPrepareParams.mpEventPathParamsList[1].mEndpointId = kTestEventEndpointId; + readPrepareParams.mpEventPathParamsList[1].mClusterId = kTestEventClusterId; readPrepareParams.mpEventPathParamsList[1].mEventId = kTestEventIdCritical; readPrepareParams.mEventPathParamsListSize = 2; @@ -2461,12 +2463,12 @@ void TestReadInteraction::TestSubscribeRoundtripStatusReportTimeout(nlTestSuite ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); chip::app::EventPathParams eventPathParams[2]; readPrepareParams.mpEventPathParamsList = eventPathParams; - readPrepareParams.mpEventPathParamsList[0].mEndpointId = kTestEndpointId; - readPrepareParams.mpEventPathParamsList[0].mClusterId = kTestClusterId; + readPrepareParams.mpEventPathParamsList[0].mEndpointId = kTestEventEndpointId; + readPrepareParams.mpEventPathParamsList[0].mClusterId = kTestEventClusterId; readPrepareParams.mpEventPathParamsList[0].mEventId = kTestEventIdDebug; - readPrepareParams.mpEventPathParamsList[1].mEndpointId = kTestEndpointId; - readPrepareParams.mpEventPathParamsList[1].mClusterId = kTestClusterId; + readPrepareParams.mpEventPathParamsList[1].mEndpointId = kTestEventEndpointId; + readPrepareParams.mpEventPathParamsList[1].mClusterId = kTestEventClusterId; readPrepareParams.mpEventPathParamsList[1].mEventId = kTestEventIdCritical; readPrepareParams.mEventPathParamsListSize = 2; @@ -2641,12 +2643,12 @@ void TestReadInteraction::TestSubscribeRoundtripChunkStatusReportTimeout(nlTestS ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); chip::app::EventPathParams eventPathParams[2]; readPrepareParams.mpEventPathParamsList = eventPathParams; - readPrepareParams.mpEventPathParamsList[0].mEndpointId = kTestEndpointId; - readPrepareParams.mpEventPathParamsList[0].mClusterId = kTestClusterId; + readPrepareParams.mpEventPathParamsList[0].mEndpointId = kTestEventEndpointId; + readPrepareParams.mpEventPathParamsList[0].mClusterId = kTestEventClusterId; readPrepareParams.mpEventPathParamsList[0].mEventId = kTestEventIdDebug; - readPrepareParams.mpEventPathParamsList[1].mEndpointId = kTestEndpointId; - readPrepareParams.mpEventPathParamsList[1].mClusterId = kTestClusterId; + readPrepareParams.mpEventPathParamsList[1].mEndpointId = kTestEventEndpointId; + readPrepareParams.mpEventPathParamsList[1].mClusterId = kTestEventClusterId; readPrepareParams.mpEventPathParamsList[1].mEventId = kTestEventIdCritical; readPrepareParams.mEventPathParamsListSize = 2; @@ -2712,12 +2714,12 @@ void TestReadInteraction::TestPostSubscribeRoundtripChunkStatusReportTimeout(nlT ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); chip::app::EventPathParams eventPathParams[2]; readPrepareParams.mpEventPathParamsList = eventPathParams; - readPrepareParams.mpEventPathParamsList[0].mEndpointId = kTestEndpointId; - readPrepareParams.mpEventPathParamsList[0].mClusterId = kTestClusterId; + readPrepareParams.mpEventPathParamsList[0].mEndpointId = kTestEventEndpointId; + readPrepareParams.mpEventPathParamsList[0].mClusterId = kTestEventClusterId; readPrepareParams.mpEventPathParamsList[0].mEventId = kTestEventIdDebug; - readPrepareParams.mpEventPathParamsList[1].mEndpointId = kTestEndpointId; - readPrepareParams.mpEventPathParamsList[1].mClusterId = kTestClusterId; + readPrepareParams.mpEventPathParamsList[1].mEndpointId = kTestEventEndpointId; + readPrepareParams.mpEventPathParamsList[1].mClusterId = kTestEventClusterId; readPrepareParams.mpEventPathParamsList[1].mEventId = kTestEventIdCritical; readPrepareParams.mEventPathParamsListSize = 2; @@ -2814,12 +2816,12 @@ void TestReadInteraction::TestPostSubscribeRoundtripChunkReportTimeout(nlTestSui ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); chip::app::EventPathParams eventPathParams[2]; readPrepareParams.mpEventPathParamsList = eventPathParams; - readPrepareParams.mpEventPathParamsList[0].mEndpointId = kTestEndpointId; - readPrepareParams.mpEventPathParamsList[0].mClusterId = kTestClusterId; + readPrepareParams.mpEventPathParamsList[0].mEndpointId = kTestEventEndpointId; + readPrepareParams.mpEventPathParamsList[0].mClusterId = kTestEventClusterId; readPrepareParams.mpEventPathParamsList[0].mEventId = kTestEventIdDebug; - readPrepareParams.mpEventPathParamsList[1].mEndpointId = kTestEndpointId; - readPrepareParams.mpEventPathParamsList[1].mClusterId = kTestClusterId; + readPrepareParams.mpEventPathParamsList[1].mEndpointId = kTestEventEndpointId; + readPrepareParams.mpEventPathParamsList[1].mClusterId = kTestEventClusterId; readPrepareParams.mpEventPathParamsList[1].mEventId = kTestEventIdCritical; readPrepareParams.mEventPathParamsListSize = 2; @@ -2915,12 +2917,12 @@ void TestReadInteraction::TestPostSubscribeRoundtripChunkReport(nlTestSuite * ap ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); chip::app::EventPathParams eventPathParams[2]; readPrepareParams.mpEventPathParamsList = eventPathParams; - readPrepareParams.mpEventPathParamsList[0].mEndpointId = kTestEndpointId; - readPrepareParams.mpEventPathParamsList[0].mClusterId = kTestClusterId; + readPrepareParams.mpEventPathParamsList[0].mEndpointId = kTestEventEndpointId; + readPrepareParams.mpEventPathParamsList[0].mClusterId = kTestEventClusterId; readPrepareParams.mpEventPathParamsList[0].mEventId = kTestEventIdDebug; - readPrepareParams.mpEventPathParamsList[1].mEndpointId = kTestEndpointId; - readPrepareParams.mpEventPathParamsList[1].mClusterId = kTestClusterId; + readPrepareParams.mpEventPathParamsList[1].mEndpointId = kTestEventEndpointId; + readPrepareParams.mpEventPathParamsList[1].mClusterId = kTestEventClusterId; readPrepareParams.mpEventPathParamsList[1].mEventId = kTestEventIdCritical; readPrepareParams.mEventPathParamsListSize = 2; diff --git a/src/app/tests/integration/chip_im_initiator.cpp b/src/app/tests/integration/chip_im_initiator.cpp index f9160e2326bc75..24ed65a26869f9 100644 --- a/src/app/tests/integration/chip_im_initiator.cpp +++ b/src/app/tests/integration/chip_im_initiator.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -659,6 +660,11 @@ bool ConcreteAttributePathExists(const ConcreteAttributePath & aPath) return true; } +Protocols::InteractionModel::Status CheckEventSupportStatus(const ConcreteEventPath & aPath) +{ + return Protocols::InteractionModel::Status::Success; +} + CHIP_ERROR WriteSingleClusterData(const Access::SubjectDescriptor & aSubjectDescriptor, const ConcreteDataAttributePath & aPath, TLV::TLVReader & aReader, WriteHandler *) { diff --git a/src/app/tests/integration/chip_im_responder.cpp b/src/app/tests/integration/chip_im_responder.cpp index 96f49f8d1b6898..f7b003754da86a 100644 --- a/src/app/tests/integration/chip_im_responder.cpp +++ b/src/app/tests/integration/chip_im_responder.cpp @@ -28,6 +28,8 @@ #include #include #include +#include +#include #include #include #include @@ -128,6 +130,11 @@ bool ConcreteAttributePathExists(const ConcreteAttributePath & aPath) return true; } +Protocols::InteractionModel::Status CheckEventSupportStatus(const ConcreteEventPath & aPath) +{ + return Protocols::InteractionModel::Status::Success; +} + const EmberAfAttributeMetadata * GetAttributeMetadata(const ConcreteAttributePath & aConcreteClusterPath) { // Note: This test does not make use of the real attribute metadata. diff --git a/src/app/tests/suites/TestEvents.yaml b/src/app/tests/suites/TestEvents.yaml index 03f314fea3bdbe..637268ecdc587d 100644 --- a/src/app/tests/suites/TestEvents.yaml +++ b/src/app/tests/suites/TestEvents.yaml @@ -37,7 +37,8 @@ tests: command: "readEvent" event: "TestEvent" endpoint: 0 - response: [] + response: + error: UNSUPPORTED_CLUSTER - label: "Generate an event on the accessory" command: "TestEmitTestEventRequest" diff --git a/src/app/util/af.h b/src/app/util/af.h index 971f6b89dfe45b..b8c397f48c3270 100644 --- a/src/app/util/af.h +++ b/src/app/util/af.h @@ -35,6 +35,8 @@ #include +#include + #include #include #include @@ -127,11 +129,6 @@ EmberAfStatus emberAfReadAttribute(chip::EndpointId endpoint, chip::ClusterId cl extern EmberAfDefinedEndpoint emAfEndpoints[]; #endif -/** - * @brief Macro that takes index of endpoint, and returns Zigbee endpoint - */ -chip::EndpointId emberAfEndpointFromIndex(uint16_t index); - /** * @brief Returns root endpoint of a composed bridged device */ @@ -191,11 +188,6 @@ uint16_t emberAfIndexFromEndpointIncludingDisabledEndpoints(chip::EndpointId end uint16_t emberAfGetClusterServerEndpointIndex(chip::EndpointId endpoint, chip::ClusterId cluster, uint16_t fixedClusterServerEndpointCount); -/** - * @brief Returns the total number of endpoints (dynamic and pre-compiled). - */ -uint16_t emberAfEndpointCount(void); - /** * @brief Returns the number of pre-compiled endpoints. */ @@ -244,11 +236,6 @@ bool emberAfIsDeviceIdentifying(chip::EndpointId endpoint); */ bool emberAfEndpointEnableDisable(chip::EndpointId endpoint, bool enable); -/** - * @brief Determine if an endpoint at the specified index is enabled or disabled - */ -bool emberAfEndpointIndexIsEnabled(uint16_t index); - /** @brief Returns true if a given ZCL data type is a list type. */ bool emberAfIsThisDataTypeAListType(EmberAfAttributeType dataType); diff --git a/src/app/util/attribute-storage.h b/src/app/util/attribute-storage.h index d8617e1837dfd1..da83bf1840e27d 100644 --- a/src/app/util/attribute-storage.h +++ b/src/app/util/attribute-storage.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -100,10 +101,6 @@ bool emAfMatchCluster(const EmberAfCluster * cluster, EmberAfAttributeSearchReco bool emAfMatchAttribute(const EmberAfCluster * cluster, const EmberAfAttributeMetadata * am, EmberAfAttributeSearchRecord * attRecord); -// Returns endpoint type for the given endpoint id if there is an enabled -// endpoint with that endpoint id. Otherwise returns null. -const EmberAfEndpointType * emberAfFindEndpointType(chip::EndpointId endpointId); - // Check if a cluster is implemented or not. If yes, the cluster is returned. // // mask = 0 -> find either client or server @@ -148,9 +145,6 @@ chip::Optional emberAfGetNthClusterId(chip::EndpointId endpoint // for the given endpoint and client/server polarity uint8_t emberAfGetClustersFromEndpoint(chip::EndpointId endpoint, chip::ClusterId * clusterList, uint8_t listLen, bool server); -// Returns server cluster within the endpoint, or NULL if it isn't there -const EmberAfCluster * emberAfFindServerCluster(chip::EndpointId endpoint, chip::ClusterId clusterId); - // Returns cluster within the endpoint; Does not ignore disabled endpoints const EmberAfCluster * emberAfFindClusterIncludingDisabledEndpoints(chip::EndpointId endpoint, chip::ClusterId clusterId, EmberAfClusterMask mask); diff --git a/src/app/util/ember-compatibility-functions.cpp b/src/app/util/ember-compatibility-functions.cpp index c28eb8152a1f75..de08342c4f18f6 100644 --- a/src/app/util/ember-compatibility-functions.cpp +++ b/src/app/util/ember-compatibility-functions.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -957,10 +958,9 @@ CHIP_ERROR prepareWriteData(const EmberAfAttributeMetadata * attributeMetadata, } } // namespace -const EmberAfAttributeMetadata * GetAttributeMetadata(const ConcreteAttributePath & aConcreteClusterPath) +const EmberAfAttributeMetadata * GetAttributeMetadata(const ConcreteAttributePath & aPath) { - return emberAfLocateAttributeMetadata(aConcreteClusterPath.mEndpointId, aConcreteClusterPath.mClusterId, - aConcreteClusterPath.mAttributeId); + return emberAfLocateAttributeMetadata(aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId); } CHIP_ERROR WriteSingleClusterData(const SubjectDescriptor & aSubjectDescriptor, const ConcreteDataAttributePath & aPath, @@ -1071,6 +1071,38 @@ bool IsDeviceTypeOnEndpoint(DeviceTypeId deviceType, EndpointId endpoint) return false; } +Protocols::InteractionModel::Status CheckEventSupportStatus(const ConcreteEventPath & aPath) +{ + using Protocols::InteractionModel::Status; + + const EmberAfEndpointType * type = emberAfFindEndpointType(aPath.mEndpointId); + if (type == nullptr) + { + return Status::UnsupportedEndpoint; + } + + const EmberAfCluster * cluster = emberAfFindClusterInType(type, aPath.mClusterId, CLUSTER_MASK_SERVER); + if (cluster == nullptr) + { + return Status::UnsupportedCluster; + } + +#if CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE + for (size_t i = 0; i < cluster->eventCount; ++i) + { + if (cluster->eventList[i] == aPath.mEventId) + { + return Status::Success; + } + } + + return Status::UnsupportedEvent; +#else + // No way to tell. Just claim supported. + return Status::Success; +#endif // CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE +} + } // namespace app } // namespace chip diff --git a/src/app/util/endpoint-config-api.h b/src/app/util/endpoint-config-api.h new file mode 100644 index 00000000000000..3358d8453c6c9f --- /dev/null +++ b/src/app/util/endpoint-config-api.h @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2023 Project CHIP Authors + * + * 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 + +/** + * Declarations of function that can be used to query the endpoint configuration + * for the device. + */ + +#include + +/** + * Returns the total number of possible endpoints (dynamic and pre-compiled). + * Not all those endpoints might be enabled, and the dynamic ones might not even + * have an EmberAfEndpointType defined. + * + * Typically only used for endpoint index iteration. + */ +uint16_t emberAfEndpointCount(void); + +/** + * Returns whether the endpoint at the specified index (which must be less than + * emberAfEndpointCount() is enabled. If an endpoint is disabled, it is not + * guaranteed to have an EmberAfEndpointType. + */ +bool emberAfEndpointIndexIsEnabled(uint16_t index); + +/** + * Returns the endpoint id of the endpoint at the given index. Will return + * kInvalidEndpointId for endpoints that are not actually configured. + */ +chip::EndpointId emberAfEndpointFromIndex(uint16_t index); + +/** + * Returns the endpoint descriptor for the given endpoint id if there is an + * enabled endpoint with that endpoint id. Otherwise returns null. + */ +const EmberAfEndpointType * emberAfFindEndpointType(chip::EndpointId endpointId); + +/** + * Returns the cluster descriptor for the given cluster on the given endpoint. + * + * If the given endpoint does not exist or is disabled, returns null. + * + * If the given endpoint does not have the given cluster, returns null. + */ +const EmberAfCluster * emberAfFindServerCluster(chip::EndpointId endpoint, chip::ClusterId clusterId); + +/** + * Returns true if the given endpoint exists, is enabled, has the given cluster, + * and that cluster has the given attribute. + */ +bool emberAfContainsAttribute(chip::EndpointId endpoint, chip::ClusterId clusterId, chip::AttributeId attributeId); diff --git a/src/app/util/mock/attribute-storage.cpp b/src/app/util/mock/attribute-storage.cpp index 208ddec7b523e4..98a905615d9a4e 100644 --- a/src/app/util/mock/attribute-storage.cpp +++ b/src/app/util/mock/attribute-storage.cpp @@ -48,6 +48,7 @@ #include #include +#include #include typedef uint8_t EmberAfClusterMask; @@ -65,6 +66,8 @@ ClusterId clusters[] = { MockClusterId(1), MockClusterId(2), MockClusterId( MockClusterId(1), MockClusterId(2), MockClusterId(3), MockClusterId(4) }; uint16_t attributeIndex[] = { 0, 2, 5, 7, 11, 16, 19, 25, 27 }; uint16_t attributeCount[] = { 2, 3, 2, 4, 5, 3, 6, 2, 2 }; +uint16_t eventIndex[] = { 0, 2, 2, 2, 2, 2, 2, 2, 2 }; +uint16_t eventCount[] = { 2, 0, 0, 0, 0, 0, 0, 0, 0 }; AttributeId attributes[] = { // clang-format off Clusters::Globals::Attributes::ClusterRevision::Id, Clusters::Globals::Attributes::FeatureMap::Id, @@ -78,6 +81,10 @@ AttributeId attributes[] = { Clusters::Globals::Attributes::ClusterRevision::Id, Clusters::Globals::Attributes::FeatureMap::Id // clang-format on }; +EventId events[] = { + MockEventId(1), + MockEventId(2), +}; uint16_t mockClusterRevision = 1; uint32_t mockFeatureMap = 0x1234; @@ -95,6 +102,32 @@ uint8_t mockAttribute4[256] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, }; +#define MOCK_CLUSTER_DECL(idx) \ + { \ + .clusterId = clusters[idx], .attributes = nullptr, /* Not used for now. */ \ + .attributeCount = attributeCount[idx], .clusterSize = 0, /* Not used for now. */ \ + .mask = CLUSTER_MASK_SERVER, .functions = nullptr, .acceptedCommandList = nullptr, /* Not used for now */ \ + .generatedCommandList = nullptr, /* Not used for now */ \ + .eventList = &events[eventIndex[idx]], .eventCount = eventCount[idx], \ + } + +EmberAfCluster clusterStructs[] = { + MOCK_CLUSTER_DECL(0), MOCK_CLUSTER_DECL(1), MOCK_CLUSTER_DECL(2), MOCK_CLUSTER_DECL(3), MOCK_CLUSTER_DECL(4), + MOCK_CLUSTER_DECL(5), MOCK_CLUSTER_DECL(6), MOCK_CLUSTER_DECL(7), MOCK_CLUSTER_DECL(8), +}; + +#define MOCK_ENDPOINT_DECL(idx) \ + { \ + .cluster = &clusterStructs[clusterIndex[idx]], .clusterCount = clusterCount[idx], \ + .endpointSize = 0, /* Not used for now */ \ + } + +EmberAfEndpointType endpointStructs[] = { + MOCK_ENDPOINT_DECL(0), + MOCK_ENDPOINT_DECL(1), + MOCK_ENDPOINT_DECL(2), +}; + } // namespace uint16_t emberAfEndpointCount() @@ -284,7 +317,37 @@ bool emberAfContainsServerFromIndex(uint16_t index, ClusterId clusterId) return false; } - return clusterId; // Mock version return true as long as the endpoint is valid + return clusterId; // Mock version return true as long as the endpoint is + // valid +} + +const EmberAfEndpointType * emberAfFindEndpointType(EndpointId endpointId) +{ + uint16_t ep = emberAfIndexFromEndpoint(endpointId); + if (ep == UINT16_MAX) + { + return nullptr; + } + return &endpointStructs[ep]; +} + +const EmberAfCluster * emberAfFindServerCluster(EndpointId endpoint, ClusterId clusterId) +{ + auto * endpointType = emberAfFindEndpointType(endpoint); + if (endpointType == nullptr) + { + return nullptr; + } + + for (decltype(endpointType->clusterCount) idx = 0; idx < endpointType->clusterCount; ++idx) + { + auto * cluster = &endpointType->cluster[idx]; + if (cluster->clusterId == clusterId && (cluster->mask & CLUSTER_MASK_SERVER)) + { + return cluster; + } + } + return nullptr; } namespace chip { diff --git a/src/app/util/util.h b/src/app/util/util.h index 120c6b07c671da..ab13ba1768d085 100644 --- a/src/app/util/util.h +++ b/src/app/util/util.h @@ -20,6 +20,7 @@ #include #include +#include // Cluster name structure typedef struct @@ -42,8 +43,6 @@ uint16_t emberAfFindClusterNameIndex(chip::ClusterId cluster); */ EmberAfDifferenceType emberAfGetDifference(uint8_t * pData, EmberAfDifferenceType value, uint8_t dataSize); -bool emberAfContainsAttribute(chip::EndpointId endpoint, chip::ClusterId clusterId, chip::AttributeId attributeId); - /* @brief returns true if the attribute is known to be volatile (i.e. RAM * storage). */ diff --git a/src/controller/tests/data_model/TestRead.cpp b/src/controller/tests/data_model/TestRead.cpp index b72c1fd8a0d6df..6e185c00dcf9ff 100644 --- a/src/controller/tests/data_model/TestRead.cpp +++ b/src/controller/tests/data_model/TestRead.cpp @@ -20,6 +20,8 @@ #include "transport/SecureSession.h" #include #include +#include +#include #include #include #include @@ -239,6 +241,11 @@ bool ConcreteAttributePathExists(const ConcreteAttributePath & aPath) return true; } +Protocols::InteractionModel::Status CheckEventSupportStatus(const ConcreteEventPath & aPath) +{ + return Protocols::InteractionModel::Status::Success; +} + } // namespace app } // namespace chip diff --git a/src/darwin/Framework/CHIP/MTRIMDispatch.mm b/src/darwin/Framework/CHIP/MTRIMDispatch.mm index e1dfd9d426344d..3b9c62c9e503d4 100644 --- a/src/darwin/Framework/CHIP/MTRIMDispatch.mm +++ b/src/darwin/Framework/CHIP/MTRIMDispatch.mm @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -223,6 +224,11 @@ void DispatchSingleClusterCommand(const ConcreteCommandPath & aPath, TLV::TLVRea } } + Protocols::InteractionModel::Status CheckEventSupportStatus(const ConcreteEventPath & aPath) + { + return Protocols::InteractionModel::Status::UnsupportedEvent; + } + } // namespace app } // namespace chip @@ -305,3 +311,42 @@ uint8_t emberAfClusterIndex(EndpointId endpoint, ClusterId clusterId, EmberAfClu } bool emberAfEndpointIndexIsEnabled(uint16_t index) { return index == 0; } + +namespace { +const CommandId acceptedCommands[] = { Clusters::OtaSoftwareUpdateProvider::Commands::QueryImage::Id, + Clusters::OtaSoftwareUpdateProvider::Commands::ApplyUpdateRequest::Id, + Clusters::OtaSoftwareUpdateProvider::Commands::NotifyUpdateApplied::Id, kInvalidCommandId }; +const CommandId generatedCommands[] = { Clusters::OtaSoftwareUpdateProvider::Commands::QueryImageResponse::Id, + Clusters::OtaSoftwareUpdateProvider::Commands::ApplyUpdateResponse::Id, kInvalidCommandId }; +const EmberAfCluster otaProviderCluster { + .clusterId = Clusters::OtaSoftwareUpdateProvider::Id, + .attributes = nullptr, + .attributeCount = 0, + .clusterSize = 0, + .mask = CLUSTER_MASK_SERVER, + .functions = nullptr, + .acceptedCommandList = acceptedCommands, + .generatedCommandList = generatedCommands, + .eventList = nullptr, + .eventCount = 0, +}; +const EmberAfEndpointType otaProviderEndpoint { .cluster = &otaProviderCluster, .clusterCount = 1, .endpointSize = 0 }; +} + +const EmberAfEndpointType * emberAfFindEndpointType(EndpointId endpoint) +{ + if (endpoint == kSupportedEndpoint) { + return &otaProviderEndpoint; + } + + return nullptr; +} + +const EmberAfCluster * emberAfFindServerCluster(EndpointId endpoint, ClusterId cluster) +{ + if (endpoint == kSupportedEndpoint && cluster == Clusters::OtaSoftwareUpdateProvider::Id) { + return &otaProviderCluster; + } + + return nullptr; +} diff --git a/src/darwin/Framework/CHIPTests/MTRDeviceTests.m b/src/darwin/Framework/CHIPTests/MTRDeviceTests.m index eb334429336849..a4e0577f8f92ef 100644 --- a/src/darwin/Framework/CHIPTests/MTRDeviceTests.m +++ b/src/darwin/Framework/CHIPTests/MTRDeviceTests.m @@ -2053,9 +2053,7 @@ - (void)test023_SubscribeMultipleAttributes XCTestExpectation * onOffReportExpectation = [self expectationWithDescription:@"report OnOff attribute"]; XCTestExpectation * attributeErrorReportExpectation = [self expectationWithDescription:@"report nonexistent attribute"]; - // TODO: Right now the server does not seem to actually produce an error - // when trying to subscribe to events on a non-existent endpoint. - // XCTestExpectation * eventErrorReportExpectation = [self expectationWithDescription:@"report nonexistent event"]; + XCTestExpectation * eventErrorReportExpectation = [self expectationWithDescription:@"report nonexistent event"]; globalReportHandler = ^(id _Nullable values, NSError * _Nullable error) { XCTAssertNil(error); XCTAssertEqual([MTRErrorTestUtils errorToZCLErrorCode:error], 0); @@ -2085,18 +2083,14 @@ - (void)test023_SubscribeMultipleAttributes } } else if (result[@"eventPath"] != nil) { MTREventPath * path = result[@"eventPath"]; - XCTAssertEqualObjects(path.endpoint, @1); - XCTAssertEqualObjects(path.cluster, failClusterId); + XCTAssertEqualObjects(path.endpoint, failEndpointId); + XCTAssertEqualObjects(path.cluster, @40); XCTAssertEqualObjects(path.event, @0); XCTAssertNil(result[@"data"]); XCTAssertNotNil(result[@"error"]); XCTAssertEqual( [MTRErrorTestUtils errorToZCLErrorCode:result[@"error"]], MTRInteractionErrorCodeUnsupportedEndpoint); - // TODO: Right now the server does not seem to actually produce an error - // when trying to subscribe to events on a non-existent - // endpoint. Catch it if it starts doing that. - XCTFail("Need to re-enable the eventErrorReportExpectation bits"); - // [eventErrorReportExpectation fulfill]; + [eventErrorReportExpectation fulfill]; } else { XCTFail("Unexpected result dictionary"); } @@ -2122,9 +2116,7 @@ - (void)test023_SubscribeMultipleAttributes resubscriptionScheduled:nil]; // Wait till establishment - [self waitForExpectations:@[ - onOffReportExpectation, attributeErrorReportExpectation, /* eventErrorReportExpectation, */ expectation - ] + [self waitForExpectations:@[ onOffReportExpectation, attributeErrorReportExpectation, eventErrorReportExpectation, expectation ] timeout:kTimeoutInSeconds]; // Set up expectation for report @@ -2133,19 +2125,28 @@ - (void)test023_SubscribeMultipleAttributes XCTAssertNil(error); XCTAssertEqual([MTRErrorTestUtils errorToZCLErrorCode:error], 0); XCTAssertTrue([values isKindOfClass:[NSArray class]]); - NSDictionary * result = values[0]; - MTRAttributePath * path = result[@"attributePath"]; - // We will only be getting incremental reports for the OnOff attribute. - XCTAssertEqualObjects(path.endpoint, @1); - XCTAssertEqualObjects(path.cluster, @6); - XCTAssertEqualObjects(path.attribute, @0); + for (NSDictionary * result in values) { + // Note: we will get updates for our event subscription too, each time + // with errors. + if (result[@"eventPath"] != nil) { + continue; + } - XCTAssertTrue([result[@"data"] isKindOfClass:[NSDictionary class]]); - XCTAssertTrue([result[@"data"][@"type"] isEqualToString:@"Boolean"]); - if ([result[@"data"][@"value"] boolValue] == YES) { - [reportExpectation fulfill]; - globalReportHandler = nil; + MTRAttributePath * path = result[@"attributePath"]; + XCTAssertNotNil(path); + + // We will only be getting incremental attribute reports for the OnOff attribute. + XCTAssertEqualObjects(path.endpoint, @1); + XCTAssertEqualObjects(path.cluster, @6); + XCTAssertEqualObjects(path.attribute, @0); + + XCTAssertTrue([result[@"data"] isKindOfClass:[NSDictionary class]]); + XCTAssertTrue([result[@"data"][@"type"] isEqualToString:@"Boolean"]); + if ([result[@"data"][@"value"] boolValue] == YES) { + [reportExpectation fulfill]; + globalReportHandler = nil; + } } }; @@ -2190,16 +2191,26 @@ - (void)test023_SubscribeMultipleAttributes XCTAssertNil(error); XCTAssertEqual([MTRErrorTestUtils errorToZCLErrorCode:error], 0); XCTAssertTrue([values isKindOfClass:[NSArray class]]); - NSDictionary * result = values[0]; - MTRAttributePath * path = result[@"attributePath"]; - XCTAssertEqualObjects(path.endpoint, @1); - XCTAssertEqualObjects(path.cluster, @6); - XCTAssertEqualObjects(path.attribute, @0); - XCTAssertTrue([result[@"data"] isKindOfClass:[NSDictionary class]]); - XCTAssertTrue([result[@"data"][@"type"] isEqualToString:@"Boolean"]); - if ([result[@"data"][@"value"] boolValue] == NO) { - [reportExpectation fulfill]; - globalReportHandler = nil; + + for (NSDictionary * result in values) { + // Note: we will get updates for our event subscription too, each time + // with errors. + if (result[@"eventPath"] != nil) { + continue; + } + + MTRAttributePath * path = result[@"attributePath"]; + XCTAssertNotNil(path); + + XCTAssertEqualObjects(path.endpoint, @1); + XCTAssertEqualObjects(path.cluster, @6); + XCTAssertEqualObjects(path.attribute, @0); + XCTAssertTrue([result[@"data"] isKindOfClass:[NSDictionary class]]); + XCTAssertTrue([result[@"data"][@"type"] isEqualToString:@"Boolean"]); + if ([result[@"data"][@"value"] boolValue] == NO) { + [reportExpectation fulfill]; + globalReportHandler = nil; + } } }; diff --git a/zzz_generated/chip-tool/zap-generated/test/Commands.h b/zzz_generated/chip-tool/zap-generated/test/Commands.h index dbf8227542040d..ac13daf482c5f8 100644 --- a/zzz_generated/chip-tool/zap-generated/test/Commands.h +++ b/zzz_generated/chip-tool/zap-generated/test/Commands.h @@ -73275,6 +73275,10 @@ class TestEventsSuite : public TestCommand case 2: switch (mTestSubStepIndex) { + case 0: + VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), EMBER_ZCL_STATUS_UNSUPPORTED_CLUSTER)); + mTestSubStepIndex++; + break; default: LogErrorOnFailure(ContinueOnChipMainThread(CHIP_ERROR_INVALID_ARGUMENT)); break; @@ -73460,7 +73464,7 @@ class TestEventsSuite : public TestCommand } case 2: { LogStep(2, "Check reading events from an invalid endpoint"); - mTestSubStepCount = 0; + mTestSubStepCount = 1; return ReadEvent(kIdentityAlpha, GetEndpoint(0), UnitTesting::Id, UnitTesting::Events::TestEvent::Id, false, chip::NullOptional); }