/* * * Copyright (c) 2021 Project CHIP Authors * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { uint8_t gDebugEventBuffer[128]; uint8_t gInfoEventBuffer[128]; uint8_t gCritEventBuffer[128]; chip::app::CircularEventBuffer gCircularEventBuffer[3]; chip::ClusterId kTestClusterId = 6; // OnOff, but not used as OnOff directly chip::ClusterId kTestEventClusterId = chip::Test::MockClusterId(1); chip::ClusterId kInvalidTestClusterId = 7; chip::EndpointId kTestEndpointId = 1; chip::EndpointId kTestEventEndpointId = chip::Test::kMockEndpoint1; chip::EventId kTestEventIdDebug = chip::Test::MockEventId(1); chip::EventId kTestEventIdCritical = chip::Test::MockEventId(2); chip::TLV::Tag kTestEventTag = chip::TLV::ContextTag(1); chip::EndpointId kInvalidTestEndpointId = 3; chip::DataVersion kTestDataVersion1 = 3; chip::DataVersion kTestDataVersion2 = 5; // Number of items in the list for MockAttributeId(4). constexpr int kMockAttribute4ListLength = 6; static chip::System::Clock::Internal::MockClock gMockClock; static chip::System::Clock::ClockBase * gRealClock; static chip::app::reporting::ReportSchedulerImpl * gReportScheduler; static bool sUsingSubSync = false; const chip::Test::MockNodeConfig & TestMockNodeConfig() { using namespace chip::app; using namespace chip::app::Clusters::Globals::Attributes; using namespace chip::Test; // clang-format off static const chip::Test::MockNodeConfig config({ MockEndpointConfig(kTestEndpointId, { MockClusterConfig(kTestClusterId, { ClusterRevision::Id, FeatureMap::Id, 1, 2, // treated as a list }), MockClusterConfig(kInvalidTestClusterId, { ClusterRevision::Id, FeatureMap::Id, 1, }), }), MockEndpointConfig(kMockEndpoint1, { MockClusterConfig(MockClusterId(1), { ClusterRevision::Id, FeatureMap::Id, }, { MockEventId(1), MockEventId(2), }), MockClusterConfig(MockClusterId(2), { ClusterRevision::Id, FeatureMap::Id, MockAttributeId(1), }), }), MockEndpointConfig(kMockEndpoint2, { MockClusterConfig(MockClusterId(1), { ClusterRevision::Id, FeatureMap::Id, }), MockClusterConfig(MockClusterId(2), { ClusterRevision::Id, FeatureMap::Id, MockAttributeId(1), MockAttributeId(2), }), MockClusterConfig(MockClusterId(3), { ClusterRevision::Id, FeatureMap::Id, MockAttributeId(1), MockAttributeId(2), MockAttributeId(3), }), }), MockEndpointConfig(chip::Test::kMockEndpoint3, { MockClusterConfig(MockClusterId(1), { ClusterRevision::Id, FeatureMap::Id, MockAttributeId(1), }), MockClusterConfig(MockClusterId(2), { ClusterRevision::Id, FeatureMap::Id, MockAttributeId(1), MockAttributeId(2), MockAttributeId(3), MockAttributeId(4), }), MockClusterConfig(MockClusterId(3), { ClusterRevision::Id, FeatureMap::Id, }), MockClusterConfig(MockClusterId(4), { ClusterRevision::Id, FeatureMap::Id, }), }), }); // clang-format on return config; } class TestEventGenerator : public chip::app::EventLoggingDelegate { public: CHIP_ERROR WriteEvent(chip::TLV::TLVWriter & aWriter) { chip::TLV::TLVType dataContainerType; ReturnErrorOnFailure(aWriter.StartContainer(chip::TLV::ContextTag(chip::to_underlying(chip::app::EventDataIB::Tag::kData)), chip::TLV::kTLVType_Structure, dataContainerType)); ReturnErrorOnFailure(aWriter.Put(kTestEventTag, mStatus)); return aWriter.EndContainer(dataContainerType); } void SetStatus(int32_t aStatus) { mStatus = aStatus; } private: int32_t mStatus; }; void GenerateEvents() { chip::EventNumber eid1, eid2; chip::app::EventOptions options1; options1.mPath = { kTestEventEndpointId, kTestEventClusterId, kTestEventIdDebug }; options1.mPriority = chip::app::PriorityLevel::Info; chip::app::EventOptions options2; options2.mPath = { kTestEventEndpointId, kTestEventClusterId, kTestEventIdCritical }; options2.mPriority = chip::app::PriorityLevel::Critical; TestEventGenerator testEventGenerator; chip::app::EventManagement & logMgmt = chip::app::EventManagement::GetInstance(); ChipLogDetail(DataManagement, "Generating Events"); testEventGenerator.SetStatus(0); EXPECT_EQ(logMgmt.LogEvent(&testEventGenerator, options1, eid1), CHIP_NO_ERROR); testEventGenerator.SetStatus(1); EXPECT_EQ(logMgmt.LogEvent(&testEventGenerator, options2, eid2), CHIP_NO_ERROR); } class MockInteractionModelApp : public chip::app::ReadClient::Callback { public: void OnEventData(const chip::app::EventHeader & aEventHeader, chip::TLV::TLVReader * apData, const chip::app::StatusIB * apStatus) override { ++mNumDataElementIndex; mGotEventResponse = true; if (apStatus != nullptr && !apStatus->IsSuccess()) { mNumReadEventFailureStatusReceived++; mLastStatusReceived = *apStatus; } else { mLastStatusReceived = chip::app::StatusIB(); } } void OnAttributeData(const chip::app::ConcreteDataAttributePath & aPath, chip::TLV::TLVReader * apData, const chip::app::StatusIB & status) override { if (status.mStatus == chip::Protocols::InteractionModel::Status::Success) { mReceivedAttributePaths.push_back(aPath); mNumAttributeResponse++; mGotReport = true; if (aPath.IsListItemOperation()) { mNumArrayItems++; } else if (aPath.IsListOperation()) { // This is an entire list of things; count up how many. chip::TLV::TLVType containerType; if (apData->EnterContainer(containerType) == CHIP_NO_ERROR) { size_t count = 0; if (chip::TLV::Utilities::Count(*apData, count, /* aRecurse = */ false) == CHIP_NO_ERROR) { mNumArrayItems += static_cast(count); } } } } mLastStatusReceived = status; } void OnError(CHIP_ERROR aError) override { mError = aError; mReadError = true; } void OnDone(chip::app::ReadClient *) override {} void OnDeallocatePaths(chip::app::ReadPrepareParams && aReadPrepareParams) override { if (aReadPrepareParams.mpAttributePathParamsList != nullptr) { delete[] aReadPrepareParams.mpAttributePathParamsList; } if (aReadPrepareParams.mpEventPathParamsList != nullptr) { delete[] aReadPrepareParams.mpEventPathParamsList; } if (aReadPrepareParams.mpDataVersionFilterList != nullptr) { delete[] aReadPrepareParams.mpDataVersionFilterList; } } int mNumDataElementIndex = 0; bool mGotEventResponse = false; int mNumReadEventFailureStatusReceived = 0; int mNumAttributeResponse = 0; int mNumArrayItems = 0; bool mGotReport = false; bool mReadError = false; chip::app::ReadHandler * mpReadHandler = nullptr; chip::app::StatusIB mLastStatusReceived; CHIP_ERROR mError = CHIP_NO_ERROR; std::vector mReceivedAttributePaths; }; // // This dummy callback is used with a bunch of the tests below that don't go through // the normal call-path of having the IM engine allocate the ReadHandler object. Instead, // the object is allocated on stack for the purposes of a very narrow, tightly-coupled test. // // The typical callback implementor is the engine, but that would proceed to return the object // back to the handler pool (which we obviously don't want in this case). This just no-ops those calls. // class NullReadHandlerCallback : public chip::app::ReadHandler::ManagementCallback { public: void OnDone(chip::app::ReadHandler & apReadHandlerObj) override {} chip::app::ReadHandler::ApplicationCallback * GetAppCallback() override { return nullptr; } chip::app::InteractionModelEngine * GetInteractionModelEngine() override { return chip::app::InteractionModelEngine::GetInstance(); } }; } // namespace using ReportScheduler = chip::app::reporting::ReportScheduler; using ReportSchedulerImpl = chip::app::reporting::ReportSchedulerImpl; using ReadHandlerNode = chip::app::reporting::ReportScheduler::ReadHandlerNode; namespace chip { namespace app { using Seconds16 = System::Clock::Seconds16; using Milliseconds32 = System::Clock::Milliseconds32; // TODO: Add support for a 2nd Test Context by making sSyncScheduler = true (this was not ported from NL Tests yet) class TestReadInteraction : public chip::Test::AppContext { public: static void SetUpTestSuite() { AppContext::SetUpTestSuite(); gRealClock = &chip::System::SystemClock(); chip::System::Clock::Internal::SetSystemClockForTesting(&gMockClock); if (sSyncScheduler) { gReportScheduler = chip::app::reporting::GetSynchronizedReportScheduler(); sUsingSubSync = true; } else { gReportScheduler = chip::app::reporting::GetDefaultReportScheduler(); } } static void TearDownTestSuite() { chip::System::Clock::Internal::SetSystemClockForTesting(gRealClock); AppContext::TearDownTestSuite(); } void SetUp() override { const chip::app::LogStorageResources logStorageResources[] = { { &gDebugEventBuffer[0], sizeof(gDebugEventBuffer), chip::app::PriorityLevel::Debug }, { &gInfoEventBuffer[0], sizeof(gInfoEventBuffer), chip::app::PriorityLevel::Info }, { &gCritEventBuffer[0], sizeof(gCritEventBuffer), chip::app::PriorityLevel::Critical }, }; AppContext::SetUp(); ASSERT_EQ(mEventCounter.Init(0), CHIP_NO_ERROR); chip::app::EventManagement::CreateEventManagement(&GetExchangeManager(), ArraySize(logStorageResources), gCircularEventBuffer, logStorageResources, &mEventCounter); mOldProvider = InteractionModelEngine::GetInstance()->SetDataModelProvider(&TestImCustomDataModel::Instance()); chip::Test::SetMockNodeConfig(TestMockNodeConfig()); chip::Test::SetVersionTo(chip::Test::kTestDataVersion1); } void TearDown() override { chip::Test::ResetMockNodeConfig(); InteractionModelEngine::GetInstance()->SetDataModelProvider(mOldProvider); chip::app::EventManagement::DestroyEventManagement(); AppContext::TearDown(); } void TestReadClient(); void TestReadUnexpectedSubscriptionId(); void TestReadHandler(); void TestReadHandlerSetMaxReportingInterval(); void TestReadClientGenerateAttributePathList(); void TestReadClientGenerateInvalidAttributePathList(); void TestReadClientInvalidReport(); void TestReadClientInvalidAttributeId(); void TestReadHandlerInvalidAttributePath(); void TestReadClientGenerateOneEventPaths(); void TestReadClientGenerateTwoEventPaths(); void TestProcessSubscribeRequest(); void TestICDProcessSubscribeRequestSupMaxIntervalCeiling(); void TestICDProcessSubscribeRequestInfMaxIntervalCeiling(); void TestICDProcessSubscribeRequestSupMinInterval(); void TestICDProcessSubscribeRequestMaxMinInterval(); void TestICDProcessSubscribeRequestInvalidIdleModeDuration(); void TestSubscribeRoundtrip(); void TestSubscribeEarlyReport(); void TestSubscribeUrgentWildcardEvent(); void TestSubscribeInvalidAttributePathRoundtrip(); void TestPostSubscribeRoundtripStatusReportTimeout(); void TestReadClientReceiveInvalidMessage(); void TestSubscribeClientReceiveInvalidStatusResponse(); void TestSubscribeClientReceiveWellFormedStatusResponse(); void TestSubscribeClientReceiveInvalidReportMessage(); void TestSubscribeClientReceiveUnsolicitedInvalidReportMessage(); void TestSubscribeClientReceiveInvalidSubscribeResponseMessage(); void TestSubscribeClientReceiveUnsolicitedReportMessageWithInvalidSubscriptionId(); void TestReadChunkingInvalidSubscriptionId(); void TestReadHandlerMalformedSubscribeRequest(); void TestReadHandlerMalformedReadRequest1(); void TestReadHandlerMalformedReadRequest2(); void TestSubscribeSendUnknownMessage(); void TestSubscribeSendInvalidStatusReport(); void TestReadHandlerInvalidSubscribeRequest(); void TestShutdownSubscription(); void TestSubscriptionReportWithDefunctSession(); enum class ReportType : uint8_t { kValid, kInvalidNoAttributeId, kInvalidOutOfRangeAttributeId, }; static void GenerateReportData(System::PacketBufferHandle & aPayload, ReportType aReportType, bool aSuppressResponse, bool aHasSubscriptionId); protected: chip::MonotonicallyIncreasingCounter mEventCounter; static bool sSyncScheduler; chip::app::DataModel::Provider * mOldProvider = nullptr; }; bool TestReadInteraction::sSyncScheduler = false; void TestReadInteraction::GenerateReportData(System::PacketBufferHandle & aPayload, ReportType aReportType, bool aSuppressResponse, bool aHasSubscriptionId = false) { System::PacketBufferTLVWriter writer; writer.Init(std::move(aPayload)); ReportDataMessage::Builder reportDataMessageBuilder; EXPECT_EQ(reportDataMessageBuilder.Init(&writer), CHIP_NO_ERROR); if (aHasSubscriptionId) { reportDataMessageBuilder.SubscriptionId(1); EXPECT_EQ(reportDataMessageBuilder.GetError(), CHIP_NO_ERROR); } AttributeReportIBs::Builder & attributeReportIBsBuilder = reportDataMessageBuilder.CreateAttributeReportIBs(); EXPECT_EQ(reportDataMessageBuilder.GetError(), CHIP_NO_ERROR); AttributeReportIB::Builder & attributeReportIBBuilder = attributeReportIBsBuilder.CreateAttributeReport(); EXPECT_EQ(attributeReportIBsBuilder.GetError(), CHIP_NO_ERROR); AttributeDataIB::Builder & attributeDataIBBuilder = attributeReportIBBuilder.CreateAttributeData(); EXPECT_EQ(attributeReportIBBuilder.GetError(), CHIP_NO_ERROR); attributeDataIBBuilder.DataVersion(2); EXPECT_EQ(attributeDataIBBuilder.GetError(), CHIP_NO_ERROR); AttributePathIB::Builder & attributePathBuilder = attributeDataIBBuilder.CreatePath(); EXPECT_EQ(attributeDataIBBuilder.GetError(), CHIP_NO_ERROR); if (aReportType == ReportType::kInvalidNoAttributeId) { attributePathBuilder.Node(1).Endpoint(2).Cluster(3).ListIndex(5).EndOfAttributePathIB(); } else if (aReportType == ReportType::kInvalidOutOfRangeAttributeId) { attributePathBuilder.Node(1).Endpoint(2).Cluster(3).Attribute(0xFFF18000).EndOfAttributePathIB(); } else { EXPECT_EQ(aReportType, ReportType::kValid); attributePathBuilder.Node(1).Endpoint(2).Cluster(3).Attribute(4).EndOfAttributePathIB(); } EXPECT_EQ(attributePathBuilder.GetError(), CHIP_NO_ERROR); // Construct attribute data { chip::TLV::TLVWriter * pWriter = attributeDataIBBuilder.GetWriter(); chip::TLV::TLVType dummyType = chip::TLV::kTLVType_NotSpecified; EXPECT_EQ(pWriter->StartContainer(chip::TLV::ContextTag(chip::to_underlying(chip::app::AttributeDataIB::Tag::kData)), chip::TLV::kTLVType_Structure, dummyType), CHIP_NO_ERROR); EXPECT_EQ(pWriter->PutBoolean(chip::TLV::ContextTag(1), true), CHIP_NO_ERROR); EXPECT_EQ(pWriter->EndContainer(dummyType), CHIP_NO_ERROR); } attributeDataIBBuilder.EndOfAttributeDataIB(); EXPECT_EQ(attributeDataIBBuilder.GetError(), CHIP_NO_ERROR); attributeReportIBBuilder.EndOfAttributeReportIB(); EXPECT_EQ(attributeReportIBBuilder.GetError(), CHIP_NO_ERROR); attributeReportIBsBuilder.EndOfAttributeReportIBs(); EXPECT_EQ(attributeReportIBsBuilder.GetError(), CHIP_NO_ERROR); reportDataMessageBuilder.MoreChunkedMessages(false); EXPECT_EQ(reportDataMessageBuilder.GetError(), CHIP_NO_ERROR); reportDataMessageBuilder.SuppressResponse(aSuppressResponse); EXPECT_EQ(reportDataMessageBuilder.GetError(), CHIP_NO_ERROR); reportDataMessageBuilder.EndOfReportDataMessage(); EXPECT_EQ(reportDataMessageBuilder.GetError(), CHIP_NO_ERROR); EXPECT_EQ(writer.Finalize(&aPayload), CHIP_NO_ERROR); } TEST_F_FROM_FIXTURE(TestReadInteraction, TestReadClient) { MockInteractionModelApp delegate; app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Read); System::PacketBufferHandle buf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); EXPECT_EQ(readClient.SendRequest(readPrepareParams), CHIP_NO_ERROR); // We don't actually want to deliver that message, because we want to // synthesize the read response. But we don't want it hanging around // forever either. GetLoopback().mNumMessagesToDrop = 1; DrainAndServiceIO(); GenerateReportData(buf, ReportType::kValid, true /* aSuppressResponse*/); EXPECT_EQ(readClient.ProcessReportData(std::move(buf), ReadClient::ReportType::kContinuingTransaction), CHIP_NO_ERROR); } TEST_F_FROM_FIXTURE(TestReadInteraction, TestReadUnexpectedSubscriptionId) { MockInteractionModelApp delegate; app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Read); System::PacketBufferHandle buf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); EXPECT_EQ(readClient.SendRequest(readPrepareParams), CHIP_NO_ERROR); // We don't actually want to deliver that message, because we want to // synthesize the read response. But we don't want it hanging around // forever either. GetLoopback().mNumMessagesToDrop = 1; DrainAndServiceIO(); // For read, we don't expect there is subscription id in report data. GenerateReportData(buf, ReportType::kValid, true /* aSuppressResponse*/, true /*aHasSubscriptionId*/); EXPECT_EQ(readClient.ProcessReportData(std::move(buf), ReadClient::ReportType::kContinuingTransaction), CHIP_ERROR_INVALID_ARGUMENT); } TEST_F_FROM_FIXTURE(TestReadInteraction, TestReadHandler) { System::PacketBufferTLVWriter writer; System::PacketBufferHandle reportDatabuf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize); System::PacketBufferHandle readRequestbuf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize); ReadRequestMessage::Builder readRequestBuilder; NullReadHandlerCallback nullCallback; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); { Messaging::ExchangeContext * exchangeCtx = NewExchangeToAlice(nullptr, false); ReadHandler readHandler(nullCallback, exchangeCtx, chip::app::ReadHandler::InteractionType::Read, gReportScheduler, CodegenDataModelProviderInstance()); GenerateReportData(reportDatabuf, ReportType::kValid, false /* aSuppressResponse*/); EXPECT_EQ(readHandler.SendReportData(std::move(reportDatabuf), false), CHIP_ERROR_INCORRECT_STATE); writer.Init(std::move(readRequestbuf)); EXPECT_EQ(readRequestBuilder.Init(&writer), CHIP_NO_ERROR); AttributePathIBs::Builder & attributePathListBuilder = readRequestBuilder.CreateAttributeRequests(); EXPECT_EQ(attributePathListBuilder.GetError(), CHIP_NO_ERROR); AttributePathIB::Builder & attributePathBuilder = attributePathListBuilder.CreatePath(); EXPECT_EQ(attributePathListBuilder.GetError(), CHIP_NO_ERROR); attributePathBuilder.Node(1).Endpoint(2).Cluster(3).Attribute(4).EndOfAttributePathIB(); EXPECT_EQ(attributePathBuilder.GetError(), CHIP_NO_ERROR); attributePathListBuilder.EndOfAttributePathIBs(); EXPECT_EQ(attributePathListBuilder.GetError(), CHIP_NO_ERROR); EXPECT_EQ(readRequestBuilder.GetError(), CHIP_NO_ERROR); readRequestBuilder.IsFabricFiltered(false).EndOfReadRequestMessage(); EXPECT_EQ(readRequestBuilder.GetError(), CHIP_NO_ERROR); EXPECT_EQ(writer.Finalize(&readRequestbuf), CHIP_NO_ERROR); // Call ProcessReadRequest directly, because OnInitialRequest sends status // messages on the wire instead of returning an error. EXPECT_EQ(readHandler.ProcessReadRequest(std::move(readRequestbuf)), CHIP_NO_ERROR); } engine->Shutdown(); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } TEST_F_FROM_FIXTURE(TestReadInteraction, TestReadHandlerSetMaxReportingInterval) { System::PacketBufferTLVWriter writer; System::PacketBufferHandle subscribeRequestbuf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize); SubscribeRequestMessage::Builder subscribeRequestBuilder; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); uint16_t kIntervalInfMinInterval = 119; uint16_t kMinInterval = 120; uint16_t kMaxIntervalCeiling = 500; Messaging::ExchangeContext * exchangeCtx = NewExchangeToAlice(nullptr, false); { uint16_t minInterval; uint16_t maxInterval; // Configure ReadHandler ReadHandler readHandler(*engine, exchangeCtx, chip::app::ReadHandler::InteractionType::Read, gReportScheduler, CodegenDataModelProviderInstance()); writer.Init(std::move(subscribeRequestbuf)); EXPECT_EQ(subscribeRequestBuilder.Init(&writer), CHIP_NO_ERROR); subscribeRequestBuilder.KeepSubscriptions(true); EXPECT_EQ(subscribeRequestBuilder.GetError(), CHIP_NO_ERROR); subscribeRequestBuilder.MinIntervalFloorSeconds(kMinInterval); EXPECT_EQ(subscribeRequestBuilder.GetError(), CHIP_NO_ERROR); subscribeRequestBuilder.MaxIntervalCeilingSeconds(kMaxIntervalCeiling); EXPECT_EQ(subscribeRequestBuilder.GetError(), CHIP_NO_ERROR); AttributePathIBs::Builder & attributePathListBuilder = subscribeRequestBuilder.CreateAttributeRequests(); EXPECT_EQ(attributePathListBuilder.GetError(), CHIP_NO_ERROR); AttributePathIB::Builder & attributePathBuilder = attributePathListBuilder.CreatePath(); EXPECT_EQ(attributePathListBuilder.GetError(), CHIP_NO_ERROR); attributePathBuilder.Node(1).Endpoint(2).Cluster(3).Attribute(4).ListIndex(5).EndOfAttributePathIB(); EXPECT_EQ(attributePathBuilder.GetError(), CHIP_NO_ERROR); attributePathListBuilder.EndOfAttributePathIBs(); EXPECT_EQ(attributePathListBuilder.GetError(), CHIP_NO_ERROR); subscribeRequestBuilder.IsFabricFiltered(false).EndOfSubscribeRequestMessage(); EXPECT_EQ(subscribeRequestBuilder.GetError(), CHIP_NO_ERROR); EXPECT_EQ(subscribeRequestBuilder.GetError(), CHIP_NO_ERROR); EXPECT_EQ(writer.Finalize(&subscribeRequestbuf), CHIP_NO_ERROR); EXPECT_EQ(readHandler.ProcessSubscribeRequest(std::move(subscribeRequestbuf)), CHIP_NO_ERROR); #if CHIP_CONFIG_ENABLE_ICD_SERVER // When an ICD build, the default behavior is to select the IdleModeDuration as MaxInterval kMaxIntervalCeiling = readHandler.GetPublisherSelectedIntervalLimit(); #endif // Try to change the MaxInterval while ReadHandler is active EXPECT_EQ(readHandler.SetMaxReportingInterval(340), CHIP_ERROR_INCORRECT_STATE); readHandler.GetReportingIntervals(minInterval, maxInterval); EXPECT_EQ(kMaxIntervalCeiling, maxInterval); // Set ReadHandler to Idle to allow MaxInterval changes readHandler.MoveToState(ReadHandler::HandlerState::Idle); // TC1: MaxInterval < MinIntervalFloor EXPECT_EQ(readHandler.SetMaxReportingInterval(kIntervalInfMinInterval), CHIP_ERROR_INVALID_ARGUMENT); readHandler.GetReportingIntervals(minInterval, maxInterval); EXPECT_EQ(kMaxIntervalCeiling, maxInterval); // TC2: MaxInterval == MinIntervalFloor EXPECT_EQ(readHandler.SetMaxReportingInterval(kMinInterval), CHIP_NO_ERROR); readHandler.GetReportingIntervals(minInterval, maxInterval); EXPECT_EQ(kMinInterval, maxInterval); // TC3: Minterval < MaxInterval < max(GetPublisherSelectedIntervalLimit(), mSubscriberRequestedMaxInterval) EXPECT_EQ(readHandler.SetMaxReportingInterval(kMaxIntervalCeiling), CHIP_NO_ERROR); readHandler.GetReportingIntervals(minInterval, maxInterval); EXPECT_EQ(kMaxIntervalCeiling, maxInterval); // TC4: MaxInterval == Subscriber Requested Max Interval EXPECT_EQ(readHandler.SetMaxReportingInterval(readHandler.GetSubscriberRequestedMaxInterval()), CHIP_NO_ERROR); readHandler.GetReportingIntervals(minInterval, maxInterval); EXPECT_EQ(readHandler.GetSubscriberRequestedMaxInterval(), maxInterval); // TC4: MaxInterval == GetPublisherSelectedIntervalLimit() EXPECT_EQ(readHandler.SetMaxReportingInterval(readHandler.GetPublisherSelectedIntervalLimit()), CHIP_NO_ERROR); readHandler.GetReportingIntervals(minInterval, maxInterval); EXPECT_EQ(readHandler.GetPublisherSelectedIntervalLimit(), maxInterval); // TC5: MaxInterval > max(GetPublisherSelectedIntervalLimit(), mSubscriberRequestedMaxInterval) EXPECT_EQ(readHandler.SetMaxReportingInterval(std::numeric_limits::max()), CHIP_ERROR_INVALID_ARGUMENT); readHandler.GetReportingIntervals(minInterval, maxInterval); EXPECT_EQ(readHandler.GetPublisherSelectedIntervalLimit(), maxInterval); } engine->Shutdown(); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } TEST_F_FROM_FIXTURE(TestReadInteraction, TestReadClientGenerateAttributePathList) { MockInteractionModelApp delegate; System::PacketBufferHandle msgBuf; System::PacketBufferTLVWriter writer; ReadRequestMessage::Builder request; msgBuf = System::PacketBufferHandle::New(kMaxSecureSduLengthBytes); EXPECT_FALSE(msgBuf.IsNull()); writer.Init(std::move(msgBuf)); EXPECT_EQ(request.Init(&writer), CHIP_NO_ERROR); app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Read); AttributePathParams attributePathParams[2]; attributePathParams[0].mAttributeId = 0; attributePathParams[1].mAttributeId = 0; attributePathParams[1].mListIndex = 0; Span attributePaths(attributePathParams, 2 /*aAttributePathParamsListSize*/); AttributePathIBs::Builder & attributePathListBuilder = request.CreateAttributeRequests(); EXPECT_EQ(readClient.GenerateAttributePaths(attributePathListBuilder, attributePaths), CHIP_NO_ERROR); } TEST_F_FROM_FIXTURE(TestReadInteraction, TestReadClientGenerateInvalidAttributePathList) { MockInteractionModelApp delegate; System::PacketBufferHandle msgBuf; System::PacketBufferTLVWriter writer; ReadRequestMessage::Builder request; msgBuf = System::PacketBufferHandle::New(kMaxSecureSduLengthBytes); EXPECT_FALSE(msgBuf.IsNull()); writer.Init(std::move(msgBuf)); app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Read); EXPECT_EQ(request.Init(&writer), CHIP_NO_ERROR); AttributePathParams attributePathParams[2]; attributePathParams[0].mAttributeId = 0; attributePathParams[1].mListIndex = 0; Span attributePaths(attributePathParams, 2 /*aAttributePathParamsListSize*/); AttributePathIBs::Builder & attributePathListBuilder = request.CreateAttributeRequests(); EXPECT_EQ(readClient.GenerateAttributePaths(attributePathListBuilder, attributePaths), CHIP_ERROR_IM_MALFORMED_ATTRIBUTE_PATH_IB); } TEST_F_FROM_FIXTURE(TestReadInteraction, TestReadClientInvalidReport) { MockInteractionModelApp delegate; System::PacketBufferHandle buf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize); app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Read); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); EXPECT_EQ(readClient.SendRequest(readPrepareParams), CHIP_NO_ERROR); // We don't actually want to deliver that message, because we want to // synthesize the read response. But we don't want it hanging around // forever either. GetLoopback().mNumMessagesToDrop = 1; DrainAndServiceIO(); GenerateReportData(buf, ReportType::kInvalidNoAttributeId, true /* aSuppressResponse*/); EXPECT_EQ(readClient.ProcessReportData(std::move(buf), ReadClient::ReportType::kContinuingTransaction), CHIP_ERROR_IM_MALFORMED_ATTRIBUTE_PATH_IB); } TEST_F_FROM_FIXTURE(TestReadInteraction, TestReadClientInvalidAttributeId) { MockInteractionModelApp delegate; System::PacketBufferHandle buf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize); app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Read); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); EXPECT_EQ(readClient.SendRequest(readPrepareParams), CHIP_NO_ERROR); // We don't actually want to deliver that message, because we want to // synthesize the read response. But we don't want it hanging around // forever either. GetLoopback().mNumMessagesToDrop = 1; DrainAndServiceIO(); GenerateReportData(buf, ReportType::kInvalidOutOfRangeAttributeId, true /* aSuppressResponse*/); // Overall processing should succeed. EXPECT_EQ(readClient.ProcessReportData(std::move(buf), ReadClient::ReportType::kContinuingTransaction), CHIP_NO_ERROR); // We should not have gotten any attribute reports or errors. EXPECT_FALSE(delegate.mGotEventResponse); EXPECT_EQ(delegate.mNumAttributeResponse, 0); EXPECT_FALSE(delegate.mGotReport); EXPECT_FALSE(delegate.mReadError); } TEST_F_FROM_FIXTURE(TestReadInteraction, TestReadHandlerInvalidAttributePath) { CHIP_ERROR err = CHIP_NO_ERROR; System::PacketBufferTLVWriter writer; System::PacketBufferHandle reportDatabuf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize); System::PacketBufferHandle readRequestbuf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize); ReadRequestMessage::Builder readRequestBuilder; NullReadHandlerCallback nullCallback; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); { Messaging::ExchangeContext * exchangeCtx = NewExchangeToAlice(nullptr, false); ReadHandler readHandler(nullCallback, exchangeCtx, chip::app::ReadHandler::InteractionType::Read, gReportScheduler, CodegenDataModelProviderInstance()); GenerateReportData(reportDatabuf, ReportType::kValid, false /* aSuppressResponse*/); EXPECT_EQ(readHandler.SendReportData(std::move(reportDatabuf), false), CHIP_ERROR_INCORRECT_STATE); writer.Init(std::move(readRequestbuf)); EXPECT_EQ(readRequestBuilder.Init(&writer), CHIP_NO_ERROR); AttributePathIBs::Builder & attributePathListBuilder = readRequestBuilder.CreateAttributeRequests(); EXPECT_EQ(attributePathListBuilder.GetError(), CHIP_NO_ERROR); AttributePathIB::Builder & attributePathBuilder = attributePathListBuilder.CreatePath(); EXPECT_EQ(attributePathListBuilder.GetError(), CHIP_NO_ERROR); attributePathBuilder.Node(1).Endpoint(2).Cluster(3).EndOfAttributePathIB(); EXPECT_EQ(attributePathBuilder.GetError(), CHIP_NO_ERROR); EXPECT_EQ(attributePathListBuilder.EndOfAttributePathIBs(), CHIP_NO_ERROR); readRequestBuilder.EndOfReadRequestMessage(); EXPECT_EQ(readRequestBuilder.GetError(), CHIP_NO_ERROR); EXPECT_EQ(writer.Finalize(&readRequestbuf), CHIP_NO_ERROR); err = readHandler.ProcessReadRequest(std::move(readRequestbuf)); ChipLogError(DataManagement, "The error is %s", ErrorStr(err)); EXPECT_EQ(err, CHIP_ERROR_END_OF_TLV); // // In the call above to ProcessReadRequest, the handler will not actually close out the EC since // it expects the ExchangeManager to do so automatically given it's not calling WillSend() on the EC, // and is not sending a response back. // // Consequently, we have to manually close out the EC here in this test since we're not actually calling // methods on these objects in a manner similar to how it would happen in normal use. // exchangeCtx->Close(); } engine->Shutdown(); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } TEST_F_FROM_FIXTURE(TestReadInteraction, TestReadClientGenerateOneEventPaths) { MockInteractionModelApp delegate; System::PacketBufferHandle msgBuf; System::PacketBufferTLVWriter writer; ReadRequestMessage::Builder request; msgBuf = System::PacketBufferHandle::New(kMaxSecureSduLengthBytes); EXPECT_FALSE(msgBuf.IsNull()); writer.Init(std::move(msgBuf)); EXPECT_EQ(request.Init(&writer), CHIP_NO_ERROR); app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Read); chip::app::EventPathParams eventPathParams[1]; eventPathParams[0].mEndpointId = 2; eventPathParams[0].mClusterId = 3; eventPathParams[0].mEventId = 4; EventPathIBs::Builder & eventPathListBuilder = request.CreateEventRequests(); Span eventPaths(eventPathParams, 1 /*aEventPathParamsListSize*/); EXPECT_EQ(readClient.GenerateEventPaths(eventPathListBuilder, eventPaths), CHIP_NO_ERROR); request.IsFabricFiltered(false).EndOfReadRequestMessage(); EXPECT_EQ(CHIP_NO_ERROR, request.GetError()); EXPECT_EQ(writer.Finalize(&msgBuf), CHIP_NO_ERROR); chip::System::PacketBufferTLVReader reader; ReadRequestMessage::Parser readRequestParser; reader.Init(msgBuf.Retain()); EXPECT_EQ(readRequestParser.Init(reader), CHIP_NO_ERROR); #if CHIP_CONFIG_IM_PRETTY_PRINT readRequestParser.PrettyPrint(); #endif EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } TEST_F_FROM_FIXTURE(TestReadInteraction, TestReadClientGenerateTwoEventPaths) { MockInteractionModelApp delegate; System::PacketBufferHandle msgBuf; System::PacketBufferTLVWriter writer; ReadRequestMessage::Builder request; msgBuf = System::PacketBufferHandle::New(kMaxSecureSduLengthBytes); EXPECT_FALSE(msgBuf.IsNull()); writer.Init(std::move(msgBuf)); EXPECT_EQ(request.Init(&writer), CHIP_NO_ERROR); app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Read); chip::app::EventPathParams eventPathParams[2]; eventPathParams[0].mEndpointId = 2; eventPathParams[0].mClusterId = 3; eventPathParams[0].mEventId = 4; eventPathParams[1].mEndpointId = 2; eventPathParams[1].mClusterId = 3; eventPathParams[1].mEventId = 5; EventPathIBs::Builder & eventPathListBuilder = request.CreateEventRequests(); Span eventPaths(eventPathParams, 2 /*aEventPathParamsListSize*/); EXPECT_EQ(readClient.GenerateEventPaths(eventPathListBuilder, eventPaths), CHIP_NO_ERROR); request.IsFabricFiltered(false).EndOfReadRequestMessage(); EXPECT_EQ(CHIP_NO_ERROR, request.GetError()); EXPECT_EQ(writer.Finalize(&msgBuf), CHIP_NO_ERROR); chip::System::PacketBufferTLVReader reader; ReadRequestMessage::Parser readRequestParser; reader.Init(msgBuf.Retain()); EXPECT_EQ(readRequestParser.Init(reader), CHIP_NO_ERROR); #if CHIP_CONFIG_IM_PRETTY_PRINT readRequestParser.PrettyPrint(); #endif EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } TEST_F(TestReadInteraction, TestReadRoundtrip) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); GenerateEvents(); MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); EXPECT_FALSE(delegate.mGotEventResponse); chip::app::EventPathParams eventPathParams[1]; eventPathParams[0].mEndpointId = kTestEventEndpointId; eventPathParams[0].mClusterId = kTestEventClusterId; chip::app::AttributePathParams attributePathParams[2]; attributePathParams[0].mEndpointId = kTestEndpointId; attributePathParams[0].mClusterId = kTestClusterId; attributePathParams[0].mAttributeId = 1; attributePathParams[1].mEndpointId = kTestEndpointId; attributePathParams[1].mClusterId = kTestClusterId; attributePathParams[1].mAttributeId = 2; attributePathParams[1].mListIndex = 1; ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); readPrepareParams.mpEventPathParamsList = eventPathParams; readPrepareParams.mEventPathParamsListSize = 1; readPrepareParams.mpAttributePathParamsList = attributePathParams; readPrepareParams.mAttributePathParamsListSize = 2; readPrepareParams.mEventNumber.SetValue(1); { app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Read); EXPECT_EQ(readClient.SendRequest(readPrepareParams), CHIP_NO_ERROR); DrainAndServiceIO(); EXPECT_EQ(delegate.mNumDataElementIndex, 1); EXPECT_TRUE(delegate.mGotEventResponse); EXPECT_EQ(delegate.mNumAttributeResponse, 2); EXPECT_TRUE(delegate.mGotReport); EXPECT_FALSE(delegate.mReadError); delegate.mGotEventResponse = false; delegate.mNumAttributeResponse = 0; delegate.mGotReport = false; } { app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Read); EXPECT_EQ(readClient.SendRequest(readPrepareParams), CHIP_NO_ERROR); DrainAndServiceIO(); EXPECT_TRUE(delegate.mGotEventResponse); EXPECT_EQ(delegate.mNumAttributeResponse, 2); EXPECT_TRUE(delegate.mGotReport); EXPECT_FALSE(delegate.mReadError); // By now we should have closed all exchanges and sent all pending acks, so // there should be no queued-up things in the retransmit table. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); } EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); engine->Shutdown(); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } TEST_F(TestReadInteraction, TestReadRoundtripWithDataVersionFilter) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); GenerateEvents(); MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); EXPECT_FALSE(delegate.mGotEventResponse); chip::app::AttributePathParams attributePathParams[2]; attributePathParams[0].mEndpointId = kTestEndpointId; attributePathParams[0].mClusterId = kTestClusterId; attributePathParams[0].mAttributeId = 1; attributePathParams[1].mEndpointId = kTestEndpointId; attributePathParams[1].mClusterId = kTestClusterId; attributePathParams[1].mAttributeId = 2; attributePathParams[1].mListIndex = 1; chip::app::DataVersionFilter dataVersionFilters[1]; dataVersionFilters[0].mEndpointId = kTestEndpointId; dataVersionFilters[0].mClusterId = kTestClusterId; dataVersionFilters[0].mDataVersion.SetValue(kTestDataVersion1); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); readPrepareParams.mpAttributePathParamsList = attributePathParams; readPrepareParams.mAttributePathParamsListSize = 2; readPrepareParams.mpDataVersionFilterList = dataVersionFilters; readPrepareParams.mDataVersionFilterListSize = 1; { app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Read); EXPECT_EQ(readClient.SendRequest(readPrepareParams), CHIP_NO_ERROR); DrainAndServiceIO(); EXPECT_EQ(delegate.mNumAttributeResponse, 0); delegate.mNumAttributeResponse = 0; } EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); engine->Shutdown(); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } TEST_F(TestReadInteraction, TestReadRoundtripWithNoMatchPathDataVersionFilter) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); GenerateEvents(); MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); chip::app::AttributePathParams attributePathParams[2]; attributePathParams[0].mEndpointId = kTestEndpointId; attributePathParams[0].mClusterId = kTestClusterId; attributePathParams[0].mAttributeId = 1; attributePathParams[1].mEndpointId = kTestEndpointId; attributePathParams[1].mClusterId = kTestClusterId; attributePathParams[1].mAttributeId = 2; attributePathParams[1].mListIndex = 1; chip::app::DataVersionFilter dataVersionFilters[2]; dataVersionFilters[0].mEndpointId = kTestEndpointId; dataVersionFilters[0].mClusterId = kInvalidTestClusterId; dataVersionFilters[0].mDataVersion.SetValue(kTestDataVersion1); dataVersionFilters[1].mEndpointId = kInvalidTestEndpointId; dataVersionFilters[1].mClusterId = kTestClusterId; dataVersionFilters[1].mDataVersion.SetValue(kTestDataVersion2); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); readPrepareParams.mpAttributePathParamsList = attributePathParams; readPrepareParams.mAttributePathParamsListSize = 2; readPrepareParams.mpDataVersionFilterList = dataVersionFilters; readPrepareParams.mDataVersionFilterListSize = 2; { app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Read); EXPECT_EQ(readClient.SendRequest(readPrepareParams), CHIP_NO_ERROR); DrainAndServiceIO(); EXPECT_EQ(delegate.mNumAttributeResponse, 2); EXPECT_FALSE(delegate.mReadError); delegate.mNumAttributeResponse = 0; } EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); engine->Shutdown(); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } TEST_F(TestReadInteraction, TestReadRoundtripWithMultiSamePathDifferentDataVersionFilter) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); GenerateEvents(); MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); EXPECT_FALSE(delegate.mGotEventResponse); chip::app::AttributePathParams attributePathParams[2]; attributePathParams[0].mEndpointId = kTestEndpointId; attributePathParams[0].mClusterId = kTestClusterId; attributePathParams[0].mAttributeId = 1; attributePathParams[1].mEndpointId = kTestEndpointId; attributePathParams[1].mClusterId = kTestClusterId; attributePathParams[1].mAttributeId = 2; attributePathParams[1].mListIndex = 1; chip::app::DataVersionFilter dataVersionFilters[2]; dataVersionFilters[0].mEndpointId = kTestEndpointId; dataVersionFilters[0].mClusterId = kTestClusterId; dataVersionFilters[0].mDataVersion.SetValue(kTestDataVersion1); dataVersionFilters[1].mEndpointId = kTestEndpointId; dataVersionFilters[1].mClusterId = kTestClusterId; dataVersionFilters[1].mDataVersion.SetValue(kTestDataVersion2); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); readPrepareParams.mpAttributePathParamsList = attributePathParams; readPrepareParams.mAttributePathParamsListSize = 2; readPrepareParams.mpDataVersionFilterList = dataVersionFilters; readPrepareParams.mDataVersionFilterListSize = 2; { app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Read); EXPECT_EQ(readClient.SendRequest(readPrepareParams), CHIP_NO_ERROR); DrainAndServiceIO(); EXPECT_EQ(delegate.mNumAttributeResponse, 2); EXPECT_FALSE(delegate.mReadError); delegate.mNumAttributeResponse = 0; } EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); engine->Shutdown(); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } TEST_F(TestReadInteraction, TestReadRoundtripWithSameDifferentPathsDataVersionFilter) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); GenerateEvents(); MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); EXPECT_FALSE(delegate.mGotEventResponse); chip::app::AttributePathParams attributePathParams[2]; attributePathParams[0].mEndpointId = kTestEndpointId; attributePathParams[0].mClusterId = kTestClusterId; attributePathParams[0].mAttributeId = 1; attributePathParams[1].mEndpointId = kTestEndpointId; attributePathParams[1].mClusterId = kTestClusterId; attributePathParams[1].mAttributeId = 2; attributePathParams[1].mListIndex = 1; chip::app::DataVersionFilter dataVersionFilters[2]; dataVersionFilters[0].mEndpointId = kTestEndpointId; dataVersionFilters[0].mClusterId = kTestClusterId; dataVersionFilters[0].mDataVersion.SetValue(kTestDataVersion1); dataVersionFilters[1].mEndpointId = kInvalidTestEndpointId; dataVersionFilters[1].mClusterId = kTestClusterId; dataVersionFilters[1].mDataVersion.SetValue(kTestDataVersion2); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); readPrepareParams.mpAttributePathParamsList = attributePathParams; readPrepareParams.mAttributePathParamsListSize = 2; readPrepareParams.mpDataVersionFilterList = dataVersionFilters; readPrepareParams.mDataVersionFilterListSize = 2; { app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Read); EXPECT_EQ(readClient.SendRequest(readPrepareParams), CHIP_NO_ERROR); DrainAndServiceIO(); EXPECT_EQ(delegate.mNumAttributeResponse, 0); EXPECT_FALSE(delegate.mReadError); delegate.mNumAttributeResponse = 0; } EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); engine->Shutdown(); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } TEST_F(TestReadInteraction, TestReadWildcard) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); GenerateEvents(); MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); EXPECT_FALSE(delegate.mGotEventResponse); chip::app::AttributePathParams attributePathParams[1]; attributePathParams[0].mEndpointId = chip::Test::kMockEndpoint2; attributePathParams[0].mClusterId = chip::Test::MockClusterId(3); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); readPrepareParams.mpEventPathParamsList = nullptr; readPrepareParams.mEventPathParamsListSize = 0; readPrepareParams.mpAttributePathParamsList = attributePathParams; readPrepareParams.mAttributePathParamsListSize = 1; { app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Read); EXPECT_EQ(readClient.SendRequest(readPrepareParams), CHIP_NO_ERROR); DrainAndServiceIO(); EXPECT_EQ(delegate.mNumAttributeResponse, 5); EXPECT_TRUE(delegate.mGotReport); EXPECT_FALSE(delegate.mReadError); // By now we should have closed all exchanges and sent all pending acks, so // there should be no queued-up things in the retransmit table. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); } EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); engine->Shutdown(); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } // TestReadChunking will try to read a few large attributes, the report won't fit into the MTU and result in chunking. TEST_F(TestReadInteraction, TestReadChunking) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); GenerateEvents(); MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); EXPECT_FALSE(delegate.mGotEventResponse); chip::app::AttributePathParams attributePathParams[1]; // Mock Attribute 4 is a big attribute, with kMockAttribute4ListLength large // OCTET_STRING elements. attributePathParams[0].mEndpointId = chip::Test::kMockEndpoint3; attributePathParams[0].mClusterId = chip::Test::MockClusterId(2); attributePathParams[0].mAttributeId = chip::Test::MockAttributeId(4); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); readPrepareParams.mpEventPathParamsList = nullptr; readPrepareParams.mEventPathParamsListSize = 0; readPrepareParams.mpAttributePathParamsList = attributePathParams; readPrepareParams.mAttributePathParamsListSize = 1; { app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Read); EXPECT_EQ(readClient.SendRequest(readPrepareParams), CHIP_NO_ERROR); DrainAndServiceIO(); // We get one chunk with 4 array elements, and then one chunk per // element, and the total size of the array is // kMockAttribute4ListLength. EXPECT_EQ(delegate.mNumAttributeResponse, 1 + (kMockAttribute4ListLength - 4)); EXPECT_EQ(delegate.mNumArrayItems, 6); EXPECT_TRUE(delegate.mGotReport); EXPECT_FALSE(delegate.mReadError); // By now we should have closed all exchanges and sent all pending acks, so // there should be no queued-up things in the retransmit table. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); } EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); engine->Shutdown(); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } TEST_F(TestReadInteraction, TestSetDirtyBetweenChunks) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); GenerateEvents(); auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); chip::app::AttributePathParams attributePathParams[2]; for (auto & attributePathParam : attributePathParams) { attributePathParam.mEndpointId = chip::Test::kMockEndpoint3; attributePathParam.mClusterId = chip::Test::MockClusterId(2); attributePathParam.mAttributeId = chip::Test::MockAttributeId(4); } ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); readPrepareParams.mpEventPathParamsList = nullptr; readPrepareParams.mEventPathParamsListSize = 0; readPrepareParams.mpAttributePathParamsList = attributePathParams; readPrepareParams.mAttributePathParamsListSize = 2; { int currentAttributeResponsesWhenSetDirty = 0; int currentArrayItemsWhenSetDirty = 0; class DirtyingMockDelegate : public MockInteractionModelApp { public: DirtyingMockDelegate(AttributePathParams (&aReadPaths)[2], int & aNumAttributeResponsesWhenSetDirty, int & aNumArrayItemsWhenSetDirty) : mReadPaths(aReadPaths), mNumAttributeResponsesWhenSetDirty(aNumAttributeResponsesWhenSetDirty), mNumArrayItemsWhenSetDirty(aNumArrayItemsWhenSetDirty) {} private: void OnAttributeData(const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, const StatusIB & status) override { MockInteractionModelApp::OnAttributeData(aPath, apData, status); if (!mGotStartOfFirstReport && aPath.mEndpointId == mReadPaths[0].mEndpointId && aPath.mClusterId == mReadPaths[0].mClusterId && aPath.mAttributeId == mReadPaths[0].mAttributeId && !aPath.IsListItemOperation()) { mGotStartOfFirstReport = true; return; } if (!mGotStartOfSecondReport && aPath.mEndpointId == mReadPaths[1].mEndpointId && aPath.mClusterId == mReadPaths[1].mClusterId && aPath.mAttributeId == mReadPaths[1].mAttributeId && !aPath.IsListItemOperation()) { mGotStartOfSecondReport = true; // We always have data chunks, so go ahead to mark things // dirty as needed. } if (!mGotStartOfSecondReport) { // Don't do any setting dirty yet; we are waiting for a data // chunk from the second path. return; } if (mDidSetDirty) { if (!aPath.IsListItemOperation()) { mGotPostSetDirtyReport = true; return; } if (!mGotPostSetDirtyReport) { // We're finishing out the message where we decided to // SetDirty. ++mNumAttributeResponsesWhenSetDirty; ++mNumArrayItemsWhenSetDirty; } } if (!mDidSetDirty) { mDidSetDirty = true; AttributePathParams dirtyPath; dirtyPath.mEndpointId = chip::Test::kMockEndpoint3; dirtyPath.mClusterId = chip::Test::MockClusterId(2); dirtyPath.mAttributeId = chip::Test::MockAttributeId(4); if (aPath.mEndpointId == dirtyPath.mEndpointId && aPath.mClusterId == dirtyPath.mClusterId && aPath.mAttributeId == dirtyPath.mAttributeId) { // At this time, we are in the middle of report for second item. mNumAttributeResponsesWhenSetDirty = mNumAttributeResponse; mNumArrayItemsWhenSetDirty = mNumArrayItems; InteractionModelEngine::GetInstance()->GetReportingEngine().SetDirty(dirtyPath); } } } // Whether we got the start of the report for our first path. bool mGotStartOfFirstReport = false; // Whether we got the start of the report for our second path. bool mGotStartOfSecondReport = false; // Whether we got a new non-list-item report after we set dirty. bool mGotPostSetDirtyReport = false; bool mDidSetDirty = false; AttributePathParams (&mReadPaths)[2]; int & mNumAttributeResponsesWhenSetDirty; int & mNumArrayItemsWhenSetDirty; }; DirtyingMockDelegate delegate(attributePathParams, currentAttributeResponsesWhenSetDirty, currentArrayItemsWhenSetDirty); EXPECT_FALSE(delegate.mGotEventResponse); app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Read); EXPECT_EQ(readClient.SendRequest(readPrepareParams), CHIP_NO_ERROR); DrainAndServiceIO(); // Our list has length kMockAttribute4ListLength. Since the underlying // path iterator should be reset to the beginning of the cluster it is // currently iterating, we expect to get another value for our // attribute. The way the packet boundaries happen to fall, that value // will encode 4 items in the first IB and then one IB per item. const int expectedIBs = 1 + (kMockAttribute4ListLength - 4); ChipLogError(DataManagement, "OLD: %d\n", currentAttributeResponsesWhenSetDirty); ChipLogError(DataManagement, "NEW: %d\n", delegate.mNumAttributeResponse); EXPECT_EQ(delegate.mNumAttributeResponse, currentAttributeResponsesWhenSetDirty + expectedIBs); EXPECT_EQ(delegate.mNumArrayItems, currentArrayItemsWhenSetDirty + kMockAttribute4ListLength); EXPECT_TRUE(delegate.mGotReport); EXPECT_FALSE(delegate.mReadError); // By now we should have closed all exchanges and sent all pending acks, so // there should be no queued-up things in the retransmit table. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); } EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); engine->Shutdown(); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } TEST_F(TestReadInteraction, TestReadInvalidAttributePathRoundtrip) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); GenerateEvents(); MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); EXPECT_FALSE(delegate.mGotEventResponse); chip::app::AttributePathParams attributePathParams[2]; attributePathParams[0].mEndpointId = kTestEndpointId; attributePathParams[0].mClusterId = kInvalidTestClusterId; attributePathParams[0].mAttributeId = 1; ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); readPrepareParams.mpAttributePathParamsList = attributePathParams; readPrepareParams.mAttributePathParamsListSize = 1; { app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Read); EXPECT_EQ(readClient.SendRequest(readPrepareParams), CHIP_NO_ERROR); DrainAndServiceIO(); EXPECT_EQ(delegate.mNumAttributeResponse, 0); // By now we should have closed all exchanges and sent all pending acks, so // there should be no queued-up things in the retransmit table. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); } engine->Shutdown(); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } TEST_F_FROM_FIXTURE(TestReadInteraction, TestProcessSubscribeRequest) { System::PacketBufferTLVWriter writer; System::PacketBufferHandle subscribeRequestbuf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize); SubscribeRequestMessage::Builder subscribeRequestBuilder; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); Messaging::ExchangeContext * exchangeCtx = NewExchangeToAlice(nullptr, false); { ReadHandler readHandler(*engine, exchangeCtx, chip::app::ReadHandler::InteractionType::Read, gReportScheduler, CodegenDataModelProviderInstance()); writer.Init(std::move(subscribeRequestbuf)); EXPECT_EQ(subscribeRequestBuilder.Init(&writer), CHIP_NO_ERROR); subscribeRequestBuilder.KeepSubscriptions(true); EXPECT_EQ(subscribeRequestBuilder.GetError(), CHIP_NO_ERROR); subscribeRequestBuilder.MinIntervalFloorSeconds(2); EXPECT_EQ(subscribeRequestBuilder.GetError(), CHIP_NO_ERROR); subscribeRequestBuilder.MaxIntervalCeilingSeconds(3); EXPECT_EQ(subscribeRequestBuilder.GetError(), CHIP_NO_ERROR); AttributePathIBs::Builder & attributePathListBuilder = subscribeRequestBuilder.CreateAttributeRequests(); EXPECT_EQ(attributePathListBuilder.GetError(), CHIP_NO_ERROR); AttributePathIB::Builder & attributePathBuilder = attributePathListBuilder.CreatePath(); EXPECT_EQ(attributePathListBuilder.GetError(), CHIP_NO_ERROR); attributePathBuilder.Node(1).Endpoint(2).Cluster(3).Attribute(4).ListIndex(5).EndOfAttributePathIB(); EXPECT_EQ(attributePathBuilder.GetError(), CHIP_NO_ERROR); attributePathListBuilder.EndOfAttributePathIBs(); EXPECT_EQ(attributePathListBuilder.GetError(), CHIP_NO_ERROR); subscribeRequestBuilder.IsFabricFiltered(false).EndOfSubscribeRequestMessage(); EXPECT_EQ(subscribeRequestBuilder.GetError(), CHIP_NO_ERROR); EXPECT_EQ(subscribeRequestBuilder.GetError(), CHIP_NO_ERROR); EXPECT_EQ(writer.Finalize(&subscribeRequestbuf), CHIP_NO_ERROR); EXPECT_EQ(readHandler.ProcessSubscribeRequest(std::move(subscribeRequestbuf)), CHIP_NO_ERROR); } engine->Shutdown(); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } #if CHIP_CONFIG_ENABLE_ICD_SERVER /** * @brief Test validates that an ICD will choose its IdleModeDuration (GetPublisherSelectedIntervalLimit) * as MaxInterval when the MaxIntervalCeiling is superior. */ TEST_F_FROM_FIXTURE(TestReadInteraction, TestICDProcessSubscribeRequestSupMaxIntervalCeiling) { System::PacketBufferTLVWriter writer; System::PacketBufferHandle subscribeRequestbuf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize); SubscribeRequestMessage::Builder subscribeRequestBuilder; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); uint16_t kMinInterval = 0; uint16_t kMaxIntervalCeiling = 1; Messaging::ExchangeContext * exchangeCtx = NewExchangeToAlice(nullptr, false); { ReadHandler readHandler(*engine, exchangeCtx, chip::app::ReadHandler::InteractionType::Read, gReportScheduler, CodegenDataModelProviderInstance()); writer.Init(std::move(subscribeRequestbuf)); EXPECT_EQ(subscribeRequestBuilder.Init(&writer), CHIP_NO_ERROR); subscribeRequestBuilder.KeepSubscriptions(true); EXPECT_EQ(subscribeRequestBuilder.GetError(), CHIP_NO_ERROR); subscribeRequestBuilder.MinIntervalFloorSeconds(kMinInterval); EXPECT_EQ(subscribeRequestBuilder.GetError(), CHIP_NO_ERROR); subscribeRequestBuilder.MaxIntervalCeilingSeconds(kMaxIntervalCeiling); EXPECT_EQ(subscribeRequestBuilder.GetError(), CHIP_NO_ERROR); AttributePathIBs::Builder & attributePathListBuilder = subscribeRequestBuilder.CreateAttributeRequests(); EXPECT_EQ(attributePathListBuilder.GetError(), CHIP_NO_ERROR); AttributePathIB::Builder & attributePathBuilder = attributePathListBuilder.CreatePath(); EXPECT_EQ(attributePathListBuilder.GetError(), CHIP_NO_ERROR); attributePathBuilder.Node(1).Endpoint(2).Cluster(3).Attribute(4).ListIndex(5).EndOfAttributePathIB(); EXPECT_EQ(attributePathBuilder.GetError(), CHIP_NO_ERROR); attributePathListBuilder.EndOfAttributePathIBs(); EXPECT_EQ(attributePathListBuilder.GetError(), CHIP_NO_ERROR); subscribeRequestBuilder.IsFabricFiltered(false).EndOfSubscribeRequestMessage(); EXPECT_EQ(subscribeRequestBuilder.GetError(), CHIP_NO_ERROR); EXPECT_EQ(subscribeRequestBuilder.GetError(), CHIP_NO_ERROR); EXPECT_EQ(writer.Finalize(&subscribeRequestbuf), CHIP_NO_ERROR); EXPECT_EQ(readHandler.ProcessSubscribeRequest(std::move(subscribeRequestbuf)), CHIP_NO_ERROR); uint16_t idleModeDuration = readHandler.GetPublisherSelectedIntervalLimit(); uint16_t minInterval; uint16_t maxInterval; readHandler.GetReportingIntervals(minInterval, maxInterval); EXPECT_EQ(minInterval, kMinInterval); EXPECT_EQ(maxInterval, idleModeDuration); EXPECT_EQ(kMaxIntervalCeiling, readHandler.GetSubscriberRequestedMaxInterval()); } engine->Shutdown(); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } /** * @brief Test validates that an ICD will choose its IdleModeDuration (GetPublisherSelectedIntervalLimit) * as MaxInterval when the MaxIntervalCeiling is inferior. */ TEST_F_FROM_FIXTURE(TestReadInteraction, TestICDProcessSubscribeRequestInfMaxIntervalCeiling) { System::PacketBufferTLVWriter writer; System::PacketBufferHandle subscribeRequestbuf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize); SubscribeRequestMessage::Builder subscribeRequestBuilder; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); uint16_t kMinInterval = 0; uint16_t kMaxIntervalCeiling = 1; Messaging::ExchangeContext * exchangeCtx = NewExchangeToAlice(nullptr, false); { ReadHandler readHandler(*engine, exchangeCtx, chip::app::ReadHandler::InteractionType::Read, gReportScheduler, CodegenDataModelProviderInstance()); writer.Init(std::move(subscribeRequestbuf)); EXPECT_EQ(subscribeRequestBuilder.Init(&writer), CHIP_NO_ERROR); subscribeRequestBuilder.KeepSubscriptions(true); EXPECT_EQ(subscribeRequestBuilder.GetError(), CHIP_NO_ERROR); subscribeRequestBuilder.MinIntervalFloorSeconds(kMinInterval); EXPECT_EQ(subscribeRequestBuilder.GetError(), CHIP_NO_ERROR); subscribeRequestBuilder.MaxIntervalCeilingSeconds(kMaxIntervalCeiling); EXPECT_EQ(subscribeRequestBuilder.GetError(), CHIP_NO_ERROR); AttributePathIBs::Builder & attributePathListBuilder = subscribeRequestBuilder.CreateAttributeRequests(); EXPECT_EQ(attributePathListBuilder.GetError(), CHIP_NO_ERROR); AttributePathIB::Builder & attributePathBuilder = attributePathListBuilder.CreatePath(); EXPECT_EQ(attributePathListBuilder.GetError(), CHIP_NO_ERROR); attributePathBuilder.Node(1).Endpoint(2).Cluster(3).Attribute(4).ListIndex(5).EndOfAttributePathIB(); EXPECT_EQ(attributePathBuilder.GetError(), CHIP_NO_ERROR); attributePathListBuilder.EndOfAttributePathIBs(); EXPECT_EQ(attributePathListBuilder.GetError(), CHIP_NO_ERROR); subscribeRequestBuilder.IsFabricFiltered(false).EndOfSubscribeRequestMessage(); EXPECT_EQ(subscribeRequestBuilder.GetError(), CHIP_NO_ERROR); EXPECT_EQ(subscribeRequestBuilder.GetError(), CHIP_NO_ERROR); EXPECT_EQ(writer.Finalize(&subscribeRequestbuf), CHIP_NO_ERROR); EXPECT_EQ(readHandler.ProcessSubscribeRequest(std::move(subscribeRequestbuf)), CHIP_NO_ERROR); uint16_t idleModeDuration = readHandler.GetPublisherSelectedIntervalLimit(); uint16_t minInterval; uint16_t maxInterval; readHandler.GetReportingIntervals(minInterval, maxInterval); EXPECT_EQ(minInterval, kMinInterval); EXPECT_EQ(maxInterval, idleModeDuration); EXPECT_EQ(kMaxIntervalCeiling, readHandler.GetSubscriberRequestedMaxInterval()); } engine->Shutdown(); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } /** * @brief Test validates that an ICD will choose a multiple of its IdleModeDuration (GetPublisherSelectedIntervalLimit) * as MaxInterval when the MinInterval > IdleModeDuration. */ TEST_F_FROM_FIXTURE(TestReadInteraction, TestICDProcessSubscribeRequestSupMinInterval) { System::PacketBufferTLVWriter writer; System::PacketBufferHandle subscribeRequestbuf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize); SubscribeRequestMessage::Builder subscribeRequestBuilder; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); uint16_t kMinInterval = 305; // Default IdleModeDuration is 300 uint16_t kMaxIntervalCeiling = 605; Messaging::ExchangeContext * exchangeCtx = NewExchangeToAlice(nullptr, false); { ReadHandler readHandler(*engine, exchangeCtx, chip::app::ReadHandler::InteractionType::Read, gReportScheduler, CodegenDataModelProviderInstance()); writer.Init(std::move(subscribeRequestbuf)); EXPECT_EQ(subscribeRequestBuilder.Init(&writer), CHIP_NO_ERROR); subscribeRequestBuilder.KeepSubscriptions(true); EXPECT_EQ(subscribeRequestBuilder.GetError(), CHIP_NO_ERROR); subscribeRequestBuilder.MinIntervalFloorSeconds(kMinInterval); EXPECT_EQ(subscribeRequestBuilder.GetError(), CHIP_NO_ERROR); subscribeRequestBuilder.MaxIntervalCeilingSeconds(kMaxIntervalCeiling); EXPECT_EQ(subscribeRequestBuilder.GetError(), CHIP_NO_ERROR); AttributePathIBs::Builder & attributePathListBuilder = subscribeRequestBuilder.CreateAttributeRequests(); EXPECT_EQ(attributePathListBuilder.GetError(), CHIP_NO_ERROR); AttributePathIB::Builder & attributePathBuilder = attributePathListBuilder.CreatePath(); EXPECT_EQ(attributePathListBuilder.GetError(), CHIP_NO_ERROR); attributePathBuilder.Node(1).Endpoint(2).Cluster(3).Attribute(4).ListIndex(5).EndOfAttributePathIB(); EXPECT_EQ(attributePathBuilder.GetError(), CHIP_NO_ERROR); attributePathListBuilder.EndOfAttributePathIBs(); EXPECT_EQ(attributePathListBuilder.GetError(), CHIP_NO_ERROR); subscribeRequestBuilder.IsFabricFiltered(false).EndOfSubscribeRequestMessage(); EXPECT_EQ(subscribeRequestBuilder.GetError(), CHIP_NO_ERROR); EXPECT_EQ(subscribeRequestBuilder.GetError(), CHIP_NO_ERROR); EXPECT_EQ(writer.Finalize(&subscribeRequestbuf), CHIP_NO_ERROR); EXPECT_EQ(readHandler.ProcessSubscribeRequest(std::move(subscribeRequestbuf)), CHIP_NO_ERROR); uint16_t idleModeDuration = readHandler.GetPublisherSelectedIntervalLimit(); uint16_t minInterval; uint16_t maxInterval; readHandler.GetReportingIntervals(minInterval, maxInterval); EXPECT_EQ(minInterval, kMinInterval); EXPECT_EQ(maxInterval, (2 * idleModeDuration)); EXPECT_EQ(kMaxIntervalCeiling, readHandler.GetSubscriberRequestedMaxInterval()); } engine->Shutdown(); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } /** * @brief Test validates that an ICD will choose a maximal value for an uint16 if the multiple of the IdleModeDuration * is greater than variable size. */ TEST_F_FROM_FIXTURE(TestReadInteraction, TestICDProcessSubscribeRequestMaxMinInterval) { System::PacketBufferTLVWriter writer; System::PacketBufferHandle subscribeRequestbuf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize); SubscribeRequestMessage::Builder subscribeRequestBuilder; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); uint16_t kMinInterval = System::Clock::Seconds16::max().count(); uint16_t kMaxIntervalCeiling = System::Clock::Seconds16::max().count(); Messaging::ExchangeContext * exchangeCtx = NewExchangeToAlice(nullptr, false); { ReadHandler readHandler(*engine, exchangeCtx, chip::app::ReadHandler::InteractionType::Read, gReportScheduler, CodegenDataModelProviderInstance()); writer.Init(std::move(subscribeRequestbuf)); EXPECT_EQ(subscribeRequestBuilder.Init(&writer), CHIP_NO_ERROR); subscribeRequestBuilder.KeepSubscriptions(true); EXPECT_EQ(subscribeRequestBuilder.GetError(), CHIP_NO_ERROR); subscribeRequestBuilder.MinIntervalFloorSeconds(kMinInterval); EXPECT_EQ(subscribeRequestBuilder.GetError(), CHIP_NO_ERROR); subscribeRequestBuilder.MaxIntervalCeilingSeconds(kMaxIntervalCeiling); EXPECT_EQ(subscribeRequestBuilder.GetError(), CHIP_NO_ERROR); AttributePathIBs::Builder & attributePathListBuilder = subscribeRequestBuilder.CreateAttributeRequests(); EXPECT_EQ(attributePathListBuilder.GetError(), CHIP_NO_ERROR); AttributePathIB::Builder & attributePathBuilder = attributePathListBuilder.CreatePath(); EXPECT_EQ(attributePathListBuilder.GetError(), CHIP_NO_ERROR); attributePathBuilder.Node(1).Endpoint(2).Cluster(3).Attribute(4).ListIndex(5).EndOfAttributePathIB(); EXPECT_EQ(attributePathBuilder.GetError(), CHIP_NO_ERROR); attributePathListBuilder.EndOfAttributePathIBs(); EXPECT_EQ(attributePathListBuilder.GetError(), CHIP_NO_ERROR); subscribeRequestBuilder.IsFabricFiltered(false).EndOfSubscribeRequestMessage(); EXPECT_EQ(subscribeRequestBuilder.GetError(), CHIP_NO_ERROR); EXPECT_EQ(subscribeRequestBuilder.GetError(), CHIP_NO_ERROR); EXPECT_EQ(writer.Finalize(&subscribeRequestbuf), CHIP_NO_ERROR); EXPECT_EQ(readHandler.ProcessSubscribeRequest(std::move(subscribeRequestbuf)), CHIP_NO_ERROR); uint16_t minInterval; uint16_t maxInterval; readHandler.GetReportingIntervals(minInterval, maxInterval); EXPECT_EQ(minInterval, kMinInterval); EXPECT_EQ(maxInterval, kMaxIntervalCeiling); EXPECT_EQ(kMaxIntervalCeiling, readHandler.GetSubscriberRequestedMaxInterval()); } engine->Shutdown(); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } /** * @brief Test validates that an ICD will choose the MaxIntervalCeiling as MaxInterval if the next multiple after the MinInterval * is greater than the IdleModeDuration and MaxIntervalCeiling */ TEST_F_FROM_FIXTURE(TestReadInteraction, TestICDProcessSubscribeRequestInvalidIdleModeDuration) { System::PacketBufferTLVWriter writer; System::PacketBufferHandle subscribeRequestbuf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize); SubscribeRequestMessage::Builder subscribeRequestBuilder; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); uint16_t kMinInterval = 400; uint16_t kMaxIntervalCeiling = 400; Messaging::ExchangeContext * exchangeCtx = NewExchangeToAlice(nullptr, false); { ReadHandler readHandler(*engine, exchangeCtx, chip::app::ReadHandler::InteractionType::Read, gReportScheduler, CodegenDataModelProviderInstance()); writer.Init(std::move(subscribeRequestbuf)); EXPECT_EQ(subscribeRequestBuilder.Init(&writer), CHIP_NO_ERROR); subscribeRequestBuilder.KeepSubscriptions(true); EXPECT_EQ(subscribeRequestBuilder.GetError(), CHIP_NO_ERROR); subscribeRequestBuilder.MinIntervalFloorSeconds(kMinInterval); EXPECT_EQ(subscribeRequestBuilder.GetError(), CHIP_NO_ERROR); subscribeRequestBuilder.MaxIntervalCeilingSeconds(kMaxIntervalCeiling); EXPECT_EQ(subscribeRequestBuilder.GetError(), CHIP_NO_ERROR); AttributePathIBs::Builder & attributePathListBuilder = subscribeRequestBuilder.CreateAttributeRequests(); EXPECT_EQ(attributePathListBuilder.GetError(), CHIP_NO_ERROR); AttributePathIB::Builder & attributePathBuilder = attributePathListBuilder.CreatePath(); EXPECT_EQ(attributePathListBuilder.GetError(), CHIP_NO_ERROR); attributePathBuilder.Node(1).Endpoint(2).Cluster(3).Attribute(4).ListIndex(5).EndOfAttributePathIB(); EXPECT_EQ(attributePathBuilder.GetError(), CHIP_NO_ERROR); attributePathListBuilder.EndOfAttributePathIBs(); EXPECT_EQ(attributePathListBuilder.GetError(), CHIP_NO_ERROR); subscribeRequestBuilder.IsFabricFiltered(false).EndOfSubscribeRequestMessage(); EXPECT_EQ(subscribeRequestBuilder.GetError(), CHIP_NO_ERROR); EXPECT_EQ(subscribeRequestBuilder.GetError(), CHIP_NO_ERROR); EXPECT_EQ(writer.Finalize(&subscribeRequestbuf), CHIP_NO_ERROR); EXPECT_EQ(readHandler.ProcessSubscribeRequest(std::move(subscribeRequestbuf)), CHIP_NO_ERROR); uint16_t minInterval; uint16_t maxInterval; readHandler.GetReportingIntervals(minInterval, maxInterval); EXPECT_EQ(minInterval, kMinInterval); EXPECT_EQ(maxInterval, kMaxIntervalCeiling); EXPECT_EQ(kMaxIntervalCeiling, readHandler.GetSubscriberRequestedMaxInterval()); } engine->Shutdown(); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } #endif // CHIP_CONFIG_ENABLE_ICD_SERVER TEST_F_FROM_FIXTURE(TestReadInteraction, TestSubscribeRoundtrip) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); EXPECT_FALSE(delegate.mGotEventResponse); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); chip::app::EventPathParams eventPathParams[2]; readPrepareParams.mpEventPathParamsList = eventPathParams; readPrepareParams.mpEventPathParamsList[0].mEndpointId = kTestEventEndpointId; readPrepareParams.mpEventPathParamsList[0].mClusterId = kTestEventClusterId; readPrepareParams.mpEventPathParamsList[0].mEventId = kTestEventIdDebug; readPrepareParams.mpEventPathParamsList[1].mEndpointId = kTestEventEndpointId; readPrepareParams.mpEventPathParamsList[1].mClusterId = kTestEventClusterId; readPrepareParams.mpEventPathParamsList[1].mEventId = kTestEventIdCritical; readPrepareParams.mEventPathParamsListSize = 2; chip::app::AttributePathParams attributePathParams[2]; readPrepareParams.mpAttributePathParamsList = attributePathParams; readPrepareParams.mpAttributePathParamsList[0].mEndpointId = kTestEndpointId; readPrepareParams.mpAttributePathParamsList[0].mClusterId = kTestClusterId; readPrepareParams.mpAttributePathParamsList[0].mAttributeId = 1; readPrepareParams.mpAttributePathParamsList[1].mEndpointId = kTestEndpointId; readPrepareParams.mpAttributePathParamsList[1].mClusterId = kTestClusterId; readPrepareParams.mpAttributePathParamsList[1].mAttributeId = 2; readPrepareParams.mAttributePathParamsListSize = 2; readPrepareParams.mMinIntervalFloorSeconds = 1; readPrepareParams.mMaxIntervalCeilingSeconds = 2; printf("\nSend first subscribe request message to Node: 0x" ChipLogFormatX64 "\n", ChipLogValueX64(chip::kTestDeviceNodeId)); { app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Subscribe); EXPECT_EQ(readClient.SendRequest(readPrepareParams), CHIP_NO_ERROR); DrainAndServiceIO(); EXPECT_TRUE(delegate.mGotReport); } delegate.mNumAttributeResponse = 0; readPrepareParams.mKeepSubscriptions = false; { app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Subscribe); delegate.mGotReport = false; EXPECT_EQ(readClient.SendRequest(readPrepareParams), CHIP_NO_ERROR); DrainAndServiceIO(); EXPECT_EQ(engine->GetNumActiveReadHandlers(), 1u); ASSERT_NE(engine->ActiveHandlerAt(0), nullptr); delegate.mpReadHandler = engine->ActiveHandlerAt(0); EXPECT_TRUE(delegate.mGotReport); EXPECT_EQ(delegate.mNumAttributeResponse, 2); EXPECT_EQ(engine->GetNumActiveReadHandlers(ReadHandler::InteractionType::Subscribe), 1u); GenerateEvents(); chip::app::AttributePathParams dirtyPath1; dirtyPath1.mClusterId = kTestClusterId; dirtyPath1.mEndpointId = kTestEndpointId; dirtyPath1.mAttributeId = 1; chip::app::AttributePathParams dirtyPath2; dirtyPath2.mClusterId = kTestClusterId; dirtyPath2.mEndpointId = kTestEndpointId; dirtyPath2.mAttributeId = 2; chip::app::AttributePathParams dirtyPath3; dirtyPath3.mClusterId = kTestClusterId; dirtyPath3.mEndpointId = kTestEndpointId; dirtyPath3.mAttributeId = 2; dirtyPath3.mListIndex = 1; chip::app::AttributePathParams dirtyPath4; dirtyPath4.mClusterId = kTestClusterId; dirtyPath4.mEndpointId = kTestEndpointId; dirtyPath4.mAttributeId = 3; chip::app::AttributePathParams dirtyPath5; dirtyPath5.mClusterId = kTestClusterId; dirtyPath5.mEndpointId = kTestEndpointId; dirtyPath5.mAttributeId = 4; // Test report with 2 different path // Advance monotonic timestamp for min interval to elapse gMockClock.AdvanceMonotonic(System::Clock::Seconds16(readPrepareParams.mMinIntervalFloorSeconds)); GetIOContext().DriveIO(); delegate.mGotReport = false; delegate.mGotEventResponse = false; delegate.mNumAttributeResponse = 0; EXPECT_EQ(engine->GetReportingEngine().SetDirty(dirtyPath1), CHIP_NO_ERROR); EXPECT_EQ(engine->GetReportingEngine().SetDirty(dirtyPath2), CHIP_NO_ERROR); DrainAndServiceIO(); EXPECT_TRUE(delegate.mGotReport); EXPECT_TRUE(delegate.mGotEventResponse); EXPECT_EQ(delegate.mNumAttributeResponse, 2); // Test report with 2 different path, and 1 same path // Advance monotonic timestamp for min interval to elapse gMockClock.AdvanceMonotonic(System::Clock::Seconds16(readPrepareParams.mMinIntervalFloorSeconds)); GetIOContext().DriveIO(); delegate.mGotReport = false; delegate.mNumAttributeResponse = 0; EXPECT_EQ(engine->GetReportingEngine().SetDirty(dirtyPath1), CHIP_NO_ERROR); EXPECT_EQ(engine->GetReportingEngine().SetDirty(dirtyPath2), CHIP_NO_ERROR); EXPECT_EQ(engine->GetReportingEngine().SetDirty(dirtyPath2), CHIP_NO_ERROR); DrainAndServiceIO(); EXPECT_TRUE(delegate.mGotReport); EXPECT_EQ(delegate.mNumAttributeResponse, 2); // Test report with 3 different path, and one path is overlapped with another // Advance monotonic timestamp for min interval to elapse gMockClock.AdvanceMonotonic(System::Clock::Seconds16(readPrepareParams.mMinIntervalFloorSeconds)); GetIOContext().DriveIO(); delegate.mGotReport = false; delegate.mNumAttributeResponse = 0; EXPECT_EQ(engine->GetReportingEngine().SetDirty(dirtyPath1), CHIP_NO_ERROR); EXPECT_EQ(engine->GetReportingEngine().SetDirty(dirtyPath2), CHIP_NO_ERROR); EXPECT_EQ(engine->GetReportingEngine().SetDirty(dirtyPath3), CHIP_NO_ERROR); DrainAndServiceIO(); EXPECT_TRUE(delegate.mGotReport); EXPECT_EQ(delegate.mNumAttributeResponse, 2); // Test report with 3 different path, all are not overlapped, one path is not interested for current subscription // Advance monotonic timestamp for min interval to elapse gMockClock.AdvanceMonotonic(System::Clock::Seconds16(readPrepareParams.mMinIntervalFloorSeconds)); GetIOContext().DriveIO(); delegate.mGotReport = false; delegate.mNumAttributeResponse = 0; EXPECT_EQ(engine->GetReportingEngine().SetDirty(dirtyPath1), CHIP_NO_ERROR); EXPECT_EQ(engine->GetReportingEngine().SetDirty(dirtyPath2), CHIP_NO_ERROR); EXPECT_EQ(engine->GetReportingEngine().SetDirty(dirtyPath4), CHIP_NO_ERROR); DrainAndServiceIO(); EXPECT_TRUE(delegate.mGotReport); EXPECT_EQ(delegate.mNumAttributeResponse, 2); uint16_t minInterval; uint16_t maxInterval; delegate.mpReadHandler->GetReportingIntervals(minInterval, maxInterval); EXPECT_EQ(readPrepareParams.mMaxIntervalCeilingSeconds, delegate.mpReadHandler->GetSubscriberRequestedMaxInterval()); // Test empty report // Advance monotonic timestamp for min interval to elapse gMockClock.AdvanceMonotonic(System::Clock::Seconds16(maxInterval)); GetIOContext().DriveIO(); EXPECT_TRUE(engine->GetReportingEngine().IsRunScheduled()); delegate.mGotReport = false; delegate.mNumAttributeResponse = 0; DrainAndServiceIO(); EXPECT_EQ(delegate.mNumAttributeResponse, 0); } // By now we should have closed all exchanges and sent all pending acks, so // there should be no queued-up things in the retransmit table. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); engine->Shutdown(); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } TEST_F_FROM_FIXTURE(TestReadInteraction, TestSubscribeEarlyReport) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); EXPECT_FALSE(delegate.mGotEventResponse); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); chip::app::EventPathParams eventPathParams[1]; readPrepareParams.mpEventPathParamsList = eventPathParams; readPrepareParams.mpEventPathParamsList[0].mEndpointId = kTestEventEndpointId; readPrepareParams.mpEventPathParamsList[0].mClusterId = kTestEventClusterId; readPrepareParams.mEventPathParamsListSize = 1; readPrepareParams.mpAttributePathParamsList = nullptr; readPrepareParams.mAttributePathParamsListSize = 0; readPrepareParams.mMinIntervalFloorSeconds = 1; readPrepareParams.mMaxIntervalCeilingSeconds = 5; readPrepareParams.mKeepSubscriptions = true; { app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Subscribe); readPrepareParams.mpEventPathParamsList[0].mIsUrgentEvent = true; delegate.mGotEventResponse = false; EXPECT_EQ(readClient.SendRequest(readPrepareParams), CHIP_NO_ERROR); DrainAndServiceIO(); System::Clock::Timestamp startTime = gMockClock.GetMonotonicTimestamp(); EXPECT_EQ(engine->GetNumActiveReadHandlers(), 1u); ASSERT_NE(engine->ActiveHandlerAt(0), nullptr); delegate.mpReadHandler = engine->ActiveHandlerAt(0); uint16_t minInterval; uint16_t maxInterval; delegate.mpReadHandler->GetReportingIntervals(minInterval, maxInterval); EXPECT_EQ(readPrepareParams.mMaxIntervalCeilingSeconds, delegate.mpReadHandler->GetSubscriberRequestedMaxInterval()); EXPECT_EQ(engine->GetNumActiveReadHandlers(ReadHandler::InteractionType::Subscribe), 1u); EXPECT_EQ(gReportScheduler->GetMinTimestampForHandler(delegate.mpReadHandler), gMockClock.GetMonotonicTimestamp() + Seconds16(readPrepareParams.mMinIntervalFloorSeconds)); EXPECT_EQ(gReportScheduler->GetMaxTimestampForHandler(delegate.mpReadHandler), gMockClock.GetMonotonicTimestamp() + Seconds16(maxInterval)); // Confirm that the node is scheduled to run EXPECT_TRUE(gReportScheduler->IsReportScheduled(delegate.mpReadHandler)); ReportScheduler::ReadHandlerNode * node = gReportScheduler->GetReadHandlerNode(delegate.mpReadHandler); ASSERT_NE(node, nullptr); GenerateEvents(); // modify the node's min timestamp to be 50ms later than the timer expiration time node->SetIntervalTimeStamps(delegate.mpReadHandler, startTime + Milliseconds32(50)); EXPECT_EQ(gReportScheduler->GetMinTimestampForHandler(delegate.mpReadHandler), gMockClock.GetMonotonicTimestamp() + Seconds16(readPrepareParams.mMinIntervalFloorSeconds) + Milliseconds32(50)); EXPECT_GT(gReportScheduler->GetMinTimestampForHandler(delegate.mpReadHandler), startTime); EXPECT_TRUE(delegate.mpReadHandler->IsDirty()); delegate.mGotEventResponse = false; // Advance monotonic timestamp for min interval to elapse gMockClock.AdvanceMonotonic(Seconds16(readPrepareParams.mMinIntervalFloorSeconds)); EXPECT_FALSE(InteractionModelEngine::GetInstance()->GetReportingEngine().IsRunScheduled()); // Service Timer expired event GetIOContext().DriveIO(); EXPECT_GT(gReportScheduler->GetMinTimestampForHandler(delegate.mpReadHandler), gMockClock.GetMonotonicTimestamp()); // The behavior on min interval elapse is different between synced subscription and non-synced subscription if (!sUsingSubSync) { // Verify the ReadHandler is considered as reportable even if its node's min timestamp has not expired EXPECT_TRUE(gReportScheduler->IsReportableNow(delegate.mpReadHandler)); EXPECT_TRUE(InteractionModelEngine::GetInstance()->GetReportingEngine().IsRunScheduled()); // Service Engine Run GetIOContext().DriveIO(); // Service EventManagement event GetIOContext().DriveIO(); GetIOContext().DriveIO(); EXPECT_TRUE(delegate.mGotEventResponse); } else { // Verify logic on Min for synced subscription // Verify the ReadHandler is not considered as reportable yet EXPECT_FALSE(gReportScheduler->IsReportableNow(delegate.mpReadHandler)); // confirm that the timer was kicked off for the next min of the node EXPECT_TRUE(gReportScheduler->IsReportScheduled(delegate.mpReadHandler)); // Expired new timer gMockClock.AdvanceMonotonic(Milliseconds32(50)); // Service Timer expired event GetIOContext().DriveIO(); EXPECT_TRUE(InteractionModelEngine::GetInstance()->GetReportingEngine().IsRunScheduled()); // Service Engine Run GetIOContext().DriveIO(); // Service EventManagement event GetIOContext().DriveIO(); GetIOContext().DriveIO(); EXPECT_TRUE(delegate.mGotEventResponse); } // The behavior is identical on max since the sync subscription will interpret an early max firing as a earlier node got // reportable and allow nodes that have passed their min to sync on it. EXPECT_FALSE(delegate.mpReadHandler->IsDirty()); delegate.mGotEventResponse = false; EXPECT_EQ(gReportScheduler->GetMinTimestampForHandler(delegate.mpReadHandler), gMockClock.GetMonotonicTimestamp() + Seconds16(readPrepareParams.mMinIntervalFloorSeconds)); EXPECT_EQ(gReportScheduler->GetMaxTimestampForHandler(delegate.mpReadHandler), gMockClock.GetMonotonicTimestamp() + Seconds16(maxInterval)); // Confirm that the node is scheduled to run EXPECT_TRUE(gReportScheduler->IsReportScheduled(delegate.mpReadHandler)); ASSERT_NE(node, nullptr); // modify the node's max timestamp to be 50ms later than the timer expiration time node->SetIntervalTimeStamps(delegate.mpReadHandler, gMockClock.GetMonotonicTimestamp() + Milliseconds32(50)); EXPECT_EQ(gReportScheduler->GetMaxTimestampForHandler(delegate.mpReadHandler), gMockClock.GetMonotonicTimestamp() + Seconds16(maxInterval) + Milliseconds32(50)); // Advance monotonic timestamp for min interval to elapse gMockClock.AdvanceMonotonic(Seconds16(maxInterval)); EXPECT_FALSE(InteractionModelEngine::GetInstance()->GetReportingEngine().IsRunScheduled()); // Service Timer expired event GetIOContext().DriveIO(); // Verify the ReadHandler is considered as reportable even if its node's min timestamp has not expired EXPECT_GT(gReportScheduler->GetMaxTimestampForHandler(delegate.mpReadHandler), gMockClock.GetMonotonicTimestamp()); EXPECT_TRUE(gReportScheduler->IsReportableNow(delegate.mpReadHandler)); EXPECT_FALSE(gReportScheduler->IsReportScheduled(delegate.mpReadHandler)); EXPECT_FALSE(delegate.mpReadHandler->IsDirty()); EXPECT_TRUE(InteractionModelEngine::GetInstance()->GetReportingEngine().IsRunScheduled()); // Service Engine Run GetIOContext().DriveIO(); // Service EventManagement event GetIOContext().DriveIO(); GetIOContext().DriveIO(); EXPECT_TRUE(gReportScheduler->IsReportScheduled(delegate.mpReadHandler)); EXPECT_FALSE(InteractionModelEngine::GetInstance()->GetReportingEngine().IsRunScheduled()); } DrainAndServiceIO(); engine->Shutdown(); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } TEST_F_FROM_FIXTURE(TestReadInteraction, TestSubscribeUrgentWildcardEvent) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); MockInteractionModelApp delegate; MockInteractionModelApp nonUrgentDelegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); EXPECT_FALSE(delegate.mGotEventResponse); EXPECT_FALSE(nonUrgentDelegate.mGotEventResponse); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); chip::app::EventPathParams eventPathParams[2]; readPrepareParams.mpEventPathParamsList = eventPathParams; readPrepareParams.mpEventPathParamsList[0].mEndpointId = kTestEventEndpointId; readPrepareParams.mpEventPathParamsList[0].mClusterId = kTestEventClusterId; readPrepareParams.mpEventPathParamsList[1].mEndpointId = kTestEventEndpointId; readPrepareParams.mpEventPathParamsList[1].mClusterId = kTestEventClusterId; readPrepareParams.mpEventPathParamsList[1].mEventId = kTestEventIdCritical; readPrepareParams.mEventPathParamsListSize = 2; readPrepareParams.mpAttributePathParamsList = nullptr; readPrepareParams.mAttributePathParamsListSize = 0; readPrepareParams.mMinIntervalFloorSeconds = 1; readPrepareParams.mMaxIntervalCeilingSeconds = 3600; printf("\nSend first subscribe request message with wildcard urgent event to Node: 0x" ChipLogFormatX64 "\n", ChipLogValueX64(chip::kTestDeviceNodeId)); readPrepareParams.mKeepSubscriptions = true; { app::ReadClient nonUrgentReadClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), nonUrgentDelegate, chip::app::ReadClient::InteractionType::Subscribe); nonUrgentDelegate.mGotReport = false; EXPECT_EQ(nonUrgentReadClient.SendRequest(readPrepareParams), CHIP_NO_ERROR); app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Subscribe); readPrepareParams.mpEventPathParamsList[0].mIsUrgentEvent = true; delegate.mGotReport = false; EXPECT_EQ(readClient.SendRequest(readPrepareParams), CHIP_NO_ERROR); DrainAndServiceIO(); System::Clock::Timestamp startTime = gMockClock.GetMonotonicTimestamp(); EXPECT_EQ(engine->GetNumActiveReadHandlers(), 2u); ASSERT_NE(engine->ActiveHandlerAt(0), nullptr); nonUrgentDelegate.mpReadHandler = engine->ActiveHandlerAt(0); ASSERT_NE(engine->ActiveHandlerAt(1), nullptr); delegate.mpReadHandler = engine->ActiveHandlerAt(1); // Adding These to be able to access private members/methods of ReadHandler EXPECT_EQ(engine->GetNumActiveReadHandlers(ReadHandler::InteractionType::Subscribe), 2u); GenerateEvents(); EXPECT_GT(gReportScheduler->GetMinTimestampForHandler(delegate.mpReadHandler), startTime); EXPECT_TRUE(delegate.mpReadHandler->IsDirty()); delegate.mGotEventResponse = false; delegate.mGotReport = false; EXPECT_GT(gReportScheduler->GetMinTimestampForHandler(nonUrgentDelegate.mpReadHandler), startTime); EXPECT_FALSE(nonUrgentDelegate.mpReadHandler->IsDirty()); nonUrgentDelegate.mGotEventResponse = false; nonUrgentDelegate.mGotReport = false; // wait for min interval 1 seconds (in test, we use 0.6 seconds considering the time variation), expect no event is // received, then wait for 0.8 seconds, then the urgent event would be sent out // currently DriveIOUntil will call `DriveIO` at least once, which means that if there is any CPU scheduling issues, // there's a chance 1.9s will already have elapsed by the time we get there, which will result in DriveIO being called when // it shouldn't. Better fix could happen inside DriveIOUntil, not sure the sideeffect there. // Advance monotonic looping to allow events to trigger gMockClock.AdvanceMonotonic(System::Clock::Milliseconds32(600)); GetIOContext().DriveIO(); EXPECT_FALSE(delegate.mGotEventResponse); EXPECT_FALSE(nonUrgentDelegate.mGotEventResponse); // Advance monotonic timestamp for min interval to elapse gMockClock.AdvanceMonotonic(System::Clock::Milliseconds32(800)); // Service Timer expired event GetIOContext().DriveIO(); // Service Engine Run GetIOContext().DriveIO(); // Service EventManagement event GetIOContext().DriveIO(); EXPECT_TRUE(delegate.mGotEventResponse); // The logic differs here depending on what Scheduler is implemented if (!sUsingSubSync) { EXPECT_FALSE(nonUrgentDelegate.mGotEventResponse); // Since we just sent a report for our urgent subscription, the min interval of the urgent subcription should have been // updated EXPECT_GT(gReportScheduler->GetMinTimestampForHandler(delegate.mpReadHandler), gMockClock.GetMonotonicTimestamp()); EXPECT_FALSE(delegate.mpReadHandler->IsDirty()); delegate.mGotEventResponse = false; // For our non-urgent subscription, we did not send anything, so the min interval should of the non urgent subcription // should be in the past EXPECT_LT(gReportScheduler->GetMinTimestampForHandler(nonUrgentDelegate.mpReadHandler), gMockClock.GetMonotonicTimestamp()); EXPECT_FALSE(nonUrgentDelegate.mpReadHandler->IsDirty()); // Advance monotonic timestamp for min interval to elapse gMockClock.AdvanceMonotonic(System::Clock::Milliseconds32(2100)); GetIOContext().DriveIO(); // No reporting should have happened. EXPECT_FALSE(delegate.mGotEventResponse); EXPECT_FALSE(nonUrgentDelegate.mGotEventResponse); // The min-interval should have elapsed for the urgent subscription, and our handler should still // not be dirty or reportable. EXPECT_LT(gReportScheduler->GetMinTimestampForHandler(delegate.mpReadHandler), System::SystemClock().GetMonotonicTimestamp()); EXPECT_FALSE(delegate.mpReadHandler->IsDirty()); EXPECT_FALSE(delegate.mpReadHandler->ShouldStartReporting()); // And the non-urgent one should not have changed state either, since // it's waiting for the max-interval. EXPECT_LT(gReportScheduler->GetMinTimestampForHandler(nonUrgentDelegate.mpReadHandler), System::SystemClock().GetMonotonicTimestamp()); EXPECT_GT(gReportScheduler->GetMaxTimestampForHandler(nonUrgentDelegate.mpReadHandler), System::SystemClock().GetMonotonicTimestamp()); EXPECT_FALSE(nonUrgentDelegate.mpReadHandler->IsDirty()); EXPECT_FALSE(nonUrgentDelegate.mpReadHandler->ShouldStartReporting()); // There should be no reporting run scheduled. This is very important; // otherwise we can get a false-positive pass below because the run was // already scheduled by here. EXPECT_FALSE(InteractionModelEngine::GetInstance()->GetReportingEngine().IsRunScheduled()); // Generate some events, which should get reported. GenerateEvents(); // Urgent read handler should now be dirty, and reportable. EXPECT_TRUE(delegate.mpReadHandler->IsDirty()); EXPECT_TRUE(delegate.mpReadHandler->ShouldStartReporting()); EXPECT_TRUE(gReportScheduler->IsReadHandlerReportable(delegate.mpReadHandler)); // Non-urgent read handler should not be reportable. EXPECT_FALSE(nonUrgentDelegate.mpReadHandler->IsDirty()); EXPECT_FALSE(nonUrgentDelegate.mpReadHandler->ShouldStartReporting()); // Still no reporting should have happened. EXPECT_FALSE(delegate.mGotEventResponse); EXPECT_FALSE(nonUrgentDelegate.mGotEventResponse); DrainAndServiceIO(); // Should get those urgent events reported. EXPECT_TRUE(delegate.mGotEventResponse); // Should get nothing reported on the non-urgent handler. EXPECT_FALSE(nonUrgentDelegate.mGotEventResponse); } else { // If we're using the sub-sync scheduler, we should have gotten the non-urgent event as well. EXPECT_TRUE(nonUrgentDelegate.mGotEventResponse); // Since we just sent a report for both our subscriptions, the min interval of the urgent subcription should have been EXPECT_GT(gReportScheduler->GetMinTimestampForHandler(delegate.mpReadHandler), gMockClock.GetMonotonicTimestamp()); EXPECT_GT(gReportScheduler->GetMinTimestampForHandler(nonUrgentDelegate.mpReadHandler), gMockClock.GetMonotonicTimestamp()); EXPECT_FALSE(delegate.mpReadHandler->IsDirty()); EXPECT_FALSE(nonUrgentDelegate.mpReadHandler->IsDirty()); delegate.mGotEventResponse = false; nonUrgentDelegate.mGotEventResponse = false; // Advance monotonic timestamp for min interval to elapse gMockClock.AdvanceMonotonic(System::Clock::Milliseconds32(2100)); GetIOContext().DriveIO(); // No reporting should have happened. EXPECT_FALSE(delegate.mGotEventResponse); EXPECT_FALSE(nonUrgentDelegate.mGotEventResponse); // The min-interval should have elapsed for both subscriptions, and our handlers should still // not be dirty or reportable. EXPECT_LT(gReportScheduler->GetMinTimestampForHandler(delegate.mpReadHandler), System::SystemClock().GetMonotonicTimestamp()); EXPECT_LT(gReportScheduler->GetMinTimestampForHandler(nonUrgentDelegate.mpReadHandler), System::SystemClock().GetMonotonicTimestamp()); EXPECT_FALSE(delegate.mpReadHandler->IsDirty()); EXPECT_FALSE(nonUrgentDelegate.mpReadHandler->IsDirty()); EXPECT_FALSE(delegate.mpReadHandler->ShouldStartReporting()); EXPECT_FALSE(nonUrgentDelegate.mpReadHandler->ShouldStartReporting()); // There should be no reporting run scheduled. This is very important; // otherwise we can get a false-positive pass below because the run was // already scheduled by here. EXPECT_FALSE(InteractionModelEngine::GetInstance()->GetReportingEngine().IsRunScheduled()); // Generate some events, which should get reported. GenerateEvents(); // Urgent read handler should now be dirty, and reportable. EXPECT_TRUE(delegate.mpReadHandler->IsDirty()); EXPECT_TRUE(delegate.mpReadHandler->ShouldStartReporting()); EXPECT_TRUE(gReportScheduler->IsReadHandlerReportable(delegate.mpReadHandler)); // Non-urgent read handler should not be reportable. EXPECT_FALSE(nonUrgentDelegate.mpReadHandler->IsDirty()); EXPECT_FALSE(nonUrgentDelegate.mpReadHandler->ShouldStartReporting()); // Still no reporting should have happened. EXPECT_FALSE(delegate.mGotEventResponse); EXPECT_FALSE(nonUrgentDelegate.mGotEventResponse); DrainAndServiceIO(); // Should get those urgent events reported and the non urgent reported for synchronisation EXPECT_TRUE(delegate.mGotEventResponse); EXPECT_TRUE(nonUrgentDelegate.mGotEventResponse); } } // By now we should have closed all exchanges and sent all pending acks, so // there should be no queued-up things in the retransmit table. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); engine->Shutdown(); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } TEST_F(TestReadInteraction, TestSubscribeWildcard) { // This test in particular is completely tied to the DefaultMockConfig in the mock // attribute storage, so reset to that (figuring out chunking location is extra hard to // maintain) chip::Test::ResetMockNodeConfig(); Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); GenerateEvents(); MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); EXPECT_FALSE(delegate.mGotEventResponse); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); readPrepareParams.mEventPathParamsListSize = 0; std::unique_ptr attributePathParams(new chip::app::AttributePathParams[2]); // Subscribe to full wildcard paths, repeat twice to ensure chunking. readPrepareParams.mpAttributePathParamsList = attributePathParams.get(); readPrepareParams.mAttributePathParamsListSize = 2; readPrepareParams.mMinIntervalFloorSeconds = 0; readPrepareParams.mMaxIntervalCeilingSeconds = 1; printf("\nSend subscribe request message to Node: 0x" ChipLogFormatX64 "\n", ChipLogValueX64(chip::kTestDeviceNodeId)); { app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Subscribe); delegate.mGotReport = false; attributePathParams.release(); EXPECT_EQ(readClient.SendAutoResubscribeRequest(std::move(readPrepareParams)), CHIP_NO_ERROR); DrainAndServiceIO(); EXPECT_TRUE(delegate.mGotReport); // Mock attribute storage that is reset and resides in src/app/util/mock/attribute-storage.cpp // has the following items: // - Endpoint 0xFFFE // - cluster 0xFFF1'FC01 (2 attributes) // - cluster 0xFFF1'FC02 (3 attributes) // - Endpoint 0xFFFD // - cluster 0xFFF1'FC01 (2 attributes) // - cluster 0xFFF1'FC02 (4 attributes) // - cluster 0xFFF1'FC03 (5 attributes) // - Endpoint 0xFFFC // - cluster 0xFFF1'FC01 (3 attributes) // - cluster 0xFFF1'FC02 (6 attributes) // - cluster 0xFFF1'FC03 (2 attributes) // - cluster 0xFFF1'FC04 (2 attributes) // // For at total of 29 attributes. There are two wildcard subscription // paths, for a total of 58 attributes. // // Attribute 0xFFFC::0xFFF1'FC02::0xFFF1'0004 (kMockEndpoint3::MockClusterId(2)::MockAttributeId(4)) // is a list of kMockAttribute4ListLength elements of size 256 bytes each, which cannot fit in a single // packet, so gets list chunking applied to it. // // Because delegate.mNumAttributeResponse counts AttributeDataIB instances, not attributes, // the count will depend on exactly how the list for attribute // 0xFFFC::0xFFF1'FC02::0xFFF1'0004 is chunked. For each of the two instances of that attribute // in the response, there will be one AttributeDataIB for the start of the list (which will include // some number of 256-byte elements), then one AttributeDataIB for each of the remaining elements. constexpr size_t kExpectedAttributeResponse = 29 * 2 + (kMockAttribute4ListLength - 4) + (kMockAttribute4ListLength - 4); EXPECT_EQ((unsigned) delegate.mNumAttributeResponse, kExpectedAttributeResponse); EXPECT_EQ(delegate.mNumArrayItems, 12); EXPECT_EQ(engine->GetNumActiveReadHandlers(ReadHandler::InteractionType::Subscribe), 1u); ASSERT_NE(engine->ActiveHandlerAt(0), nullptr); delegate.mpReadHandler = engine->ActiveHandlerAt(0); // Set a concrete path dirty { delegate.mGotReport = false; delegate.mNumAttributeResponse = 0; AttributePathParams dirtyPath; dirtyPath.mEndpointId = chip::Test::kMockEndpoint2; dirtyPath.mClusterId = chip::Test::MockClusterId(3); dirtyPath.mAttributeId = chip::Test::MockAttributeId(1); EXPECT_EQ(engine->GetReportingEngine().SetDirty(dirtyPath), CHIP_NO_ERROR); DrainAndServiceIO(); EXPECT_TRUE(delegate.mGotReport); // We subscribed wildcard path twice, so we will receive two reports here. EXPECT_EQ(delegate.mNumAttributeResponse, 2); } // Set a endpoint dirty { delegate.mGotReport = false; delegate.mNumAttributeResponse = 0; delegate.mNumArrayItems = 0; AttributePathParams dirtyPath; dirtyPath.mEndpointId = chip::Test::kMockEndpoint3; EXPECT_EQ(engine->GetReportingEngine().SetDirty(dirtyPath), CHIP_NO_ERROR); // // We need to DrainAndServiceIO() until attribute callback will be called. // This is not correct behavior and is tracked in Issue #17528. // int last; do { last = delegate.mNumAttributeResponse; DrainAndServiceIO(); } while (last != delegate.mNumAttributeResponse); // Mock endpoint3 has 13 attributes in total, and we subscribed twice. // And attribute 3/2/4 is a list with 6 elements and list chunking // is applied to it, but the way the packet boundaries fall we get two of // its items as a single list, followed by 4 more items for one // of our subscriptions, and 3 items as a single list followed by 3 // more items for the other. // // Thus we should receive 13*2 + 4 + 3 = 33 attribute data in total. ChipLogError(DataManagement, "RESPO: %d\n", delegate.mNumAttributeResponse); EXPECT_EQ(delegate.mNumAttributeResponse, 33); EXPECT_EQ(delegate.mNumArrayItems, 12); } } EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); engine->Shutdown(); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } // Subscribe (wildcard, C3, A1), then setDirty (E2, C3, wildcard), receive one attribute after setDirty TEST_F(TestReadInteraction, TestSubscribePartialOverlap) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); EXPECT_FALSE(delegate.mGotEventResponse); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); readPrepareParams.mEventPathParamsListSize = 0; std::unique_ptr attributePathParams(new chip::app::AttributePathParams[2]); attributePathParams[0].mClusterId = chip::Test::MockClusterId(3); attributePathParams[0].mAttributeId = chip::Test::MockAttributeId(1); readPrepareParams.mpAttributePathParamsList = attributePathParams.get(); readPrepareParams.mAttributePathParamsListSize = 1; readPrepareParams.mMinIntervalFloorSeconds = 0; readPrepareParams.mMaxIntervalCeilingSeconds = 1; printf("\nSend subscribe request message to Node: 0x" ChipLogFormatX64 "\n", ChipLogValueX64(chip::kTestDeviceNodeId)); { app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Subscribe); delegate.mGotReport = false; attributePathParams.release(); EXPECT_EQ(readClient.SendAutoResubscribeRequest(std::move(readPrepareParams)), CHIP_NO_ERROR); DrainAndServiceIO(); EXPECT_TRUE(delegate.mGotReport); EXPECT_EQ(delegate.mNumAttributeResponse, 1); EXPECT_EQ(engine->GetNumActiveReadHandlers(ReadHandler::InteractionType::Subscribe), 1u); ASSERT_NE(engine->ActiveHandlerAt(0), nullptr); delegate.mpReadHandler = engine->ActiveHandlerAt(0); // Set a partial overlapped path dirty { delegate.mGotReport = false; delegate.mNumAttributeResponse = 0; AttributePathParams dirtyPath; dirtyPath.mEndpointId = chip::Test::kMockEndpoint2; dirtyPath.mClusterId = chip::Test::MockClusterId(3); EXPECT_EQ(engine->GetReportingEngine().SetDirty(dirtyPath), CHIP_NO_ERROR); DrainAndServiceIO(); EXPECT_TRUE(delegate.mGotReport); EXPECT_EQ(delegate.mNumAttributeResponse, 1); EXPECT_EQ(delegate.mReceivedAttributePaths[0].mEndpointId, chip::Test::kMockEndpoint2); EXPECT_EQ(delegate.mReceivedAttributePaths[0].mClusterId, chip::Test::MockClusterId(3)); EXPECT_EQ(delegate.mReceivedAttributePaths[0].mAttributeId, chip::Test::MockAttributeId(1)); } } EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); engine->Shutdown(); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } // Subscribe (E2, C3, A1), then setDirty (wildcard, wildcard, wildcard), receive one attribute after setDirty TEST_F(TestReadInteraction, TestSubscribeSetDirtyFullyOverlap) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); EXPECT_FALSE(delegate.mGotEventResponse); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); readPrepareParams.mEventPathParamsListSize = 0; std::unique_ptr attributePathParams(new chip::app::AttributePathParams[1]); attributePathParams[0].mClusterId = chip::Test::kMockEndpoint2; attributePathParams[0].mClusterId = chip::Test::MockClusterId(3); attributePathParams[0].mAttributeId = chip::Test::MockAttributeId(1); readPrepareParams.mpAttributePathParamsList = attributePathParams.get(); readPrepareParams.mAttributePathParamsListSize = 1; readPrepareParams.mMinIntervalFloorSeconds = 0; readPrepareParams.mMaxIntervalCeilingSeconds = 1; printf("\nSend subscribe request message to Node: 0x" ChipLogFormatX64 "\n", ChipLogValueX64(chip::kTestDeviceNodeId)); { app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Subscribe); delegate.mGotReport = false; attributePathParams.release(); EXPECT_EQ(readClient.SendAutoResubscribeRequest(std::move(readPrepareParams)), CHIP_NO_ERROR); DrainAndServiceIO(); EXPECT_TRUE(delegate.mGotReport); EXPECT_EQ(delegate.mNumAttributeResponse, 1); EXPECT_EQ(engine->GetNumActiveReadHandlers(ReadHandler::InteractionType::Subscribe), 1u); ASSERT_NE(engine->ActiveHandlerAt(0), nullptr); delegate.mpReadHandler = engine->ActiveHandlerAt(0); // Set a full overlapped path dirty and expect to receive one E2C3A1 { delegate.mGotReport = false; delegate.mNumAttributeResponse = 0; AttributePathParams dirtyPath; EXPECT_EQ(engine->GetReportingEngine().SetDirty(dirtyPath), CHIP_NO_ERROR); DrainAndServiceIO(); EXPECT_TRUE(delegate.mGotReport); EXPECT_EQ(delegate.mNumAttributeResponse, 1); EXPECT_EQ(delegate.mReceivedAttributePaths[0].mEndpointId, chip::Test::kMockEndpoint2); EXPECT_EQ(delegate.mReceivedAttributePaths[0].mClusterId, chip::Test::MockClusterId(3)); EXPECT_EQ(delegate.mReceivedAttributePaths[0].mAttributeId, chip::Test::MockAttributeId(1)); } } EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); engine->Shutdown(); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } // Verify that subscription can be shut down just after receiving SUBSCRIBE RESPONSE, // before receiving any subsequent REPORT DATA. TEST_F(TestReadInteraction, TestSubscribeEarlyShutdown) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); InteractionModelEngine & engine = *InteractionModelEngine::GetInstance(); MockInteractionModelApp delegate; // Initialize Interaction Model Engine EXPECT_EQ(rm->TestGetCountRetransTable(), 0); EXPECT_EQ(engine.Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); // Subscribe to the attribute AttributePathParams attributePathParams; attributePathParams.mEndpointId = kTestEndpointId; attributePathParams.mClusterId = kTestClusterId; attributePathParams.mAttributeId = 1; ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); readPrepareParams.mpAttributePathParamsList = &attributePathParams; readPrepareParams.mAttributePathParamsListSize = 1; readPrepareParams.mMinIntervalFloorSeconds = 2; readPrepareParams.mMaxIntervalCeilingSeconds = 5; readPrepareParams.mKeepSubscriptions = false; printf("Send subscribe request message to Node: 0x" ChipLogFormatX64 "\n", ChipLogValueX64(chip::kTestDeviceNodeId)); { app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Subscribe); EXPECT_EQ(readClient.SendRequest(readPrepareParams), CHIP_NO_ERROR); DrainAndServiceIO(); EXPECT_TRUE(delegate.mGotReport); EXPECT_EQ(delegate.mNumAttributeResponse, 1); EXPECT_EQ(engine.GetNumActiveReadHandlers(ReadHandler::InteractionType::Subscribe), 1u); ASSERT_NE(engine.ActiveHandlerAt(0), nullptr); delegate.mpReadHandler = engine.ActiveHandlerAt(0); ASSERT_NE(delegate.mpReadHandler, nullptr); } // Cleanup EXPECT_EQ(engine.GetNumActiveReadClients(), 0u); EXPECT_EQ(rm->TestGetCountRetransTable(), 0); engine.Shutdown(); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } TEST_F_FROM_FIXTURE(TestReadInteraction, TestSubscribeInvalidAttributePathRoundtrip) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); GenerateEvents(); MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); EXPECT_FALSE(delegate.mGotEventResponse); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); chip::app::AttributePathParams attributePathParams[1]; readPrepareParams.mpAttributePathParamsList = attributePathParams; readPrepareParams.mpAttributePathParamsList[0].mEndpointId = kTestEndpointId; readPrepareParams.mpAttributePathParamsList[0].mClusterId = kInvalidTestClusterId; readPrepareParams.mpAttributePathParamsList[0].mAttributeId = 1; readPrepareParams.mAttributePathParamsListSize = 1; readPrepareParams.mSessionHolder.Grab(GetSessionBobToAlice()); readPrepareParams.mMinIntervalFloorSeconds = 0; readPrepareParams.mMaxIntervalCeilingSeconds = 1; printf("\nSend subscribe request message to Node: 0x" ChipLogFormatX64 "\n", ChipLogValueX64(chip::kTestDeviceNodeId)); { app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Subscribe); EXPECT_EQ(readClient.SendRequest(readPrepareParams), CHIP_NO_ERROR); delegate.mNumAttributeResponse = 0; DrainAndServiceIO(); EXPECT_EQ(delegate.mNumAttributeResponse, 0); ASSERT_NE(engine->ActiveHandlerAt(0), nullptr); delegate.mpReadHandler = engine->ActiveHandlerAt(0); uint16_t minInterval; uint16_t maxInterval; delegate.mpReadHandler->GetReportingIntervals(minInterval, maxInterval); EXPECT_EQ(readPrepareParams.mMaxIntervalCeilingSeconds, delegate.mpReadHandler->GetSubscriberRequestedMaxInterval()); // Advance monotonic timestamp for min interval to elapse gMockClock.AdvanceMonotonic(System::Clock::Seconds16(maxInterval)); GetIOContext().DriveIO(); EXPECT_TRUE(engine->GetReportingEngine().IsRunScheduled()); EXPECT_TRUE(engine->GetReportingEngine().IsRunScheduled()); DrainAndServiceIO(); EXPECT_EQ(delegate.mNumAttributeResponse, 0); } EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); engine->Shutdown(); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } TEST_F(TestReadInteraction, TestReadShutdown) { auto * engine = chip::app::InteractionModelEngine::GetInstance(); app::ReadClient * pClients[4]; MockInteractionModelApp delegate; // // Allocate a number of clients // for (auto & client : pClients) { client = Platform::New(engine, &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Subscribe); } // // Delete every other client to ensure we test out // deleting clients from the list of clients tracked by the IM // Platform::Delete(pClients[1]); Platform::Delete(pClients[3]); // // Shutdown the engine first so that we can // de-activate the internal list. // engine->Shutdown(); // // Shutdown the read clients. These should // safely destruct without causing any egregious // harm // Platform::Delete(pClients[0]); Platform::Delete(pClients[2]); } TEST_F(TestReadInteraction, TestSubscribeInvalidInterval) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); GenerateEvents(); MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); EXPECT_FALSE(delegate.mGotEventResponse); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); chip::app::AttributePathParams attributePathParams[1]; readPrepareParams.mpAttributePathParamsList = attributePathParams; readPrepareParams.mpAttributePathParamsList[0].mEndpointId = kTestEndpointId; readPrepareParams.mpAttributePathParamsList[0].mClusterId = kTestClusterId; readPrepareParams.mpAttributePathParamsList[0].mAttributeId = 1; readPrepareParams.mAttributePathParamsListSize = 1; readPrepareParams.mSessionHolder.Grab(GetSessionBobToAlice()); readPrepareParams.mMinIntervalFloorSeconds = 6; readPrepareParams.mMaxIntervalCeilingSeconds = 5; { app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Subscribe); EXPECT_EQ(readClient.SendRequest(readPrepareParams), CHIP_ERROR_INVALID_ARGUMENT); printf("\nSend subscribe request message to Node: 0x" ChipLogFormatX64 "\n", ChipLogValueX64(chip::kTestDeviceNodeId)); DrainAndServiceIO(); } EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); engine->Shutdown(); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } TEST_F_FROM_FIXTURE(TestReadInteraction, TestPostSubscribeRoundtripStatusReportTimeout) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); EXPECT_FALSE(delegate.mGotEventResponse); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); chip::app::EventPathParams eventPathParams[2]; readPrepareParams.mpEventPathParamsList = eventPathParams; readPrepareParams.mpEventPathParamsList[0].mEndpointId = kTestEventEndpointId; readPrepareParams.mpEventPathParamsList[0].mClusterId = kTestEventClusterId; readPrepareParams.mpEventPathParamsList[0].mEventId = kTestEventIdDebug; readPrepareParams.mpEventPathParamsList[1].mEndpointId = kTestEventEndpointId; readPrepareParams.mpEventPathParamsList[1].mClusterId = kTestEventClusterId; readPrepareParams.mpEventPathParamsList[1].mEventId = kTestEventIdCritical; readPrepareParams.mEventPathParamsListSize = 2; chip::app::AttributePathParams attributePathParams[2]; readPrepareParams.mpAttributePathParamsList = attributePathParams; readPrepareParams.mpAttributePathParamsList[0].mEndpointId = kTestEndpointId; readPrepareParams.mpAttributePathParamsList[0].mClusterId = kTestClusterId; readPrepareParams.mpAttributePathParamsList[0].mAttributeId = 1; readPrepareParams.mpAttributePathParamsList[1].mEndpointId = kTestEndpointId; readPrepareParams.mpAttributePathParamsList[1].mClusterId = kTestClusterId; readPrepareParams.mpAttributePathParamsList[1].mAttributeId = 2; readPrepareParams.mAttributePathParamsListSize = 2; readPrepareParams.mMinIntervalFloorSeconds = 0; readPrepareParams.mMaxIntervalCeilingSeconds = 1; delegate.mNumAttributeResponse = 0; readPrepareParams.mKeepSubscriptions = false; { app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Subscribe); printf("\nSend first subscribe request message to Node: 0x" ChipLogFormatX64 "\n", ChipLogValueX64(chip::kTestDeviceNodeId)); delegate.mGotReport = false; EXPECT_EQ(readClient.SendRequest(readPrepareParams), CHIP_NO_ERROR); DrainAndServiceIO(); EXPECT_EQ(engine->GetNumActiveReadHandlers(), 1u); ASSERT_NE(engine->ActiveHandlerAt(0), nullptr); delegate.mpReadHandler = engine->ActiveHandlerAt(0); EXPECT_TRUE(delegate.mGotReport); EXPECT_EQ(delegate.mNumAttributeResponse, 2); EXPECT_EQ(engine->GetNumActiveReadHandlers(ReadHandler::InteractionType::Subscribe), 1u); GenerateEvents(); chip::app::AttributePathParams dirtyPath1; dirtyPath1.mClusterId = kTestClusterId; dirtyPath1.mEndpointId = kTestEndpointId; dirtyPath1.mAttributeId = 1; chip::app::AttributePathParams dirtyPath2; dirtyPath2.mClusterId = kTestClusterId; dirtyPath2.mEndpointId = kTestEndpointId; dirtyPath2.mAttributeId = 2; // Test report with 2 different path delegate.mGotReport = false; delegate.mNumAttributeResponse = 0; EXPECT_EQ(engine->GetReportingEngine().SetDirty(dirtyPath1), CHIP_NO_ERROR); EXPECT_EQ(engine->GetReportingEngine().SetDirty(dirtyPath2), CHIP_NO_ERROR); DrainAndServiceIO(); EXPECT_TRUE(delegate.mGotReport); EXPECT_EQ(delegate.mNumAttributeResponse, 2); // Wait for max interval to elapse gMockClock.AdvanceMonotonic(System::Clock::Seconds16(readPrepareParams.mMaxIntervalCeilingSeconds)); GetIOContext().DriveIO(); delegate.mGotReport = false; delegate.mNumAttributeResponse = 0; ExpireSessionBobToAlice(); EXPECT_EQ(engine->GetReportingEngine().SetDirty(dirtyPath1), CHIP_NO_ERROR); EXPECT_EQ(engine->GetReportingEngine().SetDirty(dirtyPath2), CHIP_NO_ERROR); EXPECT_TRUE(engine->GetReportingEngine().IsRunScheduled()); DrainAndServiceIO(); ExpireSessionAliceToBob(); EXPECT_EQ(engine->GetReportingEngine().GetNumReportsInFlight(), 0u); EXPECT_EQ(delegate.mNumAttributeResponse, 0); } // By now we should have closed all exchanges and sent all pending acks, so // there should be no queued-up things in the retransmit table. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); engine->Shutdown(); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); CreateSessionAliceToBob(); CreateSessionBobToAlice(); } TEST_F(TestReadInteraction, TestSubscribeRoundtripStatusReportTimeout) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); EXPECT_FALSE(delegate.mGotEventResponse); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); chip::app::EventPathParams eventPathParams[2]; readPrepareParams.mpEventPathParamsList = eventPathParams; readPrepareParams.mpEventPathParamsList[0].mEndpointId = kTestEventEndpointId; readPrepareParams.mpEventPathParamsList[0].mClusterId = kTestEventClusterId; readPrepareParams.mpEventPathParamsList[0].mEventId = kTestEventIdDebug; readPrepareParams.mpEventPathParamsList[1].mEndpointId = kTestEventEndpointId; readPrepareParams.mpEventPathParamsList[1].mClusterId = kTestEventClusterId; readPrepareParams.mpEventPathParamsList[1].mEventId = kTestEventIdCritical; readPrepareParams.mEventPathParamsListSize = 2; chip::app::AttributePathParams attributePathParams[2]; readPrepareParams.mpAttributePathParamsList = attributePathParams; readPrepareParams.mpAttributePathParamsList[0].mEndpointId = kTestEndpointId; readPrepareParams.mpAttributePathParamsList[0].mClusterId = kTestClusterId; readPrepareParams.mpAttributePathParamsList[0].mAttributeId = 1; readPrepareParams.mpAttributePathParamsList[1].mEndpointId = kTestEndpointId; readPrepareParams.mpAttributePathParamsList[1].mClusterId = kTestClusterId; readPrepareParams.mpAttributePathParamsList[1].mAttributeId = 2; readPrepareParams.mAttributePathParamsListSize = 2; readPrepareParams.mMinIntervalFloorSeconds = 2; readPrepareParams.mMaxIntervalCeilingSeconds = 5; delegate.mNumAttributeResponse = 0; readPrepareParams.mKeepSubscriptions = false; { app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Subscribe); printf("\nSend first subscribe request message to Node: 0x" ChipLogFormatX64 "\n", ChipLogValueX64(chip::kTestDeviceNodeId)); delegate.mGotReport = false; EXPECT_EQ(readClient.SendRequest(readPrepareParams), CHIP_NO_ERROR); ExpireSessionAliceToBob(); DrainAndServiceIO(); ExpireSessionBobToAlice(); EXPECT_EQ(engine->GetNumActiveReadHandlers(), 0u); EXPECT_EQ(engine->GetReportingEngine().GetNumReportsInFlight(), 0u); EXPECT_EQ(delegate.mNumAttributeResponse, 0); } // By now we should have closed all exchanges and sent all pending acks, so // there should be no queued-up things in the retransmit table. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); engine->Shutdown(); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); CreateSessionAliceToBob(); CreateSessionBobToAlice(); } TEST_F(TestReadInteraction, TestReadChunkingStatusReportTimeout) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); GenerateEvents(); MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); EXPECT_FALSE(delegate.mGotEventResponse); chip::app::AttributePathParams attributePathParams[1]; // Mock Attribute 4 is a big attribute, with 6 large OCTET_STRING attributePathParams[0].mEndpointId = chip::Test::kMockEndpoint3; attributePathParams[0].mClusterId = chip::Test::MockClusterId(2); attributePathParams[0].mAttributeId = chip::Test::MockAttributeId(4); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); readPrepareParams.mpEventPathParamsList = nullptr; readPrepareParams.mEventPathParamsListSize = 0; readPrepareParams.mpAttributePathParamsList = attributePathParams; readPrepareParams.mAttributePathParamsListSize = 1; { app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Read); EXPECT_EQ(readClient.SendRequest(readPrepareParams), CHIP_NO_ERROR); ExpireSessionAliceToBob(); DrainAndServiceIO(); ExpireSessionBobToAlice(); EXPECT_EQ(engine->GetReportingEngine().GetNumReportsInFlight(), 0u); // By now we should have closed all exchanges and sent all pending acks, so // there should be no queued-up things in the retransmit table. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); } EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); engine->Shutdown(); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); CreateSessionAliceToBob(); CreateSessionBobToAlice(); } // ReadClient sends the read request, but handler fails to send the one report (SendMessage returns an error). // Since this is an un-chunked read, we are not in the AwaitingReportResponse state, so the "reports in flight" // counter should not increase. TEST_F(TestReadInteraction, TestReadReportFailure) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); EXPECT_FALSE(delegate.mGotEventResponse); chip::app::AttributePathParams attributePathParams[1]; attributePathParams[0].mEndpointId = chip::Test::kMockEndpoint2; attributePathParams[0].mClusterId = chip::Test::MockClusterId(3); attributePathParams[0].mAttributeId = chip::Test::MockAttributeId(1); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); readPrepareParams.mpEventPathParamsList = nullptr; readPrepareParams.mEventPathParamsListSize = 0; readPrepareParams.mpAttributePathParamsList = attributePathParams; readPrepareParams.mAttributePathParamsListSize = 1; { app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Read); GetLoopback().mNumMessagesToAllowBeforeError = 1; GetLoopback().mMessageSendError = CHIP_ERROR_INCORRECT_STATE; EXPECT_EQ(readClient.SendRequest(readPrepareParams), CHIP_NO_ERROR); DrainAndServiceIO(); EXPECT_EQ(engine->GetReportingEngine().GetNumReportsInFlight(), 0u); EXPECT_EQ(engine->GetNumActiveReadHandlers(), 0u); GetLoopback().mNumMessagesToAllowBeforeError = 0; GetLoopback().mMessageSendError = CHIP_NO_ERROR; } EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); engine->Shutdown(); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } TEST_F(TestReadInteraction, TestSubscribeRoundtripChunkStatusReportTimeout) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); EXPECT_FALSE(delegate.mGotEventResponse); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); chip::app::EventPathParams eventPathParams[2]; readPrepareParams.mpEventPathParamsList = eventPathParams; readPrepareParams.mpEventPathParamsList[0].mEndpointId = kTestEventEndpointId; readPrepareParams.mpEventPathParamsList[0].mClusterId = kTestEventClusterId; readPrepareParams.mpEventPathParamsList[0].mEventId = kTestEventIdDebug; readPrepareParams.mpEventPathParamsList[1].mEndpointId = kTestEventEndpointId; readPrepareParams.mpEventPathParamsList[1].mClusterId = kTestEventClusterId; readPrepareParams.mpEventPathParamsList[1].mEventId = kTestEventIdCritical; readPrepareParams.mEventPathParamsListSize = 2; chip::app::AttributePathParams attributePathParams[1]; // Mock Attribute 4 is a big attribute, with 6 large OCTET_STRING attributePathParams[0].mEndpointId = chip::Test::kMockEndpoint3; attributePathParams[0].mClusterId = chip::Test::MockClusterId(2); attributePathParams[0].mAttributeId = chip::Test::MockAttributeId(4); readPrepareParams.mpAttributePathParamsList = attributePathParams; readPrepareParams.mAttributePathParamsListSize = 1; readPrepareParams.mMinIntervalFloorSeconds = 2; readPrepareParams.mMaxIntervalCeilingSeconds = 5; delegate.mNumAttributeResponse = 0; readPrepareParams.mKeepSubscriptions = false; { app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Subscribe); printf("\nSend first subscribe request message to Node: 0x" ChipLogFormatX64 "\n", ChipLogValueX64(chip::kTestDeviceNodeId)); delegate.mGotReport = false; EXPECT_EQ(readClient.SendRequest(readPrepareParams), CHIP_NO_ERROR); ExpireSessionAliceToBob(); DrainAndServiceIO(); ExpireSessionBobToAlice(); EXPECT_EQ(engine->GetNumActiveReadHandlers(), 0u); EXPECT_EQ(engine->GetReportingEngine().GetNumReportsInFlight(), 0u); EXPECT_EQ(delegate.mNumAttributeResponse, 0); } // By now we should have closed all exchanges and sent all pending acks, so // there should be no queued-up things in the retransmit table. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); engine->Shutdown(); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); CreateSessionAliceToBob(); CreateSessionBobToAlice(); } TEST_F(TestReadInteraction, TestPostSubscribeRoundtripChunkStatusReportTimeout) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); EXPECT_FALSE(delegate.mGotEventResponse); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); chip::app::EventPathParams eventPathParams[2]; readPrepareParams.mpEventPathParamsList = eventPathParams; readPrepareParams.mpEventPathParamsList[0].mEndpointId = kTestEventEndpointId; readPrepareParams.mpEventPathParamsList[0].mClusterId = kTestEventClusterId; readPrepareParams.mpEventPathParamsList[0].mEventId = kTestEventIdDebug; readPrepareParams.mpEventPathParamsList[1].mEndpointId = kTestEventEndpointId; readPrepareParams.mpEventPathParamsList[1].mClusterId = kTestEventClusterId; readPrepareParams.mpEventPathParamsList[1].mEventId = kTestEventIdCritical; readPrepareParams.mEventPathParamsListSize = 2; chip::app::AttributePathParams attributePathParams[1]; // Mock Attribute 4 is a big attribute, with 6 large OCTET_STRING attributePathParams[0].mEndpointId = chip::Test::kMockEndpoint3; attributePathParams[0].mClusterId = chip::Test::MockClusterId(2); attributePathParams[0].mAttributeId = chip::Test::MockAttributeId(4); readPrepareParams.mpAttributePathParamsList = attributePathParams; readPrepareParams.mAttributePathParamsListSize = 1; readPrepareParams.mMinIntervalFloorSeconds = 0; readPrepareParams.mMaxIntervalCeilingSeconds = 1; delegate.mNumAttributeResponse = 0; readPrepareParams.mKeepSubscriptions = false; { app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Subscribe); printf("\nSend first subscribe request message to Node: 0x" ChipLogFormatX64 "\n", ChipLogValueX64(chip::kTestDeviceNodeId)); delegate.mGotReport = false; EXPECT_EQ(readClient.SendRequest(readPrepareParams), CHIP_NO_ERROR); DrainAndServiceIO(); EXPECT_EQ(engine->GetNumActiveReadHandlers(), 1u); ASSERT_NE(engine->ActiveHandlerAt(0), nullptr); delegate.mpReadHandler = engine->ActiveHandlerAt(0); EXPECT_TRUE(delegate.mGotReport); EXPECT_EQ(engine->GetNumActiveReadHandlers(ReadHandler::InteractionType::Subscribe), 1u); GenerateEvents(); chip::app::AttributePathParams dirtyPath1; dirtyPath1.mClusterId = chip::Test::MockClusterId(2); dirtyPath1.mEndpointId = chip::Test::kMockEndpoint3; dirtyPath1.mAttributeId = chip::Test::MockAttributeId(4); gMockClock.AdvanceMonotonic(System::Clock::Seconds16(readPrepareParams.mMaxIntervalCeilingSeconds)); GetIOContext().DriveIO(); EXPECT_EQ(engine->GetReportingEngine().SetDirty(dirtyPath1), CHIP_NO_ERROR); delegate.mGotReport = false; delegate.mNumAttributeResponse = 0; GetLoopback().mSentMessageCount = 0; GetLoopback().mNumMessagesToDrop = 1; GetLoopback().mNumMessagesToAllowBeforeDropping = 1; GetLoopback().mDroppedMessageCount = 0; DrainAndServiceIO(); // Drop status report for the first chunked report, then expire session, handler would be timeout EXPECT_EQ(engine->GetReportingEngine().GetNumReportsInFlight(), 1u); EXPECT_EQ(GetLoopback().mSentMessageCount, 2u); EXPECT_EQ(GetLoopback().mDroppedMessageCount, 1u); EXPECT_EQ(engine->GetNumActiveReadHandlers(), 1u); ExpireSessionAliceToBob(); ExpireSessionBobToAlice(); EXPECT_EQ(engine->GetNumActiveReadHandlers(), 0u); GetLoopback().mSentMessageCount = 0; GetLoopback().mNumMessagesToDrop = 0; GetLoopback().mNumMessagesToAllowBeforeDropping = 0; GetLoopback().mDroppedMessageCount = 0; } EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); engine->Shutdown(); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); CreateSessionAliceToBob(); CreateSessionBobToAlice(); } TEST_F(TestReadInteraction, TestPostSubscribeRoundtripChunkReportTimeout) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); EXPECT_FALSE(delegate.mGotEventResponse); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); chip::app::EventPathParams eventPathParams[2]; readPrepareParams.mpEventPathParamsList = eventPathParams; readPrepareParams.mpEventPathParamsList[0].mEndpointId = kTestEventEndpointId; readPrepareParams.mpEventPathParamsList[0].mClusterId = kTestEventClusterId; readPrepareParams.mpEventPathParamsList[0].mEventId = kTestEventIdDebug; readPrepareParams.mpEventPathParamsList[1].mEndpointId = kTestEventEndpointId; readPrepareParams.mpEventPathParamsList[1].mClusterId = kTestEventClusterId; readPrepareParams.mpEventPathParamsList[1].mEventId = kTestEventIdCritical; readPrepareParams.mEventPathParamsListSize = 2; chip::app::AttributePathParams attributePathParams[1]; // Mock Attribute 4 is a big attribute, with 6 large OCTET_STRING attributePathParams[0].mEndpointId = chip::Test::kMockEndpoint3; attributePathParams[0].mClusterId = chip::Test::MockClusterId(2); attributePathParams[0].mAttributeId = chip::Test::MockAttributeId(4); readPrepareParams.mpAttributePathParamsList = attributePathParams; readPrepareParams.mAttributePathParamsListSize = 1; readPrepareParams.mMinIntervalFloorSeconds = 0; readPrepareParams.mMaxIntervalCeilingSeconds = 1; delegate.mNumAttributeResponse = 0; readPrepareParams.mKeepSubscriptions = false; { app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Subscribe); printf("\nSend first subscribe request message to Node: 0x" ChipLogFormatX64 "\n", ChipLogValueX64(chip::kTestDeviceNodeId)); delegate.mGotReport = false; EXPECT_EQ(readClient.SendRequest(readPrepareParams), CHIP_NO_ERROR); DrainAndServiceIO(); EXPECT_EQ(engine->GetNumActiveReadHandlers(), 1u); ASSERT_NE(engine->ActiveHandlerAt(0), nullptr); delegate.mpReadHandler = engine->ActiveHandlerAt(0); EXPECT_TRUE(delegate.mGotReport); EXPECT_EQ(engine->GetNumActiveReadHandlers(ReadHandler::InteractionType::Subscribe), 1u); GenerateEvents(); chip::app::AttributePathParams dirtyPath1; dirtyPath1.mClusterId = chip::Test::MockClusterId(2); dirtyPath1.mEndpointId = chip::Test::kMockEndpoint3; dirtyPath1.mAttributeId = chip::Test::MockAttributeId(4); gMockClock.AdvanceMonotonic(System::Clock::Seconds16(readPrepareParams.mMaxIntervalCeilingSeconds)); GetIOContext().DriveIO(); EXPECT_EQ(engine->GetReportingEngine().SetDirty(dirtyPath1), CHIP_NO_ERROR); delegate.mGotReport = false; delegate.mNumAttributeResponse = 0; // Drop second chunked report then expire session, client would be timeout GetLoopback().mSentMessageCount = 0; GetLoopback().mNumMessagesToDrop = 1; GetLoopback().mNumMessagesToAllowBeforeDropping = 2; GetLoopback().mDroppedMessageCount = 0; DrainAndServiceIO(); EXPECT_EQ(engine->GetReportingEngine().GetNumReportsInFlight(), 1u); EXPECT_EQ(GetLoopback().mSentMessageCount, 3u); EXPECT_EQ(GetLoopback().mDroppedMessageCount, 1u); ExpireSessionAliceToBob(); ExpireSessionBobToAlice(); EXPECT_EQ(delegate.mError, CHIP_ERROR_TIMEOUT); GetLoopback().mSentMessageCount = 0; GetLoopback().mNumMessagesToDrop = 0; GetLoopback().mNumMessagesToAllowBeforeDropping = 0; GetLoopback().mDroppedMessageCount = 0; } EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); engine->Shutdown(); CreateSessionAliceToBob(); CreateSessionBobToAlice(); } TEST_F(TestReadInteraction, TestPostSubscribeRoundtripChunkReport) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); EXPECT_FALSE(delegate.mGotEventResponse); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); chip::app::EventPathParams eventPathParams[2]; readPrepareParams.mpEventPathParamsList = eventPathParams; readPrepareParams.mpEventPathParamsList[0].mEndpointId = kTestEventEndpointId; readPrepareParams.mpEventPathParamsList[0].mClusterId = kTestEventClusterId; readPrepareParams.mpEventPathParamsList[0].mEventId = kTestEventIdDebug; readPrepareParams.mpEventPathParamsList[1].mEndpointId = kTestEventEndpointId; readPrepareParams.mpEventPathParamsList[1].mClusterId = kTestEventClusterId; readPrepareParams.mpEventPathParamsList[1].mEventId = kTestEventIdCritical; readPrepareParams.mEventPathParamsListSize = 2; chip::app::AttributePathParams attributePathParams[1]; // Mock Attribute 4 is a big attribute, with 6 large OCTET_STRING attributePathParams[0].mEndpointId = chip::Test::kMockEndpoint3; attributePathParams[0].mClusterId = chip::Test::MockClusterId(2); attributePathParams[0].mAttributeId = chip::Test::MockAttributeId(4); readPrepareParams.mpAttributePathParamsList = attributePathParams; readPrepareParams.mAttributePathParamsListSize = 1; readPrepareParams.mMinIntervalFloorSeconds = 1; readPrepareParams.mMaxIntervalCeilingSeconds = 5; delegate.mNumAttributeResponse = 0; readPrepareParams.mKeepSubscriptions = false; { app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Subscribe); printf("\nSend first subscribe request message to Node: 0x" ChipLogFormatX64 "\n", ChipLogValueX64(chip::kTestDeviceNodeId)); delegate.mGotReport = false; EXPECT_EQ(readClient.SendRequest(readPrepareParams), CHIP_NO_ERROR); DrainAndServiceIO(); EXPECT_EQ(engine->GetNumActiveReadHandlers(), 1u); ASSERT_NE(engine->ActiveHandlerAt(0), nullptr); delegate.mpReadHandler = engine->ActiveHandlerAt(0); EXPECT_TRUE(delegate.mGotReport); EXPECT_EQ(engine->GetNumActiveReadHandlers(ReadHandler::InteractionType::Subscribe), 1u); GenerateEvents(); chip::app::AttributePathParams dirtyPath1; dirtyPath1.mClusterId = chip::Test::MockClusterId(2); dirtyPath1.mEndpointId = chip::Test::kMockEndpoint3; dirtyPath1.mAttributeId = chip::Test::MockAttributeId(4); engine->GetReportingEngine().SetDirty(dirtyPath1); delegate.mGotReport = false; delegate.mNumAttributeResponse = 0; delegate.mNumArrayItems = 0; // wait for min interval 1 seconds(in test, we use 0.9second considering the time variation), expect no event is // received, then wait for 0.5 seconds, then all chunked dirty reports are sent out, which would not honor minInterval gMockClock.AdvanceMonotonic(System::Clock::Milliseconds32(900)); GetIOContext().DriveIO(); EXPECT_EQ(delegate.mNumAttributeResponse, 0); System::Clock::Timestamp startTime = gMockClock.GetMonotonicTimestamp(); // Increment in time is done by steps here to allow for multiple IO processing at the right time and allow the timer to // be rescheduled accordingly while (true) { GetIOContext().DriveIO(); if ((gMockClock.GetMonotonicTimestamp() - startTime) >= System::Clock::Milliseconds32(500)) { break; } gMockClock.AdvanceMonotonic(System::Clock::Milliseconds32(10)); } } // We get one chunk with 4 array elements, and then one chunk per // element, and the total size of the array is // kMockAttribute4ListLength. EXPECT_EQ(delegate.mNumAttributeResponse, 1 + (kMockAttribute4ListLength - 4)); EXPECT_EQ(delegate.mNumArrayItems, 6); EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); engine->Shutdown(); } namespace { void CheckForInvalidAction(Test::MessageCapturer & messageLog) { EXPECT_EQ(messageLog.MessageCount(), 1u); EXPECT_TRUE(messageLog.IsMessageType(0, Protocols::InteractionModel::MsgType::StatusResponse)); CHIP_ERROR status; EXPECT_EQ(StatusResponse::ProcessStatusResponse(std::move(messageLog.MessagePayload(0)), status), CHIP_NO_ERROR); EXPECT_EQ(status, CHIP_IM_GLOBAL_STATUS(InvalidAction)); } } // anonymous namespace // Read Client sends the read request, Read Handler drops the response, then test injects unknown status reponse message for // Read Client. TEST_F_FROM_FIXTURE(TestReadInteraction, TestReadClientReceiveInvalidMessage) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); chip::app::AttributePathParams attributePathParams[2]; readPrepareParams.mpAttributePathParamsList = attributePathParams; readPrepareParams.mpAttributePathParamsList[0].mEndpointId = kTestEndpointId; readPrepareParams.mpAttributePathParamsList[0].mClusterId = kTestClusterId; readPrepareParams.mpAttributePathParamsList[0].mAttributeId = 1; readPrepareParams.mpAttributePathParamsList[1].mEndpointId = kTestEndpointId; readPrepareParams.mpAttributePathParamsList[1].mClusterId = kTestClusterId; readPrepareParams.mpAttributePathParamsList[1].mAttributeId = 2; readPrepareParams.mAttributePathParamsListSize = 2; { app::ReadClient readClient(engine, &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Read); GetLoopback().mSentMessageCount = 0; GetLoopback().mNumMessagesToDrop = 1; GetLoopback().mNumMessagesToAllowBeforeDropping = 1; GetLoopback().mDroppedMessageCount = 0; EXPECT_EQ(readClient.SendRequest(readPrepareParams), CHIP_NO_ERROR); DrainAndServiceIO(); EXPECT_EQ(GetLoopback().mSentMessageCount, 2u); EXPECT_EQ(GetLoopback().mDroppedMessageCount, 1u); System::PacketBufferHandle msgBuf = System::PacketBufferHandle::New(kMaxSecureSduLengthBytes); EXPECT_FALSE(msgBuf.IsNull()); System::PacketBufferTLVWriter writer; writer.Init(std::move(msgBuf)); StatusResponseMessage::Builder response; response.Init(&writer); response.Status(Protocols::InteractionModel::Status::Busy); EXPECT_EQ(writer.Finalize(&msgBuf), CHIP_NO_ERROR); PayloadHeader payloadHeader; payloadHeader.SetExchangeID(0); payloadHeader.SetMessageType(chip::Protocols::InteractionModel::MsgType::StatusResponse); chip::Test::MessageCapturer messageLog(*this); messageLog.mCaptureStandaloneAcks = false; // Since we are dropping packets, things are not getting acked. Set up // our MRP state to look like what it would have looked like if the // packet had not gotten dropped. PretendWeGotReplyFromServer(*this, readClient.mExchange.Get()); GetLoopback().mSentMessageCount = 0; GetLoopback().mNumMessagesToDrop = 0; GetLoopback().mNumMessagesToAllowBeforeDropping = 0; GetLoopback().mDroppedMessageCount = 0; readClient.OnMessageReceived(readClient.mExchange.Get(), payloadHeader, std::move(msgBuf)); DrainAndServiceIO(); // The ReadHandler closed its exchange when it sent the Report Data (which we dropped). // Since we synthesized the StatusResponse to the ReadClient, instead of sending it from the ReadHandler, // the only messages here are the ReadClient's StatusResponse to the unexpected message and an MRP ack. EXPECT_EQ(delegate.mError, CHIP_IM_GLOBAL_STATUS(Busy)); CheckForInvalidAction(messageLog); } engine->Shutdown(); ExpireSessionAliceToBob(); ExpireSessionBobToAlice(); CreateSessionAliceToBob(); CreateSessionBobToAlice(); } // Read Client sends the subscribe request, Read Handler drops the response, then test injects unknown status response message // for Read Client. TEST_F_FROM_FIXTURE(TestReadInteraction, TestSubscribeClientReceiveInvalidStatusResponse) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); chip::app::AttributePathParams attributePathParams[2]; readPrepareParams.mpAttributePathParamsList = attributePathParams; readPrepareParams.mpAttributePathParamsList[0].mEndpointId = kTestEndpointId; readPrepareParams.mpAttributePathParamsList[0].mClusterId = kTestClusterId; readPrepareParams.mpAttributePathParamsList[0].mAttributeId = 1; readPrepareParams.mpAttributePathParamsList[1].mEndpointId = kTestEndpointId; readPrepareParams.mpAttributePathParamsList[1].mClusterId = kTestClusterId; readPrepareParams.mpAttributePathParamsList[1].mAttributeId = 2; readPrepareParams.mAttributePathParamsListSize = 2; readPrepareParams.mMinIntervalFloorSeconds = 2; readPrepareParams.mMaxIntervalCeilingSeconds = 5; { app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Subscribe); GetLoopback().mSentMessageCount = 0; GetLoopback().mNumMessagesToDrop = 1; GetLoopback().mNumMessagesToAllowBeforeDropping = 1; GetLoopback().mDroppedMessageCount = 0; EXPECT_EQ(readClient.SendRequest(readPrepareParams), CHIP_NO_ERROR); DrainAndServiceIO(); System::PacketBufferHandle msgBuf = System::PacketBufferHandle::New(kMaxSecureSduLengthBytes); EXPECT_FALSE(msgBuf.IsNull()); System::PacketBufferTLVWriter writer; writer.Init(std::move(msgBuf)); StatusResponseMessage::Builder response; response.Init(&writer); response.Status(Protocols::InteractionModel::Status::Busy); EXPECT_EQ(writer.Finalize(&msgBuf), CHIP_NO_ERROR); PayloadHeader payloadHeader; payloadHeader.SetExchangeID(0); payloadHeader.SetMessageType(chip::Protocols::InteractionModel::MsgType::StatusResponse); // Since we are dropping packets, things are not getting acked. Set up // our MRP state to look like what it would have looked like if the // packet had not gotten dropped. PretendWeGotReplyFromServer(*this, readClient.mExchange.Get()); EXPECT_EQ(GetLoopback().mSentMessageCount, 2u); EXPECT_EQ(GetLoopback().mDroppedMessageCount, 1u); EXPECT_EQ(engine->GetNumActiveReadHandlers(), 1u); ASSERT_NE(engine->ActiveHandlerAt(0), nullptr); GetLoopback().mSentMessageCount = 0; GetLoopback().mNumMessagesToDrop = 0; GetLoopback().mNumMessagesToAllowBeforeDropping = 0; GetLoopback().mDroppedMessageCount = 0; readClient.OnMessageReceived(readClient.mExchange.Get(), payloadHeader, std::move(msgBuf)); DrainAndServiceIO(); // TODO: Need to validate what status is being sent to the ReadHandler // The ReadHandler's exchange is closed when we synthesize the subscribe response, since it sent the // Subscribe Response as the last message in the transaction. // Since we synthesized the subscribe response to the ReadClient, instead of sending it from the ReadHandler, // the only messages here are the ReadClient's StatusResponse to the unexpected message and an MRP ack. EXPECT_EQ(GetLoopback().mSentMessageCount, 2u); EXPECT_EQ(delegate.mError, CHIP_IM_GLOBAL_STATUS(Busy)); EXPECT_EQ(engine->GetNumActiveReadHandlers(), 0u); } EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); engine->Shutdown(); ExpireSessionAliceToBob(); ExpireSessionBobToAlice(); CreateSessionAliceToBob(); CreateSessionBobToAlice(); } // Read Client sends the subscribe request, Read Handler drops the response, then test injects well-formed status response // message with Success for Read Client, we expect the error with CHIP_ERROR_INVALID_MESSAGE_TYPE TEST_F_FROM_FIXTURE(TestReadInteraction, TestSubscribeClientReceiveWellFormedStatusResponse) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); chip::app::AttributePathParams attributePathParams[2]; readPrepareParams.mpAttributePathParamsList = attributePathParams; readPrepareParams.mpAttributePathParamsList[0].mEndpointId = kTestEndpointId; readPrepareParams.mpAttributePathParamsList[0].mClusterId = kTestClusterId; readPrepareParams.mpAttributePathParamsList[0].mAttributeId = 1; readPrepareParams.mpAttributePathParamsList[1].mEndpointId = kTestEndpointId; readPrepareParams.mpAttributePathParamsList[1].mClusterId = kTestClusterId; readPrepareParams.mpAttributePathParamsList[1].mAttributeId = 2; readPrepareParams.mAttributePathParamsListSize = 2; readPrepareParams.mMinIntervalFloorSeconds = 2; readPrepareParams.mMaxIntervalCeilingSeconds = 5; { app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Subscribe); GetLoopback().mSentMessageCount = 0; GetLoopback().mNumMessagesToDrop = 1; GetLoopback().mNumMessagesToAllowBeforeDropping = 1; GetLoopback().mDroppedMessageCount = 0; EXPECT_EQ(readClient.SendRequest(readPrepareParams), CHIP_NO_ERROR); DrainAndServiceIO(); System::PacketBufferHandle msgBuf = System::PacketBufferHandle::New(kMaxSecureSduLengthBytes); EXPECT_FALSE(msgBuf.IsNull()); System::PacketBufferTLVWriter writer; writer.Init(std::move(msgBuf)); StatusResponseMessage::Builder response; response.Init(&writer); response.Status(Protocols::InteractionModel::Status::Success); EXPECT_EQ(writer.Finalize(&msgBuf), CHIP_NO_ERROR); PayloadHeader payloadHeader; payloadHeader.SetExchangeID(0); payloadHeader.SetMessageType(chip::Protocols::InteractionModel::MsgType::StatusResponse); // Since we are dropping packets, things are not getting acked. Set up // our MRP state to look like what it would have looked like if the // packet had not gotten dropped. PretendWeGotReplyFromServer(*this, readClient.mExchange.Get()); EXPECT_EQ(GetLoopback().mSentMessageCount, 2u); EXPECT_EQ(GetLoopback().mDroppedMessageCount, 1u); EXPECT_EQ(engine->GetNumActiveReadHandlers(), 1u); ASSERT_NE(engine->ActiveHandlerAt(0), nullptr); GetLoopback().mSentMessageCount = 0; GetLoopback().mNumMessagesToDrop = 0; GetLoopback().mNumMessagesToAllowBeforeDropping = 0; GetLoopback().mDroppedMessageCount = 0; readClient.OnMessageReceived(readClient.mExchange.Get(), payloadHeader, std::move(msgBuf)); DrainAndServiceIO(); // TODO: Need to validate what status is being sent to the ReadHandler // The ReadHandler's exchange is still open when we synthesize the StatusResponse. // Since we synthesized the StatusResponse to the ReadClient, instead of sending it from the ReadHandler, // the only messages here are the ReadClient's StatusResponse to the unexpected message and an MRP ack. EXPECT_EQ(GetLoopback().mSentMessageCount, 2u); EXPECT_EQ(delegate.mError, CHIP_ERROR_INVALID_MESSAGE_TYPE); EXPECT_EQ(engine->GetNumActiveReadHandlers(), 0u); } EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); engine->Shutdown(); ExpireSessionAliceToBob(); ExpireSessionBobToAlice(); CreateSessionAliceToBob(); CreateSessionBobToAlice(); } // Read Client sends the subscribe request, Read Handler drops the response, then test injects invalid report message for Read // Client. TEST_F_FROM_FIXTURE(TestReadInteraction, TestSubscribeClientReceiveInvalidReportMessage) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); chip::app::AttributePathParams attributePathParams[2]; readPrepareParams.mpAttributePathParamsList = attributePathParams; readPrepareParams.mpAttributePathParamsList[0].mEndpointId = kTestEndpointId; readPrepareParams.mpAttributePathParamsList[0].mClusterId = kTestClusterId; readPrepareParams.mpAttributePathParamsList[0].mAttributeId = 1; readPrepareParams.mpAttributePathParamsList[1].mEndpointId = kTestEndpointId; readPrepareParams.mpAttributePathParamsList[1].mClusterId = kTestClusterId; readPrepareParams.mpAttributePathParamsList[1].mAttributeId = 2; readPrepareParams.mAttributePathParamsListSize = 2; readPrepareParams.mMinIntervalFloorSeconds = 2; readPrepareParams.mMaxIntervalCeilingSeconds = 5; { app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Subscribe); GetLoopback().mSentMessageCount = 0; GetLoopback().mNumMessagesToDrop = 1; GetLoopback().mNumMessagesToAllowBeforeDropping = 1; GetLoopback().mDroppedMessageCount = 0; EXPECT_EQ(readClient.SendRequest(readPrepareParams), CHIP_NO_ERROR); DrainAndServiceIO(); System::PacketBufferHandle msgBuf = System::PacketBufferHandle::New(kMaxSecureSduLengthBytes); EXPECT_FALSE(msgBuf.IsNull()); System::PacketBufferTLVWriter writer; writer.Init(std::move(msgBuf)); ReportDataMessage::Builder response; response.Init(&writer); EXPECT_EQ(writer.Finalize(&msgBuf), CHIP_NO_ERROR); PayloadHeader payloadHeader; payloadHeader.SetExchangeID(0); payloadHeader.SetMessageType(chip::Protocols::InteractionModel::MsgType::ReportData); // Since we are dropping packets, things are not getting acked. Set up // our MRP state to look like what it would have looked like if the // packet had not gotten dropped. PretendWeGotReplyFromServer(*this, readClient.mExchange.Get()); EXPECT_EQ(GetLoopback().mSentMessageCount, 2u); EXPECT_EQ(GetLoopback().mDroppedMessageCount, 1u); EXPECT_EQ(engine->GetNumActiveReadHandlers(), 1u); ASSERT_NE(engine->ActiveHandlerAt(0), nullptr); GetLoopback().mSentMessageCount = 0; GetLoopback().mNumMessagesToDrop = 0; GetLoopback().mNumMessagesToAllowBeforeDropping = 0; GetLoopback().mDroppedMessageCount = 0; readClient.OnMessageReceived(readClient.mExchange.Get(), payloadHeader, std::move(msgBuf)); DrainAndServiceIO(); // TODO: Need to validate what status is being sent to the ReadHandler // The ReadHandler's exchange is still open when we synthesize the ReportData. // Since we synthesized the ReportData to the ReadClient, instead of sending it from the ReadHandler, // the only messages here are the ReadClient's StatusResponse to the unexpected message and an MRP ack. EXPECT_EQ(GetLoopback().mSentMessageCount, 2u); EXPECT_EQ(delegate.mError, CHIP_ERROR_END_OF_TLV); EXPECT_EQ(engine->GetNumActiveReadHandlers(), 0u); } EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); engine->Shutdown(); ExpireSessionAliceToBob(); ExpireSessionBobToAlice(); CreateSessionAliceToBob(); CreateSessionBobToAlice(); } // Read Client create the subscription, handler sends unsolicited malformed report to client, // InteractionModelEngine::OnUnsolicitedReportData would process this malformed report and sends out status report TEST_F_FROM_FIXTURE(TestReadInteraction, TestSubscribeClientReceiveUnsolicitedInvalidReportMessage) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); chip::app::AttributePathParams attributePathParams[2]; readPrepareParams.mpAttributePathParamsList = attributePathParams; readPrepareParams.mpAttributePathParamsList[0].mEndpointId = kTestEndpointId; readPrepareParams.mpAttributePathParamsList[0].mClusterId = kTestClusterId; readPrepareParams.mpAttributePathParamsList[0].mAttributeId = 1; readPrepareParams.mpAttributePathParamsList[1].mEndpointId = kTestEndpointId; readPrepareParams.mpAttributePathParamsList[1].mClusterId = kTestClusterId; readPrepareParams.mpAttributePathParamsList[1].mAttributeId = 2; readPrepareParams.mAttributePathParamsListSize = 2; readPrepareParams.mMinIntervalFloorSeconds = 2; readPrepareParams.mMaxIntervalCeilingSeconds = 5; { GetLoopback().mSentMessageCount = 0; app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Subscribe); EXPECT_EQ(readClient.SendRequest(readPrepareParams), CHIP_NO_ERROR); DrainAndServiceIO(); EXPECT_EQ(GetLoopback().mSentMessageCount, 5u); EXPECT_EQ(engine->GetNumActiveReadHandlers(), 1u); ASSERT_NE(engine->ActiveHandlerAt(0), nullptr); delegate.mpReadHandler = engine->ActiveHandlerAt(0); System::PacketBufferHandle msgBuf = System::PacketBufferHandle::New(kMaxSecureSduLengthBytes); EXPECT_FALSE(msgBuf.IsNull()); System::PacketBufferTLVWriter writer; writer.Init(std::move(msgBuf)); ReportDataMessage::Builder response; response.Init(&writer); EXPECT_EQ(writer.Finalize(&msgBuf), CHIP_NO_ERROR); GetLoopback().mSentMessageCount = 0; auto exchange = InteractionModelEngine::GetInstance()->GetExchangeManager()->NewContext( delegate.mpReadHandler->mSessionHandle.Get().Value(), delegate.mpReadHandler); delegate.mpReadHandler->mExchangeCtx.Grab(exchange); EXPECT_EQ(delegate.mpReadHandler->mExchangeCtx->SendMessage(Protocols::InteractionModel::MsgType::ReportData, std::move(msgBuf), Messaging::SendMessageFlags::kExpectResponse), CHIP_NO_ERROR); DrainAndServiceIO(); // The server sends a data report. // The client receives the data report data and sends out status report with invalid action. // The server acks the status report. EXPECT_EQ(GetLoopback().mSentMessageCount, 3u); } EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); engine->Shutdown(); } // Read Client sends the subscribe request, Read Handler drops the subscribe response, then test injects invalid subscribe // response message TEST_F_FROM_FIXTURE(TestReadInteraction, TestSubscribeClientReceiveInvalidSubscribeResponseMessage) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); chip::app::AttributePathParams attributePathParams[2]; readPrepareParams.mpAttributePathParamsList = attributePathParams; readPrepareParams.mpAttributePathParamsList[0].mEndpointId = kTestEndpointId; readPrepareParams.mpAttributePathParamsList[0].mClusterId = kTestClusterId; readPrepareParams.mpAttributePathParamsList[0].mAttributeId = 1; readPrepareParams.mpAttributePathParamsList[1].mEndpointId = kTestEndpointId; readPrepareParams.mpAttributePathParamsList[1].mClusterId = kTestClusterId; readPrepareParams.mpAttributePathParamsList[1].mAttributeId = 2; readPrepareParams.mAttributePathParamsListSize = 2; readPrepareParams.mMinIntervalFloorSeconds = 2; readPrepareParams.mMaxIntervalCeilingSeconds = 5; { app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Subscribe); GetLoopback().mSentMessageCount = 0; GetLoopback().mNumMessagesToDrop = 1; GetLoopback().mNumMessagesToAllowBeforeDropping = 3; GetLoopback().mDroppedMessageCount = 0; EXPECT_EQ(readClient.SendRequest(readPrepareParams), CHIP_NO_ERROR); DrainAndServiceIO(); System::PacketBufferHandle msgBuf = System::PacketBufferHandle::New(kMaxSecureSduLengthBytes); EXPECT_FALSE(msgBuf.IsNull()); System::PacketBufferTLVWriter writer; writer.Init(std::move(msgBuf)); SubscribeResponseMessage::Builder response; response.Init(&writer); response.SubscriptionId(readClient.mSubscriptionId + 1); response.MaxInterval(1); response.EndOfSubscribeResponseMessage(); EXPECT_EQ(writer.Finalize(&msgBuf), CHIP_NO_ERROR); PayloadHeader payloadHeader; payloadHeader.SetExchangeID(0); payloadHeader.SetMessageType(chip::Protocols::InteractionModel::MsgType::SubscribeResponse); // Since we are dropping packets, things are not getting acked. Set up // our MRP state to look like what it would have looked like if the // packet had not gotten dropped. PretendWeGotReplyFromServer(*this, readClient.mExchange.Get()); EXPECT_EQ(GetLoopback().mSentMessageCount, 4u); EXPECT_EQ(GetLoopback().mDroppedMessageCount, 1u); EXPECT_EQ(engine->GetNumActiveReadHandlers(), 1u); ASSERT_NE(engine->ActiveHandlerAt(0), nullptr); GetLoopback().mSentMessageCount = 0; GetLoopback().mNumMessagesToDrop = 0; GetLoopback().mNumMessagesToAllowBeforeDropping = 0; GetLoopback().mDroppedMessageCount = 0; readClient.OnMessageReceived(readClient.mExchange.Get(), payloadHeader, std::move(msgBuf)); DrainAndServiceIO(); // TODO: Need to validate what status is being sent to the ReadHandler // The ReadHandler's exchange is still open when we synthesize the subscribe response. // Since we synthesized the subscribe response to the ReadClient, instead of sending it from the ReadHandler, // the only messages here are the ReadClient's StatusResponse to the unexpected message and an MRP ack. EXPECT_EQ(GetLoopback().mSentMessageCount, 2u); EXPECT_EQ(delegate.mError, CHIP_ERROR_INVALID_SUBSCRIPTION); } EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); engine->Shutdown(); ExpireSessionAliceToBob(); ExpireSessionBobToAlice(); CreateSessionAliceToBob(); CreateSessionBobToAlice(); } // Read Client create the subscription, handler sends unsolicited malformed report with invalid subscription id to client, // InteractionModelEngine::OnUnsolicitedReportData would process this malformed report and sends out status report TEST_F_FROM_FIXTURE(TestReadInteraction, TestSubscribeClientReceiveUnsolicitedReportMessageWithInvalidSubscriptionId) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); chip::app::AttributePathParams attributePathParams[2]; readPrepareParams.mpAttributePathParamsList = attributePathParams; readPrepareParams.mpAttributePathParamsList[0].mEndpointId = kTestEndpointId; readPrepareParams.mpAttributePathParamsList[0].mClusterId = kTestClusterId; readPrepareParams.mpAttributePathParamsList[0].mAttributeId = 1; readPrepareParams.mpAttributePathParamsList[1].mEndpointId = kTestEndpointId; readPrepareParams.mpAttributePathParamsList[1].mClusterId = kTestClusterId; readPrepareParams.mpAttributePathParamsList[1].mAttributeId = 2; readPrepareParams.mAttributePathParamsListSize = 2; readPrepareParams.mMinIntervalFloorSeconds = 2; readPrepareParams.mMaxIntervalCeilingSeconds = 5; { GetLoopback().mSentMessageCount = 0; app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Subscribe); EXPECT_EQ(readClient.SendRequest(readPrepareParams), CHIP_NO_ERROR); DrainAndServiceIO(); EXPECT_EQ(GetLoopback().mSentMessageCount, 5u); EXPECT_EQ(engine->GetNumActiveReadHandlers(), 1u); ASSERT_NE(engine->ActiveHandlerAt(0), nullptr); delegate.mpReadHandler = engine->ActiveHandlerAt(0); System::PacketBufferHandle msgBuf = System::PacketBufferHandle::New(kMaxSecureSduLengthBytes); EXPECT_FALSE(msgBuf.IsNull()); System::PacketBufferTLVWriter writer; writer.Init(std::move(msgBuf)); ReportDataMessage::Builder response; response.Init(&writer); response.SubscriptionId(readClient.mSubscriptionId + 1); response.EndOfReportDataMessage(); EXPECT_EQ(writer.Finalize(&msgBuf), CHIP_NO_ERROR); GetLoopback().mSentMessageCount = 0; auto exchange = InteractionModelEngine::GetInstance()->GetExchangeManager()->NewContext( delegate.mpReadHandler->mSessionHandle.Get().Value(), delegate.mpReadHandler); delegate.mpReadHandler->mExchangeCtx.Grab(exchange); EXPECT_EQ(delegate.mpReadHandler->mExchangeCtx->SendMessage(Protocols::InteractionModel::MsgType::ReportData, std::move(msgBuf), Messaging::SendMessageFlags::kExpectResponse), CHIP_NO_ERROR); DrainAndServiceIO(); // The server sends a data report. // The client receives the data report data and sends out status report with invalid subsciption. // The server should respond with a status report of its own, leading to 4 messages (because // the client would ack the server's status report), just sends an ack to the status report it got. EXPECT_EQ(GetLoopback().mSentMessageCount, 3u); } EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); engine->Shutdown(); } // TestReadChunkingInvalidSubscriptionId will try to read a few large attributes, the report won't fit into the MTU and result // in chunking, second report has different subscription id from the first one, read client sends out the status report with // invalid subscription TEST_F_FROM_FIXTURE(TestReadInteraction, TestReadChunkingInvalidSubscriptionId) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); GenerateEvents(); MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); EXPECT_FALSE(delegate.mGotEventResponse); chip::app::AttributePathParams attributePathParams[1]; // Mock Attribute 4 is a big attribute, with 6 large OCTET_STRING attributePathParams[0].mEndpointId = chip::Test::kMockEndpoint3; attributePathParams[0].mClusterId = chip::Test::MockClusterId(2); attributePathParams[0].mAttributeId = chip::Test::MockAttributeId(4); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); readPrepareParams.mpEventPathParamsList = nullptr; readPrepareParams.mEventPathParamsListSize = 0; readPrepareParams.mpAttributePathParamsList = attributePathParams; readPrepareParams.mAttributePathParamsListSize = 1; { app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Subscribe); GetLoopback().mSentMessageCount = 0; GetLoopback().mNumMessagesToDrop = 1; GetLoopback().mNumMessagesToAllowBeforeDropping = 3; GetLoopback().mDroppedMessageCount = 0; EXPECT_EQ(readClient.SendRequest(readPrepareParams), CHIP_NO_ERROR); DrainAndServiceIO(); System::PacketBufferHandle msgBuf = System::PacketBufferHandle::New(kMaxSecureSduLengthBytes); EXPECT_FALSE(msgBuf.IsNull()); System::PacketBufferTLVWriter writer; writer.Init(std::move(msgBuf)); ReportDataMessage::Builder response; response.Init(&writer); response.SubscriptionId(readClient.mSubscriptionId + 1); response.EndOfReportDataMessage(); PayloadHeader payloadHeader; payloadHeader.SetExchangeID(0); payloadHeader.SetMessageType(chip::Protocols::InteractionModel::MsgType::ReportData); EXPECT_EQ(writer.Finalize(&msgBuf), CHIP_NO_ERROR); // Since we are dropping packets, things are not getting acked. Set up // our MRP state to look like what it would have looked like if the // packet had not gotten dropped. PretendWeGotReplyFromServer(*this, readClient.mExchange.Get()); EXPECT_EQ(GetLoopback().mSentMessageCount, 4u); EXPECT_EQ(GetLoopback().mDroppedMessageCount, 1u); EXPECT_EQ(engine->GetNumActiveReadHandlers(), 1u); ASSERT_NE(engine->ActiveHandlerAt(0), nullptr); GetLoopback().mSentMessageCount = 0; GetLoopback().mNumMessagesToDrop = 0; GetLoopback().mNumMessagesToAllowBeforeDropping = 0; GetLoopback().mDroppedMessageCount = 0; readClient.OnMessageReceived(readClient.mExchange.Get(), payloadHeader, std::move(msgBuf)); DrainAndServiceIO(); // TODO: Need to validate what status is being sent to the ReadHandler // The ReadHandler's exchange is still open when we synthesize the report data message. // Since we synthesized the second report data message to the ReadClient with invalid subscription id, instead of // sending it from the ReadHandler, the only messages here are the ReadClient's StatusResponse to the unexpected message // and an MRP ack. EXPECT_EQ(GetLoopback().mSentMessageCount, 2u); EXPECT_EQ(delegate.mError, CHIP_ERROR_INVALID_SUBSCRIPTION); } EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); engine->Shutdown(); ExpireSessionAliceToBob(); ExpireSessionBobToAlice(); CreateSessionAliceToBob(); CreateSessionBobToAlice(); } // Read Client sends a malformed subscribe request, interaction model engine fails to parse the request and generates a status // report to client, and client is closed. TEST_F_FROM_FIXTURE(TestReadInteraction, TestReadHandlerMalformedSubscribeRequest) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); { app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Subscribe); System::PacketBufferHandle msgBuf; ReadRequestMessage::Builder request; System::PacketBufferTLVWriter writer; chip::app::InitWriterWithSpaceReserved(writer, 0); EXPECT_EQ(request.Init(&writer), CHIP_NO_ERROR); EXPECT_EQ(writer.Finalize(&msgBuf), CHIP_NO_ERROR); auto exchange = readClient.mpExchangeMgr->NewContext(readPrepareParams.mSessionHolder.Get().Value(), &readClient); ASSERT_NE(exchange, nullptr); readClient.mExchange.Grab(exchange); readClient.MoveToState(app::ReadClient::ClientState::AwaitingInitialReport); EXPECT_EQ(readClient.mExchange->SendMessage(Protocols::InteractionModel::MsgType::ReadRequest, std::move(msgBuf), Messaging::SendFlags(Messaging::SendMessageFlags::kExpectResponse)), CHIP_NO_ERROR); DrainAndServiceIO(); EXPECT_EQ(delegate.mError, CHIP_IM_GLOBAL_STATUS(InvalidAction)); } EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); engine->Shutdown(); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } // Read Client sends a malformed read request, interaction model engine fails to parse the request and generates a status report // to client, and client is closed. TEST_F_FROM_FIXTURE(TestReadInteraction, TestReadHandlerMalformedReadRequest1) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); { app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Read); System::PacketBufferHandle msgBuf; ReadRequestMessage::Builder request; System::PacketBufferTLVWriter writer; chip::app::InitWriterWithSpaceReserved(writer, 0); EXPECT_EQ(request.Init(&writer), CHIP_NO_ERROR); EXPECT_EQ(writer.Finalize(&msgBuf), CHIP_NO_ERROR); auto exchange = readClient.mpExchangeMgr->NewContext(readPrepareParams.mSessionHolder.Get().Value(), &readClient); ASSERT_NE(exchange, nullptr); readClient.mExchange.Grab(exchange); readClient.MoveToState(app::ReadClient::ClientState::AwaitingInitialReport); EXPECT_EQ(readClient.mExchange->SendMessage(Protocols::InteractionModel::MsgType::ReadRequest, std::move(msgBuf), Messaging::SendFlags(Messaging::SendMessageFlags::kExpectResponse)), CHIP_NO_ERROR); DrainAndServiceIO(); EXPECT_EQ(delegate.mError, CHIP_IM_GLOBAL_STATUS(InvalidAction)); } EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); engine->Shutdown(); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } // Read Client sends a malformed read request, read handler fails to parse the request and generates a status report to client, // and client is closed. TEST_F_FROM_FIXTURE(TestReadInteraction, TestReadHandlerMalformedReadRequest2) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); { app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Read); System::PacketBufferHandle msgBuf; ReadRequestMessage::Builder request; System::PacketBufferTLVWriter writer; chip::app::InitWriterWithSpaceReserved(writer, 0); EXPECT_EQ(request.Init(&writer), CHIP_NO_ERROR); EXPECT_EQ(request.EndOfReadRequestMessage(), CHIP_NO_ERROR); EXPECT_EQ(writer.Finalize(&msgBuf), CHIP_NO_ERROR); auto exchange = readClient.mpExchangeMgr->NewContext(readPrepareParams.mSessionHolder.Get().Value(), &readClient); ASSERT_NE(exchange, nullptr); readClient.mExchange.Grab(exchange); readClient.MoveToState(app::ReadClient::ClientState::AwaitingInitialReport); EXPECT_EQ(readClient.mExchange->SendMessage(Protocols::InteractionModel::MsgType::ReadRequest, std::move(msgBuf), Messaging::SendFlags(Messaging::SendMessageFlags::kExpectResponse)), CHIP_NO_ERROR); DrainAndServiceIO(); ChipLogError(DataManagement, "The error is %s", ErrorStr(delegate.mError)); EXPECT_EQ(delegate.mError, CHIP_IM_GLOBAL_STATUS(InvalidAction)); } EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); engine->Shutdown(); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } // Read Client creates a subscription with the server, server sends chunked reports, after the handler sends out the first // chunked report, client sends out invalid write request message, handler sends status report with invalid action and closes TEST_F_FROM_FIXTURE(TestReadInteraction, TestSubscribeSendUnknownMessage) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); chip::app::AttributePathParams attributePathParams[1]; // Mock Attribute 4 is a big attribute, with 6 large OCTET_STRING attributePathParams[0].mEndpointId = chip::Test::kMockEndpoint3; attributePathParams[0].mClusterId = chip::Test::MockClusterId(2); attributePathParams[0].mAttributeId = chip::Test::MockAttributeId(4); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); readPrepareParams.mpAttributePathParamsList = attributePathParams; readPrepareParams.mAttributePathParamsListSize = 1; { app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Subscribe); GetLoopback().mSentMessageCount = 0; GetLoopback().mNumMessagesToDrop = 1; GetLoopback().mNumMessagesToAllowBeforeDropping = 1; GetLoopback().mDroppedMessageCount = 0; EXPECT_EQ(readClient.SendRequest(readPrepareParams), CHIP_NO_ERROR); DrainAndServiceIO(); // Since we are dropping packets, things are not getting acked. Set up // our MRP state to look like what it would have looked like if the // packet had not gotten dropped. PretendWeGotReplyFromServer(*this, readClient.mExchange.Get()); EXPECT_EQ(GetLoopback().mSentMessageCount, 2u); EXPECT_EQ(GetLoopback().mDroppedMessageCount, 1u); EXPECT_EQ(engine->GetNumActiveReadHandlers(), 1u); ASSERT_NE(engine->ActiveHandlerAt(0), nullptr); GetLoopback().mSentMessageCount = 0; // Server sends out status report, client should send status report along with Piggybacking ack, but we don't do that // Instead, we send out unknown message to server System::PacketBufferHandle msgBuf; ReadRequestMessage::Builder request; System::PacketBufferTLVWriter writer; chip::app::InitWriterWithSpaceReserved(writer, 0); request.Init(&writer); writer.Finalize(&msgBuf); readClient.mExchange->SendMessage(Protocols::InteractionModel::MsgType::WriteRequest, std::move(msgBuf)); DrainAndServiceIO(); // client sends invalid write request, server sends out status report with invalid action and closes, client replies // with status report server replies with MRP Ack EXPECT_EQ(GetLoopback().mSentMessageCount, 4u); EXPECT_EQ(engine->GetNumActiveReadHandlers(), 0u); } EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); engine->Shutdown(); ExpireSessionAliceToBob(); ExpireSessionBobToAlice(); CreateSessionAliceToBob(); CreateSessionBobToAlice(); } // Read Client creates a subscription with the server, server sends chunked reports, after the handler sends out invalid status // report, client sends out invalid status report message, handler sends status report with invalid action and close TEST_F_FROM_FIXTURE(TestReadInteraction, TestSubscribeSendInvalidStatusReport) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); chip::app::AttributePathParams attributePathParams[1]; // Mock Attribute 4 is a big attribute, with 6 large OCTET_STRING attributePathParams[0].mEndpointId = chip::Test::kMockEndpoint3; attributePathParams[0].mClusterId = chip::Test::MockClusterId(2); attributePathParams[0].mAttributeId = chip::Test::MockAttributeId(4); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); readPrepareParams.mpAttributePathParamsList = attributePathParams; readPrepareParams.mAttributePathParamsListSize = 1; { app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Subscribe); GetLoopback().mSentMessageCount = 0; GetLoopback().mNumMessagesToDrop = 1; GetLoopback().mNumMessagesToAllowBeforeDropping = 1; GetLoopback().mDroppedMessageCount = 0; EXPECT_EQ(readClient.SendRequest(readPrepareParams), CHIP_NO_ERROR); DrainAndServiceIO(); // Since we are dropping packets, things are not getting acked. Set up // our MRP state to look like what it would have looked like if the // packet had not gotten dropped. PretendWeGotReplyFromServer(*this, readClient.mExchange.Get()); EXPECT_EQ(GetLoopback().mSentMessageCount, 2u); EXPECT_EQ(GetLoopback().mDroppedMessageCount, 1u); GetLoopback().mSentMessageCount = 0; EXPECT_EQ(engine->GetNumActiveReadHandlers(), 1u); ASSERT_NE(engine->ActiveHandlerAt(0), nullptr); System::PacketBufferHandle msgBuf; StatusResponseMessage::Builder request; System::PacketBufferTLVWriter writer; chip::app::InitWriterWithSpaceReserved(writer, 0); request.Init(&writer); writer.Finalize(&msgBuf); readClient.mExchange->SendMessage(Protocols::InteractionModel::MsgType::StatusResponse, std::move(msgBuf)); DrainAndServiceIO(); // client sends malformed status response, server sends out status report with invalid action and close, client replies // with status report server replies with MRP Ack EXPECT_EQ(GetLoopback().mSentMessageCount, 4u); EXPECT_EQ(engine->GetNumActiveReadHandlers(), 0u); } EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); engine->Shutdown(); ExpireSessionAliceToBob(); ExpireSessionBobToAlice(); CreateSessionAliceToBob(); CreateSessionBobToAlice(); } // Read Client sends a malformed subscribe request, the server fails to parse the request and generates a status report to the // client, and client closes itself. TEST_F_FROM_FIXTURE(TestReadInteraction, TestReadHandlerInvalidSubscribeRequest) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); { app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Subscribe); System::PacketBufferHandle msgBuf; ReadRequestMessage::Builder request; System::PacketBufferTLVWriter writer; chip::app::InitWriterWithSpaceReserved(writer, 0); request.Init(&writer); writer.Finalize(&msgBuf); auto exchange = readClient.mpExchangeMgr->NewContext(readPrepareParams.mSessionHolder.Get().Value(), &readClient); ASSERT_NE(exchange, nullptr); readClient.mExchange.Grab(exchange); readClient.MoveToState(app::ReadClient::ClientState::AwaitingInitialReport); EXPECT_EQ(readClient.mExchange->SendMessage(Protocols::InteractionModel::MsgType::SubscribeRequest, std::move(msgBuf), Messaging::SendFlags(Messaging::SendMessageFlags::kExpectResponse)), CHIP_NO_ERROR); DrainAndServiceIO(); EXPECT_EQ(delegate.mError, CHIP_IM_GLOBAL_STATUS(InvalidAction)); } EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); engine->Shutdown(); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } // Create the subscription, then remove the corresponding fabric in client and handler, the corresponding // client and handler would be released as well. TEST_F(TestReadInteraction, TestSubscribeInvalidateFabric) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); GenerateEvents(); MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); readPrepareParams.mpAttributePathParamsList = new chip::app::AttributePathParams[1]; readPrepareParams.mAttributePathParamsListSize = 1; readPrepareParams.mpAttributePathParamsList[0].mEndpointId = chip::Test::kMockEndpoint3; readPrepareParams.mpAttributePathParamsList[0].mClusterId = chip::Test::MockClusterId(2); readPrepareParams.mpAttributePathParamsList[0].mAttributeId = chip::Test::MockAttributeId(1); readPrepareParams.mMinIntervalFloorSeconds = 0; readPrepareParams.mMaxIntervalCeilingSeconds = 0; { app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Subscribe); delegate.mGotReport = false; EXPECT_EQ(readClient.SendAutoResubscribeRequest(std::move(readPrepareParams)), CHIP_NO_ERROR); DrainAndServiceIO(); EXPECT_TRUE(delegate.mGotReport); EXPECT_EQ(engine->GetNumActiveReadHandlers(ReadHandler::InteractionType::Subscribe), 1u); ASSERT_NE(engine->ActiveHandlerAt(0), nullptr); delegate.mpReadHandler = engine->ActiveHandlerAt(0); GetFabricTable().Delete(GetAliceFabricIndex()); EXPECT_EQ(engine->GetNumActiveReadHandlers(ReadHandler::InteractionType::Subscribe), 0u); GetFabricTable().Delete(GetBobFabricIndex()); EXPECT_EQ(delegate.mError, CHIP_ERROR_IM_FABRIC_DELETED); ExpireSessionAliceToBob(); ExpireSessionBobToAlice(); CreateAliceFabric(); CreateBobFabric(); CreateSessionAliceToBob(); CreateSessionBobToAlice(); } EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); engine->Shutdown(); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } TEST_F_FROM_FIXTURE(TestReadInteraction, TestShutdownSubscription) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); GenerateEvents(); MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); readPrepareParams.mpAttributePathParamsList = new chip::app::AttributePathParams[1]; readPrepareParams.mAttributePathParamsListSize = 1; readPrepareParams.mpAttributePathParamsList[0].mEndpointId = chip::Test::kMockEndpoint3; readPrepareParams.mpAttributePathParamsList[0].mClusterId = chip::Test::MockClusterId(2); readPrepareParams.mpAttributePathParamsList[0].mAttributeId = chip::Test::MockAttributeId(1); readPrepareParams.mMinIntervalFloorSeconds = 0; readPrepareParams.mMaxIntervalCeilingSeconds = 0; { app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Subscribe); delegate.mGotReport = false; EXPECT_EQ(readClient.SendAutoResubscribeRequest(std::move(readPrepareParams)), CHIP_NO_ERROR); DrainAndServiceIO(); EXPECT_TRUE(delegate.mGotReport); EXPECT_EQ(engine->GetNumActiveReadHandlers(ReadHandler::InteractionType::Subscribe), 1u); engine->ShutdownSubscription(chip::ScopedNodeId(readClient.GetPeerNodeId(), readClient.GetFabricIndex()), readClient.GetSubscriptionId().Value()); EXPECT_TRUE(readClient.IsIdle()); } engine->Shutdown(); EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } /** * Tests what happens when a subscription tries to deliver reports but the * session it has is defunct. Makes sure we correctly tear down the ReadHandler * and don't increment the "reports in flight" count. */ TEST_F_FROM_FIXTURE(TestReadInteraction, TestSubscriptionReportWithDefunctSession) { Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); // Shouldn't have anything in the retransmit table when starting the test. EXPECT_EQ(rm->TestGetCountRetransTable(), 0); MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), gReportScheduler), CHIP_NO_ERROR); AttributePathParams subscribePath(chip::Test::kMockEndpoint3, chip::Test::MockClusterId(2), chip::Test::MockAttributeId(1)); ReadPrepareParams readPrepareParams(GetSessionBobToAlice()); readPrepareParams.mpAttributePathParamsList = &subscribePath; readPrepareParams.mAttributePathParamsListSize = 1; readPrepareParams.mMinIntervalFloorSeconds = 0; readPrepareParams.mMaxIntervalCeilingSeconds = 0; { app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &GetExchangeManager(), delegate, chip::app::ReadClient::InteractionType::Subscribe); delegate.mGotReport = false; EXPECT_EQ(readClient.SendSubscribeRequest(std::move(readPrepareParams)), CHIP_NO_ERROR); DrainAndServiceIO(); EXPECT_TRUE(delegate.mGotReport); EXPECT_EQ(delegate.mNumAttributeResponse, 1); EXPECT_EQ(engine->GetNumActiveReadHandlers(ReadHandler::InteractionType::Subscribe), 1u); EXPECT_EQ(engine->GetNumActiveReadHandlers(ReadHandler::InteractionType::Read), 0u); EXPECT_EQ(engine->GetReportingEngine().GetNumReportsInFlight(), 0u); ASSERT_NE(engine->ActiveHandlerAt(0), nullptr); auto * readHandler = engine->ActiveHandlerAt(0); // Verify that the session we will reset later is the one we will mess // with now. EXPECT_EQ(SessionHandle(*readHandler->GetSession()), GetSessionAliceToBob()); // Test that we send reports as needed. delegate.mGotReport = false; delegate.mNumAttributeResponse = 0; engine->GetReportingEngine().SetDirty(subscribePath); DrainAndServiceIO(); EXPECT_TRUE(delegate.mGotReport); EXPECT_EQ(delegate.mNumAttributeResponse, 1); EXPECT_EQ(engine->GetNumActiveReadHandlers(ReadHandler::InteractionType::Subscribe), 1u); EXPECT_EQ(engine->GetNumActiveReadHandlers(ReadHandler::InteractionType::Read), 0u); EXPECT_EQ(engine->GetReportingEngine().GetNumReportsInFlight(), 0u); // Test that if the session is defunct we don't send reports and clean // up properly. readHandler->GetSession()->MarkAsDefunct(); delegate.mGotReport = false; delegate.mNumAttributeResponse = 0; engine->GetReportingEngine().SetDirty(subscribePath); DrainAndServiceIO(); EXPECT_FALSE(delegate.mGotReport); EXPECT_EQ(delegate.mNumAttributeResponse, 0); EXPECT_EQ(engine->GetNumActiveReadHandlers(ReadHandler::InteractionType::Subscribe), 0u); EXPECT_EQ(engine->GetNumActiveReadHandlers(ReadHandler::InteractionType::Read), 0u); EXPECT_EQ(engine->GetReportingEngine().GetNumReportsInFlight(), 0u); } engine->Shutdown(); EXPECT_EQ(engine->GetNumActiveReadClients(), 0u); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); // Get rid of our defunct session. ExpireSessionAliceToBob(); CreateSessionAliceToBob(); } } // namespace app } // namespace chip