From 3259572cf3cb89816b53512af4d94432ae718072 Mon Sep 17 00:00:00 2001 From: Yunhan Wang Date: Fri, 18 Feb 2022 14:49:14 -0800 Subject: [PATCH] Fix #14776 --SetResponseTimeout when it is subscribe or in chunked procedure --Call OnReportConfirm in MoveToState when state is IsAwaitingReportResponsee add missing test from PR ##15339 -- Add automatic test where initial chunked report has been received by read client during post-subscription, and exchange context and delegate has been passed from interaction model engine to read client, we expire the session so that the further chunked report cannot be receive by client so that onResponseTimeout is triggered from readClient. --- src/app/ReadHandler.cpp | 13 +- src/app/StatusResponse.cpp | 2 + src/app/WriteHandler.cpp | 1 + src/app/reporting/Engine.h | 2 + src/app/tests/TestReadInteraction.cpp | 536 +++++++++++++++++++++++++- 5 files changed, 545 insertions(+), 9 deletions(-) diff --git a/src/app/ReadHandler.cpp b/src/app/ReadHandler.cpp index 67751eeb400f79..41db9ab62150b5 100644 --- a/src/app/ReadHandler.cpp +++ b/src/app/ReadHandler.cpp @@ -95,7 +95,6 @@ ReadHandler::~ReadHandler() { InteractionModelEngine::GetInstance()->GetReportingEngine().OnReportConfirm(); } - InteractionModelEngine::GetInstance()->ReleaseClusterInfoList(mpAttributeClusterInfoList); InteractionModelEngine::GetInstance()->ReleaseClusterInfoList(mpEventClusterInfoList); InteractionModelEngine::GetInstance()->ReleaseClusterInfoList(mpDataVersionFilterList); @@ -150,7 +149,6 @@ CHIP_ERROR ReadHandler::OnStatusResponse(Messaging::ExchangeContext * apExchange case HandlerState::AwaitingReportResponse: if (IsChunkedReport()) { - InteractionModelEngine::GetInstance()->GetReportingEngine().OnReportConfirm(); MoveToState(HandlerState::GeneratingReports); if (mpExchangeCtx) { @@ -161,7 +159,6 @@ CHIP_ERROR ReadHandler::OnStatusResponse(Messaging::ExchangeContext * apExchange } else if (IsType(InteractionType::Subscribe)) { - InteractionModelEngine::GetInstance()->GetReportingEngine().OnReportConfirm(); if (IsPriming()) { err = SendSubscribeResponse(); @@ -211,7 +208,6 @@ CHIP_ERROR ReadHandler::SendStatusReport(Protocols::InteractionModel::Status aSt mpExchangeCtx = mpExchangeMgr->NewContext(mSessionHandle.Get(), this); } VerifyOrReturnLogError(mpExchangeCtx != nullptr, CHIP_ERROR_INCORRECT_STATE); - mpExchangeCtx->SetResponseTimeout(kImMessageTimeout); return StatusResponse::Send(aStatus, mpExchangeCtx, /* aExpectResponse = */ false); @@ -229,8 +225,8 @@ CHIP_ERROR ReadHandler::SendReportData(System::PacketBufferHandle && aPayload, b VerifyOrReturnLogError(mpExchangeCtx == nullptr, CHIP_ERROR_INCORRECT_STATE); VerifyOrReturnLogError(mSessionHandle, CHIP_ERROR_INCORRECT_STATE); mpExchangeCtx = mpExchangeMgr->NewContext(mSessionHandle.Get(), this); - mpExchangeCtx->SetResponseTimeout(kImMessageTimeout); } + VerifyOrReturnLogError(mpExchangeCtx != nullptr, CHIP_ERROR_INCORRECT_STATE); mIsChunkedReport = aMoreChunks; bool noResponseExpected = IsType(InteractionType::Read) && !mIsChunkedReport; @@ -238,6 +234,7 @@ CHIP_ERROR ReadHandler::SendReportData(System::PacketBufferHandle && aPayload, b { MoveToState(HandlerState::AwaitingReportResponse); } + mpExchangeCtx->SetResponseTimeout(kImMessageTimeout); CHIP_ERROR err = mpExchangeCtx->SendMessage(Protocols::InteractionModel::MsgType::ReportData, std::move(aPayload), Messaging::SendFlags(noResponseExpected ? Messaging::SendMessageFlags::kNone @@ -578,6 +575,11 @@ const char * ReadHandler::GetStateStr() const void ReadHandler::MoveToState(const HandlerState aTargetState) { + if (IsAwaitingReportResponse() && aTargetState != HandlerState::AwaitingReportResponse) + { + InteractionModelEngine::GetInstance()->GetReportingEngine().OnReportConfirm(); + } + mState = aTargetState; ChipLogDetail(DataManagement, "IM RH moving to [%s]", GetStateStr()); } @@ -627,7 +629,6 @@ CHIP_ERROR ReadHandler::SendSubscribeResponse() mIsPrimingReports = false; MoveToState(HandlerState::GeneratingReports); - return mpExchangeCtx->SendMessage(Protocols::InteractionModel::MsgType::SubscribeResponse, std::move(packet)); } diff --git a/src/app/StatusResponse.cpp b/src/app/StatusResponse.cpp index 27b241bb8a8307..094c411f21c32a 100644 --- a/src/app/StatusResponse.cpp +++ b/src/app/StatusResponse.cpp @@ -16,6 +16,7 @@ * limitations under the License. */ +#include #include #include @@ -36,6 +37,7 @@ CHIP_ERROR StatusResponse::Send(Protocols::InteractionModel::Status aStatus, Mes response.Status(aStatus); ReturnErrorOnFailure(response.GetError()); ReturnErrorOnFailure(writer.Finalize(&msgBuf)); + apExchangeContext->SetResponseTimeout(kImMessageTimeout); ReturnErrorOnFailure(apExchangeContext->SendMessage(Protocols::InteractionModel::MsgType::StatusResponse, std::move(msgBuf), aExpectResponse ? Messaging::SendMessageFlags::kExpectResponse : Messaging::SendMessageFlags::kNone)); diff --git a/src/app/WriteHandler.cpp b/src/app/WriteHandler.cpp index 40c0daa89e0b23..1d5eb4e5860768 100644 --- a/src/app/WriteHandler.cpp +++ b/src/app/WriteHandler.cpp @@ -188,6 +188,7 @@ CHIP_ERROR WriteHandler::SendWriteResponse(System::PacketBufferTLVWriter && aMes SuccessOrExit(err); VerifyOrExit(mpExchangeCtx != nullptr, err = CHIP_ERROR_INCORRECT_STATE); + mpExchangeCtx->SetResponseTimeout(kImMessageTimeout); err = mpExchangeCtx->SendMessage(Protocols::InteractionModel::MsgType::WriteResponse, std::move(packet), mHasMoreChunks ? Messaging::SendMessageFlags::kExpectResponse : Messaging::SendMessageFlags::kNone); diff --git a/src/app/reporting/Engine.h b/src/app/reporting/Engine.h index ff5bbf42dd2302..9b8c74e1c08ffb 100644 --- a/src/app/reporting/Engine.h +++ b/src/app/reporting/Engine.h @@ -118,6 +118,8 @@ class Engine } } + uint32_t GetNumReportsInFlight() { return mNumReportsInFlight; } + private: friend class TestReportingEngine; /** diff --git a/src/app/tests/TestReadInteraction.cpp b/src/app/tests/TestReadInteraction.cpp index 409aea3512e5d1..3ebd201655b0d2 100644 --- a/src/app/tests/TestReadInteraction.cpp +++ b/src/app/tests/TestReadInteraction.cpp @@ -60,6 +60,9 @@ chip::EndpointId kInvalidTestEndpointId = 3; chip::DataVersion kTestDataVersion1 = 3; chip::DataVersion kTestDataVersion2 = 5; +chip::Test::AppContext sContext; +auto & sLoopback = sContext.GetLoopback(); + class TestContext : public chip::Test::AppContext { public: @@ -156,7 +159,11 @@ class MockInteractionModelApp : public chip::app::ReadClient::Callback } } - void OnError(CHIP_ERROR aError) override { mReadError = true; } + void OnError(CHIP_ERROR aError) override + { + mError = aError; + mReadError = true; + } void OnDone() override {} @@ -184,6 +191,7 @@ class MockInteractionModelApp : public chip::app::ReadClient::Callback bool mGotReport = false; bool mReadError = false; chip::app::ReadHandler * mpReadHandler = nullptr; + CHIP_ERROR mError = CHIP_NO_ERROR; }; // @@ -278,6 +286,12 @@ class TestReadInteraction static void TestSubscribeInvalidIterval(nlTestSuite * apSuite, void * apContext); static void TestReadShutdown(nlTestSuite * apSuite, void * apContext); static void TestResubscribeRoundtrip(nlTestSuite * apSuite, void * apContext); + static void TestSubscribeRoundtripStatusReportTimeout(nlTestSuite * apSuite, void * apContext); + static void TestPostSubscribeRoundtripStatusReportTimeout(nlTestSuite * apSuite, void * apContext); + static void TestReadChunkingStatusReportTimeout(nlTestSuite * apSuite, void * apContext); + static void TestSubscribeRoundtripChunkStatusReportTimeout(nlTestSuite * apSuite, void * apContext); + static void TestPostSubscribeRoundtripChunkStatusReportTimeout(nlTestSuite * apSuite, void * apContext); + static void TestPostSubscribeRoundtripChunkReportTimeout(nlTestSuite * apSuite, void * apContext); private: static void GenerateReportData(nlTestSuite * apSuite, void * apContext, System::PacketBufferHandle & aPayload, @@ -1742,6 +1756,517 @@ void TestReadInteraction::TestSubscribeInvalidIterval(nlTestSuite * apSuite, voi NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); } +void TestReadInteraction::TestPostSubscribeRoundtripStatusReportTimeout(nlTestSuite * apSuite, void * apContext) +{ + TestContext & ctx = *static_cast(apContext); + CHIP_ERROR err = CHIP_NO_ERROR; + + Messaging::ReliableMessageMgr * rm = ctx.GetExchangeManager().GetReliableMessageMgr(); + // Shouldn't have anything in the retransmit table when starting the test. + NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); + + MockInteractionModelApp delegate; + auto * engine = chip::app::InteractionModelEngine::GetInstance(); + err = engine->Init(&ctx.GetExchangeManager()); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); + + ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); + chip::app::EventPathParams eventPathParams[2]; + readPrepareParams.mpEventPathParamsList = eventPathParams; + readPrepareParams.mpEventPathParamsList[0].mEndpointId = kTestEndpointId; + readPrepareParams.mpEventPathParamsList[0].mClusterId = kTestClusterId; + readPrepareParams.mpEventPathParamsList[0].mEventId = kTestEventIdDebug; + + readPrepareParams.mpEventPathParamsList[1].mEndpointId = kTestEndpointId; + readPrepareParams.mpEventPathParamsList[1].mClusterId = kTestClusterId; + 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; + printf("\nSend first subscribe request message to Node: %" PRIu64 "\n", chip::kTestDeviceNodeId); + + delegate.mNumAttributeResponse = 0; + readPrepareParams.mKeepSubscriptions = false; + + { + app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), delegate, + chip::app::ReadClient::InteractionType::Subscribe); + + err = readClient.SendRequest(readPrepareParams); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + delegate.mGotReport = false; + engine->GetReportingEngine().Run(); + + NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadHandlers() == 1); + NL_TEST_ASSERT(apSuite, engine->ActiveHandlerAt(0) != nullptr); + delegate.mpReadHandler = engine->ActiveHandlerAt(0); + + NL_TEST_ASSERT(apSuite, delegate.mGotEventResponse); + NL_TEST_ASSERT(apSuite, delegate.mGotReport); + NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 2); + NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadHandlers(ReadHandler::InteractionType::Subscribe) == 1); + + GenerateEvents(apSuite, apContext, true /*aIsUrgent*/); + NL_TEST_ASSERT(apSuite, delegate.mpReadHandler->mHoldReport == false); + NL_TEST_ASSERT(apSuite, delegate.mpReadHandler->mDirty == true); + chip::app::ClusterInfo dirtyPath1; + dirtyPath1.mClusterId = kTestClusterId; + dirtyPath1.mEndpointId = kTestEndpointId; + dirtyPath1.mAttributeId = 1; + + chip::app::ClusterInfo dirtyPath2; + dirtyPath2.mClusterId = kTestClusterId; + dirtyPath2.mEndpointId = kTestEndpointId; + dirtyPath2.mAttributeId = 2; + + // Test report with 2 different path + delegate.mpReadHandler->mHoldReport = false; + delegate.mGotReport = false; + delegate.mNumAttributeResponse = 0; + + err = engine->GetReportingEngine().SetDirty(dirtyPath1); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + err = engine->GetReportingEngine().SetDirty(dirtyPath2); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + engine->GetReportingEngine().Run(); + NL_TEST_ASSERT(apSuite, delegate.mGotReport); + NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 2); + + delegate.mpReadHandler->mHoldReport = false; + delegate.mpReadHandler->mHoldSync = false; + delegate.mGotReport = false; + delegate.mNumAttributeResponse = 0; + ctx.ExpireSessionBobToAlice(); + engine->GetReportingEngine().Run(); + ctx.ExpireSessionAliceToBob(); + NL_TEST_ASSERT(apSuite, engine->GetReportingEngine().GetNumReportsInFlight() == 0); + NL_TEST_ASSERT(apSuite, 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. + NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); + + NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadClients() == 0); + engine->Shutdown(); + NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + ctx.CreateSessionAliceToBob(); + ctx.CreateSessionBobToAlice(); +} + +void TestReadInteraction::TestSubscribeRoundtripStatusReportTimeout(nlTestSuite * apSuite, void * apContext) +{ + TestContext & ctx = *static_cast(apContext); + CHIP_ERROR err = CHIP_NO_ERROR; + + Messaging::ReliableMessageMgr * rm = ctx.GetExchangeManager().GetReliableMessageMgr(); + // Shouldn't have anything in the retransmit table when starting the test. + NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); + + MockInteractionModelApp delegate; + auto * engine = chip::app::InteractionModelEngine::GetInstance(); + err = engine->Init(&ctx.GetExchangeManager()); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); + + ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); + chip::app::EventPathParams eventPathParams[2]; + readPrepareParams.mpEventPathParamsList = eventPathParams; + readPrepareParams.mpEventPathParamsList[0].mEndpointId = kTestEndpointId; + readPrepareParams.mpEventPathParamsList[0].mClusterId = kTestClusterId; + readPrepareParams.mpEventPathParamsList[0].mEventId = kTestEventIdDebug; + + readPrepareParams.mpEventPathParamsList[1].mEndpointId = kTestEndpointId; + readPrepareParams.mpEventPathParamsList[1].mClusterId = kTestClusterId; + 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; + printf("\nSend first subscribe request message to Node: %" PRIu64 "\n", chip::kTestDeviceNodeId); + + delegate.mNumAttributeResponse = 0; + readPrepareParams.mKeepSubscriptions = false; + + { + app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), delegate, + chip::app::ReadClient::InteractionType::Subscribe); + + err = readClient.SendRequest(readPrepareParams); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + delegate.mGotReport = false; + + ctx.ExpireSessionBobToAlice(); + engine->GetReportingEngine().Run(); + ctx.ExpireSessionAliceToBob(); + + NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadHandlers() == 0); + NL_TEST_ASSERT(apSuite, engine->GetReportingEngine().GetNumReportsInFlight() == 0); + NL_TEST_ASSERT(apSuite, 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. + NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); + + NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadClients() == 0); + engine->Shutdown(); + NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + ctx.CreateSessionAliceToBob(); + ctx.CreateSessionBobToAlice(); +} + +void TestReadInteraction::TestReadChunkingStatusReportTimeout(nlTestSuite * apSuite, void * apContext) +{ + TestContext & ctx = *static_cast(apContext); + CHIP_ERROR err = CHIP_NO_ERROR; + + Messaging::ReliableMessageMgr * rm = ctx.GetExchangeManager().GetReliableMessageMgr(); + // Shouldn't have anything in the retransmit table when starting the test. + NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); + + GenerateEvents(apSuite, apContext); + + MockInteractionModelApp delegate; + auto * engine = chip::app::InteractionModelEngine::GetInstance(); + err = engine->Init(&ctx.GetExchangeManager()); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); + + chip::app::AttributePathParams attributePathParams[1]; + // Mock Attribute 4 is a big attribute, with 6 large OCTET_STRING + attributePathParams[0].mEndpointId = Test::kMockEndpoint3; + attributePathParams[0].mClusterId = Test::MockClusterId(2); + attributePathParams[0].mAttributeId = Test::MockAttributeId(4); + + ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); + readPrepareParams.mpEventPathParamsList = nullptr; + readPrepareParams.mEventPathParamsListSize = 0; + readPrepareParams.mpAttributePathParamsList = attributePathParams; + readPrepareParams.mAttributePathParamsListSize = 1; + + { + app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), delegate, + chip::app::ReadClient::InteractionType::Read); + + err = readClient.SendRequest(readPrepareParams); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + ctx.ExpireSessionBobToAlice(); + InteractionModelEngine::GetInstance()->GetReportingEngine().Run(); + ctx.ExpireSessionAliceToBob(); + + NL_TEST_ASSERT(apSuite, engine->GetReportingEngine().GetNumReportsInFlight() == 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. + NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); + } + + NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadClients() == 0); + engine->Shutdown(); + NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + ctx.CreateSessionAliceToBob(); + ctx.CreateSessionBobToAlice(); +} + +void TestReadInteraction::TestSubscribeRoundtripChunkStatusReportTimeout(nlTestSuite * apSuite, void * apContext) +{ + TestContext & ctx = *static_cast(apContext); + CHIP_ERROR err = CHIP_NO_ERROR; + + Messaging::ReliableMessageMgr * rm = ctx.GetExchangeManager().GetReliableMessageMgr(); + // Shouldn't have anything in the retransmit table when starting the test. + NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); + + MockInteractionModelApp delegate; + auto * engine = chip::app::InteractionModelEngine::GetInstance(); + err = engine->Init(&ctx.GetExchangeManager()); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); + + ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); + chip::app::EventPathParams eventPathParams[2]; + readPrepareParams.mpEventPathParamsList = eventPathParams; + readPrepareParams.mpEventPathParamsList[0].mEndpointId = kTestEndpointId; + readPrepareParams.mpEventPathParamsList[0].mClusterId = kTestClusterId; + readPrepareParams.mpEventPathParamsList[0].mEventId = kTestEventIdDebug; + + readPrepareParams.mpEventPathParamsList[1].mEndpointId = kTestEndpointId; + readPrepareParams.mpEventPathParamsList[1].mClusterId = kTestClusterId; + 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 = Test::kMockEndpoint3; + attributePathParams[0].mClusterId = Test::MockClusterId(2); + attributePathParams[0].mAttributeId = Test::MockAttributeId(4); + + readPrepareParams.mpAttributePathParamsList = attributePathParams; + readPrepareParams.mAttributePathParamsListSize = 1; + + readPrepareParams.mMinIntervalFloorSeconds = 2; + readPrepareParams.mMaxIntervalCeilingSeconds = 5; + printf("\nSend first subscribe request message to Node: %" PRIu64 "\n", chip::kTestDeviceNodeId); + + delegate.mNumAttributeResponse = 0; + readPrepareParams.mKeepSubscriptions = false; + + { + app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), delegate, + chip::app::ReadClient::InteractionType::Subscribe); + + err = readClient.SendRequest(readPrepareParams); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + delegate.mGotReport = false; + + ctx.ExpireSessionBobToAlice(); + engine->GetReportingEngine().Run(); + ctx.ExpireSessionAliceToBob(); + + NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadHandlers() == 0); + NL_TEST_ASSERT(apSuite, engine->GetReportingEngine().GetNumReportsInFlight() == 0); + NL_TEST_ASSERT(apSuite, 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. + NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); + + NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadClients() == 0); + engine->Shutdown(); + NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + ctx.CreateSessionAliceToBob(); + ctx.CreateSessionBobToAlice(); +} + +void TestReadInteraction::TestPostSubscribeRoundtripChunkStatusReportTimeout(nlTestSuite * apSuite, void * apContext) +{ + TestContext & ctx = *static_cast(apContext); + CHIP_ERROR err = CHIP_NO_ERROR; + + Messaging::ReliableMessageMgr * rm = ctx.GetExchangeManager().GetReliableMessageMgr(); + // Shouldn't have anything in the retransmit table when starting the test. + NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); + + MockInteractionModelApp delegate; + auto * engine = chip::app::InteractionModelEngine::GetInstance(); + err = engine->Init(&ctx.GetExchangeManager()); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); + + ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); + chip::app::EventPathParams eventPathParams[2]; + readPrepareParams.mpEventPathParamsList = eventPathParams; + readPrepareParams.mpEventPathParamsList[0].mEndpointId = kTestEndpointId; + readPrepareParams.mpEventPathParamsList[0].mClusterId = kTestClusterId; + readPrepareParams.mpEventPathParamsList[0].mEventId = kTestEventIdDebug; + + readPrepareParams.mpEventPathParamsList[1].mEndpointId = kTestEndpointId; + readPrepareParams.mpEventPathParamsList[1].mClusterId = kTestClusterId; + 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 = Test::kMockEndpoint3; + attributePathParams[0].mClusterId = Test::MockClusterId(2); + attributePathParams[0].mAttributeId = Test::MockAttributeId(4); + + readPrepareParams.mpAttributePathParamsList = attributePathParams; + readPrepareParams.mAttributePathParamsListSize = 1; + + readPrepareParams.mMinIntervalFloorSeconds = 2; + readPrepareParams.mMaxIntervalCeilingSeconds = 5; + printf("\nSend first subscribe request message to Node: %" PRIu64 "\n", chip::kTestDeviceNodeId); + + delegate.mNumAttributeResponse = 0; + readPrepareParams.mKeepSubscriptions = false; + + { + app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), delegate, + chip::app::ReadClient::InteractionType::Subscribe); + + err = readClient.SendRequest(readPrepareParams); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + delegate.mGotReport = false; + engine->GetReportingEngine().Run(); + engine->GetReportingEngine().Run(); + engine->GetReportingEngine().Run(); + NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadHandlers() == 1); + NL_TEST_ASSERT(apSuite, engine->ActiveHandlerAt(0) != nullptr); + delegate.mpReadHandler = engine->ActiveHandlerAt(0); + + NL_TEST_ASSERT(apSuite, delegate.mGotEventResponse); + NL_TEST_ASSERT(apSuite, delegate.mGotReport); + NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadHandlers(ReadHandler::InteractionType::Subscribe) == 1); + + GenerateEvents(apSuite, apContext, true /*aIsUrgent*/); + NL_TEST_ASSERT(apSuite, delegate.mpReadHandler->mHoldReport == false); + NL_TEST_ASSERT(apSuite, delegate.mpReadHandler->mDirty == true); + chip::app::ClusterInfo dirtyPath1; + dirtyPath1.mClusterId = Test::MockClusterId(2); + dirtyPath1.mEndpointId = Test::kMockEndpoint3; + dirtyPath1.mAttributeId = Test::MockAttributeId(4); + + delegate.mpReadHandler->mHoldReport = false; + delegate.mGotReport = false; + delegate.mNumAttributeResponse = 0; + + err = engine->GetReportingEngine().SetDirty(dirtyPath1); + delegate.mpReadHandler->mHoldReport = false; + delegate.mpReadHandler->mHoldSync = false; + delegate.mGotReport = false; + delegate.mNumAttributeResponse = 0; + ctx.ExpireSessionBobToAlice(); + engine->GetReportingEngine().Run(); + ctx.ExpireSessionAliceToBob(); + NL_TEST_ASSERT(apSuite, engine->GetReportingEngine().GetNumReportsInFlight() == 0); + NL_TEST_ASSERT(apSuite, 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. + NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); + + NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadClients() == 0); + engine->Shutdown(); + NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + ctx.CreateSessionAliceToBob(); + ctx.CreateSessionBobToAlice(); +} + +void TestReadInteraction::TestPostSubscribeRoundtripChunkReportTimeout(nlTestSuite * apSuite, void * apContext) +{ + TestContext & ctx = *static_cast(apContext); + CHIP_ERROR err = CHIP_NO_ERROR; + + Messaging::ReliableMessageMgr * rm = ctx.GetExchangeManager().GetReliableMessageMgr(); + // Shouldn't have anything in the retransmit table when starting the test. + NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); + + MockInteractionModelApp delegate; + auto * engine = chip::app::InteractionModelEngine::GetInstance(); + err = engine->Init(&ctx.GetExchangeManager()); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); + + ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); + chip::app::EventPathParams eventPathParams[2]; + readPrepareParams.mpEventPathParamsList = eventPathParams; + readPrepareParams.mpEventPathParamsList[0].mEndpointId = kTestEndpointId; + readPrepareParams.mpEventPathParamsList[0].mClusterId = kTestClusterId; + readPrepareParams.mpEventPathParamsList[0].mEventId = kTestEventIdDebug; + + readPrepareParams.mpEventPathParamsList[1].mEndpointId = kTestEndpointId; + readPrepareParams.mpEventPathParamsList[1].mClusterId = kTestClusterId; + 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 = Test::kMockEndpoint3; + attributePathParams[0].mClusterId = Test::MockClusterId(2); + attributePathParams[0].mAttributeId = Test::MockAttributeId(4); + + readPrepareParams.mpAttributePathParamsList = attributePathParams; + readPrepareParams.mAttributePathParamsListSize = 1; + + readPrepareParams.mMinIntervalFloorSeconds = 2; + readPrepareParams.mMaxIntervalCeilingSeconds = 5; + printf("\nSend first subscribe request message to Node: %" PRIu64 "\n", chip::kTestDeviceNodeId); + + delegate.mNumAttributeResponse = 0; + readPrepareParams.mKeepSubscriptions = false; + + { + app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), delegate, + chip::app::ReadClient::InteractionType::Subscribe); + + err = readClient.SendRequest(readPrepareParams); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + delegate.mGotReport = false; + engine->GetReportingEngine().Run(); + engine->GetReportingEngine().Run(); + engine->GetReportingEngine().Run(); + NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadHandlers() == 1); + NL_TEST_ASSERT(apSuite, engine->ActiveHandlerAt(0) != nullptr); + delegate.mpReadHandler = engine->ActiveHandlerAt(0); + + NL_TEST_ASSERT(apSuite, delegate.mGotEventResponse); + NL_TEST_ASSERT(apSuite, delegate.mGotReport); + NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadHandlers(ReadHandler::InteractionType::Subscribe) == 1); + + GenerateEvents(apSuite, apContext, true /*aIsUrgent*/); + NL_TEST_ASSERT(apSuite, delegate.mpReadHandler->mHoldReport == false); + NL_TEST_ASSERT(apSuite, delegate.mpReadHandler->mDirty == true); + chip::app::ClusterInfo dirtyPath1; + dirtyPath1.mClusterId = Test::MockClusterId(2); + dirtyPath1.mEndpointId = Test::kMockEndpoint3; + dirtyPath1.mAttributeId = Test::MockAttributeId(4); + + delegate.mpReadHandler->mHoldReport = false; + delegate.mGotReport = false; + delegate.mNumAttributeResponse = 0; + + err = engine->GetReportingEngine().SetDirty(dirtyPath1); + delegate.mpReadHandler->mHoldReport = false; + delegate.mpReadHandler->mHoldSync = false; + delegate.mGotReport = false; + delegate.mNumAttributeResponse = 0; + + engine->GetReportingEngine().Run(); + ctx.ExpireSessionBobToAlice(); + + NL_TEST_ASSERT(apSuite, engine->GetReportingEngine().GetNumReportsInFlight() == 0); + NL_TEST_ASSERT(apSuite, delegate.mError == CHIP_ERROR_TIMEOUT); + } + + // 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. + NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); + + NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadClients() == 0); + engine->Shutdown(); + NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + ctx.CreateSessionBobToAlice(); +} } // namespace app } // namespace chip @@ -1777,6 +2302,12 @@ const nlTest sTests[] = NL_TEST_DEF("TestSubscribeInvalidAttributePathRoundtrip", chip::app::TestReadInteraction::TestSubscribeInvalidAttributePathRoundtrip), NL_TEST_DEF("TestReadInvalidAttributePathRoundtrip", chip::app::TestReadInteraction::TestReadInvalidAttributePathRoundtrip), NL_TEST_DEF("TestSubscribeInvalidIterval", chip::app::TestReadInteraction::TestSubscribeInvalidIterval), + NL_TEST_DEF("TestSubscribeRoundtripStatusReportTimeout", chip::app::TestReadInteraction::TestSubscribeRoundtripStatusReportTimeout), + NL_TEST_DEF("TestPostSubscribeRoundtripStatusReportTimeout", chip::app::TestReadInteraction::TestPostSubscribeRoundtripStatusReportTimeout), + NL_TEST_DEF("TestReadChunkingStatusReportTimeout", chip::app::TestReadInteraction::TestReadChunkingStatusReportTimeout), + NL_TEST_DEF("TestSubscribeRoundtripChunkStatusReportTimeout", chip::app::TestReadInteraction::TestSubscribeRoundtripChunkStatusReportTimeout), + NL_TEST_DEF("TestPostSubscribeRoundtripChunkStatusReportTimeout", chip::app::TestReadInteraction::TestPostSubscribeRoundtripChunkStatusReportTimeout), + NL_TEST_DEF("TestPostSubscribeRoundtripChunkReportTimeout", chip::app::TestReadInteraction::TestPostSubscribeRoundtripChunkReportTimeout), NL_TEST_DEF("TestReadShutdown", chip::app::TestReadInteraction::TestReadShutdown), NL_TEST_SENTINEL() }; @@ -1796,8 +2327,7 @@ nlTestSuite sSuite = int TestReadInteraction() { - TestContext gContext; - nlTestRunner(&sSuite, &gContext); + nlTestRunner(&sSuite, &sContext); return (nlTestRunnerStats(&sSuite)); }