From 1114997269400804741c7b64a8a7d11253f5ea9e Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Wed, 14 Jun 2023 12:15:18 -0400 Subject: [PATCH] Do proper ACL checks on event reads/subscriptions. (#26761) * Do proper ACL checks on event reads/subscriptions. This adds the following functionality: 1. We now correctly detect subsciptions that don't have any access to anything, even if they have an event path in the subscribe request. For paths with a wildcard event id, this check assumes read privileges are needed when event lists are disabled, and uses the actual event-specific privileges when event lists are enabled. 2. When doing reads of an unsupported event, correctly return an errors instead of an empty event list. 3. Fix various unit test mocks to provide the information needed for the new checks. 4. Update expectation in existing YAML test that was checking an "unimplemented event" case. * Address review comments. * Fix darwin build. * Fix Darwin tests, now that we get errors for unsupported events. * Move function declarations to a non-codegen-dependent header. * Handle ACL checks for event wildcards even if we have no EventList. * Update to spec change for unsupported event errors. * Address review comments. --- src/app/InteractionModelEngine.cpp | 176 ++++++++++++++++-- src/app/InteractionModelEngine.h | 21 ++- src/app/reporting/Engine.cpp | 20 +- src/app/tests/TestAclAttribute.cpp | 12 ++ src/app/tests/TestReadInteraction.cpp | 78 ++++---- .../tests/integration/chip_im_initiator.cpp | 6 + .../tests/integration/chip_im_responder.cpp | 7 + src/app/tests/suites/TestEvents.yaml | 3 +- src/app/util/af.h | 17 +- src/app/util/attribute-storage.h | 8 +- .../util/ember-compatibility-functions.cpp | 38 +++- src/app/util/endpoint-config-api.h | 67 +++++++ src/app/util/mock/attribute-storage.cpp | 65 ++++++- src/app/util/util.h | 3 +- src/controller/tests/data_model/TestRead.cpp | 7 + src/darwin/Framework/CHIP/MTRIMDispatch.mm | 45 +++++ .../Framework/CHIPTests/MTRDeviceTests.m | 79 ++++---- .../chip-tool/zap-generated/test/Commands.h | 6 +- 18 files changed, 535 insertions(+), 123 deletions(-) create mode 100644 src/app/util/endpoint-config-api.h 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); }