diff --git a/src/app/InteractionModelEngine.cpp b/src/app/InteractionModelEngine.cpp index 569d0b0fdf4a4e..933b5a4c118254 100644 --- a/src/app/InteractionModelEngine.cpp +++ b/src/app/InteractionModelEngine.cpp @@ -51,16 +51,18 @@ InteractionModelEngine * InteractionModelEngine::GetInstance() } CHIP_ERROR InteractionModelEngine::Init(Messaging::ExchangeManager * apExchangeMgr, FabricTable * apFabricTable, - CASESessionManager * apCASESessionMgr, + reporting::ReportScheduler * reportScheduler, CASESessionManager * apCASESessionMgr, SubscriptionResumptionStorage * subscriptionResumptionStorage) { VerifyOrReturnError(apFabricTable != nullptr, CHIP_ERROR_INVALID_ARGUMENT); VerifyOrReturnError(apExchangeMgr != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(reportScheduler != nullptr, CHIP_ERROR_INVALID_ARGUMENT); mpExchangeMgr = apExchangeMgr; mpFabricTable = apFabricTable; mpCASESessionMgr = apCASESessionMgr; mpSubscriptionResumptionStorage = subscriptionResumptionStorage; + mReportScheduler = reportScheduler; ReturnErrorOnFailure(mpFabricTable->AddFabricDelegate(this)); ReturnErrorOnFailure(mpExchangeMgr->RegisterUnsolicitedMessageHandlerForProtocol(Protocols::InteractionModel::Id, this)); @@ -741,7 +743,7 @@ Protocols::InteractionModel::Status InteractionModelEngine::OnReadInitialRequest // We have already reserved enough resources for read requests, and have granted enough resources for current subscriptions, so // we should be able to allocate resources requested by this request. - ReadHandler * handler = mReadHandlers.CreateObject(*this, apExchangeContext, aInteractionType); + ReadHandler * handler = mReadHandlers.CreateObject(*this, apExchangeContext, aInteractionType, mReportScheduler); if (handler == nullptr) { ChipLogProgress(InteractionModel, "no resource for %s interaction", @@ -1845,7 +1847,7 @@ void InteractionModelEngine::ResumeSubscriptionsTimerCallback(System::Layer * ap return; } - ReadHandler * handler = imEngine->mReadHandlers.CreateObject(*imEngine); + ReadHandler * handler = imEngine->mReadHandlers.CreateObject(*imEngine, imEngine->GetReportScheduler()); if (handler == nullptr) { ChipLogProgress(InteractionModel, "no resource for ReadHandler creation"); diff --git a/src/app/InteractionModelEngine.h b/src/app/InteractionModelEngine.h index 9e6ed36cce2396..6236ff63efc5ec 100644 --- a/src/app/InteractionModelEngine.h +++ b/src/app/InteractionModelEngine.h @@ -57,6 +57,7 @@ #include #include #include +#include #include #include @@ -115,7 +116,7 @@ class InteractionModelEngine : public Messaging::UnsolicitedMessageHandler, * */ CHIP_ERROR Init(Messaging::ExchangeManager * apExchangeMgr, FabricTable * apFabricTable, - CASESessionManager * apCASESessionMgr = nullptr, + reporting::ReportScheduler * reportScheduler, CASESessionManager * apCASESessionMgr = nullptr, SubscriptionResumptionStorage * subscriptionResumptionStorage = nullptr); void Shutdown(); @@ -178,6 +179,8 @@ class InteractionModelEngine : public Messaging::UnsolicitedMessageHandler, reporting::Engine & GetReportingEngine() { return mReportingEngine; } + reporting::ReportScheduler * GetReportScheduler() { return mReportScheduler; } + void ReleaseAttributePathList(ObjectList *& aAttributePathList); CHIP_ERROR PushFrontAttributePathList(ObjectList *& aAttributePathList, @@ -566,6 +569,7 @@ class InteractionModelEngine : public Messaging::UnsolicitedMessageHandler, ObjectPool mTimedHandlers; WriteHandler mWriteHandlers[CHIP_IM_MAX_NUM_WRITE_HANDLER]; reporting::Engine mReportingEngine; + reporting::ReportScheduler * mReportScheduler = nullptr; static constexpr size_t kReservedHandlersForReads = kMinSupportedReadRequestsPerFabric * (CHIP_CONFIG_MAX_FABRICS); static constexpr size_t kReservedPathsForReads = kMinSupportedPathsPerReadRequest * kReservedHandlersForReads; diff --git a/src/app/ReadHandler.cpp b/src/app/ReadHandler.cpp index bbaf9f7992eb30..1c3a40cc962273 100644 --- a/src/app/ReadHandler.cpp +++ b/src/app/ReadHandler.cpp @@ -77,16 +77,9 @@ ReadHandler::ReadHandler(ManagementCallback & apCallback, Messaging::ExchangeCon mSessionHandle.Grab(mExchangeCtx->GetSessionHandle()); -// TODO (#27672): Uncomment when the ReportScheduler is implemented -#if 0 - if (nullptr != observer) - { - if (CHIP_NO_ERROR == SetObserver(observer)) - { - mObserver->OnReadHandlerCreated(this); - } - } -#endif + VerifyOrDie(observer != nullptr); + mObserver = observer; + mObserver->OnReadHandlerCreated(this); } #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS @@ -97,16 +90,9 @@ ReadHandler::ReadHandler(ManagementCallback & apCallback, Observer * observer) : mInteractionType = InteractionType::Subscribe; mFlags.ClearAll(); -// TODO (#27672): Uncomment when the ReportScheduler is implemented -#if 0 - if (nullptr != observer) - { - if (CHIP_NO_ERROR == SetObserver(observer)) - { - mObserver->OnReadHandlerCreated(this); - } - } -#endif + VerifyOrDie(observer != nullptr); + mObserver = observer; + mObserver->OnReadHandlerCreated(this); } void ReadHandler::ResumeSubscription(CASESessionManager & caseSessionManager, @@ -150,28 +136,14 @@ void ReadHandler::ResumeSubscription(CASESessionManager & caseSessionManager, ReadHandler::~ReadHandler() { - // TODO (#27672): Enable when the ReportScheduler is implemented and move in Close() after testing -#if 0 - if (nullptr != mObserver) - { - mObserver->OnReadHandlerDestroyed(this); - } -#endif + mObserver->OnReadHandlerDestroyed(this); + auto * appCallback = mManagementCallback.GetAppCallback(); if (mFlags.Has(ReadHandlerFlags::ActiveSubscription) && appCallback) { appCallback->OnSubscriptionTerminated(*this); } - if (IsType(InteractionType::Subscribe)) - { - InteractionModelEngine::GetInstance()->GetExchangeManager()->GetSessionManager()->SystemLayer()->CancelTimer( - MinIntervalExpiredCallback, this); - - InteractionModelEngine::GetInstance()->GetExchangeManager()->GetSessionManager()->SystemLayer()->CancelTimer( - MaxIntervalExpiredCallback, this); - } - if (IsAwaitingReportResponse()) { InteractionModelEngine::GetInstance()->GetReportingEngine().OnReportConfirm(); @@ -290,7 +262,7 @@ CHIP_ERROR ReadHandler::OnStatusResponse(Messaging::ExchangeContext * apExchange CHIP_ERROR ReadHandler::SendStatusReport(Protocols::InteractionModel::Status aStatus) { - VerifyOrReturnLogError(IsReportableNow(), CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnLogError(mState == HandlerState::GeneratingReports, CHIP_ERROR_INCORRECT_STATE); if (IsPriming() || IsChunkedReport()) { mSessionHandle.Grab(mExchangeCtx->GetSessionHandle()); @@ -314,7 +286,7 @@ CHIP_ERROR ReadHandler::SendStatusReport(Protocols::InteractionModel::Status aSt CHIP_ERROR ReadHandler::SendReportData(System::PacketBufferHandle && aPayload, bool aMoreChunks) { - VerifyOrReturnLogError(IsReportableNow(), CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnLogError(mState == HandlerState::GeneratingReports, CHIP_ERROR_INCORRECT_STATE); VerifyOrDie(!IsAwaitingReportResponse()); // Should not be reportable! if (IsPriming() || IsChunkedReport()) { @@ -359,21 +331,11 @@ CHIP_ERROR ReadHandler::SendReportData(System::PacketBufferHandle && aPayload, b InteractionModelEngine::GetInstance()->GetReportingEngine().OnReportConfirm(); } - if (IsType(InteractionType::Subscribe) && !IsPriming()) + // If we just finished a non-priming subscription report, notify our observers. + // Priming reports are handled when we send a SubscribeResponse. + if (IsType(InteractionType::Subscribe) && !IsPriming() && !IsChunkedReport()) { -// TODO (#27672): Enable when the ReportScheduler is implemented and remove call to UpdateReportTimer, will be handled by -// the report Scheduler -#if 0 - if (nullptr != mObserver) - { - mObserver->OnSubscriptionAction(this); - } -#endif - - // Ignore the error from UpdateReportTimer. If we've - // successfully sent the message, we need to return success from - // this method. - UpdateReportTimer(); + mObserver->OnSubscriptionAction(this); } } if (!aMoreChunks) @@ -641,16 +603,10 @@ void ReadHandler::MoveToState(const HandlerState aTargetState) // If we just unblocked sending reports, let's go ahead and schedule the reporting // engine to run to kick that off. // - if (aTargetState == HandlerState::GeneratingReports && IsReportableNow()) + if (aTargetState == HandlerState::GeneratingReports) { -// TODO (#27672): Enable when the ReportScheduler is implemented and remove the call to ScheduleRun() -#if 0 - if(nullptr != mObserver) - { - mObserver->OnBecameReportable(this); - } -#endif - InteractionModelEngine::GetInstance()->GetReportingEngine().ScheduleRun(); + // mObserver will take care of scheduling the report as soon as allowed + mObserver->OnBecameReportable(this); } } @@ -691,15 +647,7 @@ CHIP_ERROR ReadHandler::SendSubscribeResponse() ReturnErrorOnFailure(writer.Finalize(&packet)); VerifyOrReturnLogError(mExchangeCtx, CHIP_ERROR_INCORRECT_STATE); - // TODO (#27672): Uncomment when the ReportScheduler is implemented and remove call to UpdateReportTimer, handled by - // the report Scheduler -#if 0 - if (nullptr != mObserver) - { - mObserver->OnSubscriptionAction(this); - } -#endif - ReturnErrorOnFailure(UpdateReportTimer()); + mObserver->OnSubscriptionAction(this); ClearStateFlag(ReadHandlerFlags::PrimingReports); return mExchangeCtx->SendMessage(Protocols::InteractionModel::MsgType::SubscribeResponse, std::move(packet)); @@ -818,50 +766,6 @@ void ReadHandler::PersistSubscription() } } -// TODO (#27672): Remove when ReportScheduler is enabled as timing will now be handled by the ReportScheduler -void ReadHandler::MinIntervalExpiredCallback(System::Layer * apSystemLayer, void * apAppState) -{ - VerifyOrReturn(apAppState != nullptr); - ReadHandler * readHandler = static_cast(apAppState); - ChipLogDetail(DataManagement, "Unblock report hold after min %d seconds", readHandler->mMinIntervalFloorSeconds); - readHandler->ClearStateFlag(ReadHandlerFlags::WaitingUntilMinInterval); - InteractionModelEngine::GetInstance()->GetExchangeManager()->GetSessionManager()->SystemLayer()->StartTimer( - System::Clock::Seconds16(readHandler->mMaxInterval - readHandler->mMinIntervalFloorSeconds), MaxIntervalExpiredCallback, - readHandler); -} - -// TODO (#27672): Remove when ReportScheduler is enabled as timing will now be handled by the ReportScheduler -void ReadHandler::MaxIntervalExpiredCallback(System::Layer * apSystemLayer, void * apAppState) -{ - VerifyOrReturn(apAppState != nullptr); - ReadHandler * readHandler = static_cast(apAppState); - readHandler->ClearStateFlag(ReadHandlerFlags::WaitingUntilMaxInterval); - ChipLogProgress(DataManagement, "Refresh subscribe timer sync after %d seconds", - readHandler->mMaxInterval - readHandler->mMinIntervalFloorSeconds); -} - -// TODO (#27672): Remove when ReportScheduler is enabled as timing will now be handled by the ReportScheduler -CHIP_ERROR ReadHandler::UpdateReportTimer() -{ - InteractionModelEngine::GetInstance()->GetExchangeManager()->GetSessionManager()->SystemLayer()->CancelTimer( - MinIntervalExpiredCallback, this); - InteractionModelEngine::GetInstance()->GetExchangeManager()->GetSessionManager()->SystemLayer()->CancelTimer( - MaxIntervalExpiredCallback, this); - - if (!IsChunkedReport()) - { - ChipLogProgress(DataManagement, "Refresh Subscribe Sync Timer with min %d seconds and max %d seconds", - mMinIntervalFloorSeconds, mMaxInterval); - SetStateFlag(ReadHandlerFlags::WaitingUntilMinInterval); - SetStateFlag(ReadHandlerFlags::WaitingUntilMaxInterval); - ReturnErrorOnFailure( - InteractionModelEngine::GetInstance()->GetExchangeManager()->GetSessionManager()->SystemLayer()->StartTimer( - System::Clock::Seconds16(mMinIntervalFloorSeconds), MinIntervalExpiredCallback, this)); - } - - return CHIP_NO_ERROR; -} - void ReadHandler::ResetPathIterator() { mAttributePathExpandIterator = AttributePathExpandIterator(mpAttributePathList); @@ -897,17 +801,8 @@ void ReadHandler::AttributePathIsDirty(const AttributePathParams & aAttributeCha mAttributeEncoderState = AttributeValueEncoder::AttributeEncodeState(); } - if (IsReportableNow()) - { - // TODO (#27672): Enable when the ReportScheduler is implemented and remove the call to ScheduleRun() -#if 0 - if(nullptr != mObserver) - { - mObserver->OnBecameReportable(this); - } -#endif - InteractionModelEngine::GetInstance()->GetReportingEngine().ScheduleRun(); - } + // ReportScheduler will take care of verifying the reportability of the handler and schedule the run + mObserver->OnBecameReportable(this); } Transport::SecureSession * ReadHandler::GetSession() const @@ -926,20 +821,14 @@ void ReadHandler::ForceDirtyState() void ReadHandler::SetStateFlag(ReadHandlerFlags aFlag, bool aValue) { - bool oldReportable = IsReportableNow(); + bool oldReportable = IsReportable(); mFlags.Set(aFlag, aValue); // If we became reportable, schedule a reporting run. - if (!oldReportable && IsReportableNow()) + if (!oldReportable && IsReportable()) { -// TODO (#27672): Enable when the ReportScheduler is implemented and remove the call to ScheduleRun() -#if 0 - if(nullptr != mObserver) - { - mObserver->OnBecameReportable(this); - } -#endif - InteractionModelEngine::GetInstance()->GetReportingEngine().ScheduleRun(); + // If we became reportable, the scheduler will schedule a run as soon as allowed + mObserver->OnBecameReportable(this); } } diff --git a/src/app/ReadHandler.h b/src/app/ReadHandler.h index 5b9bb346eb7894..5f1d3221886084 100644 --- a/src/app/ReadHandler.h +++ b/src/app/ReadHandler.h @@ -202,7 +202,7 @@ class ReadHandler : public Messaging::ExchangeDelegate * */ ReadHandler(ManagementCallback & apCallback, Messaging::ExchangeContext * apExchangeContext, InteractionType aInteractionType, - Observer * observer = nullptr); + Observer * observer); #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS /** @@ -212,7 +212,7 @@ class ReadHandler : public Messaging::ExchangeDelegate * The callback passed in has to outlive this handler object. * */ - ReadHandler(ManagementCallback & apCallback, Observer * observer = nullptr); + ReadHandler(ManagementCallback & apCallback, Observer * observer); #endif const ObjectList * GetAttributePathList() const { return mpAttributePathList; } @@ -250,18 +250,6 @@ class ReadHandler : public Messaging::ExchangeDelegate return CHIP_NO_ERROR; } - /// @brief Add an observer to the read handler, currently only one observer is supported but all other callbacks should be - /// merged with a general observer type to allow multiple object to observe readhandlers - /// @param aObserver observer to be added - /// @return CHIP_ERROR_INVALID_ARGUMENT if passing in nullptr - CHIP_ERROR SetObserver(Observer * aObserver) - { - VerifyOrReturnError(nullptr != aObserver, CHIP_ERROR_INVALID_ARGUMENT); - // TODO (#27675) : After merging the callbacks and observer, change so the method adds a new observer to an observer pool - mObserver = aObserver; - return CHIP_NO_ERROR; - } - private: PriorityLevel GetCurrentPriority() const { return mCurrentPriority; } EventNumber & GetEventMin() { return mEventMin; } @@ -275,26 +263,16 @@ class ReadHandler : public Messaging::ExchangeDelegate enum class ReadHandlerFlags : uint8_t { - // WaitingUntilMinInterval is used to prevent subscription data delivery while we are - // waiting for the min reporting interval to elapse. - WaitingUntilMinInterval = (1 << 0), // TODO (#27672): Remove once ReportScheduler is implemented or change to test flag - - // WaitingUntilMaxInterval is used to prevent subscription empty report delivery while we - // are waiting for the max reporting interval to elaps. When WaitingUntilMaxInterval - // becomes false, we are allowed to send an empty report to keep the - // subscription alive on the client. - WaitingUntilMaxInterval = (1 << 1), // TODO (#27672): Remove once ReportScheduler is implemented - // The flag indicating we are in the middle of a series of chunked report messages, this flag will be cleared during // sending last chunked message. - ChunkedReport = (1 << 2), + ChunkedReport = (1 << 0), // Tracks whether we're in the initial phase of receiving priming // reports, which is always true for reads and true for subscriptions // prior to receiving a subscribe response. - PrimingReports = (1 << 3), - ActiveSubscription = (1 << 4), - FabricFiltered = (1 << 5), + PrimingReports = (1 << 1), + ActiveSubscription = (1 << 2), + FabricFiltered = (1 << 3), // For subscriptions, we record the dirty set generation when we started to generate the last report. // The mCurrentReportsBeginGeneration records the generation at the start of the current report. This only/ // has a meaningful value while IsReporting() is true. @@ -304,10 +282,10 @@ class ReadHandler : public Messaging::ExchangeDelegate // mPreviousReportsBeginGeneration has had its value sent to the client. // when receiving initial request, it needs mark current handler as dirty. // when there is urgent event, it needs mark current handler as dirty. - ForceDirty = (1 << 6), + ForceDirty = (1 << 4), // Don't need the response for report data if true - SuppressResponse = (1 << 7), + SuppressResponse = (1 << 5), }; /** @@ -354,17 +332,12 @@ class ReadHandler : public Messaging::ExchangeDelegate bool IsIdle() const { return mState == HandlerState::Idle; } - // TODO (#27672): Change back to IsReportable once ReportScheduler is implemented so this can assess reportability without - // considering timing. The ReporScheduler will handle timing. - /// @brief Returns whether the ReadHandler is in a state where it can immediately send a report. This function - /// is used to determine whether a report generation should be scheduled for the handler. - bool IsReportableNow() const + /// @brief Returns whether the ReadHandler is in a state where it can send a report and there is data to report. + bool IsReportable() const { - // Important: Anything that changes the state IsReportableNow depends on in - // a way that causes IsReportableNow to become true must call ScheduleRun - // on the reporting engine. - return mState == HandlerState::GeneratingReports && !mFlags.Has(ReadHandlerFlags::WaitingUntilMinInterval) && - (IsDirty() || !mFlags.Has(ReadHandlerFlags::WaitingUntilMaxInterval)); + // Important: Anything that changes the state IsReportable must call mObserver->OnBecameReportable(this) for the scheduler + // to plan the next run accordingly. + return mState == HandlerState::GeneratingReports && IsDirty(); } bool IsGeneratingReports() const { return mState == HandlerState::GeneratingReports; } bool IsAwaitingReportResponse() const { return mState == HandlerState::AwaitingReportResponse; } @@ -445,8 +418,8 @@ class ReadHandler : public Messaging::ExchangeDelegate friend class chip::app::reporting::Engine; friend class chip::app::InteractionModelEngine; - // The report scheduler needs to be able to access StateFlag private functions IsGeneratingReports() and IsDirty() to - // know when to schedule a run so it is declared as a friend class. + // The report scheduler needs to be able to access StateFlag private functions IsReportable(), IsGeneratingReports() and + // IsDirty() to know when to schedule a run so it is declared as a friend class. friend class chip::app::reporting::ReportScheduler; enum class HandlerState : uint8_t @@ -472,15 +445,6 @@ class ReadHandler : public Messaging::ExchangeDelegate */ void Close(CloseOptions options = CloseOptions::kDropPersistedSubscription); - /// @brief This function is called when the min interval timer has expired, it restarts the timer on a timeout equal to the - /// difference between the max interval and the min interval. - static void MinIntervalExpiredCallback(System::Layer * apSystemLayer, void * apAppState); // TODO (#27672): Remove once - // ReportScheduler is implemented. - static void MaxIntervalExpiredCallback(System::Layer * apSystemLayer, void * apAppState); // TODO (#27672): Remove once - // ReportScheduler is implemented. - /// @brief This function is called when a report is sent and it restarts the min interval timer. - CHIP_ERROR UpdateReportTimer(); // TODO (#27672) : Remove once ReportScheduler is implemented. - CHIP_ERROR SendSubscribeResponse(); CHIP_ERROR ProcessSubscribeRequest(System::PacketBufferHandle && aPayload); CHIP_ERROR ProcessReadRequest(System::PacketBufferHandle && aPayload); diff --git a/src/app/TimerDelegates.h b/src/app/TimerDelegates.h new file mode 100644 index 00000000000000..8e24fd4aef09bd --- /dev/null +++ b/src/app/TimerDelegates.h @@ -0,0 +1,57 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +namespace chip { +namespace app { + +class DefaultTimerDelegate : public reporting::ReportScheduler::TimerDelegate +{ +public: + using TimerContext = reporting::TimerContext; + using Timeout = System::Clock::Timeout; + static void TimerCallbackInterface(System::Layer * aLayer, void * aAppState) + { + TimerContext * context = static_cast(aAppState); + context->TimerFired(); + } + CHIP_ERROR StartTimer(TimerContext * context, Timeout aTimeout) override + { + return InteractionModelEngine::GetInstance()->GetExchangeManager()->GetSessionManager()->SystemLayer()->StartTimer( + aTimeout, TimerCallbackInterface, context); + } + void CancelTimer(TimerContext * context) override + { + InteractionModelEngine::GetInstance()->GetExchangeManager()->GetSessionManager()->SystemLayer()->CancelTimer( + TimerCallbackInterface, context); + } + bool IsTimerActive(TimerContext * context) override + { + return InteractionModelEngine::GetInstance()->GetExchangeManager()->GetSessionManager()->SystemLayer()->IsTimerActive( + TimerCallbackInterface, context); + } + + System::Clock::Timestamp GetCurrentMonotonicTimestamp() override { return System::SystemClock().GetMonotonicTimestamp(); } +}; + +} // namespace app +} // namespace chip diff --git a/src/app/reporting/Engine.cpp b/src/app/reporting/Engine.cpp index f0fea66cc0b16c..1243fff3ac1775 100644 --- a/src/app/reporting/Engine.cpp +++ b/src/app/reporting/Engine.cpp @@ -636,8 +636,7 @@ void Engine::Run() ReadHandler * readHandler = imEngine->ActiveHandlerAt(mCurReadHandlerIdx % (uint32_t) imEngine->mReadHandlers.Allocated()); VerifyOrDie(readHandler != nullptr); - // TODO (#27672): Replace with check with Report Scheduler if the read handler is reportable - if (readHandler->IsReportableNow()) + if (imEngine->GetReportScheduler()->IsReportableNow(readHandler)) { mRunningReadHandler = readHandler; CHIP_ERROR err = BuildAndSendSingleReportData(readHandler); diff --git a/src/app/reporting/ReportScheduler.h b/src/app/reporting/ReportScheduler.h index 0449ba3114f857..d691e4b36889ed 100644 --- a/src/app/reporting/ReportScheduler.h +++ b/src/app/reporting/ReportScheduler.h @@ -20,7 +20,6 @@ #include #include -#include #include namespace chip { @@ -32,6 +31,13 @@ class TestReportScheduler; using Timestamp = System::Clock::Timestamp; +class TimerContext +{ +public: + virtual ~TimerContext() {} + virtual void TimerFired() = 0; +}; + class ReportScheduler : public ReadHandler::Observer { public: @@ -45,17 +51,28 @@ class ReportScheduler : public ReadHandler::Observer /// CancelTimer) before starting a new one for that context. /// @param context context to pass to the timer callback. /// @param aTimeout time in miliseconds before the timer expires - virtual CHIP_ERROR StartTimer(void * context, System::Clock::Timeout aTimeout) = 0; + virtual CHIP_ERROR StartTimer(TimerContext * context, System::Clock::Timeout aTimeout) = 0; /// @brief Cancel a timer for a given context /// @param context used to identify the timer to cancel - virtual void CancelTimer(void * context) = 0; - virtual bool IsTimerActive(void * context) = 0; - virtual Timestamp GetCurrentMonotonicTimestamp() = 0; + virtual void CancelTimer(TimerContext * context) = 0; + virtual bool IsTimerActive(TimerContext * context) = 0; + virtual Timestamp GetCurrentMonotonicTimestamp() = 0; }; - class ReadHandlerNode : public IntrusiveListNodeBase<> + class ReadHandlerNode : public TimerContext { public: +#ifdef CONFIG_BUILD_FOR_HOST_UNIT_TEST + /// Test flags to allow TestReadInteraction to simulate expiration of the minimal and maximal intervals without + /// waiting + enum class TestFlags : uint8_t{ + MinIntervalElapsed = (1 << 0), + MaxIntervalElapsed = (1 << 1), + }; + void SetTestFlags(TestFlags aFlag, bool aValue) { mFlags.Set(aFlag, aValue); } + bool GetTestFlags(TestFlags aFlag) const { return mFlags.Has(aFlag); } +#endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST + ReadHandlerNode(ReadHandler * aReadHandler, TimerDelegate * aTimerDelegate, ReportScheduler * aScheduler) : mTimerDelegate(aTimerDelegate), mScheduler(aScheduler) { @@ -67,20 +84,37 @@ class ReportScheduler : public ReadHandler::Observer SetIntervalTimeStamps(aReadHandler); } ReadHandler * GetReadHandler() const { return mReadHandler; } + /// @brief Check if the Node is reportable now, meaning its readhandler was made reportable by attribute dirtying and /// handler state, and minimal time interval since last report has elapsed, or the maximal time interval since last /// report has elapsed bool IsReportableNow() const { - // TODO: Add flags to allow for test to simulate waiting for the min interval or max intrval to elapse when integrating - // the scheduler in the ReadHandler Timestamp now = mTimerDelegate->GetCurrentMonotonicTimestamp(); + +#ifdef CONFIG_BUILD_FOR_HOST_UNIT_TEST + return (mReadHandler->IsGeneratingReports() && (now >= mMinTimestamp || mFlags.Has(TestFlags::MinIntervalElapsed)) && + (mReadHandler->IsDirty() || (now >= mMaxTimestamp || mFlags.Has(TestFlags::MaxIntervalElapsed)) || + now >= mSyncTimestamp)); +#else return (mReadHandler->IsGeneratingReports() && (now >= mMinTimestamp && (mReadHandler->IsDirty() || now >= mMaxTimestamp || now >= mSyncTimestamp))); +#endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST } bool IsEngineRunScheduled() const { return mEngineRunScheduled; } - void SetEngineRunScheduled(bool aEnginRunScheduled) { mEngineRunScheduled = aEnginRunScheduled; } + void SetEngineRunScheduled(bool aEngineRunScheduled) + { + mEngineRunScheduled = aEngineRunScheduled; +#ifdef CONFIG_BUILD_FOR_HOST_UNIT_TEST + // If the engine becomes unscheduled, this means a run just took place so we reset the test flags + if (!mEngineRunScheduled) + { + mFlags.Set(TestFlags::MinIntervalElapsed, false); + mFlags.Set(TestFlags::MaxIntervalElapsed, false); + } +#endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST + } void SetIntervalTimeStamps(ReadHandler * aReadHandler) { @@ -92,7 +126,7 @@ class ReportScheduler : public ReadHandler::Observer mSyncTimestamp = mMaxTimestamp; } - void RunCallback() + void TimerFired() override { mScheduler->ReportTimerCallback(); SetEngineRunScheduled(true); @@ -111,6 +145,9 @@ class ReportScheduler : public ReadHandler::Observer System::Clock::Timestamp GetSyncTimestamp() const { return mSyncTimestamp; } private: +#ifdef CONFIG_BUILD_FOR_HOST_UNIT_TEST + BitFlags mFlags; +#endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST TimerDelegate * mTimerDelegate; ReadHandler * mReadHandler; ReportScheduler * mScheduler; @@ -132,19 +169,42 @@ class ReportScheduler : public ReadHandler::Observer /// @brief Check whether a ReadHandler is reportable right now, taking into account its minimum and maximum intervals. /// @param aReadHandler read handler to check - bool IsReportableNow(ReadHandler * aReadHandler) - { - return FindReadHandlerNode(aReadHandler)->IsReportableNow(); - } // TODO: Change the IsReportableNow to IsReportable() for readHandlers + bool IsReportableNow(ReadHandler * aReadHandler) { return FindReadHandlerNode(aReadHandler)->IsReportableNow(); } /// @brief Check if a ReadHandler is reportable without considering the timing - bool IsReadHandlerReportable(ReadHandler * aReadHandler) const - { - return aReadHandler->IsGeneratingReports() && aReadHandler->IsDirty(); - } + bool IsReadHandlerReportable(ReadHandler * aReadHandler) const { return aReadHandler->IsReportable(); } /// @brief Get the number of ReadHandlers registered in the scheduler's node pool size_t GetNumReadHandlers() const { return mNodesPool.Allocated(); } +#ifdef CONFIG_BUILD_FOR_HOST_UNIT_TEST + void RunNodeCallbackForHandler(const ReadHandler * aReadHandler) + { + ReadHandlerNode * node = FindReadHandlerNode(aReadHandler); + node->TimerFired(); + } + void SetFlagsForHandler(const ReadHandler * aReadHandler, ReadHandlerNode::TestFlags aFlag, bool aValue) + { + ReadHandlerNode * node = FindReadHandlerNode(aReadHandler); + node->SetTestFlags(aFlag, aValue); + } + + bool CheckFlagsForHandler(const ReadHandler * aReadHandler, ReadHandlerNode::TestFlags aFlag) + { + ReadHandlerNode * node = FindReadHandlerNode(aReadHandler); + return node->GetTestFlags(aFlag); + } + Timestamp GetMinTimestampForHandler(const ReadHandler * aReadHandler) + { + ReadHandlerNode * node = FindReadHandlerNode(aReadHandler); + return node->GetMinTimestamp(); + } + Timestamp GetMaxTimestampForHandler(const ReadHandler * aReadHandler) + { + ReadHandlerNode * node = FindReadHandlerNode(aReadHandler); + return node->GetMaxTimestamp(); + } +#endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST + protected: friend class chip::app::reporting::TestReportScheduler; @@ -153,17 +213,19 @@ class ReportScheduler : public ReadHandler::Observer /// @return Node Address if node was found, nullptr otherwise ReadHandlerNode * FindReadHandlerNode(const ReadHandler * aReadHandler) { - for (auto & iter : mReadHandlerList) - { - if (iter.GetReadHandler() == aReadHandler) + ReadHandlerNode * foundNode = nullptr; + mNodesPool.ForEachActiveObject([&foundNode, aReadHandler](ReadHandlerNode * node) { + if (node->GetReadHandler() == aReadHandler) { - return &iter; + foundNode = node; + return Loop::Break; } - } - return nullptr; + + return Loop::Continue; + }); + return foundNode; } - IntrusiveList mReadHandlerList; ObjectPool mNodesPool; TimerDelegate * mTimerDelegate; }; diff --git a/src/app/reporting/ReportSchedulerImpl.cpp b/src/app/reporting/ReportSchedulerImpl.cpp index b49884aa3f699f..b6170fba9e0fdf 100644 --- a/src/app/reporting/ReportSchedulerImpl.cpp +++ b/src/app/reporting/ReportSchedulerImpl.cpp @@ -46,7 +46,6 @@ void ReportSchedulerImpl::OnReadHandlerCreated(ReadHandler * aReadHandler) // The NodePool is the same size as the ReadHandler pool from the IM Engine, so we don't need a check for size here since if a // ReadHandler was created, space should be available. newNode = mNodesPool.CreateObject(aReadHandler, mTimerDelegate, this); - mReadHandlerList.PushBack(newNode); ChipLogProgress(DataManagement, "Registered a ReadHandler that will schedule a report between system Timestamp: %" PRIu64 @@ -59,23 +58,21 @@ void ReportSchedulerImpl::OnReadHandlerCreated(ReadHandler * aReadHandler) ScheduleReport(newTimeout, newNode); } -/// @brief When a ReadHandler becomes reportable, schedule, verifies if the min interval of a handleris elapsed. If not, -/// reschedule the report to happen when the min interval is elapsed. If it is, schedule an engine run. +/// @brief When a ReadHandler becomes reportable, schedule, recalculate and reschedule the report. void ReportSchedulerImpl::OnBecameReportable(ReadHandler * aReadHandler) { ReadHandlerNode * node = FindReadHandlerNode(aReadHandler); VerifyOrReturn(nullptr != node); - Milliseconds32 newTimeout; CalculateNextReportTimeout(newTimeout, node); ScheduleReport(newTimeout, node); } -void ReportSchedulerImpl::OnSubscriptionAction(ReadHandler * apReadHandler) +void ReportSchedulerImpl::OnSubscriptionAction(ReadHandler * aReadHandler) { - ReadHandlerNode * node = FindReadHandlerNode(apReadHandler); + ReadHandlerNode * node = FindReadHandlerNode(aReadHandler); VerifyOrReturn(nullptr != node); - node->SetIntervalTimeStamps(apReadHandler); + node->SetIntervalTimeStamps(aReadHandler); Milliseconds32 newTimeout; CalculateNextReportTimeout(newTimeout, node); ScheduleReport(newTimeout, node); @@ -91,7 +88,6 @@ void ReportSchedulerImpl::OnReadHandlerDestroyed(ReadHandler * aReadHandler) // Nothing to remove if the handler is not found in the list VerifyOrReturn(nullptr != removeNode); - mReadHandlerList.Remove(removeNode); mNodesPool.ReleaseObject(removeNode); } @@ -99,6 +95,11 @@ CHIP_ERROR ReportSchedulerImpl::ScheduleReport(Timeout timeout, ReadHandlerNode { // Cancel Report if it is currently scheduled mTimerDelegate->CancelTimer(node); + if (timeout == Milliseconds32(0)) + { + node->TimerFired(); + return CHIP_NO_ERROR; + } ReturnErrorOnFailure(mTimerDelegate->StartTimer(node, timeout)); return CHIP_NO_ERROR; @@ -113,11 +114,10 @@ void ReportSchedulerImpl::CancelReport(ReadHandler * aReadHandler) void ReportSchedulerImpl::UnregisterAllHandlers() { - while (!mReadHandlerList.Empty()) - { - ReadHandler * firstReadHandler = mReadHandlerList.begin()->GetReadHandler(); - OnReadHandlerDestroyed(firstReadHandler); - } + mNodesPool.ForEachActiveObject([this](ReadHandlerNode * node) { + this->OnReadHandlerDestroyed(node->GetReadHandler()); + return Loop::Continue; + }); } bool ReportSchedulerImpl::IsReportScheduled(ReadHandler * aReadHandler) @@ -129,7 +129,7 @@ bool ReportSchedulerImpl::IsReportScheduled(ReadHandler * aReadHandler) CHIP_ERROR ReportSchedulerImpl::CalculateNextReportTimeout(Timeout & timeout, ReadHandlerNode * aNode) { - VerifyOrReturnError(mReadHandlerList.Contains(aNode), CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(nullptr != FindReadHandlerNode(aNode->GetReadHandler()), CHIP_ERROR_INVALID_ARGUMENT); Timestamp now = mTimerDelegate->GetCurrentMonotonicTimestamp(); // If the handler is reportable now, just schedule a report immediately diff --git a/src/app/reporting/SynchronizedReportSchedulerImpl.cpp b/src/app/reporting/SynchronizedReportSchedulerImpl.cpp index 16713e37e05741..b3f700b8cbcc0f 100644 --- a/src/app/reporting/SynchronizedReportSchedulerImpl.cpp +++ b/src/app/reporting/SynchronizedReportSchedulerImpl.cpp @@ -29,16 +29,15 @@ using ReadHandlerNode = ReportScheduler::ReadHandlerNode; void SynchronizedReportSchedulerImpl::OnReadHandlerDestroyed(ReadHandler * aReadHandler) { // Verify list is populated - VerifyOrReturn((!mReadHandlerList.Empty())); + VerifyOrReturn(mNodesPool.Allocated()); ReadHandlerNode * removeNode = FindReadHandlerNode(aReadHandler); // Nothing to remove if the handler is not found in the list VerifyOrReturn(nullptr != removeNode); - mReadHandlerList.Remove(removeNode); mNodesPool.ReleaseObject(removeNode); - if (mReadHandlerList.Empty()) + if (!mNodesPool.Allocated()) { // Only cancel the timer if there are no more handlers registered CancelReport(); @@ -49,6 +48,11 @@ CHIP_ERROR SynchronizedReportSchedulerImpl::ScheduleReport(Timeout timeout, Read { // Cancel Report if it is currently scheduled mTimerDelegate->CancelTimer(this); + if (timeout == Milliseconds32(0)) + { + ReportTimerCallback(); + return CHIP_NO_ERROR; + } ReturnErrorOnFailure(mTimerDelegate->StartTimer(this, timeout)); mTestNextReportTimestamp = mTimerDelegate->GetCurrentMonotonicTimestamp() + timeout; @@ -61,8 +65,7 @@ void SynchronizedReportSchedulerImpl::CancelReport() mTimerDelegate->CancelTimer(this); } -/// @brief Checks if the timer is active for the given ReadHandler. Since all read handlers are scheduled on the same timer, we -/// check if the node is in the list and if the timer is active for the ReportScheduler +/// @brief Checks if the timer is active for the ReportScheduler bool SynchronizedReportSchedulerImpl::IsReportScheduled() { return mTimerDelegate->IsTimerActive(this); @@ -72,17 +75,18 @@ bool SynchronizedReportSchedulerImpl::IsReportScheduled() /// @return NO_ERROR if the smallest maximum interval was found, error otherwise, INVALID LIST LENGTH if the list is empty CHIP_ERROR SynchronizedReportSchedulerImpl::FindNextMaxInterval() { - VerifyOrReturnError(!mReadHandlerList.Empty(), CHIP_ERROR_INVALID_LIST_LENGTH); + VerifyOrReturnError(mNodesPool.Allocated(), CHIP_ERROR_INVALID_LIST_LENGTH); System::Clock::Timestamp now = mTimerDelegate->GetCurrentMonotonicTimestamp(); System::Clock::Timestamp earliest = now + Seconds16::max(); - for (auto & iter : mReadHandlerList) - { - if (iter.GetMaxTimestamp() < earliest && iter.GetMaxTimestamp() > now) + mNodesPool.ForEachActiveObject([&earliest, now](ReadHandlerNode * node) { + if (node->GetMaxTimestamp() < earliest && node->GetMaxTimestamp() > now) { - earliest = iter.GetMaxTimestamp(); + earliest = node->GetMaxTimestamp(); } - } + + return Loop::Continue; + }); mNextMaxTimestamp = earliest; @@ -95,20 +99,19 @@ CHIP_ERROR SynchronizedReportSchedulerImpl::FindNextMaxInterval() /// @return NO_ERROR if the highest minimum timestamp was found, error otherwise, INVALID LIST LENGTH if the list is empty CHIP_ERROR SynchronizedReportSchedulerImpl::FindNextMinInterval() { - VerifyOrReturnError(!mReadHandlerList.Empty(), CHIP_ERROR_INVALID_LIST_LENGTH); + VerifyOrReturnError(mNodesPool.Allocated(), CHIP_ERROR_INVALID_LIST_LENGTH); System::Clock::Timestamp latest = mTimerDelegate->GetCurrentMonotonicTimestamp(); - for (auto & iter : mReadHandlerList) - { - if (iter.GetMinTimestamp() > latest && IsReadHandlerReportable(iter.GetReadHandler())) + mNodesPool.ForEachActiveObject([&latest, this](ReadHandlerNode * node) { + if (node->GetMinTimestamp() > latest && this->IsReadHandlerReportable(node->GetReadHandler()) && + node->GetMinTimestamp() <= this->mNextMaxTimestamp) { // We do not want the new min to be set above the max for any handler - if (iter.GetMinTimestamp() <= mNextMaxTimestamp) - { - latest = iter.GetMinTimestamp(); - } + latest = node->GetMinTimestamp(); } - } + + return Loop::Continue; + }); mNextMinTimestamp = latest; @@ -117,7 +120,7 @@ CHIP_ERROR SynchronizedReportSchedulerImpl::FindNextMinInterval() CHIP_ERROR SynchronizedReportSchedulerImpl::CalculateNextReportTimeout(Timeout & timeout, ReadHandlerNode * aNode) { - VerifyOrReturnError(mReadHandlerList.Contains(aNode), CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(nullptr != FindReadHandlerNode(aNode->GetReadHandler()), CHIP_ERROR_INVALID_ARGUMENT); ReturnErrorOnFailure(FindNextMaxInterval()); ReturnErrorOnFailure(FindNextMinInterval()); bool reportableNow = false; @@ -125,22 +128,23 @@ CHIP_ERROR SynchronizedReportSchedulerImpl::CalculateNextReportTimeout(Timeout & Timestamp now = mTimerDelegate->GetCurrentMonotonicTimestamp(); - for (auto & iter : mReadHandlerList) - { - if (!iter.IsEngineRunScheduled()) + mNodesPool.ForEachActiveObject([&reportableNow, &reportableAtMin, this](ReadHandlerNode * node) { + if (!node->IsEngineRunScheduled()) { - if (iter.IsReportableNow()) + if (node->IsReportableNow()) { reportableNow = true; - break; + return Loop::Break; } - if (IsReadHandlerReportable(iter.GetReadHandler()) && iter.GetMinTimestamp() <= mNextMaxTimestamp) + if (this->IsReadHandlerReportable(node->GetReadHandler()) && node->GetMinTimestamp() <= this->mNextMaxTimestamp) { reportableAtMin = true; } } - } + + return Loop::Continue; + }); // Find out if any handler is reportable now @@ -159,36 +163,36 @@ CHIP_ERROR SynchronizedReportSchedulerImpl::CalculateNextReportTimeout(Timeout & } // Updates the synching time of each handler - for (auto & iter : mReadHandlerList) - { + mNodesPool.ForEachActiveObject([now, timeout](ReadHandlerNode * node) { // Prevent modifying the sync if the handler is currently reportable, sync's purpose is to allow handler to become // reportable earlier than their max interval - if (!iter.IsReportableNow()) + if (!node->IsReportableNow()) { - iter.SetSyncTimestamp(Milliseconds64(now + timeout)); + node->SetSyncTimestamp(Milliseconds64(now + timeout)); } - } + + return Loop::Continue; + }); return CHIP_NO_ERROR; } /// @brief Callback called when the report timer expires to schedule an engine run regardless of the state of the ReadHandlers, as /// the engine already verifies that read handlers are reportable before sending a report -void SynchronizedReportSchedulerImpl::ReportTimerCallback() +void SynchronizedReportSchedulerImpl::TimerFired() { - ReportSchedulerImpl::ReportTimerCallback(); + InteractionModelEngine::GetInstance()->GetReportingEngine().ScheduleRun(); - Timestamp now = mTimerDelegate->GetCurrentMonotonicTimestamp(); - ChipLogProgress(DataManagement, "Engine run at time: %" PRIu64 " for Handlers:", now.count()); - for (auto & iter : mReadHandlerList) - { - if (iter.IsReportableNow()) + mNodesPool.ForEachActiveObject([](ReadHandlerNode * node) { + if (node->IsReportableNow()) { - iter.SetEngineRunScheduled(true); - ChipLogProgress(DataManagement, "Handler: %p with min: %" PRIu64 " and max: %" PRIu64 " and sync: %" PRIu64, (&iter), - iter.GetMinTimestamp().count(), iter.GetMaxTimestamp().count(), iter.GetSyncTimestamp().count()); + node->SetEngineRunScheduled(true); + ChipLogProgress(DataManagement, "Handler: %p with min: %" PRIu64 " and max: %" PRIu64 " and sync: %" PRIu64, (node), + node->GetMinTimestamp().count(), node->GetMaxTimestamp().count(), node->GetSyncTimestamp().count()); } - } + + return Loop::Continue; + }); } } // namespace reporting diff --git a/src/app/reporting/SynchronizedReportSchedulerImpl.h b/src/app/reporting/SynchronizedReportSchedulerImpl.h index 18ee69520e5651..9fd4a8ca0aa684 100644 --- a/src/app/reporting/SynchronizedReportSchedulerImpl.h +++ b/src/app/reporting/SynchronizedReportSchedulerImpl.h @@ -30,17 +30,17 @@ using Milliseconds64 = System::Clock::Milliseconds64; using ReadHandlerNode = ReportScheduler::ReadHandlerNode; using TimerDelegate = ReportScheduler::TimerDelegate; -class SynchronizedReportSchedulerImpl : public ReportSchedulerImpl +class SynchronizedReportSchedulerImpl : public ReportSchedulerImpl, public TimerContext { public: void OnReadHandlerDestroyed(ReadHandler * aReadHandler) override; SynchronizedReportSchedulerImpl(TimerDelegate * aTimerDelegate) : ReportSchedulerImpl(aTimerDelegate) {} - ~SynchronizedReportSchedulerImpl() {} + ~SynchronizedReportSchedulerImpl() override { UnregisterAllHandlers(); } bool IsReportScheduled(); - void ReportTimerCallback() override; + void TimerFired() override; protected: CHIP_ERROR ScheduleReport(System::Clock::Timeout timeout, ReadHandlerNode * node) override; diff --git a/src/app/reporting/tests/MockReportScheduler.cpp b/src/app/reporting/tests/MockReportScheduler.cpp new file mode 100644 index 00000000000000..e42cf22e973e00 --- /dev/null +++ b/src/app/reporting/tests/MockReportScheduler.cpp @@ -0,0 +1,46 @@ +/* + * + * Copyright (c) 2023 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 + +namespace chip { +namespace app { +namespace reporting { + +/// @brief Static instance of the default report scheduler and synchronized report scheduler meant for injection into IM engine in +/// tests + +static chip::app::DefaultTimerDelegate sTimerDelegate; +static ReportSchedulerImpl sTestDefaultReportScheduler(&sTimerDelegate); +static SynchronizedReportSchedulerImpl sTestReportScheduler(&sTimerDelegate); + +ReportSchedulerImpl * GetDefaultReportScheduler() +{ + return &sTestDefaultReportScheduler; +} + +SynchronizedReportSchedulerImpl * GetSynchronizedReportScheduler() +{ + return &sTestReportScheduler; +} + +} // namespace reporting +} // namespace app +} // namespace chip diff --git a/src/app/reporting/tests/MockReportScheduler.h b/src/app/reporting/tests/MockReportScheduler.h new file mode 100644 index 00000000000000..9b3b3474a4fd49 --- /dev/null +++ b/src/app/reporting/tests/MockReportScheduler.h @@ -0,0 +1,34 @@ +/* + * + * Copyright (c) 2023 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. + */ + +#pragma once + +#include +#include + +namespace chip { +namespace app { +namespace reporting { + +ReportSchedulerImpl * GetDefaultReportScheduler(); + +SynchronizedReportSchedulerImpl * GetSynchronizedReportScheduler(); + +} // namespace reporting +} // namespace app +} // namespace chip diff --git a/src/app/server/Server.cpp b/src/app/server/Server.cpp index 0dfa76bd6ad59b..96847ace574100 100644 --- a/src/app/server/Server.cpp +++ b/src/app/server/Server.cpp @@ -316,8 +316,8 @@ CHIP_ERROR Server::Init(const ServerInitParams & initParams) &mCertificateValidityPolicy, mGroupsProvider); SuccessOrExit(err); - err = chip::app::InteractionModelEngine::GetInstance()->Init(&mExchangeMgr, &GetFabricTable(), &mCASESessionManager, - mSubscriptionResumptionStorage); + err = chip::app::InteractionModelEngine::GetInstance()->Init(&mExchangeMgr, &GetFabricTable(), &mReportScheduler, + &mCASESessionManager, mSubscriptionResumptionStorage); SuccessOrExit(err); // This code is necessary to restart listening to existing groups after a reboot diff --git a/src/app/server/Server.h b/src/app/server/Server.h index 9c4a3ee499dca3..c41a9e3127e0d9 100644 --- a/src/app/server/Server.h +++ b/src/app/server/Server.h @@ -63,7 +63,13 @@ #if CONFIG_NETWORK_LAYER_BLE #include #endif +#include #include +#if CHIP_CONFIG_SYNCHRONOUS_REPORTS_ENABLED +#include +#else +#include +#endif #if CHIP_CONFIG_ENABLE_ICD_SERVER #include // nogncheck @@ -254,6 +260,7 @@ struct CommonCaseDeviceServerInitParams : public ServerInitParams static PersistentStorageOperationalKeystore sPersistentStorageOperationalKeystore; static Credentials::PersistentStorageOpCertStore sPersistentStorageOpCertStore; static Credentials::GroupDataProviderImpl sGroupDataProvider; + #if CHIP_CONFIG_ENABLE_SESSION_RESUMPTION static SimpleSessionResumptionStorage sSessionResumptionStorage; #endif @@ -333,6 +340,8 @@ class Server app::DefaultAttributePersistenceProvider & GetDefaultAttributePersister() { return mAttributePersister; } + app::reporting::ReportScheduler & GetReportScheduler() { return mReportScheduler; } + /** * This function causes the ShutDown event to be generated async on the * Matter event loop. Should be called before stopping the event loop. @@ -351,7 +360,7 @@ class Server static Server & GetInstance() { return sServer; } private: - Server() = default; + Server() : mTimerDelegate(), mReportScheduler(&mTimerDelegate) {} static Server sServer; @@ -585,6 +594,12 @@ class Server app::DefaultAttributePersistenceProvider mAttributePersister; GroupDataProviderListener mListener; ServerFabricDelegate mFabricDelegate; + app::DefaultTimerDelegate mTimerDelegate; +#if CHIP_CONFIG_SYNCHRONOUS_REPORTS_ENABLED + app::reporting::SynchronizedReportSchedulerImpl mReportScheduler; +#else + app::reporting::ReportSchedulerImpl mReportScheduler; +#endif Access::AccessControl mAccessControl; app::AclStorage * mAclStorage; diff --git a/src/app/tests/AppTestContext.cpp b/src/app/tests/AppTestContext.cpp index 3a63c3e21c5952..abac16a531b090 100644 --- a/src/app/tests/AppTestContext.cpp +++ b/src/app/tests/AppTestContext.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -40,7 +41,8 @@ namespace Test { CHIP_ERROR AppContext::Init() { ReturnErrorOnFailure(Super::Init()); - ReturnErrorOnFailure(chip::app::InteractionModelEngine::GetInstance()->Init(&GetExchangeManager(), &GetFabricTable())); + ReturnErrorOnFailure(chip::app::InteractionModelEngine::GetInstance()->Init(&GetExchangeManager(), &GetFabricTable(), + app::reporting::GetDefaultReportScheduler())); Access::SetAccessControl(gPermissiveAccessControl); ReturnErrorOnFailure( diff --git a/src/app/tests/BUILD.gn b/src/app/tests/BUILD.gn index e5f39d95ab10dd..5378b199aa17f7 100644 --- a/src/app/tests/BUILD.gn +++ b/src/app/tests/BUILD.gn @@ -24,6 +24,7 @@ static_library("helpers") { output_dir = "${root_out_dir}/lib" sources = [ + "${chip_root}/src/app/reporting/tests/MockReportScheduler.cpp", "AppTestContext.cpp", "AppTestContext.h", "integration/RequiredPrivilegeStubs.cpp", diff --git a/src/app/tests/TestAclAttribute.cpp b/src/app/tests/TestAclAttribute.cpp index cd7d774ef3429a..1f0e0c13f17023 100644 --- a/src/app/tests/TestAclAttribute.cpp +++ b/src/app/tests/TestAclAttribute.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -170,7 +171,7 @@ void TestAclAttribute::TestACLDeniedAttribute(nlTestSuite * apSuite, void * apCo MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); { diff --git a/src/app/tests/TestAclEvent.cpp b/src/app/tests/TestAclEvent.cpp index be9b61affdb3ca..fcb477761aa342 100644 --- a/src/app/tests/TestAclEvent.cpp +++ b/src/app/tests/TestAclEvent.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -235,7 +236,7 @@ void TestAclEvent::TestReadRoundtripWithEventStatusIBInEventReport(nlTestSuite * GenerateEvents(apSuite, apContext); auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); // A custom AccessControl::Delegate has been installed that grants privilege to any cluster except the test cluster. diff --git a/src/app/tests/TestInteractionModelEngine.cpp b/src/app/tests/TestInteractionModelEngine.cpp index 6fc5b1da27dcbe..a9a21071d25475 100644 --- a/src/app/tests/TestInteractionModelEngine.cpp +++ b/src/app/tests/TestInteractionModelEngine.cpp @@ -23,6 +23,7 @@ */ #include +#include #include #include #include @@ -70,7 +71,8 @@ void TestInteractionModelEngine::TestAttributePathParamsPushRelease(nlTestSuite { TestContext & ctx = *static_cast(apContext); CHIP_ERROR err = CHIP_NO_ERROR; - err = InteractionModelEngine::GetInstance()->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = InteractionModelEngine::GetInstance()->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), + app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); ObjectList * attributePathParamsList = nullptr; AttributePathParams attributePathParams1; @@ -107,7 +109,8 @@ void TestInteractionModelEngine::TestRemoveDuplicateConcreteAttribute(nlTestSuit { TestContext & ctx = *static_cast(apContext); CHIP_ERROR err = CHIP_NO_ERROR; - err = InteractionModelEngine::GetInstance()->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = InteractionModelEngine::GetInstance()->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), + app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); ObjectList * attributePathParamsList = nullptr; AttributePathParams attributePathParams1; @@ -231,7 +234,8 @@ void TestInteractionModelEngine::TestSubscriptionResumptionTimer(nlTestSuite * a { TestContext & ctx = *static_cast(apContext); CHIP_ERROR err = CHIP_NO_ERROR; - err = InteractionModelEngine::GetInstance()->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = InteractionModelEngine::GetInstance()->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), + app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); uint32_t timeTillNextResubscriptionMs; diff --git a/src/app/tests/TestReadInteraction.cpp b/src/app/tests/TestReadInteraction.cpp index 9ec3d59330c216..f886d6ead65bd8 100644 --- a/src/app/tests/TestReadInteraction.cpp +++ b/src/app/tests/TestReadInteraction.cpp @@ -29,6 +29,8 @@ #include #include #include +#include +#include #include #include #include @@ -45,7 +47,6 @@ #include #include #include - #include namespace { @@ -253,8 +254,13 @@ class NullReadHandlerCallback : public chip::app::ReadHandler::ManagementCallbac } // namespace +using ReportScheduler = chip::app::reporting::ReportScheduler; +using ReportSchedulerImpl = chip::app::reporting::ReportSchedulerImpl; +using ReadHandlerNode = chip::app::reporting::ReportScheduler::ReadHandlerNode; + namespace chip { namespace app { + CHIP_ERROR ReadSingleClusterData(const Access::SubjectDescriptor & aSubjectDescriptor, bool aIsFabricFiltered, const ConcreteReadAttributePath & aPath, AttributeReportIBs::Builder & aAttributeReports, AttributeValueEncoder::AttributeEncodeState * apEncoderState) @@ -512,12 +518,13 @@ void TestReadInteraction::TestReadHandler(nlTestSuite * apSuite, void * apContex NullReadHandlerCallback nullCallback; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); { Messaging::ExchangeContext * exchangeCtx = ctx.NewExchangeToAlice(nullptr, false); - ReadHandler readHandler(nullCallback, exchangeCtx, chip::app::ReadHandler::InteractionType::Read); + ReadHandler readHandler(nullCallback, exchangeCtx, chip::app::ReadHandler::InteractionType::Read, + app::reporting::GetDefaultReportScheduler()); GenerateReportData(apSuite, apContext, reportDatabuf, false /*aNeedInvalidReport*/, false /* aSuppressResponse*/); err = readHandler.SendReportData(std::move(reportDatabuf), false); @@ -655,12 +662,13 @@ void TestReadInteraction::TestReadHandlerInvalidAttributePath(nlTestSuite * apSu NullReadHandlerCallback nullCallback; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); { Messaging::ExchangeContext * exchangeCtx = ctx.NewExchangeToAlice(nullptr, false); - ReadHandler readHandler(nullCallback, exchangeCtx, chip::app::ReadHandler::InteractionType::Read); + ReadHandler readHandler(nullCallback, exchangeCtx, chip::app::ReadHandler::InteractionType::Read, + app::reporting::GetDefaultReportScheduler()); GenerateReportData(apSuite, apContext, reportDatabuf, false /*aNeedInvalidReport*/, false /* aSuppressResponse*/); err = readHandler.SendReportData(std::move(reportDatabuf), false); @@ -817,7 +825,7 @@ void TestReadInteraction::TestReadRoundtrip(nlTestSuite * apSuite, void * apCont MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -899,7 +907,7 @@ void TestReadInteraction::TestReadRoundtripWithDataVersionFilter(nlTestSuite * a MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -955,7 +963,7 @@ void TestReadInteraction::TestReadRoundtripWithNoMatchPathDataVersionFilter(nlTe MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); chip::app::AttributePathParams attributePathParams[2]; @@ -1015,7 +1023,7 @@ void TestReadInteraction::TestReadRoundtripWithMultiSamePathDifferentDataVersion MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -1076,7 +1084,7 @@ void TestReadInteraction::TestReadRoundtripWithSameDifferentPathsDataVersionFilt MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -1137,7 +1145,7 @@ void TestReadInteraction::TestReadWildcard(nlTestSuite * apSuite, void * apConte MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -1187,7 +1195,7 @@ void TestReadInteraction::TestReadChunking(nlTestSuite * apSuite, void * apConte MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -1239,7 +1247,7 @@ void TestReadInteraction::TestSetDirtyBetweenChunks(nlTestSuite * apSuite, void GenerateEvents(apSuite, apContext); auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); chip::app::AttributePathParams attributePathParams[2]; @@ -1390,7 +1398,7 @@ void TestReadInteraction::TestReadInvalidAttributePathRoundtrip(nlTestSuite * ap MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -1431,13 +1439,14 @@ void TestReadInteraction::TestProcessSubscribeRequest(nlTestSuite * apSuite, voi SubscribeRequestMessage::Builder subscribeRequestBuilder; MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); Messaging::ExchangeContext * exchangeCtx = ctx.NewExchangeToAlice(nullptr, false); { - ReadHandler readHandler(*engine, exchangeCtx, chip::app::ReadHandler::InteractionType::Read); + ReadHandler readHandler(*engine, exchangeCtx, chip::app::ReadHandler::InteractionType::Read, + app::reporting::GetDefaultReportScheduler()); writer.Init(std::move(subscribeRequestbuf)); err = subscribeRequestBuilder.Init(&writer); @@ -1492,8 +1501,9 @@ void TestReadInteraction::TestSubscribeRoundtrip(nlTestSuite * apSuite, void * a NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); MockInteractionModelApp delegate; - auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + auto * engine = chip::app::InteractionModelEngine::GetInstance(); + ReportSchedulerImpl * reportScheduler = app::reporting::GetDefaultReportScheduler(); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), reportScheduler); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -1587,7 +1597,7 @@ void TestReadInteraction::TestSubscribeRoundtrip(nlTestSuite * apSuite, void * a dirtyPath5.mAttributeId = 4; // Test report with 2 different path - delegate.mpReadHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval, false); + reportScheduler->SetFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed, true); delegate.mGotReport = false; delegate.mGotEventResponse = false; delegate.mNumAttributeResponse = 0; @@ -1599,12 +1609,15 @@ void TestReadInteraction::TestSubscribeRoundtrip(nlTestSuite * apSuite, void * a ctx.DrainAndServiceIO(); + NL_TEST_ASSERT( + apSuite, + !reportScheduler->CheckFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed)); NL_TEST_ASSERT(apSuite, delegate.mGotReport); NL_TEST_ASSERT(apSuite, delegate.mGotEventResponse == true); NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 2); // Test report with 2 different path, and 1 same path - delegate.mpReadHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval, false); + reportScheduler->SetFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed, true); delegate.mGotReport = false; delegate.mNumAttributeResponse = 0; err = engine->GetReportingEngine().SetDirty(dirtyPath1); @@ -1616,11 +1629,14 @@ void TestReadInteraction::TestSubscribeRoundtrip(nlTestSuite * apSuite, void * a ctx.DrainAndServiceIO(); + NL_TEST_ASSERT( + apSuite, + !reportScheduler->CheckFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed)); NL_TEST_ASSERT(apSuite, delegate.mGotReport); NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 2); // Test report with 3 different path, and one path is overlapped with another - delegate.mpReadHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval, false); + reportScheduler->SetFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed, true); delegate.mGotReport = false; delegate.mNumAttributeResponse = 0; err = engine->GetReportingEngine().SetDirty(dirtyPath1); @@ -1632,11 +1648,14 @@ void TestReadInteraction::TestSubscribeRoundtrip(nlTestSuite * apSuite, void * a ctx.DrainAndServiceIO(); + NL_TEST_ASSERT( + apSuite, + !reportScheduler->CheckFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed)); NL_TEST_ASSERT(apSuite, delegate.mGotReport); NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 2); // Test report with 3 different path, all are not overlapped, one path is not interested for current subscription - delegate.mpReadHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval, false); + reportScheduler->SetFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed, true); delegate.mGotReport = false; delegate.mNumAttributeResponse = 0; err = engine->GetReportingEngine().SetDirty(dirtyPath1); @@ -1648,12 +1667,19 @@ void TestReadInteraction::TestSubscribeRoundtrip(nlTestSuite * apSuite, void * a ctx.DrainAndServiceIO(); + NL_TEST_ASSERT( + apSuite, + !reportScheduler->CheckFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed)); NL_TEST_ASSERT(apSuite, delegate.mGotReport); NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 2); // Test empty report - delegate.mpReadHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval, false); - delegate.mpReadHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMaxInterval, false); + reportScheduler->SetFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed, true); + reportScheduler->SetFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MaxIntervalElapsed, true); + // Manually trigger the callback that would schedule the next report as it would normally have been called if the time had + // elapsed as simulated above + reportScheduler->RunNodeCallbackForHandler(delegate.mpReadHandler); + NL_TEST_ASSERT(apSuite, engine->GetReportingEngine().IsRunScheduled()); delegate.mGotReport = false; delegate.mNumAttributeResponse = 0; @@ -1683,8 +1709,9 @@ void TestReadInteraction::TestSubscribeUrgentWildcardEvent(nlTestSuite * apSuite MockInteractionModelApp delegate; MockInteractionModelApp nonUrgentDelegate; - auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + auto * engine = chip::app::InteractionModelEngine::GetInstance(); + ReportSchedulerImpl * reportScheduler = app::reporting::GetDefaultReportScheduler(); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), reportScheduler); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); NL_TEST_ASSERT(apSuite, !nonUrgentDelegate.mGotEventResponse); @@ -1740,13 +1767,12 @@ void TestReadInteraction::TestSubscribeUrgentWildcardEvent(nlTestSuite * apSuite GenerateEvents(apSuite, apContext); - NL_TEST_ASSERT(apSuite, delegate.mpReadHandler->mFlags.Has(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval)); + NL_TEST_ASSERT(apSuite, reportScheduler->GetMinTimestampForHandler(delegate.mpReadHandler) > startTime); NL_TEST_ASSERT(apSuite, delegate.mpReadHandler->IsDirty()); delegate.mGotEventResponse = false; delegate.mGotReport = false; - NL_TEST_ASSERT(apSuite, - nonUrgentDelegate.mpReadHandler->mFlags.Has(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval)); + NL_TEST_ASSERT(apSuite, reportScheduler->GetMinTimestampForHandler(nonUrgentDelegate.mpReadHandler) > startTime); NL_TEST_ASSERT(apSuite, !nonUrgentDelegate.mpReadHandler->IsDirty()); nonUrgentDelegate.mGotEventResponse = false; nonUrgentDelegate.mGotReport = false; @@ -1780,16 +1806,19 @@ void TestReadInteraction::TestSubscribeUrgentWildcardEvent(nlTestSuite * apSuite NL_TEST_ASSERT(apSuite, delegate.mGotEventResponse); NL_TEST_ASSERT(apSuite, !nonUrgentDelegate.mGotEventResponse); - // Since we just sent a report for our urgent subscription, we should have our min interval timer - // running again. - NL_TEST_ASSERT(apSuite, delegate.mpReadHandler->mFlags.Has(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval)); + // Since we just sent a report for our urgent subscription, the min interval of the urgent subcription should have been + // updated + NL_TEST_ASSERT(apSuite, + reportScheduler->GetMinTimestampForHandler(delegate.mpReadHandler) > + System::SystemClock().GetMonotonicTimestamp()); NL_TEST_ASSERT(apSuite, !delegate.mpReadHandler->IsDirty()); delegate.mGotEventResponse = false; - // For our non-urgent subscription, we did not send anything, so we - // should not have a min interval timer running there. + // 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 NL_TEST_ASSERT(apSuite, - !nonUrgentDelegate.mpReadHandler->mFlags.Has(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval)); + reportScheduler->GetMinTimestampForHandler(nonUrgentDelegate.mpReadHandler) < + System::SystemClock().GetMonotonicTimestamp()); NL_TEST_ASSERT(apSuite, !nonUrgentDelegate.mpReadHandler->IsDirty()); // Wait for the min interval timer to fire. @@ -1807,18 +1836,24 @@ void TestReadInteraction::TestSubscribeUrgentWildcardEvent(nlTestSuite * apSuite NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); NL_TEST_ASSERT(apSuite, !nonUrgentDelegate.mGotEventResponse); - // min-interval timer should have fired, and our handler should still - // not be dirty or even reportable. - NL_TEST_ASSERT(apSuite, !delegate.mpReadHandler->mFlags.Has(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval)); + // The min-interval should have elapsed for urgen subscription, and our handler should still + // not be dirty or reportable. + NL_TEST_ASSERT(apSuite, + reportScheduler->GetMinTimestampForHandler(delegate.mpReadHandler) < + System::SystemClock().GetMonotonicTimestamp()); NL_TEST_ASSERT(apSuite, !delegate.mpReadHandler->IsDirty()); - NL_TEST_ASSERT(apSuite, !delegate.mpReadHandler->IsReportableNow()); + NL_TEST_ASSERT(apSuite, !delegate.mpReadHandler->IsReportable()); // And the non-urgent one should not have changed state either, since // it's waiting for the max-interval. NL_TEST_ASSERT(apSuite, - !nonUrgentDelegate.mpReadHandler->mFlags.Has(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval)); + reportScheduler->GetMinTimestampForHandler(nonUrgentDelegate.mpReadHandler) < + System::SystemClock().GetMonotonicTimestamp()); + NL_TEST_ASSERT(apSuite, + reportScheduler->GetMaxTimestampForHandler(nonUrgentDelegate.mpReadHandler) > + System::SystemClock().GetMonotonicTimestamp()); NL_TEST_ASSERT(apSuite, !nonUrgentDelegate.mpReadHandler->IsDirty()); - NL_TEST_ASSERT(apSuite, !nonUrgentDelegate.mpReadHandler->IsReportableNow()); + NL_TEST_ASSERT(apSuite, !nonUrgentDelegate.mpReadHandler->IsReportable()); // There should be no reporting run scheduled. This is very important; // otherwise we can get a false-positive pass below because the run was @@ -1830,11 +1865,12 @@ void TestReadInteraction::TestSubscribeUrgentWildcardEvent(nlTestSuite * apSuite // Urgent read handler should now be dirty, and reportable. NL_TEST_ASSERT(apSuite, delegate.mpReadHandler->IsDirty()); - NL_TEST_ASSERT(apSuite, delegate.mpReadHandler->IsReportableNow()); + NL_TEST_ASSERT(apSuite, delegate.mpReadHandler->IsReportable()); + NL_TEST_ASSERT(apSuite, reportScheduler->IsReadHandlerReportable(delegate.mpReadHandler)); // Non-urgent read handler should not be reportable. NL_TEST_ASSERT(apSuite, !nonUrgentDelegate.mpReadHandler->IsDirty()); - NL_TEST_ASSERT(apSuite, !nonUrgentDelegate.mpReadHandler->IsReportableNow()); + NL_TEST_ASSERT(apSuite, !nonUrgentDelegate.mpReadHandler->IsReportable()); // Still no reporting should have happened. NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -1870,8 +1906,9 @@ void TestReadInteraction::TestSubscribeWildcard(nlTestSuite * apSuite, void * ap GenerateEvents(apSuite, apContext); MockInteractionModelApp delegate; - auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + ReportSchedulerImpl * reportScheduler = app::reporting::GetDefaultReportScheduler(); + auto * engine = chip::app::InteractionModelEngine::GetInstance(); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), reportScheduler); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -1917,7 +1954,7 @@ void TestReadInteraction::TestSubscribeWildcard(nlTestSuite * apSuite, void * ap // Set a concrete path dirty { - delegate.mpReadHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval, false); + reportScheduler->SetFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed, true); delegate.mGotReport = false; delegate.mNumAttributeResponse = 0; @@ -1931,6 +1968,9 @@ void TestReadInteraction::TestSubscribeWildcard(nlTestSuite * apSuite, void * ap ctx.DrainAndServiceIO(); + NL_TEST_ASSERT( + apSuite, + !reportScheduler->CheckFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed)); NL_TEST_ASSERT(apSuite, delegate.mGotReport); // We subscribed wildcard path twice, so we will receive two reports here. NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 2); @@ -1938,7 +1978,7 @@ void TestReadInteraction::TestSubscribeWildcard(nlTestSuite * apSuite, void * ap // Set a endpoint dirty { - delegate.mpReadHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval, false); + reportScheduler->SetFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed, true); delegate.mGotReport = false; delegate.mNumAttributeResponse = 0; delegate.mNumArrayItems = 0; @@ -1960,6 +2000,9 @@ void TestReadInteraction::TestSubscribeWildcard(nlTestSuite * apSuite, void * ap ctx.DrainAndServiceIO(); } while (last != delegate.mNumAttributeResponse); + NL_TEST_ASSERT( + apSuite, + !reportScheduler->CheckFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed)); NL_TEST_ASSERT(apSuite, delegate.mGotReport); // 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 @@ -1991,8 +2034,9 @@ void TestReadInteraction::TestSubscribePartialOverlap(nlTestSuite * apSuite, voi NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); MockInteractionModelApp delegate; - auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + ReportSchedulerImpl * reportScheduler = app::reporting::GetDefaultReportScheduler(); + auto * engine = chip::app::InteractionModelEngine::GetInstance(); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), reportScheduler); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -2030,7 +2074,7 @@ void TestReadInteraction::TestSubscribePartialOverlap(nlTestSuite * apSuite, voi // Set a partial overlapped path dirty { - delegate.mpReadHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval, false); + reportScheduler->SetFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed, true); delegate.mGotReport = false; delegate.mNumAttributeResponse = 0; @@ -2043,6 +2087,9 @@ void TestReadInteraction::TestSubscribePartialOverlap(nlTestSuite * apSuite, voi ctx.DrainAndServiceIO(); + NL_TEST_ASSERT( + apSuite, + !reportScheduler->CheckFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed)); NL_TEST_ASSERT(apSuite, delegate.mGotReport); NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 1); NL_TEST_ASSERT(apSuite, delegate.mReceivedAttributePaths[0].mEndpointId == Test::kMockEndpoint2); @@ -2067,8 +2114,9 @@ void TestReadInteraction::TestSubscribeSetDirtyFullyOverlap(nlTestSuite * apSuit NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); MockInteractionModelApp delegate; - auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + ReportSchedulerImpl * reportScheduler = app::reporting::GetDefaultReportScheduler(); + auto * engine = chip::app::InteractionModelEngine::GetInstance(); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), reportScheduler); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -2107,7 +2155,7 @@ void TestReadInteraction::TestSubscribeSetDirtyFullyOverlap(nlTestSuite * apSuit // Set a full overlapped path dirty and expect to receive one E2C3A1 { - delegate.mpReadHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval, false); + reportScheduler->SetFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed, true); delegate.mGotReport = false; delegate.mNumAttributeResponse = 0; @@ -2117,6 +2165,9 @@ void TestReadInteraction::TestSubscribeSetDirtyFullyOverlap(nlTestSuite * apSuit ctx.DrainAndServiceIO(); + NL_TEST_ASSERT( + apSuite, + !reportScheduler->CheckFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed)); NL_TEST_ASSERT(apSuite, delegate.mGotReport); NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 1); NL_TEST_ASSERT(apSuite, delegate.mReceivedAttributePaths[0].mEndpointId == Test::kMockEndpoint2); @@ -2141,7 +2192,9 @@ void TestReadInteraction::TestSubscribeEarlyShutdown(nlTestSuite * apSuite, void // Initialize Interaction Model Engine NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); - NL_TEST_ASSERT(apSuite, engine.Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()) == CHIP_NO_ERROR); + NL_TEST_ASSERT(apSuite, + engine.Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()) == + CHIP_NO_ERROR); // Subscribe to the attribute AttributePathParams attributePathParams; @@ -2194,8 +2247,9 @@ void TestReadInteraction::TestSubscribeInvalidAttributePathRoundtrip(nlTestSuite GenerateEvents(apSuite, apContext); MockInteractionModelApp delegate; - auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + ReportSchedulerImpl * reportScheduler = app::reporting::GetDefaultReportScheduler(); + auto * engine = chip::app::InteractionModelEngine::GetInstance(); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), reportScheduler); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -2228,8 +2282,11 @@ void TestReadInteraction::TestSubscribeInvalidAttributePathRoundtrip(nlTestSuite NL_TEST_ASSERT(apSuite, engine->ActiveHandlerAt(0) != nullptr); delegate.mpReadHandler = engine->ActiveHandlerAt(0); - delegate.mpReadHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval, false); - delegate.mpReadHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMaxInterval, false); + reportScheduler->SetFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed, true); + reportScheduler->SetFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MaxIntervalElapsed, true); + // Manually trigger the callback that would schedule the next report as it would normally have been called if the time had + // elapsed as simulated above + reportScheduler->RunNodeCallbackForHandler(delegate.mpReadHandler); NL_TEST_ASSERT(apSuite, engine->GetReportingEngine().IsRunScheduled()); ctx.DrainAndServiceIO(); @@ -2293,7 +2350,7 @@ void TestReadInteraction::TestSubscribeInvalidInterval(nlTestSuite * apSuite, vo MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -2337,8 +2394,9 @@ void TestReadInteraction::TestPostSubscribeRoundtripStatusReportTimeout(nlTestSu NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); MockInteractionModelApp delegate; - auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + ReportSchedulerImpl * reportScheduler = app::reporting::GetDefaultReportScheduler(); + auto * engine = chip::app::InteractionModelEngine::GetInstance(); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), reportScheduler); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -2404,7 +2462,7 @@ void TestReadInteraction::TestPostSubscribeRoundtripStatusReportTimeout(nlTestSu dirtyPath2.mAttributeId = 2; // Test report with 2 different path - delegate.mpReadHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval, false); + reportScheduler->SetFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed, true); delegate.mGotReport = false; delegate.mNumAttributeResponse = 0; @@ -2415,11 +2473,14 @@ void TestReadInteraction::TestPostSubscribeRoundtripStatusReportTimeout(nlTestSu ctx.DrainAndServiceIO(); + NL_TEST_ASSERT( + apSuite, + !reportScheduler->CheckFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed)); NL_TEST_ASSERT(apSuite, delegate.mGotReport); NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 2); - delegate.mpReadHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval, false); - delegate.mpReadHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMaxInterval, false); + reportScheduler->SetFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed, true); + reportScheduler->SetFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MaxIntervalElapsed, true); delegate.mGotReport = false; delegate.mNumAttributeResponse = 0; ctx.ExpireSessionBobToAlice(); @@ -2459,7 +2520,7 @@ void TestReadInteraction::TestSubscribeRoundtripStatusReportTimeout(nlTestSuite MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -2537,7 +2598,7 @@ void TestReadInteraction::TestReadChunkingStatusReportTimeout(nlTestSuite * apSu MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -2591,7 +2652,7 @@ void TestReadInteraction::TestReadReportFailure(nlTestSuite * apSuite, void * ap MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -2639,7 +2700,7 @@ void TestReadInteraction::TestSubscribeRoundtripChunkStatusReportTimeout(nlTestS MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -2709,8 +2770,9 @@ void TestReadInteraction::TestPostSubscribeRoundtripChunkStatusReportTimeout(nlT NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); MockInteractionModelApp delegate; - auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + ReportSchedulerImpl * reportScheduler = app::reporting::GetDefaultReportScheduler(); + auto * engine = chip::app::InteractionModelEngine::GetInstance(); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), reportScheduler); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -2766,8 +2828,8 @@ void TestReadInteraction::TestPostSubscribeRoundtripChunkStatusReportTimeout(nlT dirtyPath1.mEndpointId = Test::kMockEndpoint3; dirtyPath1.mAttributeId = Test::MockAttributeId(4); - delegate.mpReadHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval, false); - delegate.mpReadHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMaxInterval, false); + reportScheduler->SetFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed, true); + reportScheduler->SetFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MaxIntervalElapsed, true); err = engine->GetReportingEngine().SetDirty(dirtyPath1); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); delegate.mGotReport = false; @@ -2811,8 +2873,9 @@ void TestReadInteraction::TestPostSubscribeRoundtripChunkReportTimeout(nlTestSui NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); MockInteractionModelApp delegate; - auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + ReportSchedulerImpl * reportScheduler = app::reporting::GetDefaultReportScheduler(); + auto * engine = chip::app::InteractionModelEngine::GetInstance(); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), reportScheduler); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -2868,8 +2931,8 @@ void TestReadInteraction::TestPostSubscribeRoundtripChunkReportTimeout(nlTestSui dirtyPath1.mEndpointId = Test::kMockEndpoint3; dirtyPath1.mAttributeId = Test::MockAttributeId(4); - delegate.mpReadHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval, false); - delegate.mpReadHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMaxInterval, false); + reportScheduler->SetFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed, true); + reportScheduler->SetFlagsForHandler(delegate.mpReadHandler, ReadHandlerNode::TestFlags::MaxIntervalElapsed, true); err = engine->GetReportingEngine().SetDirty(dirtyPath1); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); delegate.mGotReport = false; @@ -2913,7 +2976,7 @@ void TestReadInteraction::TestPostSubscribeRoundtripChunkReport(nlTestSuite * ap MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -3067,7 +3130,7 @@ void TestReadInteraction::TestReadClientReceiveInvalidMessage(nlTestSuite * apSu MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); @@ -3153,7 +3216,7 @@ void TestReadInteraction::TestSubscribeClientReceiveInvalidStatusResponse(nlTest MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); @@ -3246,7 +3309,7 @@ void TestReadInteraction::TestSubscribeClientReceiveWellFormedStatusResponse(nlT MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); @@ -3338,7 +3401,7 @@ void TestReadInteraction::TestSubscribeClientReceiveInvalidReportMessage(nlTestS MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); @@ -3430,7 +3493,7 @@ void TestReadInteraction::TestSubscribeClientReceiveUnsolicitedInvalidReportMess MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); @@ -3502,7 +3565,7 @@ void TestReadInteraction::TestSubscribeClientReceiveInvalidSubscribeResponseMess MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); @@ -3596,7 +3659,7 @@ void TestReadInteraction::TestSubscribeClientReceiveUnsolicitedReportMessageWith MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); @@ -3675,7 +3738,7 @@ void TestReadInteraction::TestReadChunkingInvalidSubscriptionId(nlTestSuite * ap MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); @@ -3766,7 +3829,7 @@ void TestReadInteraction::TestReadHandlerMalformedSubscribeRequest(nlTestSuite * MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); @@ -3811,7 +3874,7 @@ void TestReadInteraction::TestReadHandlerMalformedReadRequest1(nlTestSuite * apS MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); @@ -3856,7 +3919,7 @@ void TestReadInteraction::TestReadHandlerMalformedReadRequest2(nlTestSuite * apS MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); @@ -3903,7 +3966,7 @@ void TestReadInteraction::TestSubscribeSendUnknownMessage(nlTestSuite * apSuite, MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); chip::app::AttributePathParams attributePathParams[1]; @@ -3979,7 +4042,7 @@ void TestReadInteraction::TestSubscribeSendInvalidStatusReport(nlTestSuite * apS MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); chip::app::AttributePathParams attributePathParams[1]; @@ -4054,7 +4117,7 @@ void TestReadInteraction::TestReadHandlerInvalidSubscribeRequest(nlTestSuite * a MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); @@ -4100,7 +4163,7 @@ void TestReadInteraction::TestSubscribeInvalidateFabric(nlTestSuite * apSuite, v MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); @@ -4159,7 +4222,7 @@ void TestReadInteraction::TestShutdownSubscription(nlTestSuite * apSuite, void * MockInteractionModelApp delegate; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); @@ -4211,8 +4274,9 @@ void TestReadInteraction::TestSubscriptionReportWithDefunctSession(nlTestSuite * NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); MockInteractionModelApp delegate; - auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + ReportSchedulerImpl * reportScheduler = app::reporting::GetDefaultReportScheduler(); + auto * engine = chip::app::InteractionModelEngine::GetInstance(); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), reportScheduler); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); AttributePathParams subscribePath(Test::kMockEndpoint3, Test::MockClusterId(2), Test::MockAttributeId(1)); @@ -4249,13 +4313,14 @@ void TestReadInteraction::TestSubscriptionReportWithDefunctSession(nlTestSuite * NL_TEST_ASSERT(apSuite, SessionHandle(*readHandler->GetSession()) == ctx.GetSessionAliceToBob()); // Test that we send reports as needed. - readHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval, false); + reportScheduler->SetFlagsForHandler(readHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed, true); delegate.mGotReport = false; delegate.mNumAttributeResponse = 0; engine->GetReportingEngine().SetDirty(subscribePath); ctx.DrainAndServiceIO(); - + NL_TEST_ASSERT(apSuite, + !reportScheduler->CheckFlagsForHandler(readHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed)); NL_TEST_ASSERT(apSuite, delegate.mGotReport); NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 1); NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadHandlers(ReadHandler::InteractionType::Subscribe) == 1); @@ -4265,7 +4330,7 @@ void TestReadInteraction::TestSubscriptionReportWithDefunctSession(nlTestSuite * // Test that if the session is defunct we don't send reports and clean // up properly. readHandler->GetSession()->MarkAsDefunct(); - readHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::WaitingUntilMinInterval, false); + reportScheduler->SetFlagsForHandler(readHandler, ReadHandlerNode::TestFlags::MinIntervalElapsed, true); delegate.mGotReport = false; delegate.mNumAttributeResponse = 0; engine->GetReportingEngine().SetDirty(subscribePath); diff --git a/src/app/tests/TestReportScheduler.cpp b/src/app/tests/TestReportScheduler.cpp index c789865bcc50b2..9d19f85636e15e 100644 --- a/src/app/tests/TestReportScheduler.cpp +++ b/src/app/tests/TestReportScheduler.cpp @@ -138,16 +138,16 @@ class TestTimerDelegate : public ReportScheduler::TimerDelegate // Normaly we would call the callback here, thus scheduling an engine run, but we don't need it for this test as we simulate // all the callbacks related to report emissions. The actual callback should look like this: // - // ReadHandlerNode * node = static_cast(aAppState); - // node->RunCallback(); + TimerContext * context = static_cast(aAppState); + context->TimerFired(); ChipLogProgress(DataManagement, "Simluating engine run for Handler: %p", aAppState); } - virtual CHIP_ERROR StartTimer(void * context, System::Clock::Timeout aTimeout) override + virtual CHIP_ERROR StartTimer(TimerContext * context, System::Clock::Timeout aTimeout) override { return insertPair(static_cast(context), aTimeout + mMockSystemTimestamp); } - virtual void CancelTimer(void * context) override { removePair(static_cast(context)); } - virtual bool IsTimerActive(void * context) override + virtual void CancelTimer(TimerContext * context) override { removePair(static_cast(context)); } + virtual bool IsTimerActive(TimerContext * context) override { size_t position; NodeTimeoutPair * pair = FindPair(static_cast(context), position); @@ -184,30 +184,29 @@ class TestTimerSynchronizedDelegate : public ReportScheduler::TimerDelegate public: static void TimerCallbackInterface(System::Layer * aLayer, void * aAppState) { - SynchronizedReportSchedulerImpl * scheduler = static_cast(aAppState); - scheduler->ReportTimerCallback(); + TimerContext * context = static_cast(aAppState); + context->TimerFired(); } - virtual CHIP_ERROR StartTimer(void * context, System::Clock::Timeout aTimeout) override + virtual CHIP_ERROR StartTimer(TimerContext * context, System::Clock::Timeout aTimeout) override { - SynchronizedReportSchedulerImpl * scheduler = static_cast(context); - if (nullptr == scheduler) + if (nullptr == context) { return CHIP_ERROR_INCORRECT_STATE; } - mSyncScheduler = scheduler; - mTimerTimeout = mMockSystemTimestamp + aTimeout; + mTimerContext = context; + mTimerTimeout = mMockSystemTimestamp + aTimeout; return CHIP_NO_ERROR; } - virtual void CancelTimer(void * context) override + virtual void CancelTimer(TimerContext * context) override { - VerifyOrReturn(nullptr != mSyncScheduler); - mSyncScheduler = nullptr; - mTimerTimeout = System::Clock::Milliseconds64(0x7FFFFFFFFFFFFFFF); + VerifyOrReturn(nullptr != mTimerContext); + mTimerContext = nullptr; + mTimerTimeout = System::Clock::Milliseconds64(0x7FFFFFFFFFFFFFFF); } - virtual bool IsTimerActive(void * context) override + virtual bool IsTimerActive(TimerContext * context) override { - return (nullptr != mSyncScheduler) && (mTimerTimeout > mMockSystemTimestamp); + return (nullptr != mTimerContext) && (mTimerTimeout > mMockSystemTimestamp); } virtual System::Clock::Timestamp GetCurrentMonotonicTimestamp() override { return mMockSystemTimestamp; } @@ -223,7 +222,7 @@ class TestTimerSynchronizedDelegate : public ReportScheduler::TimerDelegate mMockSystemTimestamp++; if (mMockSystemTimestamp == mTimerTimeout) { - TimerCallbackInterface(nullptr, mSyncScheduler); + TimerCallbackInterface(nullptr, mTimerContext); } } @@ -231,14 +230,14 @@ class TestTimerSynchronizedDelegate : public ReportScheduler::TimerDelegate { if (mMockSystemTimestamp == mTimerTimeout) { - TimerCallbackInterface(nullptr, mSyncScheduler); + TimerCallbackInterface(nullptr, mTimerContext); } } } - SynchronizedReportSchedulerImpl * mSyncScheduler = nullptr; - System::Clock::Timeout mTimerTimeout = System::Clock::Milliseconds64(0x7FFFFFFFFFFFFFFF); - System::Clock::Timestamp mMockSystemTimestamp = System::Clock::Milliseconds64(0); + TimerContext * mTimerContext = nullptr; + System::Clock::Timeout mTimerTimeout = System::Clock::Milliseconds64(0x7FFFFFFFFFFFFFFF); + System::Clock::Timestamp mMockSystemTimestamp = System::Clock::Milliseconds64(0); }; TestTimerDelegate sTestTimerDelegate; @@ -250,6 +249,25 @@ SynchronizedReportSchedulerImpl syncScheduler(&sTestTimerSynchronizedDelegate); class TestReportScheduler { public: + static ReadHandler * GetReadHandlerFromPool(ReportScheduler * scheduler, uint32_t target) + { + uint32_t i = 0; + ReadHandler * ret = nullptr; + + scheduler->mNodesPool.ForEachActiveObject([target, &i, &ret](ReadHandlerNode * node) { + if (i == target) + { + ret = node->GetReadHandler(); + return Loop::Break; + } + + i++; + return Loop::Continue; + }); + + return ret; + } + static void TestReadHandlerList(nlTestSuite * aSuite, void * aContext) { TestContext & ctx = *static_cast(aContext); @@ -266,11 +284,9 @@ class TestReportScheduler for (size_t i = 0; i < kNumMaxReadHandlers; i++) { ReadHandler * readHandler = - readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe); + readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe, &sScheduler); NL_TEST_ASSERT(aSuite, nullptr != readHandler); VerifyOrReturn(nullptr != readHandler); - // Register ReadHandler using callback method - sScheduler.OnReadHandlerCreated(readHandler); NL_TEST_ASSERT(aSuite, nullptr != sScheduler.FindReadHandlerNode(readHandler)); } @@ -279,26 +295,26 @@ class TestReportScheduler NL_TEST_ASSERT(aSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 1); // Test unregister first ReadHandler - ReadHandler * firstReadHandler = sScheduler.mReadHandlerList.begin()->GetReadHandler(); + uint32_t target = 0; + ReadHandler * firstReadHandler = GetReadHandlerFromPool(&sScheduler, target); + + NL_TEST_ASSERT(aSuite, nullptr != firstReadHandler); sScheduler.OnReadHandlerDestroyed(firstReadHandler); NL_TEST_ASSERT(aSuite, sScheduler.GetNumReadHandlers() == kNumMaxReadHandlers - 1); NL_TEST_ASSERT(aSuite, nullptr == sScheduler.FindReadHandlerNode(firstReadHandler)); // Test unregister middle ReadHandler - auto iter = sScheduler.mReadHandlerList.begin(); - for (size_t i = 0; i < static_cast(kNumMaxReadHandlers / 2); i++) - { - iter++; - } - ReadHandler * middleReadHandler = iter->GetReadHandler(); + target = static_cast(sScheduler.GetNumReadHandlers() / 2); + ReadHandler * middleReadHandler = GetReadHandlerFromPool(&sScheduler, target); + + NL_TEST_ASSERT(aSuite, nullptr != middleReadHandler); sScheduler.OnReadHandlerDestroyed(middleReadHandler); NL_TEST_ASSERT(aSuite, sScheduler.GetNumReadHandlers() == kNumMaxReadHandlers - 2); NL_TEST_ASSERT(aSuite, nullptr == sScheduler.FindReadHandlerNode(middleReadHandler)); // Test unregister last ReadHandler - iter = sScheduler.mReadHandlerList.end(); - iter--; - ReadHandler * lastReadHandler = iter->GetReadHandler(); + target = static_cast(sScheduler.GetNumReadHandlers() - 1); + ReadHandler * lastReadHandler = GetReadHandlerFromPool(&sScheduler, target); sScheduler.OnReadHandlerDestroyed(lastReadHandler); NL_TEST_ASSERT(aSuite, sScheduler.GetNumReadHandlers() == kNumMaxReadHandlers - 3); NL_TEST_ASSERT(aSuite, nullptr == sScheduler.FindReadHandlerNode(lastReadHandler)); @@ -330,32 +346,34 @@ class TestReportScheduler sTestTimerDelegate.SetMockSystemTimestamp(Milliseconds64(0)); // Dirty read handler, will be triggered at min interval + // Test OnReadHandler created ReadHandler * readHandler1 = - readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe); + readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe, &sScheduler); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler1->SetMaxReportingInterval(2)); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler1->SetMinReportingIntervalForTests(1)); - // Do those manually to avoid scheduling an engine run - readHandler1->mState = ReadHandler::HandlerState::GeneratingReports; - sScheduler.OnReadHandlerCreated(readHandler1); + ReadHandlerNode * node = sScheduler.FindReadHandlerNode(readHandler1); + node->SetIntervalTimeStamps(readHandler1); + readHandler1->MoveToState(ReadHandler::HandlerState::GeneratingReports); readHandler1->ForceDirtyState(); // Clean read handler, will be triggered at max interval ReadHandler * readHandler2 = - readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe); + readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe, &sScheduler); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler2->SetMaxReportingInterval(3)); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler2->SetMinReportingIntervalForTests(0)); - // Do those manually to avoid scheduling an engine run - readHandler2->mState = ReadHandler::HandlerState::GeneratingReports; - sScheduler.OnReadHandlerCreated(readHandler2); + node = sScheduler.FindReadHandlerNode(readHandler2); + node->SetIntervalTimeStamps(readHandler2); + readHandler2->MoveToState(ReadHandler::HandlerState::GeneratingReports); // Clean read handler, will be triggered at max interval, but will be cancelled before ReadHandler * readHandler3 = - readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe); + readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe, &sScheduler); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler3->SetMaxReportingInterval(3)); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler3->SetMinReportingIntervalForTests(0)); - // Do those manually to avoid scheduling an engine run - readHandler3->mState = ReadHandler::HandlerState::GeneratingReports; - sScheduler.OnReadHandlerCreated(readHandler3); + node = sScheduler.FindReadHandlerNode(readHandler3); + node->SetIntervalTimeStamps(readHandler3); + readHandler3->MoveToState(ReadHandler::HandlerState::GeneratingReports); // Confirms that none of the ReadHandlers are currently reportable NL_TEST_ASSERT(aSuite, !sScheduler.IsReportableNow(readHandler1)); @@ -384,6 +402,10 @@ class TestReportScheduler // and it is in generating report state NL_TEST_ASSERT(aSuite, sScheduler.IsReportableNow(readHandler3)); + // Clear dirty flag on readHandler1 and confirm it is still reportable by time + readHandler1->ClearForceDirtyFlag(); + NL_TEST_ASSERT(aSuite, sScheduler.IsReportableNow(readHandler1)); + sScheduler.UnregisterAllHandlers(); readHandlerPool.ReleaseAll(); exchangeCtx->Close(); @@ -404,26 +426,31 @@ class TestReportScheduler sTestTimerDelegate.SetMockSystemTimestamp(Milliseconds64(0)); ReadHandler * readHandler = - readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe); - NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler->SetMaxReportingInterval(2)); - NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler->SetMinReportingIntervalForTests(1)); - // Do those manually to avoid scheduling an engine run - readHandler->mState = ReadHandler::HandlerState::GeneratingReports; - readHandler->SetObserver(&sScheduler); + readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe, &sScheduler); + // Test OnReadHandler created registered the ReadHandler in the scheduler + NL_TEST_ASSERT(aSuite, nullptr != sScheduler.FindReadHandlerNode(readHandler)); - // Test OnReadHandlerCreated - readHandler->mObserver->OnReadHandlerCreated(readHandler); // Should have registered the read handler in the scheduler and scheduled a report NL_TEST_ASSERT(aSuite, sScheduler.GetNumReadHandlers() == 1); - NL_TEST_ASSERT(aSuite, sScheduler.IsReportScheduled(readHandler)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler->SetMaxReportingInterval(2)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler->SetMinReportingIntervalForTests(1)); ReadHandlerNode * node = sScheduler.FindReadHandlerNode(readHandler); + node->SetIntervalTimeStamps(readHandler); + + // Test OnReportingIntervalsChanged modified the intervals and re-scheduled a report + NL_TEST_ASSERT(aSuite, node->GetMinTimestamp().count() == 1000); + NL_TEST_ASSERT(aSuite, node->GetMaxTimestamp().count() == 2000); + + // Do those manually to avoid scheduling an engine run + readHandler->MoveToState(ReadHandler::HandlerState::GeneratingReports); + NL_TEST_ASSERT(aSuite, sScheduler.IsReportScheduled(readHandler)); + NL_TEST_ASSERT(aSuite, nullptr != node); VerifyOrReturn(nullptr != node); NL_TEST_ASSERT(aSuite, node->GetReadHandler() == readHandler); // Test OnBecameReportable readHandler->ForceDirtyState(); - readHandler->mObserver->OnBecameReportable(readHandler); // Should have changed the scheduled timeout to the handler's min interval, to check, we wait for the min interval to // expire // Simulate system clock increment @@ -457,7 +484,6 @@ class TestReportScheduler NL_TEST_ASSERT(aSuite, sScheduler.GetNumReadHandlers() == 0); NL_TEST_ASSERT(aSuite, nullptr == sScheduler.FindReadHandlerNode(readHandler)); - sScheduler.OnReadHandlerDestroyed(readHandler); readHandlerPool.ReleaseAll(); exchangeCtx->Close(); NL_TEST_ASSERT(aSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); @@ -474,31 +500,28 @@ class TestReportScheduler // Read handler pool ObjectPool readHandlerPool; - // Initilaize the mock system time + // Initialize the mock system time sTestTimerSynchronizedDelegate.SetMockSystemTimestamp(System::Clock::Milliseconds64(0)); ReadHandler * readHandler1 = - readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe); + readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe, &syncScheduler); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler1->SetMaxReportingInterval(2)); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler1->SetMinReportingIntervalForTests(0)); + ReadHandlerNode * node1 = syncScheduler.FindReadHandlerNode(readHandler1); + node1->SetIntervalTimeStamps(readHandler1); readHandler1->MoveToState(ReadHandler::HandlerState::GeneratingReports); - readHandler1->SetObserver(&syncScheduler); - readHandler1->mObserver->OnReadHandlerCreated(readHandler1); ReadHandler * readHandler2 = - readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe); + readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe, &syncScheduler); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler2->SetMaxReportingInterval(3)); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler2->SetMinReportingIntervalForTests(1)); + ReadHandlerNode * node2 = syncScheduler.FindReadHandlerNode(readHandler2); + node2->SetIntervalTimeStamps(readHandler2); readHandler2->MoveToState(ReadHandler::HandlerState::GeneratingReports); - readHandler2->SetObserver(&syncScheduler); - readHandler2->mObserver->OnReadHandlerCreated(readHandler2); // Confirm all handler are currently registered in the scheduler NL_TEST_ASSERT(aSuite, syncScheduler.GetNumReadHandlers() == 2); - ReadHandlerNode * node1 = syncScheduler.FindReadHandlerNode(readHandler1); - ReadHandlerNode * node2 = syncScheduler.FindReadHandlerNode(readHandler2); - // Confirm that a report emission is scheduled NL_TEST_ASSERT(aSuite, syncScheduler.IsReportScheduled()); @@ -532,24 +555,17 @@ class TestReportScheduler // Confirm behavior when a read handler becomes dirty readHandler2->ForceDirtyState(); + // OnBecomeReportable should have been called on ForceDirtyState because readHandler callbacks are now integrated NL_TEST_ASSERT(aSuite, !syncScheduler.IsReportableNow(readHandler2)); NL_TEST_ASSERT(aSuite, !syncScheduler.IsReportableNow(readHandler1)); // Simulate wait enough for min timestamp of readHandler2 to be reached (1s) sTestTimerSynchronizedDelegate.IncrementMockTimestamp(System::Clock::Milliseconds64(1000)); NL_TEST_ASSERT(aSuite, syncScheduler.IsReportableNow(readHandler2)); - NL_TEST_ASSERT(aSuite, !syncScheduler.IsReportableNow(readHandler1)); - readHandler2->mObserver->OnBecameReportable(readHandler2); + NL_TEST_ASSERT(aSuite, syncScheduler.IsReportableNow(readHandler1)); // confirm report scheduled now - NL_TEST_ASSERT(aSuite, - syncScheduler.mTestNextReportTimestamp == sTestTimerSynchronizedDelegate.GetCurrentMonotonicTimestamp()); - // Increment the timestamp by 0 here to trigger an engine run as the mock timer is only calling the timeout callback if we - // increment the mock timestamp - sTestTimerSynchronizedDelegate.IncrementMockTimestamp(System::Clock::Milliseconds64(0)); - - // since the min interval on readHandler1 is 0, it should also be reportable now by sync mechanism - NL_TEST_ASSERT(aSuite, syncScheduler.IsReportableNow(readHandler1)); + NL_TEST_ASSERT(aSuite, syncScheduler.mTestNextReportTimestamp == node2->GetMinTimestamp()); NL_TEST_ASSERT(aSuite, node1->GetSyncTimestamp() == node2->GetMinTimestamp()); // Confirm that the next report emission is scheduled on the min timestamp of readHandler2 as it is the highest reportable @@ -569,16 +585,13 @@ class TestReportScheduler NL_TEST_ASSERT(aSuite, node1->GetMaxTimestamp() > sTestTimerSynchronizedDelegate.GetCurrentMonotonicTimestamp()); NL_TEST_ASSERT(aSuite, syncScheduler.mTestNextReportTimestamp == node1->GetMaxTimestamp()); - // Simulate readHandler1 becoming dirty after less than 1 seconds + // Simulate readHandler1 becoming dirty after less than 1 seconds, since it is reportable now, this will Schedule an Engin + // run immediately sTestTimerSynchronizedDelegate.IncrementMockTimestamp(System::Clock::Milliseconds64(900)); readHandler1->ForceDirtyState(); NL_TEST_ASSERT(aSuite, syncScheduler.IsReportableNow(readHandler1)); NL_TEST_ASSERT(aSuite, !syncScheduler.IsReportableNow(readHandler2)); - readHandler1->mObserver->OnBecameReportable(readHandler1); - // Validate next report scheduled on the min timestamp of readHandler1 (readHandler 2 is not currently reportable) - NL_TEST_ASSERT(aSuite, - syncScheduler.mTestNextReportTimestamp == sTestTimerSynchronizedDelegate.GetCurrentMonotonicTimestamp()); // Simulate a report emission for readHandler1 readHandler1->ClearForceDirtyFlag(); readHandler1->mObserver->OnSubscriptionAction(readHandler1); @@ -604,16 +617,15 @@ class TestReportScheduler sTestTimerSynchronizedDelegate.IncrementMockTimestamp(System::Clock::Milliseconds64(1000)); ReadHandler * readHandler3 = - readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe); + readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe, &syncScheduler); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler3->SetMaxReportingInterval(3)); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler3->SetMinReportingIntervalForTests(2)); + ReadHandlerNode * node3 = syncScheduler.FindReadHandlerNode(readHandler3); + node3->SetIntervalTimeStamps(readHandler3); readHandler3->MoveToState(ReadHandler::HandlerState::GeneratingReports); - readHandler3->SetObserver(&syncScheduler); - readHandler3->mObserver->OnReadHandlerCreated(readHandler3); // Confirm all handler are currently registered in the scheduler NL_TEST_ASSERT(aSuite, syncScheduler.GetNumReadHandlers() == 3); - ReadHandlerNode * node3 = syncScheduler.FindReadHandlerNode(readHandler3); // Since the min interval on readHandler3 is 2, it should be above the current max timestamp, therefore the next report // should still happen on the max timestamp of readHandler1 and the sync should be done on future reports @@ -662,16 +674,15 @@ class TestReportScheduler // Now simulate a new readHandler being added with a max forcing a conflict ReadHandler * readHandler4 = - readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe); + readHandlerPool.CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe, &syncScheduler); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler4->SetMaxReportingInterval(1)); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler4->SetMinReportingIntervalForTests(0)); + ReadHandlerNode * node4 = syncScheduler.FindReadHandlerNode(readHandler4); + node4->SetIntervalTimeStamps(readHandler4); readHandler4->MoveToState(ReadHandler::HandlerState::GeneratingReports); - readHandler4->SetObserver(&syncScheduler); - readHandler4->mObserver->OnReadHandlerCreated(readHandler4); // Confirm all handler are currently registered in the scheduler NL_TEST_ASSERT(aSuite, syncScheduler.GetNumReadHandlers() == 4); - ReadHandlerNode * node4 = syncScheduler.FindReadHandlerNode(readHandler4); // Confirm next report is scheduled on the max timestamp of readHandler4 and other handlers 1 and 2 are synced NL_TEST_ASSERT(aSuite, syncScheduler.mTestNextReportTimestamp == node4->GetMaxTimestamp()); @@ -752,8 +763,8 @@ class TestReportScheduler NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == readHandler1->SetMinReportingIntervalForTests(0)); readHandler1->MoveToState(ReadHandler::HandlerState::GeneratingReports); syncScheduler.OnReadHandlerCreated(readHandler1); + // Forcing the dirty flag to make the scheduler call Engine::ScheduleRun() immediately readHandler1->ForceDirtyState(); - syncScheduler.OnBecameReportable(readHandler1); NL_TEST_ASSERT(aSuite, syncScheduler.IsReportableNow(readHandler1)); readHandler2->MoveToState(ReadHandler::HandlerState::Idle); @@ -762,14 +773,11 @@ class TestReportScheduler readHandler2->MoveToState(ReadHandler::HandlerState::GeneratingReports); syncScheduler.OnReadHandlerCreated(readHandler2); readHandler2->ForceDirtyState(); - syncScheduler.OnBecameReportable(readHandler2); NL_TEST_ASSERT(aSuite, !syncScheduler.IsReportableNow(readHandler2)); node1 = syncScheduler.FindReadHandlerNode(readHandler1); node2 = syncScheduler.FindReadHandlerNode(readHandler2); - // Verify report is scheduled immediately as readHandler1 is dirty and its min == 0 - NL_TEST_ASSERT(aSuite, syncScheduler.mTestNextReportTimestamp == node1->GetMinTimestamp()); readHandler1->ClearForceDirtyFlag(); // report got emited so clear dirty flag syncScheduler.OnSubscriptionAction(readHandler1); NL_TEST_ASSERT(aSuite, !syncScheduler.IsReportableNow(readHandler1)); diff --git a/src/app/tests/TestReportingEngine.cpp b/src/app/tests/TestReportingEngine.cpp index 6921ea66d711b0..ddf793ec8880ae 100644 --- a/src/app/tests/TestReportingEngine.cpp +++ b/src/app/tests/TestReportingEngine.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -130,7 +131,8 @@ void TestReportingEngine::TestBuildAndSendSingleReportData(nlTestSuite * apSuite ReadRequestMessage::Builder readRequestBuilder; DummyDelegate dummy; - err = InteractionModelEngine::GetInstance()->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = InteractionModelEngine::GetInstance()->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), + app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); TestExchangeDelegate delegate; Messaging::ExchangeContext * exchangeCtx = ctx.NewExchangeToAlice(&delegate); @@ -156,7 +158,8 @@ void TestReportingEngine::TestBuildAndSendSingleReportData(nlTestSuite * apSuite NL_TEST_ASSERT(apSuite, readRequestBuilder.GetError() == CHIP_NO_ERROR); err = writer.Finalize(&readRequestbuf); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); - app::ReadHandler readHandler(dummy, exchangeCtx, chip::app::ReadHandler::InteractionType::Read); + app::ReadHandler readHandler(dummy, exchangeCtx, chip::app::ReadHandler::InteractionType::Read, + app::reporting::GetDefaultReportScheduler()); readHandler.OnInitialRequest(std::move(readRequestbuf)); err = InteractionModelEngine::GetInstance()->GetReportingEngine().BuildAndSendSingleReportData(&readHandler); @@ -169,7 +172,8 @@ void TestReportingEngine::TestMergeOverlappedAttributePath(nlTestSuite * apSuite { TestContext & ctx = *static_cast(apContext); CHIP_ERROR err = CHIP_NO_ERROR; - err = InteractionModelEngine::GetInstance()->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = InteractionModelEngine::GetInstance()->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), + app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); AttributePathParams * clusterInfo = InteractionModelEngine::GetInstance()->GetReportingEngine().mGlobalDirtySet.CreateObject(); @@ -240,7 +244,8 @@ void TestReportingEngine::TestMergeAttributePathWhenDirtySetPoolExhausted(nlTest { TestContext & ctx = *static_cast(apContext); CHIP_ERROR err = CHIP_NO_ERROR; - err = InteractionModelEngine::GetInstance()->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = InteractionModelEngine::GetInstance()->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), + app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); InteractionModelEngine::GetInstance()->GetReportingEngine().mGlobalDirtySet.ReleaseAll(); diff --git a/src/app/tests/TestWriteInteraction.cpp b/src/app/tests/TestWriteInteraction.cpp index 95cb83eaf21d65..a8f5d27979c152 100644 --- a/src/app/tests/TestWriteInteraction.cpp +++ b/src/app/tests/TestWriteInteraction.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -366,7 +367,7 @@ void TestWriteInteraction::TestWriteRoundtripWithClusterObjects(nlTestSuite * ap TestWriteClientCallback callback; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); app::WriteClient writeClient(engine->GetExchangeManager(), &callback, Optional::Missing()); @@ -434,7 +435,7 @@ void TestWriteInteraction::TestWriteRoundtripWithClusterObjectsVersionMatch(nlTe TestWriteClientCallback callback; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); app::WriteClient writeClient(engine->GetExchangeManager(), &callback, Optional::Missing()); @@ -483,7 +484,7 @@ void TestWriteInteraction::TestWriteRoundtripWithClusterObjectsVersionMismatch(n TestWriteClientCallback callback; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); app::WriteClient writeClient(engine->GetExchangeManager(), &callback, Optional::Missing()); @@ -534,7 +535,7 @@ void TestWriteInteraction::TestWriteRoundtrip(nlTestSuite * apSuite, void * apCo TestWriteClientCallback callback; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); app::WriteClient writeClient(engine->GetExchangeManager(), &callback, Optional::Missing()); @@ -575,7 +576,7 @@ void TestWriteInteraction::TestWriteHandlerReceiveInvalidMessage(nlTestSuite * a TestWriteClientCallback writeCallback; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); app::WriteClient writeClient(&ctx.GetExchangeManager(), &writeCallback, Optional::Missing(), @@ -643,7 +644,7 @@ void TestWriteInteraction::TestWriteHandlerInvalidateFabric(nlTestSuite * apSuit TestWriteClientCallback writeCallback; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); app::WriteClient writeClient(&ctx.GetExchangeManager(), &writeCallback, Optional::Missing(), @@ -725,7 +726,7 @@ void TestWriteInteraction::TestWriteInvalidMessage1(nlTestSuite * apSuite, void TestWriteClientCallback callback; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); app::WriteClient writeClient(engine->GetExchangeManager(), &callback, Optional::Missing()); @@ -796,7 +797,7 @@ void TestWriteInteraction::TestWriteInvalidMessage2(nlTestSuite * apSuite, void TestWriteClientCallback callback; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); app::WriteClient writeClient(engine->GetExchangeManager(), &callback, Optional::Missing()); @@ -866,7 +867,7 @@ void TestWriteInteraction::TestWriteInvalidMessage3(nlTestSuite * apSuite, void TestWriteClientCallback callback; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); app::WriteClient writeClient(engine->GetExchangeManager(), &callback, Optional::Missing()); @@ -937,7 +938,7 @@ void TestWriteInteraction::TestWriteInvalidMessage4(nlTestSuite * apSuite, void TestWriteClientCallback callback; auto * engine = chip::app::InteractionModelEngine::GetInstance(); - err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); app::WriteClient writeClient(engine->GetExchangeManager(), &callback, Optional::Missing()); diff --git a/src/app/tests/integration/BUILD.gn b/src/app/tests/integration/BUILD.gn index 05e06d897ca690..0381691b43ffbd 100644 --- a/src/app/tests/integration/BUILD.gn +++ b/src/app/tests/integration/BUILD.gn @@ -21,6 +21,7 @@ assert(chip_build_tools) executable("chip-im-initiator") { sources = [ + "${chip_root}/src/app/reporting/tests/MockReportScheduler.cpp", "RequiredPrivilegeStubs.cpp", "chip_im_initiator.cpp", "common.cpp", @@ -41,6 +42,7 @@ executable("chip-im-initiator") { executable("chip-im-responder") { sources = [ + "${chip_root}/src/app/reporting/tests/MockReportScheduler.cpp", "MockEvents.cpp", "RequiredPrivilegeStubs.cpp", "chip_im_responder.cpp", diff --git a/src/app/tests/integration/chip_im_initiator.cpp b/src/app/tests/integration/chip_im_initiator.cpp index 24ed65a26869f9..1353e211c0ac36 100644 --- a/src/app/tests/integration/chip_im_initiator.cpp +++ b/src/app/tests/integration/chip_im_initiator.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -731,7 +732,8 @@ int main(int argc, char * argv[]) err = gMessageCounterManager.Init(&gExchangeManager); SuccessOrExit(err); - err = chip::app::InteractionModelEngine::GetInstance()->Init(&gExchangeManager, &gFabricTable); + err = chip::app::InteractionModelEngine::GetInstance()->Init(&gExchangeManager, &gFabricTable, + chip::app::reporting::GetDefaultReportScheduler()); SuccessOrExit(err); // Start the CHIP connection to the CHIP im responder. diff --git a/src/app/tests/integration/chip_im_responder.cpp b/src/app/tests/integration/chip_im_responder.cpp index f7b003754da86a..b090425bebf093 100644 --- a/src/app/tests/integration/chip_im_responder.cpp +++ b/src/app/tests/integration/chip_im_responder.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -214,7 +215,8 @@ int main(int argc, char * argv[]) err = gMessageCounterManager.Init(&gExchangeManager); SuccessOrExit(err); - err = chip::app::InteractionModelEngine::GetInstance()->Init(&gExchangeManager, &gFabricTable); + err = chip::app::InteractionModelEngine::GetInstance()->Init(&gExchangeManager, &gFabricTable, + chip::app::reporting::GetDefaultReportScheduler()); SuccessOrExit(err); err = InitializeEventLogging(&gExchangeManager); diff --git a/src/controller/CHIPDeviceControllerFactory.cpp b/src/controller/CHIPDeviceControllerFactory.cpp index d2edbd840894dc..f39a3c7a65ed0e 100644 --- a/src/controller/CHIPDeviceControllerFactory.cpp +++ b/src/controller/CHIPDeviceControllerFactory.cpp @@ -25,6 +25,8 @@ #include #include +#include +#include #include #include #include @@ -170,6 +172,8 @@ CHIP_ERROR DeviceControllerFactory::InitSystemState(FactoryInitParams params) stateParams.exchangeMgr = chip::Platform::New(); stateParams.messageCounterManager = chip::Platform::New(); stateParams.groupDataProvider = params.groupDataProvider; + stateParams.timerDelegate = chip::Platform::New(); + stateParams.reportScheduler = chip::Platform::New(stateParams.timerDelegate); stateParams.sessionKeystore = params.sessionKeystore; // if no fabricTable was provided, create one and track it in stateParams for cleanup @@ -270,8 +274,8 @@ CHIP_ERROR DeviceControllerFactory::InitSystemState(FactoryInitParams params) stateParams.caseSessionManager = Platform::New(); ReturnErrorOnFailure(stateParams.caseSessionManager->Init(stateParams.systemLayer, sessionManagerConfig)); - ReturnErrorOnFailure(chip::app::InteractionModelEngine::GetInstance()->Init(stateParams.exchangeMgr, stateParams.fabricTable, - stateParams.caseSessionManager)); + ReturnErrorOnFailure(chip::app::InteractionModelEngine::GetInstance()->Init( + stateParams.exchangeMgr, stateParams.fabricTable, stateParams.reportScheduler, stateParams.caseSessionManager)); // store the system state mSystemState = chip::Platform::New(std::move(stateParams)); @@ -487,6 +491,18 @@ void DeviceControllerSystemState::Shutdown() mSessionMgr = nullptr; } + if (mReportScheduler != nullptr) + { + chip::Platform::Delete(mReportScheduler); + mReportScheduler = nullptr; + } + + if (mTimerDelegate != nullptr) + { + chip::Platform::Delete(mTimerDelegate); + mTimerDelegate = nullptr; + } + if (mTempFabricTable != nullptr) { mTempFabricTable->Shutdown(); diff --git a/src/controller/CHIPDeviceControllerFactory.h b/src/controller/CHIPDeviceControllerFactory.h index 11f84e6a42c58b..f807f39e1fd570 100644 --- a/src/controller/CHIPDeviceControllerFactory.h +++ b/src/controller/CHIPDeviceControllerFactory.h @@ -114,6 +114,7 @@ struct FactoryInitParams PersistentStorageDelegate * fabricIndependentStorage = nullptr; Credentials::CertificateValidityPolicy * certificateValidityPolicy = nullptr; Credentials::GroupDataProvider * groupDataProvider = nullptr; + app::reporting::ReportScheduler::TimerDelegate * timerDelegate = nullptr; Crypto::SessionKeystore * sessionKeystore = nullptr; Inet::EndPointManager * tcpEndPointManager = nullptr; Inet::EndPointManager * udpEndPointManager = nullptr; diff --git a/src/controller/CHIPDeviceControllerSystemState.h b/src/controller/CHIPDeviceControllerSystemState.h index e191411d0016fa..37f85b1cd90784 100644 --- a/src/controller/CHIPDeviceControllerSystemState.h +++ b/src/controller/CHIPDeviceControllerSystemState.h @@ -31,6 +31,7 @@ #include #include +#include #include #include #include @@ -98,6 +99,8 @@ struct DeviceControllerSystemStateParams SessionSetupPool * sessionSetupPool = nullptr; CASEClientPool * caseClientPool = nullptr; FabricTable::Delegate * fabricTableDelegate = nullptr; + chip::app::reporting::ReportScheduler::TimerDelegate * timerDelegate = nullptr; + chip::app::reporting::ReportScheduler * reportScheduler = nullptr; }; // A representation of the internal state maintained by the DeviceControllerFactory. @@ -127,9 +130,9 @@ class DeviceControllerSystemState mUnsolicitedStatusHandler(params.unsolicitedStatusHandler), mExchangeMgr(params.exchangeMgr), mMessageCounterManager(params.messageCounterManager), mFabrics(params.fabricTable), mCASEServer(params.caseServer), mCASESessionManager(params.caseSessionManager), mSessionSetupPool(params.sessionSetupPool), - mCASEClientPool(params.caseClientPool), mGroupDataProvider(params.groupDataProvider), - mSessionKeystore(params.sessionKeystore), mFabricTableDelegate(params.fabricTableDelegate), - mSessionResumptionStorage(std::move(params.sessionResumptionStorage)) + mCASEClientPool(params.caseClientPool), mGroupDataProvider(params.groupDataProvider), mTimerDelegate(params.timerDelegate), + mReportScheduler(params.reportScheduler), mSessionKeystore(params.sessionKeystore), + mFabricTableDelegate(params.fabricTableDelegate), mSessionResumptionStorage(std::move(params.sessionResumptionStorage)) { #if CONFIG_NETWORK_LAYER_BLE mBleLayer = params.bleLayer; @@ -168,7 +171,8 @@ class DeviceControllerSystemState return mSystemLayer != nullptr && mUDPEndPointManager != nullptr && mTransportMgr != nullptr && mSessionMgr != nullptr && mUnsolicitedStatusHandler != nullptr && mExchangeMgr != nullptr && mMessageCounterManager != nullptr && mFabrics != nullptr && mCASESessionManager != nullptr && mSessionSetupPool != nullptr && mCASEClientPool != nullptr && - mGroupDataProvider != nullptr && mSessionKeystore != nullptr; + mGroupDataProvider != nullptr && mReportScheduler != nullptr && mTimerDelegate != nullptr && + mSessionKeystore != nullptr; }; System::Layer * SystemLayer() const { return mSystemLayer; }; @@ -184,6 +188,8 @@ class DeviceControllerSystemState #endif CASESessionManager * CASESessionMgr() const { return mCASESessionManager; } Credentials::GroupDataProvider * GetGroupDataProvider() const { return mGroupDataProvider; } + chip::app::reporting::ReportScheduler * GetReportScheduler() const { return mReportScheduler; } + Crypto::SessionKeystore * GetSessionKeystore() const { return mSessionKeystore; } void SetTempFabricTable(FabricTable * tempFabricTable, bool enableServerInteractions) { @@ -211,6 +217,8 @@ class DeviceControllerSystemState SessionSetupPool * mSessionSetupPool = nullptr; CASEClientPool * mCASEClientPool = nullptr; Credentials::GroupDataProvider * mGroupDataProvider = nullptr; + app::reporting::ReportScheduler::TimerDelegate * mTimerDelegate = nullptr; + app::reporting::ReportScheduler * mReportScheduler = nullptr; Crypto::SessionKeystore * mSessionKeystore = nullptr; FabricTable::Delegate * mFabricTableDelegate = nullptr; Platform::UniquePtr mSessionResumptionStorage; diff --git a/src/lib/core/CHIPConfig.h b/src/lib/core/CHIPConfig.h index f83951ba3775c7..d1e6dc45cd8c7b 100644 --- a/src/lib/core/CHIPConfig.h +++ b/src/lib/core/CHIPConfig.h @@ -1556,5 +1556,17 @@ extern const char CHIP_NON_PRODUCTION_MARKER[]; #endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_RETRY_INTERVAL_SECS /** - * @} + * @def CHIP_CONFIG_SYNCHRONOUS_REPORTS_ENABLED + * + * @brief Controls whether the synchronized report scheduler is used. + * + * The use of the synchronous reports feature aims to reduce the number of times an ICD needs to wake up to emit reports to its + * various subscribers. + */ +#ifndef CHIP_CONFIG_SYNCHRONOUS_REPORTS_ENABLED +#define CHIP_CONFIG_SYNCHRONOUS_REPORTS_ENABLED 0 +#endif + +/** + * @} */