From f1dfa089432659b428bd1d32235651a353c1058a Mon Sep 17 00:00:00 2001 From: Yunhan Wang Date: Fri, 10 Jun 2022 15:07:27 -0700 Subject: [PATCH] address comments --- .../MessageDef/SubscribeResponseMessage.cpp | 11 +- src/app/MessageDef/SubscribeResponseMessage.h | 4 +- src/app/ReadClient.cpp | 18 +- src/app/ReadClient.h | 20 +- src/app/ReadHandler.h | 11 +- src/app/tests/TestMessageDef.cpp | 2 +- src/controller/tests/data_model/TestRead.cpp | 481 +++++++++++++++++- 7 files changed, 493 insertions(+), 54 deletions(-) diff --git a/src/app/MessageDef/SubscribeResponseMessage.cpp b/src/app/MessageDef/SubscribeResponseMessage.cpp index 309ebfe5f6f3d7..97e7100c4e5670 100644 --- a/src/app/MessageDef/SubscribeResponseMessage.cpp +++ b/src/app/MessageDef/SubscribeResponseMessage.cpp @@ -53,8 +53,7 @@ CHIP_ERROR SubscribeResponseMessage::Parser::CheckSchemaValidity() const #endif // CHIP_DETAIL_LOGGING break; case to_underlying(Tag::kMaxInterval): - VerifyOrReturnError(!(tagPresenceMask & (1 << to_underlying(Tag::kMaxInterval))), - CHIP_ERROR_INVALID_TLV_TAG); + VerifyOrReturnError(!(tagPresenceMask & (1 << to_underlying(Tag::kMaxInterval))), CHIP_ERROR_INVALID_TLV_TAG); tagPresenceMask |= (1 << to_underlying(Tag::kMaxInterval)); VerifyOrReturnError(TLV::kTLVType_UnsignedInteger == reader.GetType(), CHIP_ERROR_WRONG_TLV_TYPE); #if CHIP_DETAIL_LOGGING @@ -78,9 +77,8 @@ CHIP_ERROR SubscribeResponseMessage::Parser::CheckSchemaValidity() const if (CHIP_END_OF_TLV == err) { - const uint16_t requiredFields = - (1 << to_underlying(Tag::kSubscriptionId)) | (1 << to_underlying(Tag::kMaxInterval)); - err = (tagPresenceMask & requiredFields) == requiredFields ? CHIP_NO_ERROR + const uint16_t requiredFields = (1 << to_underlying(Tag::kSubscriptionId)) | (1 << to_underlying(Tag::kMaxInterval)); + err = (tagPresenceMask & requiredFields) == requiredFields ? CHIP_NO_ERROR : CHIP_ERROR_IM_MALFORMED_SUBSCRIBE_RESPONSE_MESSAGE; } ReturnErrorOnFailure(err); @@ -107,8 +105,7 @@ SubscribeResponseMessage::Builder & SubscribeResponseMessage::Builder::Subscript return *this; } -SubscribeResponseMessage::Builder & -SubscribeResponseMessage::Builder::MaxInterval(const uint16_t aMaxInterval) +SubscribeResponseMessage::Builder & SubscribeResponseMessage::Builder::MaxInterval(const uint16_t aMaxInterval) { if (mError == CHIP_NO_ERROR) { diff --git a/src/app/MessageDef/SubscribeResponseMessage.h b/src/app/MessageDef/SubscribeResponseMessage.h index c51160b5ea628c..19a44e6c0f2f74 100644 --- a/src/app/MessageDef/SubscribeResponseMessage.h +++ b/src/app/MessageDef/SubscribeResponseMessage.h @@ -31,8 +31,8 @@ namespace app { namespace SubscribeResponseMessage { enum class Tag : uint8_t { - kSubscriptionId = 0, - kMaxInterval = 2, + kSubscriptionId = 0, + kMaxInterval = 2, }; class Parser : public MessageParser diff --git a/src/app/ReadClient.cpp b/src/app/ReadClient.cpp index 96aaf01f921e95..01db106995905b 100644 --- a/src/app/ReadClient.cpp +++ b/src/app/ReadClient.cpp @@ -91,12 +91,12 @@ ReadClient::ReadClient(InteractionModelEngine * apImEngine, Messaging::ExchangeM void ReadClient::ClearActiveSubscriptionState() { - mIsInitialReport = true; - mIsPrimingReports = true; - mPendingMoreChunks = false; - mMinIntervalFloorSeconds = 0; - mMaxInterval = 0; - mSubscriptionId = 0; + mIsInitialReport = true; + mIsPrimingReports = true; + mPendingMoreChunks = false; + mMinIntervalFloorSeconds = 0; + mMaxInterval = 0; + mSubscriptionId = 0; MoveToState(ClientState::Idle); } @@ -754,8 +754,7 @@ CHIP_ERROR ReadClient::RefreshLivenessCheckTimer() VerifyOrReturnError(mpExchangeCtx != nullptr, err = CHIP_ERROR_INCORRECT_STATE); VerifyOrReturnError(mpExchangeCtx->HasSessionHandle(), err = CHIP_ERROR_INCORRECT_STATE); - System::Clock::Timeout timeout = - System::Clock::Seconds16(mMaxInterval) + mpExchangeCtx->GetSessionHandle()->GetAckTimeout(); + System::Clock::Timeout timeout = System::Clock::Seconds16(mMaxInterval) + mpExchangeCtx->GetSessionHandle()->GetAckTimeout(); // EFR32/MBED/INFINION/K32W's chrono count return long unsinged, but other platform returns unsigned ChipLogProgress(DataManagement, "Refresh LivenessCheckTime for %lu milliseconds with SubscriptionId = 0x%08" PRIx32 @@ -824,8 +823,7 @@ CHIP_ERROR ReadClient::ProcessSubscribeResponse(System::PacketBufferHandle && aP ChipLogProgress(DataManagement, "Subscription established with SubscriptionID = 0x%08" PRIx32 " MinInterval = %u" "s MaxInterval = %us Peer = %02x:" ChipLogFormatX64, - mSubscriptionId, mMinIntervalFloorSeconds, mMaxInterval, mFabricIndex, - ChipLogValueX64(mPeerNodeId)); + mSubscriptionId, mMinIntervalFloorSeconds, mMaxInterval, mFabricIndex, ChipLogValueX64(mPeerNodeId)); ReturnErrorOnFailure(subscribeResponse.ExitContainer()); diff --git a/src/app/ReadClient.h b/src/app/ReadClient.h index 49600731b2cd00..e79bab6e867dd0 100644 --- a/src/app/ReadClient.h +++ b/src/app/ReadClient.h @@ -413,16 +413,16 @@ class ReadClient : public Messaging::ExchangeDelegate Messaging::ExchangeManager * mpExchangeMgr = nullptr; Messaging::ExchangeContext * mpExchangeCtx = nullptr; Callback & mpCallback; - ClientState mState = ClientState::Idle; - bool mIsInitialReport = true; - bool mIsPrimingReports = true; - bool mPendingMoreChunks = false; - uint16_t mMinIntervalFloorSeconds = 0; - uint16_t mMaxInterval = 0; - SubscriptionId mSubscriptionId = 0; - NodeId mPeerNodeId = kUndefinedNodeId; - FabricIndex mFabricIndex = kUndefinedFabricIndex; - InteractionType mInteractionType = InteractionType::Read; + ClientState mState = ClientState::Idle; + bool mIsInitialReport = true; + bool mIsPrimingReports = true; + bool mPendingMoreChunks = false; + uint16_t mMinIntervalFloorSeconds = 0; + uint16_t mMaxInterval = 0; + SubscriptionId mSubscriptionId = 0; + NodeId mPeerNodeId = kUndefinedNodeId; + FabricIndex mFabricIndex = kUndefinedFabricIndex; + InteractionType mInteractionType = InteractionType::Read; Timestamp mEventTimestamp; bool mSawAttributeReportsInCurrentReport = false; diff --git a/src/app/ReadHandler.h b/src/app/ReadHandler.h index 3613a85c1e4d10..eab32872245732 100644 --- a/src/app/ReadHandler.h +++ b/src/app/ReadHandler.h @@ -45,7 +45,7 @@ #include #include -//https://github.com/CHIP-Specifications/connectedhomeip-spec/blob/61a9d19e6af12fdfb0872bcff26d19de6c680a1a/src/Ch02_Architecture.adoc#1122-subscribe-interaction-limits +// https://github.com/CHIP-Specifications/connectedhomeip-spec/blob/61a9d19e6af12fdfb0872bcff26d19de6c680a1a/src/Ch02_Architecture.adoc#1122-subscribe-interaction-limits constexpr uint16_t kSubscriptionMaxIntervalPublisherLimit = 3600; // 3600 seconds namespace chip { @@ -183,8 +183,7 @@ class ReadHandler : public Messaging::ExchangeDelegate { VerifyOrReturnError(IsIdle(), CHIP_ERROR_INCORRECT_STATE); VerifyOrReturnError(mMinIntervalFloorSeconds <= aMaxInterval, CHIP_ERROR_INVALID_ARGUMENT); - VerifyOrReturnError(aMaxInterval <= - std::max(kSubscriptionMaxIntervalPublisherLimit, mMaxInterval), + VerifyOrReturnError(aMaxInterval <= std::max(kSubscriptionMaxIntervalPublisherLimit, mMaxInterval), CHIP_ERROR_INVALID_ARGUMENT); mMaxInterval = aMaxInterval; return CHIP_NO_ERROR; @@ -434,9 +433,9 @@ class ReadHandler : public Messaging::ExchangeDelegate // engine, the "oldest" subscription is the subscription with the smallest generation. uint64_t mSubscriptionStartGeneration = 0; - SubscriptionId mSubscriptionId = 0; - uint16_t mMinIntervalFloorSeconds = 0; - uint16_t mMaxInterval = 0; + SubscriptionId mSubscriptionId = 0; + uint16_t mMinIntervalFloorSeconds = 0; + uint16_t mMaxInterval = 0; EventNumber mEventMin = 0; diff --git a/src/app/tests/TestMessageDef.cpp b/src/app/tests/TestMessageDef.cpp index 524b632d83452a..fbd40af24d4ee1 100644 --- a/src/app/tests/TestMessageDef.cpp +++ b/src/app/tests/TestMessageDef.cpp @@ -1343,7 +1343,7 @@ void ParseSubscribeResponseMessage(nlTestSuite * apSuite, chip::TLV::TLVReader & SubscribeResponseMessage::Parser subscribeResponseParser; chip::SubscriptionId subscriptionId = 0; - uint16_t maxInterval = 0; + uint16_t maxInterval = 0; err = subscribeResponseParser.Init(aReader); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); #if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK diff --git a/src/controller/tests/data_model/TestRead.cpp b/src/controller/tests/data_model/TestRead.cpp index 3032d321d0faf8..9c7c3f00913293 100644 --- a/src/controller/tests/data_model/TestRead.cpp +++ b/src/controller/tests/data_model/TestRead.cpp @@ -218,8 +218,14 @@ class TestReadInteraction : public app::ReadHandler::ApplicationCallback static void TestReadHandler_OneSubscribeMultipleReads(nlTestSuite * apSuite, void * apContext); static void TestReadHandler_TwoSubscribesMultipleReads(nlTestSuite * apSuite, void * apContext); static void TestReadHandler_MultipleSubscriptionsWithDataVersionFilter(nlTestSuite * apSuite, void * apContext); - static void TestReadHandler_SubscriptionAlteredReportingIntervals(nlTestSuite * apSuite, void * apContext); - static void TestReadHandler_SubscriptionLargeAlteredReportingIntervals(nlTestSuite * apSuite, void * apContext); + static void TestReadHandler_SubscriptionReportingIntervalsTest1(nlTestSuite * apSuite, void * apContext); + static void TestReadHandler_SubscriptionReportingIntervalsTest2(nlTestSuite * apSuite, void * apContext); + static void TestReadHandler_SubscriptionReportingIntervalsTest3(nlTestSuite * apSuite, void * apContext); + static void TestReadHandler_SubscriptionReportingIntervalsTest4(nlTestSuite * apSuite, void * apContext); + static void TestReadHandler_SubscriptionReportingIntervalsTest5(nlTestSuite * apSuite, void * apContext); + static void TestReadHandler_SubscriptionReportingIntervalsTest6(nlTestSuite * apSuite, void * apContext); + static void TestReadHandler_SubscriptionReportingIntervalsTest7(nlTestSuite * apSuite, void * apContext); + static void TestReadHandler_SubscriptionReportingIntervalsTest8(nlTestSuite * apSuite, void * apContext); static void TestReadHandlerResourceExhaustion_MultipleReads(nlTestSuite * apSuite, void * apContext); static void TestReadSubscribeAttributeResponseWithCache(nlTestSuite * apSuite, void * apContext); static void TestReadHandler_KillOverQuotaSubscriptions(nlTestSuite * apSuite, void * apContext); @@ -229,8 +235,6 @@ class TestReadInteraction : public app::ReadHandler::ApplicationCallback static void TestReadHandler_TwoParallelReadsSecondTooManyPaths(nlTestSuite * apSuite, void * apContext); private: - static constexpr uint16_t kTestMaxInterval = 66; - static uint16_t mMaxInterval; CHIP_ERROR OnSubscriptionRequested(app::ReadHandler & aReadHandler, Transport::SecureSession & aSecureSession) @@ -1619,7 +1623,84 @@ void TestReadInteraction::TestReadHandler_SubscriptionAppRejection(nlTestSuite * gTestReadInteraction.mEmitSubscriptionError = false; } -void TestReadInteraction::TestReadHandler_SubscriptionAlteredReportingIntervals(nlTestSuite * apSuite, void * apContext) +// Subscriber send the request with particular max-interval value: +// Max interval equal to client-requested min-interval. +void TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest1(nlTestSuite * apSuite, void * apContext) +{ + TestContext & ctx = *static_cast(apContext); + auto sessionHandle = ctx.GetSessionBobToAlice(); + uint32_t numSuccessCalls = 0; + uint32_t numFailureCalls = 0; + uint32_t numSubscriptionEstablishedCalls = 0; + + responseDirective = kSendDataResponse; + + // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's + // not safe to do so. + auto onSuccessCb = [&numSuccessCalls](const app::ConcreteDataAttributePath & attributePath, const auto & dataResponse) { + numSuccessCalls++; + }; + + // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's + // not safe to do so. + auto onFailureCb = [&numFailureCalls](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) { + numFailureCalls++; + }; + + auto onSubscriptionEstablishedCb = [&numSubscriptionEstablishedCalls, &apSuite](const app::ReadClient & readClient) { + uint16_t minInterval = 0, maxInterval = 0; + + CHIP_ERROR err = readClient.GetReportingIntervals(minInterval, maxInterval); + + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + NL_TEST_ASSERT(apSuite, minInterval == 5); + NL_TEST_ASSERT(apSuite, maxInterval == 5); + + numSubscriptionEstablishedCalls++; + }; + + // + // Test the application callback as well to ensure we get the right number of SubscriptionEstablishment/Termination + // callbacks. + // + app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(&gTestReadInteraction); + + // + // Test the server-side application altering the subscription intervals. + // + gTestReadInteraction.mAlterSubscriptionIntervals = false; + + NL_TEST_ASSERT(apSuite, + Controller::SubscribeAttribute( + &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 5, 5, + onSubscriptionEstablishedCb, nullptr, true) == CHIP_NO_ERROR); + + ctx.DrainAndServiceIO(); + + // + // Failures won't get routed to us here since re-subscriptions are enabled by default in the Controller::SubscribeAttribute + // implementation. + // + NL_TEST_ASSERT(apSuite, numSuccessCalls != 0); + NL_TEST_ASSERT(apSuite, numFailureCalls == 0); + NL_TEST_ASSERT(apSuite, numSubscriptionEstablishedCalls == 1); + NL_TEST_ASSERT(apSuite, gTestReadInteraction.mNumActiveSubscriptions == 1); + + app::InteractionModelEngine::GetInstance()->ShutdownActiveReads(); + + NL_TEST_ASSERT(apSuite, gTestReadInteraction.mNumActiveSubscriptions == 0); + + NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + + app::InteractionModelEngine::GetInstance()->UnregisterReadHandlerAppCallback(); + gTestReadInteraction.mAlterSubscriptionIntervals = false; +} + +// Subscriber send the request with particular max-interval value: +// Max interval greater than client-requested min-interval but lower than 60m: +// With no server adjustment. +void TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest2(nlTestSuite * apSuite, void * apContext) { TestContext & ctx = *static_cast(apContext); auto sessionHandle = ctx.GetSessionBobToAlice(); @@ -1649,7 +1730,7 @@ void TestReadInteraction::TestReadHandler_SubscriptionAlteredReportingIntervals( NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, minInterval == 0); - NL_TEST_ASSERT(apSuite, maxInterval == kTestMaxInterval); + NL_TEST_ASSERT(apSuite, maxInterval == 10); numSubscriptionEstablishedCalls++; }; @@ -1663,7 +1744,7 @@ void TestReadInteraction::TestReadHandler_SubscriptionAlteredReportingIntervals( // // Test the server-side application altering the subscription intervals. // - gTestReadInteraction.mAlterSubscriptionIntervals = true; + gTestReadInteraction.mAlterSubscriptionIntervals = false; NL_TEST_ASSERT(apSuite, Controller::SubscribeAttribute( @@ -1691,15 +1772,18 @@ void TestReadInteraction::TestReadHandler_SubscriptionAlteredReportingIntervals( gTestReadInteraction.mAlterSubscriptionIntervals = false; } -void TestReadInteraction::TestReadHandler_SubscriptionLargeAlteredReportingIntervals(nlTestSuite * apSuite, void * apContext) +// Subscriber send the request with particular max-interval value: +// Max interval greater than client-requested min-interval but lower than 60m: +// With server adjustment to a value greater than client-requested, but less than 60m (allowed). +void TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest3(nlTestSuite * apSuite, void * apContext) { TestContext & ctx = *static_cast(apContext); auto sessionHandle = ctx.GetSessionBobToAlice(); uint32_t numSuccessCalls = 0; uint32_t numFailureCalls = 0; uint32_t numSubscriptionEstablishedCalls = 0; - uint16_t largeMaxInterval = 6000; - responseDirective = kSendDataResponse; + + responseDirective = kSendDataResponse; // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. @@ -1713,13 +1797,16 @@ void TestReadInteraction::TestReadHandler_SubscriptionLargeAlteredReportingInter numFailureCalls++; }; - auto onSubscriptionEstablishedCb = [&numSubscriptionEstablishedCalls, &apSuite, - largeMaxInterval](const app::ReadClient & readClient) { + auto onSubscriptionEstablishedCb = [&numSubscriptionEstablishedCalls, &apSuite](const app::ReadClient & readClient) { uint16_t minInterval = 0, maxInterval = 0; + CHIP_ERROR err = readClient.GetReportingIntervals(minInterval, maxInterval); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(apSuite, minInterval == 0); - NL_TEST_ASSERT(apSuite, maxInterval == largeMaxInterval); + NL_TEST_ASSERT(apSuite, maxInterval == 3000); + numSubscriptionEstablishedCalls++; }; @@ -1733,12 +1820,77 @@ void TestReadInteraction::TestReadHandler_SubscriptionLargeAlteredReportingInter // Test the server-side application altering the subscription intervals. // gTestReadInteraction.mAlterSubscriptionIntervals = true; - gTestReadInteraction.mMaxInterval = largeMaxInterval; + gTestReadInteraction.mMaxInterval = 3000; + NL_TEST_ASSERT(apSuite, + Controller::SubscribeAttribute( + &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 10, + onSubscriptionEstablishedCb, nullptr, true) == CHIP_NO_ERROR); + ctx.DrainAndServiceIO(); + + // + // Failures won't get routed to us here since re-subscriptions are enabled by default in the Controller::SubscribeAttribute + // implementation. + // + NL_TEST_ASSERT(apSuite, numSuccessCalls != 0); + NL_TEST_ASSERT(apSuite, numFailureCalls == 0); + NL_TEST_ASSERT(apSuite, numSubscriptionEstablishedCalls == 1); + NL_TEST_ASSERT(apSuite, gTestReadInteraction.mNumActiveSubscriptions == 1); + + app::InteractionModelEngine::GetInstance()->ShutdownActiveReads(); + + NL_TEST_ASSERT(apSuite, gTestReadInteraction.mNumActiveSubscriptions == 0); + + NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + + app::InteractionModelEngine::GetInstance()->UnregisterReadHandlerAppCallback(); + gTestReadInteraction.mAlterSubscriptionIntervals = false; +} + +// Subscriber send the request with particular max-interval value: +// Max interval greater than client-requested min-interval but lower than 60m: +// server adjustment to a value greater than client-requested, but greater than 60 (not allowed). +void TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest4(nlTestSuite * apSuite, void * apContext) +{ + TestContext & ctx = *static_cast(apContext); + auto sessionHandle = ctx.GetSessionBobToAlice(); + uint32_t numSuccessCalls = 0; + uint32_t numFailureCalls = 0; + uint32_t numSubscriptionEstablishedCalls = 0; + + responseDirective = kSendDataResponse; + + // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's + // not safe to do so. + auto onSuccessCb = [&numSuccessCalls](const app::ConcreteDataAttributePath & attributePath, const auto & dataResponse) { + numSuccessCalls++; + }; + + // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's + // not safe to do so. + auto onFailureCb = [&numFailureCalls](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) { + numFailureCalls++; + }; + + auto onSubscriptionEstablishedCb = [&numSubscriptionEstablishedCalls](const app::ReadClient & readClient) { + numSubscriptionEstablishedCalls++; + }; + + // + // Test the application callback as well to ensure we get the right number of SubscriptionEstablishment/Termination + // callbacks. + // + app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(&gTestReadInteraction); + + // + // Test the server-side application altering the subscription intervals. + // + gTestReadInteraction.mAlterSubscriptionIntervals = true; + gTestReadInteraction.mMaxInterval = 3700; NL_TEST_ASSERT(apSuite, Controller::SubscribeAttribute( &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 10, - onSubscriptionEstablishedCb, false, true) == CHIP_NO_ERROR); + onSubscriptionEstablishedCb, nullptr, true) == CHIP_NO_ERROR); ctx.DrainAndServiceIO(); @@ -1746,7 +1898,230 @@ void TestReadInteraction::TestReadHandler_SubscriptionLargeAlteredReportingInter // Failures won't get routed to us here since re-subscriptions are enabled by default in the Controller::SubscribeAttribute // implementation. // - NL_TEST_ASSERT(apSuite, numSuccessCalls == 1); + NL_TEST_ASSERT(apSuite, numSuccessCalls == 0); + NL_TEST_ASSERT(apSuite, numSubscriptionEstablishedCalls == 0); + NL_TEST_ASSERT(apSuite, gTestReadInteraction.mNumActiveSubscriptions == 0); + + app::InteractionModelEngine::GetInstance()->ShutdownActiveReads(); + + NL_TEST_ASSERT(apSuite, gTestReadInteraction.mNumActiveSubscriptions == 0); + + NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + + app::InteractionModelEngine::GetInstance()->UnregisterReadHandlerAppCallback(); + gTestReadInteraction.mAlterSubscriptionIntervals = false; +} + +// Subscriber send the request with particular max-interval value: +// Max interval greater than client-requested min-interval but greater than 60m: +// With no server adjustment. +void TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest5(nlTestSuite * apSuite, void * apContext) +{ + TestContext & ctx = *static_cast(apContext); + auto sessionHandle = ctx.GetSessionBobToAlice(); + uint32_t numSuccessCalls = 0; + uint32_t numFailureCalls = 0; + uint32_t numSubscriptionEstablishedCalls = 0; + + responseDirective = kSendDataResponse; + + // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's + // not safe to do so. + auto onSuccessCb = [&numSuccessCalls](const app::ConcreteDataAttributePath & attributePath, const auto & dataResponse) { + numSuccessCalls++; + }; + + // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's + // not safe to do so. + auto onFailureCb = [&numFailureCalls](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) { + numFailureCalls++; + }; + + auto onSubscriptionEstablishedCb = [&numSubscriptionEstablishedCalls, &apSuite](const app::ReadClient & readClient) { + uint16_t minInterval = 0, maxInterval = 0; + + CHIP_ERROR err = readClient.GetReportingIntervals(minInterval, maxInterval); + + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + NL_TEST_ASSERT(apSuite, minInterval == 0); + NL_TEST_ASSERT(apSuite, maxInterval == 4000); + + numSubscriptionEstablishedCalls++; + }; + + // + // Test the application callback as well to ensure we get the right number of SubscriptionEstablishment/Termination + // callbacks. + // + app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(&gTestReadInteraction); + + // + // Test the server-side application altering the subscription intervals. + // + gTestReadInteraction.mAlterSubscriptionIntervals = false; + + NL_TEST_ASSERT(apSuite, + Controller::SubscribeAttribute( + &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 4000, + onSubscriptionEstablishedCb, nullptr, true) == CHIP_NO_ERROR); + + ctx.DrainAndServiceIO(); + + // + // Failures won't get routed to us here since re-subscriptions are enabled by default in the Controller::SubscribeAttribute + // implementation. + // + NL_TEST_ASSERT(apSuite, numSuccessCalls != 0); + NL_TEST_ASSERT(apSuite, numFailureCalls == 0); + NL_TEST_ASSERT(apSuite, numSubscriptionEstablishedCalls == 1); + NL_TEST_ASSERT(apSuite, gTestReadInteraction.mNumActiveSubscriptions == 1); + + app::InteractionModelEngine::GetInstance()->ShutdownActiveReads(); + + NL_TEST_ASSERT(apSuite, gTestReadInteraction.mNumActiveSubscriptions == 0); + + NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + + app::InteractionModelEngine::GetInstance()->UnregisterReadHandlerAppCallback(); + gTestReadInteraction.mAlterSubscriptionIntervals = false; +} + +// Subscriber send the request with particular max-interval value: +// Max interval greater than client-requested min-interval but greater than 60m: +// With server adjustment to a value lower than 60m. Allowed +void TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest6(nlTestSuite * apSuite, void * apContext) +{ + TestContext & ctx = *static_cast(apContext); + auto sessionHandle = ctx.GetSessionBobToAlice(); + uint32_t numSuccessCalls = 0; + uint32_t numFailureCalls = 0; + uint32_t numSubscriptionEstablishedCalls = 0; + + responseDirective = kSendDataResponse; + + // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's + // not safe to do so. + auto onSuccessCb = [&numSuccessCalls](const app::ConcreteDataAttributePath & attributePath, const auto & dataResponse) { + numSuccessCalls++; + }; + + // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's + // not safe to do so. + auto onFailureCb = [&numFailureCalls](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) { + numFailureCalls++; + }; + + auto onSubscriptionEstablishedCb = [&numSubscriptionEstablishedCalls, &apSuite](const app::ReadClient & readClient) { + uint16_t minInterval = 0, maxInterval = 0; + + CHIP_ERROR err = readClient.GetReportingIntervals(minInterval, maxInterval); + + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + NL_TEST_ASSERT(apSuite, minInterval == 0); + NL_TEST_ASSERT(apSuite, maxInterval == 3000); + + numSubscriptionEstablishedCalls++; + }; + + // + // Test the application callback as well to ensure we get the right number of SubscriptionEstablishment/Termination + // callbacks. + // + app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(&gTestReadInteraction); + + // + // Test the server-side application altering the subscription intervals. + // + gTestReadInteraction.mAlterSubscriptionIntervals = true; + gTestReadInteraction.mMaxInterval = 3000; + NL_TEST_ASSERT(apSuite, + Controller::SubscribeAttribute( + &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 4000, + onSubscriptionEstablishedCb, nullptr, true) == CHIP_NO_ERROR); + + ctx.DrainAndServiceIO(); + + // + // Failures won't get routed to us here since re-subscriptions are enabled by default in the Controller::SubscribeAttribute + // implementation. + // + NL_TEST_ASSERT(apSuite, numSuccessCalls != 0); + NL_TEST_ASSERT(apSuite, numFailureCalls == 0); + NL_TEST_ASSERT(apSuite, numSubscriptionEstablishedCalls == 1); + NL_TEST_ASSERT(apSuite, gTestReadInteraction.mNumActiveSubscriptions == 1); + + app::InteractionModelEngine::GetInstance()->ShutdownActiveReads(); + + NL_TEST_ASSERT(apSuite, gTestReadInteraction.mNumActiveSubscriptions == 0); + + NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + + app::InteractionModelEngine::GetInstance()->UnregisterReadHandlerAppCallback(); + gTestReadInteraction.mAlterSubscriptionIntervals = false; +} + +// Subscriber send the request with particular max-interval value: +// Max interval greater than client-requested min-interval but greater than 60m: +// With server adjustment to a value larger than 60m, but less than max interval. Allowed +void TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest7(nlTestSuite * apSuite, void * apContext) +{ + TestContext & ctx = *static_cast(apContext); + auto sessionHandle = ctx.GetSessionBobToAlice(); + uint32_t numSuccessCalls = 0; + uint32_t numFailureCalls = 0; + uint32_t numSubscriptionEstablishedCalls = 0; + + responseDirective = kSendDataResponse; + + // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's + // not safe to do so. + auto onSuccessCb = [&numSuccessCalls](const app::ConcreteDataAttributePath & attributePath, const auto & dataResponse) { + numSuccessCalls++; + }; + + // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's + // not safe to do so. + auto onFailureCb = [&numFailureCalls](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) { + numFailureCalls++; + }; + + auto onSubscriptionEstablishedCb = [&numSubscriptionEstablishedCalls, &apSuite](const app::ReadClient & readClient) { + uint16_t minInterval = 0, maxInterval = 0; + + CHIP_ERROR err = readClient.GetReportingIntervals(minInterval, maxInterval); + + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + NL_TEST_ASSERT(apSuite, minInterval == 0); + NL_TEST_ASSERT(apSuite, maxInterval == 3700); + + numSubscriptionEstablishedCalls++; + }; + // + // Test the application callback as well to ensure we get the right number of SubscriptionEstablishment/Termination + // callbacks. + // + app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(&gTestReadInteraction); + + // + // Test the server-side application altering the subscription intervals. + // + gTestReadInteraction.mAlterSubscriptionIntervals = true; + gTestReadInteraction.mMaxInterval = 3700; + NL_TEST_ASSERT(apSuite, + Controller::SubscribeAttribute( + &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 4000, + onSubscriptionEstablishedCb, nullptr, true) == CHIP_NO_ERROR); + + ctx.DrainAndServiceIO(); + + // + // Failures won't get routed to us here since re-subscriptions are enabled by default in the Controller::SubscribeAttribute + // implementation. + // + NL_TEST_ASSERT(apSuite, numSuccessCalls != 0); NL_TEST_ASSERT(apSuite, numFailureCalls == 0); NL_TEST_ASSERT(apSuite, numSubscriptionEstablishedCalls == 1); NL_TEST_ASSERT(apSuite, gTestReadInteraction.mNumActiveSubscriptions == 1); @@ -1761,6 +2136,70 @@ void TestReadInteraction::TestReadHandler_SubscriptionLargeAlteredReportingInter gTestReadInteraction.mAlterSubscriptionIntervals = false; } +// Subscriber send the request with particular max-interval value: +// Max interval greater than client-requested min-interval but greater than 60m: +// With server adjustment to a value larger than 60m, but larger than max interval. Disallowed +void TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest8(nlTestSuite * apSuite, void * apContext) +{ + TestContext & ctx = *static_cast(apContext); + auto sessionHandle = ctx.GetSessionBobToAlice(); + uint32_t numSuccessCalls = 0; + uint32_t numFailureCalls = 0; + uint32_t numSubscriptionEstablishedCalls = 0; + + responseDirective = kSendDataResponse; + + // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's + // not safe to do so. + auto onSuccessCb = [&numSuccessCalls](const app::ConcreteDataAttributePath & attributePath, const auto & dataResponse) { + numSuccessCalls++; + }; + + // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's + // not safe to do so. + auto onFailureCb = [&numFailureCalls](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) { + numFailureCalls++; + }; + + auto onSubscriptionEstablishedCb = [&numSubscriptionEstablishedCalls](const app::ReadClient & readClient) { + numSubscriptionEstablishedCalls++; + }; + // + // Test the application callback as well to ensure we get the right number of SubscriptionEstablishment/Termination + // callbacks. + // + app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(&gTestReadInteraction); + + // + // Test the server-side application altering the subscription intervals. + // + gTestReadInteraction.mAlterSubscriptionIntervals = true; + gTestReadInteraction.mMaxInterval = 4100; + NL_TEST_ASSERT(apSuite, + Controller::SubscribeAttribute( + &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 4000, + onSubscriptionEstablishedCb, nullptr, true) == CHIP_NO_ERROR); + + ctx.DrainAndServiceIO(); + + // + // Failures won't get routed to us here since re-subscriptions are enabled by default in the Controller::SubscribeAttribute + // implementation. + // + NL_TEST_ASSERT(apSuite, numSuccessCalls == 0); + NL_TEST_ASSERT(apSuite, numSubscriptionEstablishedCalls == 0); + NL_TEST_ASSERT(apSuite, gTestReadInteraction.mNumActiveSubscriptions == 0); + + app::InteractionModelEngine::GetInstance()->ShutdownActiveReads(); + + NL_TEST_ASSERT(apSuite, gTestReadInteraction.mNumActiveSubscriptions == 0); + + NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); + + app::InteractionModelEngine::GetInstance()->UnregisterReadHandlerAppCallback(); + gTestReadInteraction.mAlterSubscriptionIntervals = false; +} + void TestReadInteraction::TestReadHandler_MultipleReads(nlTestSuite * apSuite, void * apContext) { TestContext & ctx = *static_cast(apContext); @@ -2690,8 +3129,14 @@ const nlTest sTests[] = NL_TEST_DEF("TestReadHandlerResourceExhaustion_MultipleReads", TestReadInteraction::TestReadHandlerResourceExhaustion_MultipleReads), NL_TEST_DEF("TestReadAttributeTimeout", TestReadInteraction::TestReadAttributeTimeout), NL_TEST_DEF("TestSubscribeAttributeTimeout", TestReadInteraction::TestSubscribeAttributeTimeout), - NL_TEST_DEF("TestReadHandler_SubscriptionAlteredReportingIntervals", TestReadInteraction::TestReadHandler_SubscriptionAlteredReportingIntervals), - NL_TEST_DEF("TestReadHandler_SubscriptionLargeAlteredReportingIntervals", TestReadInteraction::TestReadHandler_SubscriptionLargeAlteredReportingIntervals), + NL_TEST_DEF("TestReadHandler_SubscriptionReportingIntervalsTest1", TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest1), + NL_TEST_DEF("TestReadHandler_SubscriptionReportingIntervalsTest2", TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest2), + NL_TEST_DEF("TestReadHandler_SubscriptionReportingIntervalsTest3", TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest3), + NL_TEST_DEF("TestReadHandler_SubscriptionReportingIntervalsTest4", TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest4), + NL_TEST_DEF("TestReadHandler_SubscriptionReportingIntervalsTest5", TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest5), + NL_TEST_DEF("TestReadHandler_SubscriptionReportingIntervalsTest6", TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest6), + NL_TEST_DEF("TestReadHandler_SubscriptionReportingIntervalsTest7", TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest7), + NL_TEST_DEF("TestReadHandler_SubscriptionReportingIntervalsTest8", TestReadInteraction::TestReadHandler_SubscriptionReportingIntervalsTest8), NL_TEST_DEF("TestReadSubscribeAttributeResponseWithCache", TestReadInteraction::TestReadSubscribeAttributeResponseWithCache), NL_TEST_DEF("TestReadHandler_KillOverQuotaSubscriptions", TestReadInteraction::TestReadHandler_KillOverQuotaSubscriptions), NL_TEST_DEF("TestReadHandler_KillOldestSubscriptions", TestReadInteraction::TestReadHandler_KillOldestSubscriptions),