diff --git a/src/app/ReadHandler.cpp b/src/app/ReadHandler.cpp index 43dae0a99f9c64..4794ead97eb86b 100644 --- a/src/app/ReadHandler.cpp +++ b/src/app/ReadHandler.cpp @@ -741,7 +741,9 @@ CHIP_ERROR ReadHandler::ProcessSubscribeRequest(System::PacketBufferHandle && aP ReturnErrorOnFailure(err); ReturnErrorOnFailure(subscribeRequestParser.GetMinIntervalFloorSeconds(&mMinIntervalFloorSeconds)); - ReturnErrorOnFailure(subscribeRequestParser.GetMaxIntervalCeilingSeconds(&mMaxInterval)); + ReturnErrorOnFailure(subscribeRequestParser.GetMaxIntervalCeilingSeconds(&mSubscriberRequestedMaxInterval)); + mMaxInterval = mSubscriberRequestedMaxInterval; + VerifyOrReturnError(mMinIntervalFloorSeconds <= mMaxInterval, CHIP_ERROR_INVALID_ARGUMENT); #if CHIP_CONFIG_ENABLE_ICD_SERVER diff --git a/src/app/ReadHandler.h b/src/app/ReadHandler.h index 1ddcb4ebf0abb1..85976ec6bc9e62 100644 --- a/src/app/ReadHandler.h +++ b/src/app/ReadHandler.h @@ -229,12 +229,31 @@ class ReadHandler : public Messaging::ExchangeDelegate const SingleLinkedListNode * GetEventPathList() const { return mpEventPathList; } const SingleLinkedListNode * GetDataVersionFilterList() const { return mpDataVersionFilterList; } + /** + * @brief Returns the reporting intervals that will used by the ReadHandler for the subscription being requested. + * After the subscription is established, these will be the set reporting intervals and cannot be changed. + * + * @param[out] aMinInterval minimum time delta between two reports for the subscription + * @param[in] aMaxInterval maximum time delta between two reports for the subscription + */ void GetReportingIntervals(uint16_t & aMinInterval, uint16_t & aMaxInterval) const { aMinInterval = mMinIntervalFloorSeconds; aMaxInterval = mMaxInterval; } + /** + * @brief Returns the maximum reporting interval that was initially requested by the subscriber + * This is the same value as the mMaxInterval member if the max interval is not changed by the publisher. + * + * @note If the device is an ICD, the MaxInterval of a subscription is automatically set to a multiple of the IdleModeDuration. + * This function is the only way to get the requested max interval once the OnSubscriptionRequested application callback + * is called. + * + * @return uint16_t subscriber requested maximum reporting interval + */ + inline uint16_t GetSubscriberRequestedMaxInterval() const { return mSubscriberRequestedMaxInterval; } + CHIP_ERROR SetMinReportingIntervalForTests(uint16_t aMinInterval) { VerifyOrReturnError(IsIdle(), CHIP_ERROR_INCORRECT_STATE); @@ -254,7 +273,7 @@ class ReadHandler : public Messaging::ExchangeDelegate { VerifyOrReturnError(IsIdle(), CHIP_ERROR_INCORRECT_STATE); VerifyOrReturnError(mMinIntervalFloorSeconds <= aMaxInterval, CHIP_ERROR_INVALID_ARGUMENT); - VerifyOrReturnError(aMaxInterval <= std::max(GetPublisherSelectedIntervalLimit(), mMaxInterval), + VerifyOrReturnError(aMaxInterval <= std::max(GetPublisherSelectedIntervalLimit(), mSubscriberRequestedMaxInterval), CHIP_ERROR_INVALID_ARGUMENT); mMaxInterval = aMaxInterval; return CHIP_NO_ERROR; @@ -542,9 +561,10 @@ class ReadHandler : public Messaging::ExchangeDelegate // engine, the "oldest" subscription is the subscription with the smallest generation. uint64_t mTransactionStartGeneration = 0; - SubscriptionId mSubscriptionId = 0; - uint16_t mMinIntervalFloorSeconds = 0; - uint16_t mMaxInterval = 0; + SubscriptionId mSubscriptionId = 0; + uint16_t mMinIntervalFloorSeconds = 0; + uint16_t mMaxInterval = 0; + uint16_t mSubscriberRequestedMaxInterval = 0; EventNumber mEventMin = 0; diff --git a/src/app/tests/TestReadInteraction.cpp b/src/app/tests/TestReadInteraction.cpp index f8f192d92e7512..5baab570f6e071 100644 --- a/src/app/tests/TestReadInteraction.cpp +++ b/src/app/tests/TestReadInteraction.cpp @@ -343,6 +343,7 @@ class TestReadInteraction : public chip::Test::AppContext void TestReadClient(); void TestReadUnexpectedSubscriptionId(); void TestReadHandler(); + void TestReadHandlerSetMaxReportingInterval(); void TestReadClientGenerateAttributePathList(); void TestReadClientGenerateInvalidAttributePathList(); void TestReadClientInvalidReport(); @@ -568,6 +569,115 @@ TEST_F_FROM_FIXTURE(TestReadInteraction, TestReadHandler) 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; @@ -1517,6 +1627,7 @@ TEST_F_FROM_FIXTURE(TestReadInteraction, TestICDProcessSubscribeRequestSupMaxInt EXPECT_EQ(minInterval, kMinInterval); EXPECT_EQ(maxInterval, idleModeDuration); + EXPECT_EQ(kMaxIntervalCeiling, readHandler.GetSubscriberRequestedMaxInterval()); } engine->Shutdown(); @@ -1584,6 +1695,7 @@ TEST_F_FROM_FIXTURE(TestReadInteraction, TestICDProcessSubscribeRequestInfMaxInt EXPECT_EQ(minInterval, kMinInterval); EXPECT_EQ(maxInterval, idleModeDuration); + EXPECT_EQ(kMaxIntervalCeiling, readHandler.GetSubscriberRequestedMaxInterval()); } engine->Shutdown(); @@ -1651,6 +1763,7 @@ TEST_F_FROM_FIXTURE(TestReadInteraction, TestICDProcessSubscribeRequestSupMinInt EXPECT_EQ(minInterval, kMinInterval); EXPECT_EQ(maxInterval, (2 * idleModeDuration)); + EXPECT_EQ(kMaxIntervalCeiling, readHandler.GetSubscriberRequestedMaxInterval()); } engine->Shutdown(); @@ -1716,6 +1829,7 @@ TEST_F_FROM_FIXTURE(TestReadInteraction, TestICDProcessSubscribeRequestMaxMinInt EXPECT_EQ(minInterval, kMinInterval); EXPECT_EQ(maxInterval, kMaxIntervalCeiling); + EXPECT_EQ(kMaxIntervalCeiling, readHandler.GetSubscriberRequestedMaxInterval()); } engine->Shutdown(); @@ -1781,6 +1895,7 @@ TEST_F_FROM_FIXTURE(TestReadInteraction, TestICDProcessSubscribeRequestInvalidId EXPECT_EQ(minInterval, kMinInterval); EXPECT_EQ(maxInterval, kMaxIntervalCeiling); + EXPECT_EQ(kMaxIntervalCeiling, readHandler.GetSubscriberRequestedMaxInterval()); } engine->Shutdown(); @@ -1959,6 +2074,7 @@ TEST_F_FROM_FIXTURE(TestReadInteraction, TestSubscribeRoundtrip) 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 @@ -2028,6 +2144,7 @@ TEST_F_FROM_FIXTURE(TestReadInteraction, TestSubscribeEarlyReport) 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); @@ -2760,6 +2877,7 @@ TEST_F_FROM_FIXTURE(TestReadInteraction, TestSubscribeInvalidAttributePathRoundt 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));