From 17646045a1a3a9b3ba4af1b2ae10c1342cf61343 Mon Sep 17 00:00:00 2001 From: Song GUO Date: Thu, 16 Jun 2022 21:26:56 +0800 Subject: [PATCH] [IM] Update read transaction resource minimas (#18884) * [IM] Update read transaction resource minimas * Update test cases * Fix build, add more tests for the PASE sessions * Address some comments * Add GetGuaranteedReadRequestsPerFabric * Update * Update tests * Address comments * Update --- src/app/InteractionModelEngine.cpp | 309 ++++- src/app/InteractionModelEngine.h | 188 +-- src/app/ReadHandler.cpp | 12 +- src/app/ReadHandler.h | 4 +- src/controller/tests/data_model/TestRead.cpp | 1082 ++++++++++++++++-- src/lib/core/CHIPConfig.h | 52 +- src/messaging/tests/MessagingContext.cpp | 29 + src/messaging/tests/MessagingContext.h | 14 +- 8 files changed, 1468 insertions(+), 222 deletions(-) diff --git a/src/app/InteractionModelEngine.cpp b/src/app/InteractionModelEngine.cpp index 6134d9ffb753ae..61a7523f413ff0 100644 --- a/src/app/InteractionModelEngine.cpp +++ b/src/app/InteractionModelEngine.cpp @@ -271,12 +271,12 @@ CHIP_ERROR InteractionModelEngine::OnInvokeCommandRequest(Messaging::ExchangeCon if (commandHandler == nullptr) { ChipLogProgress(InteractionModel, "no resource for Invoke interaction"); - aStatus = Protocols::InteractionModel::Status::Busy; + aStatus = Status::Busy; return CHIP_ERROR_NO_MEMORY; } ReturnErrorOnFailure( commandHandler->OnInvokeCommandRequest(apExchangeContext, aPayloadHeader, std::move(aPayload), aIsTimedInvoke)); - aStatus = Protocols::InteractionModel::Status::Success; + aStatus = Status::Success; return CHIP_NO_ERROR; } @@ -384,18 +384,53 @@ Protocols::InteractionModel::Status InteractionModelEngine::OnReadInitialRequest } } -#if CONFIG_IM_BUILD_FOR_UNIT_TEST - size_t handlerPoolCapacity = mReadHandlers.Capacity(); - if (mReadHandlerCapacityOverride != -1) + if (aInteractionType == ReadHandler::InteractionType::Read) { - handlerPoolCapacity = (size_t) mReadHandlerCapacityOverride; - } + System::PacketBufferTLVReader reader; + reader.Init(aPayload.Retain()); - if ((handlerPoolCapacity - GetNumActiveReadHandlers()) == 0) - { - return Status::ResourceExhausted; + ReadRequestMessage::Parser readRequestParser; + VerifyOrReturnError(readRequestParser.Init(reader) == CHIP_NO_ERROR, Status::Failure); + + { + size_t requestedAttributePathCount = 0; + size_t requestedEventPathCount = 0; + AttributePathIBs::Parser attributePathListParser; + CHIP_ERROR err = readRequestParser.GetAttributeRequests(&attributePathListParser); + if (err == CHIP_NO_ERROR) + { + TLV::TLVReader pathReader; + attributePathListParser.GetReader(&pathReader); + ReturnErrorCodeIf(TLV::Utilities::Count(pathReader, requestedAttributePathCount, false) != CHIP_NO_ERROR, + Status::InvalidAction); + } + else if (err != CHIP_ERROR_END_OF_TLV) + { + return Status::InvalidAction; + } + EventPathIBs::Parser eventpathListParser; + err = readRequestParser.GetEventRequests(&eventpathListParser); + if (err == CHIP_NO_ERROR) + { + TLV::TLVReader pathReader; + eventpathListParser.GetReader(&pathReader); + ReturnErrorCodeIf(TLV::Utilities::Count(pathReader, requestedEventPathCount, false) != CHIP_NO_ERROR, + Status::InvalidAction); + } + else if (err != CHIP_ERROR_END_OF_TLV) + { + return Status::InvalidAction; + } + + // The following cast is safe, since we can only hold a few tens of paths in one request. + Status checkResult = EnsureResourceForRead(apExchangeContext->GetSessionHandle()->GetFabricIndex(), + requestedAttributePathCount, requestedEventPathCount); + if (checkResult != Status::Success) + { + return checkResult; + } + } } -#endif // 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. @@ -445,13 +480,13 @@ CHIP_ERROR InteractionModelEngine::OnTimedRequest(Messaging::ExchangeContext * a if (handler == nullptr) { ChipLogProgress(InteractionModel, "no resource for Timed interaction"); - aStatus = Protocols::InteractionModel::Status::Busy; + aStatus = Status::Busy; return CHIP_ERROR_NO_MEMORY; } // The timed handler takes over handling of this exchange and will do its // own status reporting as needed. - aStatus = Protocols::InteractionModel::Status::Success; + aStatus = Status::Success; apExchangeContext->SetDelegate(handler); return handler->OnMessageReceived(apExchangeContext, aPayloadHeader, std::move(aPayload)); } @@ -502,7 +537,7 @@ CHIP_ERROR InteractionModelEngine::OnMessageReceived(Messaging::ExchangeContext { using namespace Protocols::InteractionModel; - Protocols::InteractionModel::Status status = Protocols::InteractionModel::Status::Failure; + Protocols::InteractionModel::Status status = Status::Failure; // Group Message can only be an InvokeCommandRequest or WriteRequest if (apExchangeContext->IsGroupExchangeContext() && @@ -533,7 +568,7 @@ CHIP_ERROR InteractionModelEngine::OnMessageReceived(Messaging::ExchangeContext else if (aPayloadHeader.HasMessageType(Protocols::InteractionModel::MsgType::ReportData)) { ReturnErrorOnFailure(OnUnsolicitedReportData(apExchangeContext, aPayloadHeader, std::move(aPayload))); - status = Protocols::InteractionModel::Status::Success; + status = Status::Success; } else if (aPayloadHeader.HasMessageType(MsgType::TimedRequest)) { @@ -544,7 +579,7 @@ CHIP_ERROR InteractionModelEngine::OnMessageReceived(Messaging::ExchangeContext ChipLogProgress(InteractionModel, "Msg type %d not supported", aPayloadHeader.GetMessageType()); } - if (status != Protocols::InteractionModel::Status::Success && !apExchangeContext->IsGroupExchangeContext()) + if (status != Status::Success && !apExchangeContext->IsGroupExchangeContext()) { return StatusResponse::Send(status, apExchangeContext, false /*aExpectResponse*/); } @@ -564,10 +599,10 @@ void InteractionModelEngine::AddReadClient(ReadClient * apReadClient) mpActiveReadClientList = apReadClient; } -bool InteractionModelEngine::TrimFabric(FabricIndex aFabricIndex, bool aForceEvict) +bool InteractionModelEngine::TrimFabricForSubscriptions(FabricIndex aFabricIndex, bool aForceEvict) { - const size_t pathPoolCapacity = GetPathPoolCapacity(); - const size_t readHandlerPoolCapacity = GetReadHandlerPoolCapacity(); + const size_t pathPoolCapacity = GetPathPoolCapacityForSubscriptions(); + const size_t readHandlerPoolCapacity = GetReadHandlerPoolCapacityForSubscriptions(); uint8_t fabricCount = mpFabricTable->FabricCount(); size_t attributePathsSubscribedByCurrentFabric = 0; @@ -579,14 +614,14 @@ bool InteractionModelEngine::TrimFabric(FabricIndex aFabricIndex, bool aForceEvi return false; } - size_t perFabricPathCapacity = - static_cast(pathPoolCapacity - kReservedPathsForReads) / static_cast(fabricCount); - size_t perFabricSubscriptionCapacity = - static_cast(readHandlerPoolCapacity - kReservedHandlersForReads) / static_cast(fabricCount); + // Note: This is OK only when we have assumed the fabricCount is not zero. Should be revised when adding support to + // subscriptions on PASE sessions. + size_t perFabricPathCapacity = pathPoolCapacity / static_cast(fabricCount); + size_t perFabricSubscriptionCapacity = readHandlerPoolCapacity / static_cast(fabricCount); ReadHandler * candidate = nullptr; - size_t candicateAttributePathsUsed = 0; - size_t candicateEventPathsUsed = 0; + size_t candidateAttributePathsUsed = 0; + size_t candidateEventPathsUsed = 0; // It is safe to use & here since this function will be called on current stack. mReadHandlers.ForEachActiveObject([&](ReadHandler * handler) { @@ -608,17 +643,17 @@ bool InteractionModelEngine::TrimFabric(FabricIndex aFabricIndex, bool aForceEvi } // This handler uses more resources than the one we picked before. else if ((attributePathsUsed > perFabricPathCapacity || eventPathsUsed > perFabricPathCapacity) && - (candicateAttributePathsUsed <= perFabricPathCapacity && candicateEventPathsUsed <= perFabricPathCapacity)) + (candidateAttributePathsUsed <= perFabricPathCapacity && candidateEventPathsUsed <= perFabricPathCapacity)) { candidate = handler; - candicateAttributePathsUsed = attributePathsUsed; - candicateEventPathsUsed = eventPathsUsed; + candidateAttributePathsUsed = attributePathsUsed; + candidateEventPathsUsed = eventPathsUsed; } // This handler is older than the one we picked before. - else if (handler->GetSubscriptionStartGeneration() < candidate->GetSubscriptionStartGeneration() && + else if (handler->GetTransactionStartGeneration() < candidate->GetTransactionStartGeneration() && // And the level of resource usage is the same (both exceed or neither exceed) ((attributePathsUsed > perFabricPathCapacity || eventPathsUsed > perFabricPathCapacity) == - (candicateAttributePathsUsed > perFabricPathCapacity || candicateEventPathsUsed > perFabricPathCapacity))) + (candidateAttributePathsUsed > perFabricPathCapacity || candidateEventPathsUsed > perFabricPathCapacity))) { candidate = handler; } @@ -652,14 +687,13 @@ bool InteractionModelEngine::EnsureResourceForSubscription(FabricIndex aFabricIn // Don't couple with read requests, always reserve enough resource for read requests. - const size_t pathPoolCapacity = GetPathPoolCapacity(); - const size_t readHandlerPoolCapacity = GetReadHandlerPoolCapacity(); + const size_t pathPoolCapacity = GetPathPoolCapacityForSubscriptions(); + const size_t readHandlerPoolCapacity = GetReadHandlerPoolCapacityForSubscriptions(); // If we return early here, the compiler will complain about the unreachable code, so we add a always-true check. - const size_t attributePathCap = allowUnlimited ? SIZE_MAX : static_cast(pathPoolCapacity - kReservedPathsForReads); - const size_t eventPathCap = allowUnlimited ? SIZE_MAX : static_cast(pathPoolCapacity - kReservedPathsForReads); - const size_t readHandlerCap = - allowUnlimited ? SIZE_MAX : static_cast(readHandlerPoolCapacity - kReservedHandlersForReads); + const size_t attributePathCap = allowUnlimited ? SIZE_MAX : pathPoolCapacity; + const size_t eventPathCap = allowUnlimited ? SIZE_MAX : pathPoolCapacity; + const size_t readHandlerCap = allowUnlimited ? SIZE_MAX : readHandlerPoolCapacity; size_t usedAttributePaths = 0; size_t usedEventPaths = 0; @@ -699,7 +733,7 @@ bool InteractionModelEngine::EnsureResourceForSubscription(FabricIndex aFabricIn } const auto evictAndUpdateResourceUsage = [&](FabricIndex fabricIndex, bool forceEvict) { - bool ret = TrimFabric(fabricIndex, forceEvict); + bool ret = TrimFabricForSubscriptions(fabricIndex, forceEvict); countResourceUsage(); return ret; }; @@ -751,7 +785,78 @@ bool InteractionModelEngine::EnsureResourceForSubscription(FabricIndex aFabricIn return true; } -CHIP_ERROR InteractionModelEngine::CanEstablishReadTransaction(const ReadHandler * apReadHandler) +bool InteractionModelEngine::TrimFabricForRead(FabricIndex aFabricIndex) +{ + const size_t guaranteedReadRequestsPerFabric = GetGuaranteedReadRequestsPerFabric(); + const size_t minSupportedPathsPerFabricForRead = guaranteedReadRequestsPerFabric * kMinSupportedPathsPerReadRequest; + + size_t attributePathsUsedByCurrentFabric = 0; + size_t eventPathsUsedByCurrentFabric = 0; + size_t readTransactionsOnCurrentFabric = 0; + + ReadHandler * candidate = nullptr; + size_t candidateAttributePathsUsed = 0; + size_t candidateEventPathsUsed = 0; + + // It is safe to use & here since this function will be called on current stack. + mReadHandlers.ForEachActiveObject([&](ReadHandler * handler) { + if (handler->GetAccessingFabricIndex() != aFabricIndex || !handler->IsType(ReadHandler::InteractionType::Read)) + { + return Loop::Continue; + } + + size_t attributePathsUsed = handler->GetAttributePathCount(); + size_t eventPathsUsed = handler->GetEventPathCount(); + + attributePathsUsedByCurrentFabric += attributePathsUsed; + eventPathsUsedByCurrentFabric += eventPathsUsed; + readTransactionsOnCurrentFabric++; + + if (candidate == nullptr) + { + candidate = handler; + } + // Oversized read handlers will be evicted first. + else if ((attributePathsUsed > kMinSupportedPathsPerReadRequest || eventPathsUsed > kMinSupportedPathsPerReadRequest) && + (candidateAttributePathsUsed <= kMinSupportedPathsPerReadRequest && + candidateEventPathsUsed <= kMinSupportedPathsPerReadRequest)) + { + candidate = handler; + } + // Read Handlers are "first come first served", so we give eariler read transactions a higher priority. + else if (handler->GetTransactionStartGeneration() > candidate->GetTransactionStartGeneration() && + // And the level of resource usage is the same (both exceed or neither exceed) + ((attributePathsUsed > kMinSupportedPathsPerReadRequest || eventPathsUsed > kMinSupportedPathsPerReadRequest) == + (candidateAttributePathsUsed > kMinSupportedPathsPerReadRequest || + candidateEventPathsUsed > kMinSupportedPathsPerReadRequest))) + { + candidate = handler; + } + + if (candidate == handler) + { + candidateAttributePathsUsed = attributePathsUsed; + candidateEventPathsUsed = eventPathsUsed; + } + return Loop::Continue; + }); + + if (candidate != nullptr && + ((attributePathsUsedByCurrentFabric > minSupportedPathsPerFabricForRead || + eventPathsUsedByCurrentFabric > minSupportedPathsPerFabricForRead || + readTransactionsOnCurrentFabric > guaranteedReadRequestsPerFabric) || + // Always evict the transactions on PASE sessions if the fabric table is full. + (aFabricIndex == kUndefinedFabricIndex && mpFabricTable->FabricCount() == GetConfigMaxFabrics()))) + { + candidate->Abort(); + return true; + } + return false; +} + +Protocols::InteractionModel::Status InteractionModelEngine::EnsureResourceForRead(FabricIndex aFabricIndex, + size_t aRequestedAttributePathCount, + size_t aRequestedEventPathCount) { #if CHIP_SYSTEM_CONFIG_POOL_USE_HEAP && !CHIP_CONFIG_IM_FORCE_FABRIC_QUOTA_CHECK #if CONFIG_IM_BUILD_FOR_UNIT_TEST @@ -764,34 +869,126 @@ CHIP_ERROR InteractionModelEngine::CanEstablishReadTransaction(const ReadHandler const bool allowUnlimited = false; #endif // CHIP_SYSTEM_CONFIG_POOL_USE_HEAP && !CHIP_CONFIG_IM_FORCE_FABRIC_QUOTA_CHECK - FabricIndex currentFabricIndex = apReadHandler->GetAccessingFabricIndex(); - size_t activeReadHandlersOnCurrentFabric = 0; + // If we return early here, the compiler will complain about the unreachable code, so we add a always-true check. + const size_t attributePathCap = allowUnlimited ? SIZE_MAX : GetPathPoolCapacityForReads(); + const size_t eventPathCap = allowUnlimited ? SIZE_MAX : GetPathPoolCapacityForReads(); + const size_t readHandlerCap = allowUnlimited ? SIZE_MAX : GetReadHandlerPoolCapacityForReads(); - // It is safe to use & here since this function will be called on current stack. - if (apReadHandler->GetAttributePathCount() > kReservedPathsPerReadRequest || - apReadHandler->GetEventPathCount() > kReservedPathsPerReadRequest || - apReadHandler->GetDataVersionFilterCount() > kReservedPathsPerReadRequest) + const size_t guaranteedReadRequestsPerFabric = GetGuaranteedReadRequestsPerFabric(); + const size_t guaranteedPathsPerFabric = kMinSupportedPathsPerReadRequest * guaranteedReadRequestsPerFabric; + + size_t usedAttributePaths = 0; + size_t usedEventPaths = 0; + size_t usedReadHandlers = 0; + + auto countResourceUsage = [&]() { + usedAttributePaths = 0; + usedEventPaths = 0; + usedReadHandlers = 0; + mReadHandlers.ForEachActiveObject([&](auto * handler) { + if (!handler->IsType(ReadHandler::InteractionType::Read)) + { + return Loop::Continue; + } + usedAttributePaths += handler->GetAttributePathCount(); + usedEventPaths += handler->GetEventPathCount(); + usedReadHandlers++; + return Loop::Continue; + }); + }; + + auto haveEnoughResourcesForTheRequest = [&]() { + return usedAttributePaths + aRequestedAttributePathCount <= attributePathCap && + usedEventPaths + aRequestedEventPathCount <= eventPathCap && usedReadHandlers < readHandlerCap; + }; + + countResourceUsage(); + + if (haveEnoughResourcesForTheRequest()) { - // Doesn't matter how many other reads are in progress; we are not - // going to handle this request. - return (allowUnlimited ? CHIP_NO_ERROR : CHIP_IM_GLOBAL_STATUS(PathsExhausted)); + // We have enough resources, then we serve the requests in a best-effort manner. + return Status::Success; } - mReadHandlers.ForEachActiveObject([&](ReadHandler * handler) { - if (handler->GetAccessingFabricIndex() == currentFabricIndex && handler->IsType(ReadHandler::InteractionType::Read)) + if ((aRequestedAttributePathCount > kMinSupportedPathsPerReadRequest && + usedAttributePaths + aRequestedAttributePathCount > attributePathCap) || + (aRequestedEventPathCount > kMinSupportedPathsPerReadRequest && usedEventPaths + aRequestedEventPathCount > eventPathCap)) + { + // We cannot offer enough resources, and the read transaction is requesting more than the spec limit. + return Status::PathsExhausted; + } + + // If we have commissioned CHIP_CONFIG_MAX_FABRICS already, and this transaction doesn't have an associated fabric index, reject + // the request if we don't have sufficient resources for this request. + if (mpFabricTable->FabricCount() == GetConfigMaxFabrics() && aFabricIndex == kUndefinedFabricIndex) + { + return Status::Busy; + } + + size_t usedAttributePathsInFabric = 0; + size_t usedEventPathsInFabric = 0; + size_t usedReadHandlersInFabric = 0; + mReadHandlers.ForEachActiveObject([&](auto * handler) { + if (!handler->IsType(ReadHandler::InteractionType::Read) || handler->GetAccessingFabricIndex() != aFabricIndex) { - activeReadHandlersOnCurrentFabric++; + return Loop::Continue; } + usedAttributePathsInFabric += handler->GetAttributePathCount(); + usedEventPathsInFabric += handler->GetEventPathCount(); + usedReadHandlersInFabric++; return Loop::Continue; }); - // The incoming read handler here is also counted above. - if (activeReadHandlersOnCurrentFabric > kReservedReadHandlersPerFabricForReadRequests) + // Busy, since there are already some read requests ongoing on this fabric, please retry later. + if (usedAttributePathsInFabric + aRequestedAttributePathCount > guaranteedPathsPerFabric || + usedEventPathsInFabric + aRequestedEventPathCount > guaranteedPathsPerFabric || + usedReadHandlersInFabric >= guaranteedReadRequestsPerFabric) { - return (allowUnlimited ? CHIP_NO_ERROR : CHIP_IM_GLOBAL_STATUS(Busy)); + return Status::Busy; } - return CHIP_NO_ERROR; + const auto evictAndUpdateResourceUsage = [&](FabricIndex fabricIndex) { + bool ret = TrimFabricForRead(fabricIndex); + countResourceUsage(); + return ret; + }; + + // + // At this point, we have an inbound request that respects minimas but we still don't have enough resources to handle it. Which + // means that we definitely have handlers on existing fabrics that are over limits and need to evict at least one of them to + // make space. + // + bool didEvictHandler = true; + while (didEvictHandler) + { + didEvictHandler = false; + didEvictHandler = didEvictHandler || evictAndUpdateResourceUsage(kUndefinedFabricIndex); + if (haveEnoughResourcesForTheRequest()) + { + break; + } + // If the fabric table is full, we won't evict read requests from normal fabrics before we have evicted all read requests + // from PASE sessions. + if (mpFabricTable->FabricCount() == GetConfigMaxFabrics() && didEvictHandler) + { + continue; + } + for (const auto & fabric : *mpFabricTable) + { + didEvictHandler = didEvictHandler || evictAndUpdateResourceUsage(fabric.GetFabricIndex()); + // If we now have enough resources to serve this request, stop evicting things. + if (haveEnoughResourcesForTheRequest()) + { + break; + } + } + } + + // Now all fabrics are not oversized (since we have trimmed the oversized fabrics in the loop above), and the read handler is + // also not oversized, we should be able to handle this read transaction. + VerifyOrDie(haveEnoughResourcesForTheRequest()); + + return Status::Success; } void InteractionModelEngine::RemoveReadClient(ReadClient * apReadClient) @@ -1178,14 +1375,14 @@ bool InteractionModelEngine::HasActiveRead() uint16_t InteractionModelEngine::GetMinSubscriptionsPerFabric() const { uint8_t fabricCount = mpFabricTable->FabricCount(); - const size_t readHandlerPoolCapacity = GetReadHandlerPoolCapacity(); + const size_t readHandlerPoolCapacity = GetReadHandlerPoolCapacityForSubscriptions(); if (fabricCount == 0) { return kMinSupportedSubscriptionsPerFabric; } - size_t perFabricSubscriptionCapacity = static_cast(readHandlerPoolCapacity - kReservedPathsForReads) / fabricCount; + size_t perFabricSubscriptionCapacity = readHandlerPoolCapacity / fabricCount; return static_cast(perFabricSubscriptionCapacity); } diff --git a/src/app/InteractionModelEngine.h b/src/app/InteractionModelEngine.h index 9dd82052b6eed9..6f1090e4f2b692 100644 --- a/src/app/InteractionModelEngine.h +++ b/src/app/InteractionModelEngine.h @@ -87,10 +87,11 @@ class InteractionModelEngine : public Messaging::UnsolicitedMessageHandler, * Spec 8.5.1 A publisher SHALL always ensure that every fabric the node is commissioned into can create at least three * subscriptions to the publisher and that each subscription SHALL support at least 3 attribute/event paths. */ - static constexpr size_t kMinSupportedSubscriptionsPerFabric = 2; - static constexpr size_t kMinSupportedPathsPerSubscription = 2; - static constexpr size_t kReservedPathsPerReadRequest = 9; - static constexpr size_t kReservedReadHandlersPerFabricForReadRequests = 1; + static constexpr size_t kMinSupportedSubscriptionsPerFabric = 2; + static constexpr size_t kMinSupportedPathsPerSubscription = 2; + static constexpr size_t kMinSupportedPathsPerReadRequest = 9; + static constexpr size_t kMinSupportedReadRequestsPerFabric = 1; + static constexpr size_t kReadHandlerPoolSize = CHIP_IM_MAX_NUM_SUBSCRIPTIONS + CHIP_IM_MAX_NUM_READS; // TODO: Per spec, the above numbers should be 3, 3, 9, 1, however, we use a lower limit to reduce the memory usage and should // fix it when we have reduced the memory footprint of ReadHandlers. @@ -242,21 +243,6 @@ class InteractionModelEngine : public Messaging::UnsolicitedMessageHandler, */ bool HasConflictWriteRequests(const WriteHandler * apWriteHandler, const ConcreteAttributePath & aPath); - /** - * We only allow one active read transaction per fabric, and the number of paths used is limited by - * kReservedPathsPerReadRequest. This function will check if the given ReadHandler will exceed the limitations for the accessing - * fabric. - * - * If CHIP_NO_ERROR is returned, it's OK to proceed with the read. - * - * Otherwise the CHIP_ERROR encodes an interaction model status that needs - * to turn into a Status Response to the client. - * - * TODO: (#17418) We are now reserving resources for read requests, could be changed to similar algorithm for read resources - * minimas. - */ - CHIP_ERROR CanEstablishReadTransaction(const ReadHandler * apReadHandler); - /** * Select the oldest (and the one that exceeds the per subscription resource minimum if there are any) read handler on the * fabric with the given fabric index. Evict it when the fabric uses more resources than the per fabric quota or aForceEvict is @@ -264,7 +250,18 @@ class InteractionModelEngine : public Messaging::UnsolicitedMessageHandler, * * @retval Whether we have evicted a subscription. */ - bool TrimFabric(FabricIndex aFabricIndex, bool aForceEvict); + bool TrimFabricForSubscriptions(FabricIndex aFabricIndex, bool aForceEvict); + + /** + * Select a read handler and abort the read transaction if the fabric is using more resources (number of paths or number of read + * handlers) then we guaranteed. + * + * - The youngest oversized read handlers will be chosen first. + * - If there are no oversized read handlers, the youngest read handlers will be chosen. + * + * @retval Whether we have evicted a read transaction. + */ + bool TrimFabricForRead(FabricIndex aFabricIndex); uint16_t GetMinSubscriptionsPerFabric() const; @@ -275,37 +272,21 @@ class InteractionModelEngine : public Messaging::UnsolicitedMessageHandler, auto & GetReadHandlerPool() { return mReadHandlers; } // - // Override the maximal capacity of the underlying read handler pool to mimic - // out of memory scenarios in unit-tests. - // - // This function did not considered the resources reserved for read handlers, - // SetHandlerCapacityForSubscriptions if there are subscriptions in the tests. + // Override the maximal capacity of the fabric table only for interaction model engine // // If -1 is passed in, no override is instituted and default behavior resumes. // - void SetHandlerCapacity(int32_t sz) { mReadHandlerCapacityOverride = sz; } - - // - // Override the maximal capacity of the underlying attribute path pool and event path pool to mimic - // out of paths exhausted scenarios in unit-tests. - // - // This function did not considered the resources reserved for read handlers, - // SetPathPoolCapacityForSubscriptions if there are subscriptions in the tests. - // - // If -1 is passed in, no override is instituted and default behavior resumes. - // - void SetPathPoolCapacity(int32_t sz) { mPathPoolCapacityOverride = sz; } + void SetConfigMaxFabrics(int32_t sz) { mMaxNumFabricsOverride = sz; } // // Override the maximal capacity of the underlying read handler pool to mimic - // out of memory scenarios in unit-tests. + // out of memory scenarios in unit-tests. You need to SetConfigMaxFabrics to make GetGuaranteedReadRequestsPerFabric + // working correctly. // // If -1 is passed in, no override is instituted and default behavior resumes. // - void SetHandlerCapacityForSubscriptions(int32_t sz) - { - SetHandlerCapacity(sz == -1 ? -1 : sz + static_cast(kReservedHandlersForReads)); - } + void SetHandlerCapacityForReads(int32_t sz) { mReadHandlerCapacityForReadsOverride = sz; } + void SetHandlerCapacityForSubscriptions(int32_t sz) { mReadHandlerCapacityForSubscriptionsOverride = sz; } // // Override the maximal capacity of the underlying attribute path pool and event path pool to mimic @@ -313,10 +294,8 @@ class InteractionModelEngine : public Messaging::UnsolicitedMessageHandler, // // If -1 is passed in, no override is instituted and default behavior resumes. // - void SetPathPoolCapacityForSubscriptions(int32_t sz) - { - SetPathPoolCapacity(sz == -1 ? -1 : sz + static_cast(kReservedPathsForReads)); - } + void SetPathPoolCapacityForReads(int32_t sz) { mPathPoolCapacityForReadsOverride = sz; } + void SetPathPoolCapacityForSubscriptions(int32_t sz) { mPathPoolCapacityForSubscriptionsOverride = sz; } // // We won't limit the handler used per fabric on platforms that are using heap for memory pools, so we introduces a flag to @@ -416,26 +395,61 @@ class InteractionModelEngine : public Messaging::UnsolicitedMessageHandler, CHIP_ERROR ShutdownExistingSubscriptionsIfNeeded(Messaging::ExchangeContext * apExchangeContext, System::PacketBufferHandle && aPayload); - inline size_t GetPathPoolCapacity() const + inline size_t GetPathPoolCapacityForReads() const { #if CONFIG_IM_BUILD_FOR_UNIT_TEST - return (mPathPoolCapacityOverride == -1) ? CHIP_IM_SERVER_MAX_NUM_PATH_GROUPS - : static_cast(mPathPoolCapacityOverride); + return (mPathPoolCapacityForReadsOverride == -1) ? CHIP_IM_SERVER_MAX_NUM_PATH_GROUPS_FOR_READS + : static_cast(mPathPoolCapacityForReadsOverride); #else - return CHIP_IM_SERVER_MAX_NUM_PATH_GROUPS; + return CHIP_IM_SERVER_MAX_NUM_PATH_GROUPS_FOR_READS; #endif } - inline size_t GetReadHandlerPoolCapacity() const + inline size_t GetReadHandlerPoolCapacityForReads() const { #if CONFIG_IM_BUILD_FOR_UNIT_TEST - return (mReadHandlerCapacityOverride == -1) ? CHIP_IM_MAX_NUM_READ_HANDLER - : static_cast(mReadHandlerCapacityOverride); + return (mReadHandlerCapacityForReadsOverride == -1) ? CHIP_IM_MAX_NUM_READS + : static_cast(mReadHandlerCapacityForReadsOverride); #else - return CHIP_IM_MAX_NUM_READ_HANDLER; + return CHIP_IM_MAX_NUM_READS; #endif } + inline size_t GetPathPoolCapacityForSubscriptions() const + { +#if CONFIG_IM_BUILD_FOR_UNIT_TEST + return (mPathPoolCapacityForSubscriptionsOverride == -1) ? CHIP_IM_SERVER_MAX_NUM_PATH_GROUPS_FOR_SUBSCRIPTIONS + : static_cast(mPathPoolCapacityForSubscriptionsOverride); +#else + return CHIP_IM_SERVER_MAX_NUM_PATH_GROUPS_FOR_SUBSCRIPTIONS; +#endif + } + + inline size_t GetReadHandlerPoolCapacityForSubscriptions() const + { +#if CONFIG_IM_BUILD_FOR_UNIT_TEST + return (mReadHandlerCapacityForSubscriptionsOverride == -1) + ? CHIP_IM_MAX_NUM_SUBSCRIPTIONS + : static_cast(mReadHandlerCapacityForSubscriptionsOverride); +#else + return CHIP_IM_MAX_NUM_SUBSCRIPTIONS; +#endif + } + + inline uint8_t GetConfigMaxFabrics() const + { +#if CONFIG_IM_BUILD_FOR_UNIT_TEST + return (mMaxNumFabricsOverride == -1) ? CHIP_CONFIG_MAX_FABRICS : static_cast(mMaxNumFabricsOverride); +#else + return CHIP_CONFIG_MAX_FABRICS; +#endif + } + + inline size_t GetGuaranteedReadRequestsPerFabric() const + { + return GetReadHandlerPoolCapacityForReads() / GetConfigMaxFabrics(); + } + /** * Verify and ensure (by killing oldest read handlers that make the resources used by the current fabric exceed the fabric * quota) @@ -451,6 +465,29 @@ class InteractionModelEngine : public Messaging::UnsolicitedMessageHandler, bool EnsureResourceForSubscription(FabricIndex aFabricIndex, size_t aRequestedAttributePathCount, size_t aRequestedEventPathCount); + /** + * Verify and ensure (by killing oldest read handlers that make the resources used by the current fabric exceed the fabric + * quota) the resources for handling a new read transaction with the given resource requirments. + * - PASE sessions will be counted in a virtual fabric (i.e. kInvalidFabricIndex will be consided as a "valid" fabric in this + * function) + * - If the existing resources can serve this read transaction, this function will return Status::Success. + * - or if the resources used by read transactions in the fabric index meets the per fabric resource limit (i.e. 9 paths & 1 + * read) after accepting this read request, this function will always return Status::Success by evicting existing read + * transactions from other fabrics which are using more than the guaranteed minimum number of read. + * - or if the resources used by read transactions in the fabric index will exceed the per fabric resource limit (i.e. 9 paths & + * 1 read) after accepting this read request, this function will return a failure status without evicting any existing + * transaction. + * - However, read transactions on PASE sessions won't evict any existing read transactions when we have already commissioned + * CHIP_CONFIG_MAX_FABRICS fabrics on the device. + * + * @retval Status::Success: The read transaction can be accepted. + * @retval Status::Busy: The remaining resource is insufficient to handle this read request, and the accessing fabric for this + * read request will use more resources than we guaranteed, the client is expected to retry later. + * @retval Status::PathsExhausted: The attribute / event path pool is exhausted, and the read request is requesting more + * resources than we guaranteed. + */ + Status EnsureResourceForRead(FabricIndex aFabricIndex, size_t aRequestedAttributePathCount, size_t aRequestedEventPathCount); + template void ReleasePool(ObjectList *& aObjectList, ObjectPool, N> & aObjectPool); template @@ -465,31 +502,46 @@ class InteractionModelEngine : public Messaging::UnsolicitedMessageHandler, WriteHandler mWriteHandlers[CHIP_IM_MAX_NUM_WRITE_HANDLER]; reporting::Engine mReportingEngine; - static constexpr size_t kReservedHandlersForReads = kReservedReadHandlersPerFabricForReadRequests * (CHIP_CONFIG_MAX_FABRICS); - static constexpr size_t kReservedPathsForReads = kReservedPathsPerReadRequest * kReservedHandlersForReads; + static constexpr size_t kReservedHandlersForReads = kMinSupportedReadRequestsPerFabric * (CHIP_CONFIG_MAX_FABRICS); + static constexpr size_t kReservedPathsForReads = kMinSupportedPathsPerReadRequest * kReservedHandlersForReads; #if !CHIP_SYSTEM_CONFIG_POOL_USE_HEAP - static_assert(CHIP_IM_SERVER_MAX_NUM_PATH_GROUPS >= CHIP_CONFIG_MAX_FABRICS * - (kMinSupportedPathsPerSubscription * kMinSupportedSubscriptionsPerFabric + kReservedPathsPerReadRequest), - "CHIP_IM_SERVER_MAX_NUM_PATH_GROUPS is too small to match the requirements of spec 8.5.1"); - static_assert(CHIP_IM_MAX_NUM_READ_HANDLER >= CHIP_CONFIG_MAX_FABRICS * - (kMinSupportedSubscriptionsPerFabric + kReservedReadHandlersPerFabricForReadRequests), - "CHIP_IM_SERVER_MAX_NUM_PATH_GROUPS is too small to match the requirements of spec 8.5.1"); + static_assert(CHIP_IM_SERVER_MAX_NUM_PATH_GROUPS_FOR_SUBSCRIPTIONS >= + CHIP_CONFIG_MAX_FABRICS * (kMinSupportedPathsPerSubscription * kMinSupportedSubscriptionsPerFabric), + "CHIP_IM_SERVER_MAX_NUM_PATH_GROUPS_FOR_SUBSCRIPTIONS is too small to match the requirements of spec 8.5.1"); + static_assert(CHIP_IM_SERVER_MAX_NUM_PATH_GROUPS_FOR_READS >= + CHIP_CONFIG_MAX_FABRICS * (kMinSupportedReadRequestsPerFabric * kMinSupportedPathsPerReadRequest), + "CHIP_IM_SERVER_MAX_NUM_PATH_GROUPS_FOR_READS is too small to match the requirements of spec 8.5.1"); + static_assert(CHIP_IM_MAX_NUM_SUBSCRIPTIONS >= CHIP_CONFIG_MAX_FABRICS * kMinSupportedSubscriptionsPerFabric, + "CHIP_IM_MAX_NUM_SUBSCRIPTIONS is too small to match the requirements of spec 8.5.1"); + static_assert(CHIP_IM_MAX_NUM_READS >= CHIP_CONFIG_MAX_FABRICS * kMinSupportedReadRequestsPerFabric, + "CHIP_IM_MAX_NUM_READS is too small to match the requirements of spec 8.5.1"); #endif - ObjectPool, CHIP_IM_SERVER_MAX_NUM_PATH_GROUPS> mAttributePathPool; - ObjectPool, CHIP_IM_SERVER_MAX_NUM_PATH_GROUPS> mEventPathPool; - ObjectPool, CHIP_IM_SERVER_MAX_NUM_PATH_GROUPS> mDataVersionFilterPool; + ObjectPool, + CHIP_IM_SERVER_MAX_NUM_PATH_GROUPS_FOR_READS + CHIP_IM_SERVER_MAX_NUM_PATH_GROUPS_FOR_SUBSCRIPTIONS> + mAttributePathPool; + ObjectPool, + CHIP_IM_SERVER_MAX_NUM_PATH_GROUPS_FOR_READS + CHIP_IM_SERVER_MAX_NUM_PATH_GROUPS_FOR_SUBSCRIPTIONS> + mEventPathPool; + ObjectPool, + CHIP_IM_SERVER_MAX_NUM_PATH_GROUPS_FOR_READS + CHIP_IM_SERVER_MAX_NUM_PATH_GROUPS_FOR_SUBSCRIPTIONS> + mDataVersionFilterPool; - ObjectPool mReadHandlers; + ObjectPool mReadHandlers; ReadClient * mpActiveReadClientList = nullptr; ReadHandler::ApplicationCallback * mpReadHandlerApplicationCallback = nullptr; #if CONFIG_IM_BUILD_FOR_UNIT_TEST - int mReadHandlerCapacityOverride = -1; - int mPathPoolCapacityOverride = -1; + int mReadHandlerCapacityForSubscriptionsOverride = -1; + int mPathPoolCapacityForSubscriptionsOverride = -1; + + int mReadHandlerCapacityForReadsOverride = -1; + int mPathPoolCapacityForReadsOverride = -1; + + int mMaxNumFabricsOverride = -1; // We won't limit the handler used per fabric on platforms that are using heap for memory pools, so we introduces a flag to // enforce such check based on the configured size. This flag is used for unit tests only, there is another compare time flag diff --git a/src/app/ReadHandler.cpp b/src/app/ReadHandler.cpp index 2459e4e12f7008..d80292233fdcd8 100644 --- a/src/app/ReadHandler.cpp +++ b/src/app/ReadHandler.cpp @@ -41,10 +41,10 @@ ReadHandler::ReadHandler(ManagementCallback & apCallback, Messaging::ExchangeCon InteractionType aInteractionType) : mManagementCallback(apCallback) { - mpExchangeCtx = apExchangeContext; - mInteractionType = aInteractionType; - mLastWrittenEventsBytes = 0; - mSubscriptionStartGeneration = InteractionModelEngine::GetInstance()->GetReportingEngine().GetDirtySetGeneration(); + mpExchangeCtx = apExchangeContext; + mInteractionType = aInteractionType; + mLastWrittenEventsBytes = 0; + mTransactionStartGeneration = InteractionModelEngine::GetInstance()->GetReportingEngine().GetDirtySetGeneration(); mFlags.ClearAll(); mFlags.Set(ReadHandlerFlags::PrimingReports, true); if (apExchangeContext != nullptr) @@ -385,10 +385,6 @@ CHIP_ERROR ReadHandler::ProcessReadRequest(System::PacketBufferHandle && aPayloa } ReturnErrorOnFailure(err); - // Ensure the read transaction doesn't exceed the resources dedicated to - // read transactions. - ReturnErrorOnFailure(InteractionModelEngine::GetInstance()->CanEstablishReadTransaction(this)); - bool isFabricFiltered; ReturnErrorOnFailure(readRequestParser.GetIsFabricFiltered(&isFabricFiltered)); mFlags.Set(ReadHandlerFlags::FabricFiltered, isFabricFiltered); diff --git a/src/app/ReadHandler.h b/src/app/ReadHandler.h index eab32872245732..b3191760a5ae4c 100644 --- a/src/app/ReadHandler.h +++ b/src/app/ReadHandler.h @@ -315,7 +315,7 @@ class ReadHandler : public Messaging::ExchangeDelegate Transport::SecureSession * GetSession() const; SubjectDescriptor GetSubjectDescriptor() const { return GetSession()->GetSubjectDescriptor(); } - auto GetSubscriptionStartGeneration() const { return mSubscriptionStartGeneration; } + auto GetTransactionStartGeneration() const { return mTransactionStartGeneration; } void UnblockUrgentEventDelivery() { @@ -431,7 +431,7 @@ class ReadHandler : public Messaging::ExchangeDelegate // When we don't have enough resources for a new subscription, the oldest subscription might be evicted by interaction model // engine, the "oldest" subscription is the subscription with the smallest generation. - uint64_t mSubscriptionStartGeneration = 0; + uint64_t mTransactionStartGeneration = 0; SubscriptionId mSubscriptionId = 0; uint16_t mMinIntervalFloorSeconds = 0; diff --git a/src/controller/tests/data_model/TestRead.cpp b/src/controller/tests/data_model/TestRead.cpp index 8741685ed3cdc5..bb6edbdbf6cc58 100644 --- a/src/controller/tests/data_model/TestRead.cpp +++ b/src/controller/tests/data_model/TestRead.cpp @@ -39,13 +39,14 @@ using namespace chip::Protocols; namespace { -constexpr EndpointId kTestEndpointId = 1; -constexpr DataVersion kDataVersion = 5; -constexpr AttributeId kNeverEndAttributeid = Test::MockAttributeId(1); -bool expectedAttribute1 = true; -int16_t expectedAttribute2 = 42; -uint64_t expectedAttribute3 = 0xdeadbeef0000cafe; -uint8_t expectedAttribute4[256] = { +constexpr EndpointId kTestEndpointId = 1; +constexpr DataVersion kDataVersion = 5; +constexpr AttributeId kPerpetualAttributeid = Test::MockAttributeId(1); +constexpr ClusterId kPerpetualClusterId = Test::MockClusterId(2); +bool expectedAttribute1 = true; +int16_t expectedAttribute2 = 42; +uint64_t expectedAttribute3 = 0xdeadbeef0000cafe; +uint8_t expectedAttribute4[256] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, @@ -110,7 +111,8 @@ CHIP_ERROR ReadSingleClusterData(const Access::SubjectDescriptor & aSubjectDescr return valueEncoder.Encode(++totalReadCount); } - if (aPath.mClusterId == app::Clusters::TestCluster::Id && aPath.mAttributeId == kNeverEndAttributeid) + if (aPath.mClusterId == kPerpetualClusterId || + (aPath.mClusterId == app::Clusters::TestCluster::Id && aPath.mAttributeId == kPerpetualAttributeid)) { AttributeValueEncoder::AttributeEncodeState state = AttributeValueEncoder::AttributeEncodeState(); AttributeValueEncoder valueEncoder(aAttributeReports, aSubjectDescriptor.fabricIndex, aPath, @@ -231,7 +233,7 @@ class TestReadInteraction : public app::ReadHandler::ApplicationCallback static void TestReadSubscribeAttributeResponseWithCache(nlTestSuite * apSuite, void * apContext); static void TestReadHandler_KillOverQuotaSubscriptions(nlTestSuite * apSuite, void * apContext); static void TestReadHandler_KillOldestSubscriptions(nlTestSuite * apSuite, void * apContext); - static void TestReadHandler_TwoParallelReads(nlTestSuite * apSuite, void * apContext); + static void TestReadHandler_ParallelReads(nlTestSuite * apSuite, void * apContext); static void TestReadHandler_TooManyPaths(nlTestSuite * apSuite, void * apContext); static void TestReadHandler_TwoParallelReadsSecondTooManyPaths(nlTestSuite * apSuite, void * apContext); @@ -1531,11 +1533,11 @@ void TestReadInteraction::TestReadHandler_MultipleSubscriptions(nlTestSuite * ap app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(&gTestReadInteraction); // - // Try to issue parallel subscriptions that will exceed the value for CHIP_IM_MAX_NUM_READ_HANDLER. + // Try to issue parallel subscriptions that will exceed the value for app::InteractionModelEngine::kReadHandlerPoolSize. // If heap allocation is correctly setup, this should result in it successfully servicing more than the number // present in that define. // - for (int i = 0; i < (CHIP_IM_MAX_NUM_READ_HANDLER + 1); i++) + for (size_t i = 0; i < (app::InteractionModelEngine::kReadHandlerPoolSize + 1); i++) { NL_TEST_ASSERT(apSuite, Controller::SubscribeAttribute( @@ -1546,13 +1548,14 @@ void TestReadInteraction::TestReadHandler_MultipleSubscriptions(nlTestSuite * ap // There are too many messages and the test (gcc_debug, which includes many sanity checks) will be quite slow. Note: report // engine is using ScheduleWork which cannot be handled by DrainAndServiceIO correctly. ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(60), [&]() { - return numSuccessCalls == (CHIP_IM_MAX_NUM_READ_HANDLER + 1) && - numSubscriptionEstablishedCalls == (CHIP_IM_MAX_NUM_READ_HANDLER + 1); + return numSuccessCalls == (app::InteractionModelEngine::kReadHandlerPoolSize + 1) && + numSubscriptionEstablishedCalls == (app::InteractionModelEngine::kReadHandlerPoolSize + 1); }); - NL_TEST_ASSERT(apSuite, numSuccessCalls == (CHIP_IM_MAX_NUM_READ_HANDLER + 1)); - NL_TEST_ASSERT(apSuite, numSubscriptionEstablishedCalls == (CHIP_IM_MAX_NUM_READ_HANDLER + 1)); - NL_TEST_ASSERT(apSuite, gTestReadInteraction.mNumActiveSubscriptions == (CHIP_IM_MAX_NUM_READ_HANDLER + 1)); + NL_TEST_ASSERT(apSuite, numSuccessCalls == (app::InteractionModelEngine::kReadHandlerPoolSize + 1)); + NL_TEST_ASSERT(apSuite, numSubscriptionEstablishedCalls == (app::InteractionModelEngine::kReadHandlerPoolSize + 1)); + NL_TEST_ASSERT(apSuite, + gTestReadInteraction.mNumActiveSubscriptions == (app::InteractionModelEngine::kReadHandlerPoolSize + 1)); app::InteractionModelEngine::GetInstance()->ShutdownActiveReads(); @@ -2267,7 +2270,7 @@ void TestReadInteraction::TestReadHandler_MultipleReads(nlTestSuite * apSuite, v { TestContext & ctx = *static_cast(apContext); - static_assert(CHIP_IM_MAX_REPORTS_IN_FLIGHT <= CHIP_IM_MAX_NUM_READ_HANDLER, + static_assert(CHIP_IM_MAX_REPORTS_IN_FLIGHT <= app::InteractionModelEngine::kReadHandlerPoolSize, "How can we have more reports in flight than read handlers?"); MultipleReadHelper(apSuite, ctx, CHIP_IM_MAX_REPORTS_IN_FLIGHT); @@ -2281,7 +2284,7 @@ void TestReadInteraction::TestReadHandler_OneSubscribeMultipleReads(nlTestSuite { TestContext & ctx = *static_cast(apContext); - static_assert(CHIP_IM_MAX_REPORTS_IN_FLIGHT <= CHIP_IM_MAX_NUM_READ_HANDLER, + static_assert(CHIP_IM_MAX_REPORTS_IN_FLIGHT <= app::InteractionModelEngine::kReadHandlerPoolSize, "How can we have more reports in flight than read handlers?"); static_assert(CHIP_IM_MAX_REPORTS_IN_FLIGHT > 1, "We won't do any reads"); @@ -2296,7 +2299,7 @@ void TestReadInteraction::TestReadHandler_TwoSubscribesMultipleReads(nlTestSuite { TestContext & ctx = *static_cast(apContext); - static_assert(CHIP_IM_MAX_REPORTS_IN_FLIGHT <= CHIP_IM_MAX_NUM_READ_HANDLER, + static_assert(CHIP_IM_MAX_REPORTS_IN_FLIGHT <= app::InteractionModelEngine::kReadHandlerPoolSize, "How can we have more reports in flight than read handlers?"); static_assert(CHIP_IM_MAX_REPORTS_IN_FLIGHT > 2, "We won't do any reads"); @@ -2424,12 +2427,12 @@ void TestReadInteraction::TestReadHandler_MultipleSubscriptionsWithDataVersionFi }; // - // Try to issue parallel subscriptions that will exceed the value for CHIP_IM_MAX_NUM_READ_HANDLER. + // Try to issue parallel subscriptions that will exceed the value for app::InteractionModelEngine::kReadHandlerPoolSize. // If heap allocation is correctly setup, this should result in it successfully servicing more than the number // present in that define. // chip::Optional dataVersion(1); - for (int i = 0; i < (CHIP_IM_MAX_NUM_READ_HANDLER + 1); i++) + for (size_t i = 0; i < (app::InteractionModelEngine::kReadHandlerPoolSize + 1); i++) { NL_TEST_ASSERT(apSuite, Controller::SubscribeAttribute( @@ -2440,16 +2443,16 @@ void TestReadInteraction::TestReadHandler_MultipleSubscriptionsWithDataVersionFi // There are too many messages and the test (gcc_debug, which includes many sanity checks) will be quite slow. Note: report // engine is using ScheduleWork which cannot be handled by DrainAndServiceIO correctly. ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(30), [&]() { - return numSubscriptionEstablishedCalls == (CHIP_IM_MAX_NUM_READ_HANDLER + 1) && - numSuccessCalls == (CHIP_IM_MAX_NUM_READ_HANDLER + 1); + return numSubscriptionEstablishedCalls == (app::InteractionModelEngine::kReadHandlerPoolSize + 1) && + numSuccessCalls == (app::InteractionModelEngine::kReadHandlerPoolSize + 1); }); ChipLogError(Zcl, "Success call cnt: %u (expect %u) subscription cnt: %u (expect %u)", numSuccessCalls, - uint32_t(CHIP_IM_MAX_NUM_READ_HANDLER + 1), numSubscriptionEstablishedCalls, - uint32_t(CHIP_IM_MAX_NUM_READ_HANDLER + 1)); + uint32_t(app::InteractionModelEngine::kReadHandlerPoolSize + 1), numSubscriptionEstablishedCalls, + uint32_t(app::InteractionModelEngine::kReadHandlerPoolSize + 1)); - NL_TEST_ASSERT(apSuite, numSuccessCalls == (CHIP_IM_MAX_NUM_READ_HANDLER + 1)); - NL_TEST_ASSERT(apSuite, numSubscriptionEstablishedCalls == (CHIP_IM_MAX_NUM_READ_HANDLER + 1)); + NL_TEST_ASSERT(apSuite, numSuccessCalls == (app::InteractionModelEngine::kReadHandlerPoolSize + 1)); + NL_TEST_ASSERT(apSuite, numSubscriptionEstablishedCalls == (app::InteractionModelEngine::kReadHandlerPoolSize + 1)); app::InteractionModelEngine::GetInstance()->ShutdownActiveReads(); NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); @@ -2475,11 +2478,12 @@ void TestReadInteraction::TestReadHandlerResourceExhaustion_MultipleReads(nlTest auto onFailureCb = [&apSuite, &numFailureCalls](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) { numFailureCalls++; - NL_TEST_ASSERT(apSuite, aError == CHIP_IM_GLOBAL_STATUS(ResourceExhausted)); + NL_TEST_ASSERT(apSuite, aError == CHIP_IM_GLOBAL_STATUS(Busy)); NL_TEST_ASSERT(apSuite, attributePath == nullptr); }; - app::InteractionModelEngine::GetInstance()->SetHandlerCapacity(0); + app::InteractionModelEngine::GetInstance()->SetHandlerCapacityForReads(0); + app::InteractionModelEngine::GetInstance()->SetForceHandlerQuota(true); NL_TEST_ASSERT(apSuite, Controller::ReadAttribute( @@ -2487,7 +2491,8 @@ void TestReadInteraction::TestReadHandlerResourceExhaustion_MultipleReads(nlTest ctx.DrainAndServiceIO(); - app::InteractionModelEngine::GetInstance()->SetHandlerCapacity(-1); + app::InteractionModelEngine::GetInstance()->SetHandlerCapacityForReads(-1); + app::InteractionModelEngine::GetInstance()->SetForceHandlerQuota(false); app::InteractionModelEngine::GetInstance()->ShutdownActiveReads(); NL_TEST_ASSERT(apSuite, numSuccessCalls == 0); @@ -2663,10 +2668,12 @@ class TestPerpetualListReadCallback : public app::ReadClient::Callback void OnDone(chip::app::ReadClient *) override {} + void ClearCounter() { reportsReceived = 0; } + int32_t reportsReceived = 0; }; -void EstablishReadOrSubscriptions(nlTestSuite * apSuite, SessionHandle sessionHandle, int32_t numSubs, int32_t pathPerSub, +void EstablishReadOrSubscriptions(nlTestSuite * apSuite, const SessionHandle & sessionHandle, int32_t numSubs, int32_t pathPerSub, app::AttributePathParams path, app::ReadClient::InteractionType type, app::ReadClient::Callback * callback, std::vector> & readClients) { @@ -2714,10 +2721,10 @@ void TestReadInteraction::TestReadHandler_KillOverQuotaSubscriptions(nlTestSuite std::vector> readClients; EstablishReadOrSubscriptions(apSuite, ctx.GetSessionAliceToBob(), 1, 1, - app::AttributePathParams(kTestEndpointId, TestCluster::Id, kNeverEndAttributeid), + app::AttributePathParams(kTestEndpointId, TestCluster::Id, kPerpetualAttributeid), app::ReadClient::InteractionType::Read, &perpetualReadCallback, readClients); EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, 1, - app::AttributePathParams(kTestEndpointId, TestCluster::Id, kNeverEndAttributeid), + app::AttributePathParams(kTestEndpointId, TestCluster::Id, kPerpetualAttributeid), app::ReadClient::InteractionType::Read, &perpetualReadCallback, readClients); ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Read) == 2; @@ -3013,59 +3020,992 @@ void TestReadInteraction::TestReadHandler_KillOldestSubscriptions(nlTestSuite * app::InteractionModelEngine::GetInstance()->SetPathPoolCapacityForSubscriptions(-1); } -void TestReadInteraction::TestReadHandler_TwoParallelReads(nlTestSuite * apSuite, void * apContext) +struct TestReadHandler_ParallelReads_TestCase_Parameters { - using namespace chip::app; + int ReadHandlerCapacity = -1; + int PathPoolCapacity = -1; + int MaxFabrics = -1; +}; +static void TestReadHandler_ParallelReads_TestCase(nlTestSuite * apSuite, void * apContext, + const TestReadHandler_ParallelReads_TestCase_Parameters & params, + std::function body) +{ TestContext & ctx = *static_cast(apContext); - chip::Messaging::ReliableMessageMgr * rm = ctx.GetExchangeManager().GetReliableMessageMgr(); - // Shouldn't have anything in the retransmit table when starting the test. - NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); + app::InteractionModelEngine::GetInstance()->SetForceHandlerQuota(true); + app::InteractionModelEngine::GetInstance()->SetHandlerCapacityForReads(params.ReadHandlerCapacity); + app::InteractionModelEngine::GetInstance()->SetConfigMaxFabrics(params.MaxFabrics); + app::InteractionModelEngine::GetInstance()->SetPathPoolCapacityForReads(params.PathPoolCapacity); - auto * engine = app::InteractionModelEngine::GetInstance(); - engine->SetForceHandlerQuota(true); + body(); - app::ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); - // Read full wildcard paths, repeat twice to ensure chunking. - chip::app::AttributePathParams attributePathParams[2]; - readPrepareParams.mpAttributePathParamsList = attributePathParams; - readPrepareParams.mAttributePathParamsListSize = ArraySize(attributePathParams); + // Clean up + app::InteractionModelEngine::GetInstance()->ShutdownActiveReads(); + ctx.DrainAndServiceIO(); - { - MockInteractionModelApp delegate1; - NL_TEST_ASSERT(apSuite, delegate1.mNumAttributeResponse == 0); - NL_TEST_ASSERT(apSuite, !delegate1.mReadError); - app::ReadClient readClient1(InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), delegate1, - ReadClient::InteractionType::Read); + // Sanity check + NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); - MockInteractionModelApp delegate2; - NL_TEST_ASSERT(apSuite, delegate2.mNumAttributeResponse == 0); - NL_TEST_ASSERT(apSuite, !delegate2.mReadError); - ReadClient readClient2(InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), delegate2, - ReadClient::InteractionType::Read); + app::InteractionModelEngine::GetInstance()->SetForceHandlerQuota(false); + app::InteractionModelEngine::GetInstance()->SetHandlerCapacityForReads(-1); + app::InteractionModelEngine::GetInstance()->SetConfigMaxFabrics(-1); + app::InteractionModelEngine::GetInstance()->SetPathPoolCapacityForReads(-1); +} - CHIP_ERROR err = readClient1.SendRequest(readPrepareParams); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); +void TestReadInteraction::TestReadHandler_ParallelReads(nlTestSuite * apSuite, void * apContext) +{ + // Note: We cannot use ctx.DrainAndServiceIO() except at the end of each test case since the perpetual read transactions will + // never end. + // Note: We use ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { CONDITION }); and NL_TEST_ASSERT(apSuite, + // CONDITION ) to ensure the CONDITION is satisfied. + using namespace SubscriptionPathQuotaHelpers; + using Params = TestReadHandler_ParallelReads_TestCase_Parameters; - err = readClient2.SendRequest(readPrepareParams); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + TestContext & ctx = *static_cast(apContext); + auto sessionHandle = ctx.GetSessionBobToAlice(); - ctx.DrainAndServiceIO(); + app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(&gTestReadInteraction); - NL_TEST_ASSERT(apSuite, delegate1.mNumAttributeResponse != 0); - NL_TEST_ASSERT(apSuite, !delegate1.mReadError); + auto TestCase = [&](const TestReadHandler_ParallelReads_TestCase_Parameters & params, std::function body) { + TestReadHandler_ParallelReads_TestCase(apSuite, apContext, params, body); + }; - NL_TEST_ASSERT(apSuite, delegate2.mNumAttributeResponse == 0); - NL_TEST_ASSERT(apSuite, delegate2.mReadError); + // Case 1.1: 2 reads used up the path pool (but not the ReadHandler pool), and one incoming oversized read request => + // PathsExhausted. + TestCase( + Params{ + .ReadHandlerCapacity = 3, + .PathPoolCapacity = 2 * app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + .MaxFabrics = 2, + }, + [&]() { + TestReadCallback readCallback; + TestPerpetualListReadCallback backgroundReadCallback1; + TestPerpetualListReadCallback backgroundReadCallback2; + std::vector> readClients; + + EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, + app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), + app::ReadClient::InteractionType::Read, &backgroundReadCallback1, readClients); + EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, + app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), + app::ReadClient::InteractionType::Read, &backgroundReadCallback2, readClients); + + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { + return backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0; + }); + NL_TEST_ASSERT(apSuite, backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); - StatusIB status(delegate2.mError); - NL_TEST_ASSERT(apSuite, status.mStatus == Protocols::InteractionModel::Status::Busy); - } + backgroundReadCallback1.ClearCounter(); + backgroundReadCallback2.ClearCounter(); + + EstablishReadOrSubscriptions( + apSuite, ctx.GetSessionAliceToBob(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest + 1, + app::AttributePathParams(kTestEndpointId, TestCluster::Id, TestCluster::Attributes::Int16u::Id), + app::ReadClient::InteractionType::Read, &readCallback, readClients); + + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); + + // The two subscriptions should still alive + NL_TEST_ASSERT(apSuite, backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); + // The new read request should be rejected + NL_TEST_ASSERT(apSuite, readCallback.mOnError != 0); + NL_TEST_ASSERT(apSuite, readCallback.mLastError == CHIP_IM_GLOBAL_STATUS(PathsExhausted)); + }); + + // Case 1.2: 2 reads used up the ReadHandler pool (not the PathPool), and one incoming oversized read request => Busy. + // Note: This Busy code comes from the check for fabric resource limit (see case 1.3). + TestCase( + Params{ + .ReadHandlerCapacity = 2, + .PathPoolCapacity = 2 * app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + .MaxFabrics = 2, + }, + [&]() { + TestReadCallback readCallback; + TestPerpetualListReadCallback backgroundReadCallback1; + TestPerpetualListReadCallback backgroundReadCallback2; + std::vector> readClients; + + EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, 1, + app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), + app::ReadClient::InteractionType::Read, &backgroundReadCallback1, readClients); + EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, 1, + app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), + app::ReadClient::InteractionType::Read, &backgroundReadCallback2, readClients); + + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { + return backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0; + }); + NL_TEST_ASSERT(apSuite, backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); + + backgroundReadCallback1.ClearCounter(); + backgroundReadCallback2.ClearCounter(); + + EstablishReadOrSubscriptions( + apSuite, ctx.GetSessionAliceToBob(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest + 1, + app::AttributePathParams(kTestEndpointId, TestCluster::Id, TestCluster::Attributes::Int16u::Id), + app::ReadClient::InteractionType::Read, &readCallback, readClients); + + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); + + // The two subscriptions should still alive + NL_TEST_ASSERT(apSuite, backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); + // The new read request should be rejected + NL_TEST_ASSERT(apSuite, readCallback.mOnError != 0); + NL_TEST_ASSERT(apSuite, readCallback.mLastError == CHIP_IM_GLOBAL_STATUS(Busy)); + }); + + // Case 1.3.1: If we have enough resource, any read requests will be accepted (case for oversized read request). + TestCase( + Params{ + .ReadHandlerCapacity = 3, + .PathPoolCapacity = 3 * app::InteractionModelEngine::kMinSupportedPathsPerReadRequest + 1, + .MaxFabrics = 2, + }, + [&]() { + TestReadCallback readCallback; + TestPerpetualListReadCallback backgroundReadCallback1; + TestPerpetualListReadCallback backgroundReadCallback2; + std::vector> readClients; + + EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, + app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), + app::ReadClient::InteractionType::Read, &backgroundReadCallback1, readClients); + EstablishReadOrSubscriptions(apSuite, ctx.GetSessionAliceToBob(), 1, + app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), + app::ReadClient::InteractionType::Read, &backgroundReadCallback2, readClients); + + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { + return backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0; + }); + NL_TEST_ASSERT(apSuite, backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); + + EstablishReadOrSubscriptions( + apSuite, ctx.GetSessionAliceToBob(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest + 1, + app::AttributePathParams(kTestEndpointId, TestCluster::Id, TestCluster::Attributes::Int16u::Id), + app::ReadClient::InteractionType::Read, &readCallback, readClients); + + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); + + // The new read request should be accepted + NL_TEST_ASSERT(apSuite, + readCallback.mAttributeCount == app::InteractionModelEngine::kMinSupportedPathsPerReadRequest + 1); + NL_TEST_ASSERT(apSuite, readCallback.mOnError == 0); + + // The two subscriptions should still alive + backgroundReadCallback1.ClearCounter(); + backgroundReadCallback2.ClearCounter(); + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { + return backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0; + }); + NL_TEST_ASSERT(apSuite, backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); + }); + + // Case 1.3.2: If we have enough resource, any read requests will be accepted (case for non-oversized read requests) + TestCase( + Params{ + .ReadHandlerCapacity = 3, + .PathPoolCapacity = 3 * app::InteractionModelEngine::kMinSupportedPathsPerReadRequest + 1, + .MaxFabrics = 2, + }, + [&]() { + TestReadCallback readCallback; + TestPerpetualListReadCallback backgroundReadCallback1; + TestPerpetualListReadCallback backgroundReadCallback2; + std::vector> readClients; + + EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, + app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), + app::ReadClient::InteractionType::Read, &backgroundReadCallback1, readClients); + EstablishReadOrSubscriptions(apSuite, ctx.GetSessionAliceToBob(), 1, + app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), + app::ReadClient::InteractionType::Read, &backgroundReadCallback2, readClients); + + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { + return backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0; + }); + NL_TEST_ASSERT(apSuite, backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); + + backgroundReadCallback1.ClearCounter(); + backgroundReadCallback2.ClearCounter(); + + EstablishReadOrSubscriptions( + apSuite, ctx.GetSessionAliceToBob(), 1, 1, + app::AttributePathParams(kTestEndpointId, TestCluster::Id, TestCluster::Attributes::Int16u::Id), + app::ReadClient::InteractionType::Read, &readCallback, readClients); + + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); + + // The new read request should be accepted + NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == 1); + NL_TEST_ASSERT(apSuite, readCallback.mOnError == 0); + + // The two subscriptions should still alive + backgroundReadCallback1.ClearCounter(); + backgroundReadCallback2.ClearCounter(); + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { + return backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0; + }); + NL_TEST_ASSERT(apSuite, backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); + }); + + // Case 2: 1 oversized read and one non-oversized read, and one incoming read request from __another__ fabric => accept by + // evicting the oversized read request. + TestCase( + Params{ + .ReadHandlerCapacity = 2, + .PathPoolCapacity = 2 * app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + .MaxFabrics = 2, + }, + [&]() { + TestReadCallback readCallback; + TestPerpetualListReadCallback readCallbackForOversizedRead; + TestPerpetualListReadCallback backgroundReadCallback; + std::vector> readClients; + + EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, + app::InteractionModelEngine::kMinSupportedPathsPerReadRequest + 1, + app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), + app::ReadClient::InteractionType::Read, &readCallbackForOversizedRead, readClients); + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), + [&]() { return readCallbackForOversizedRead.reportsReceived > 0; }); + + EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, 1, + app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), + app::ReadClient::InteractionType::Read, &backgroundReadCallback, readClients); + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), + [&]() { return backgroundReadCallback.reportsReceived > 0; }); + + NL_TEST_ASSERT(apSuite, readCallbackForOversizedRead.reportsReceived > 0 && backgroundReadCallback.reportsReceived > 0); + + EstablishReadOrSubscriptions( + apSuite, ctx.GetSessionAliceToBob(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + app::AttributePathParams(kTestEndpointId, TestCluster::Id, TestCluster::Attributes::Int16u::Id), + app::ReadClient::InteractionType::Read, &readCallback, readClients); + + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); + + // The new read request should be accepted. + NL_TEST_ASSERT(apSuite, readCallback.mOnError == 0); + NL_TEST_ASSERT(apSuite, readCallback.mOnDone == 1); + NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == app::InteractionModelEngine::kMinSupportedPathsPerReadRequest); + + // The oversized read handler should be evicted -> We should have one active read handler. + NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 1); + + backgroundReadCallback.ClearCounter(); + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), + [&]() { return backgroundReadCallback.reportsReceived > 0; }); + + // We don't check the readCallbackForOversizedRead, since it cannot prove anything -- it can be 0 even when the + // oversized read request is alive. We ensure this by checking (1) we have only one active read handler, (2) the one + // active read handler is the non-oversized one. + + // The non-oversized read handler should not be evicted. + NL_TEST_ASSERT(apSuite, backgroundReadCallback.reportsReceived > 0); + }); + + // Case 2 (Repeat): we swapped the order of the oversized and non-oversized read handler to ensure we always evict the oversized + // read handler regardless the order. + TestCase( + Params{ + .ReadHandlerCapacity = 2, + .PathPoolCapacity = 2 * app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + .MaxFabrics = 2, + }, + [&]() { + TestReadCallback readCallback; + TestPerpetualListReadCallback readCallbackForOversizedRead; + TestPerpetualListReadCallback backgroundReadCallback; + std::vector> readClients; + + EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, 1, + app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), + app::ReadClient::InteractionType::Read, &backgroundReadCallback, readClients); + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), + [&]() { return backgroundReadCallback.reportsReceived > 0; }); + + EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, + app::InteractionModelEngine::kMinSupportedPathsPerReadRequest + 1, + app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), + app::ReadClient::InteractionType::Read, &readCallbackForOversizedRead, readClients); + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), + [&]() { return readCallbackForOversizedRead.reportsReceived > 0; }); + + NL_TEST_ASSERT(apSuite, readCallbackForOversizedRead.reportsReceived > 0 && backgroundReadCallback.reportsReceived > 0); + + EstablishReadOrSubscriptions( + apSuite, ctx.GetSessionAliceToBob(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + app::AttributePathParams(kTestEndpointId, TestCluster::Id, TestCluster::Attributes::Int16u::Id), + app::ReadClient::InteractionType::Read, &readCallback, readClients); + + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); + + // The new read request should be accepted. + NL_TEST_ASSERT(apSuite, readCallback.mOnError == 0); + NL_TEST_ASSERT(apSuite, readCallback.mOnDone == 1); + NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == app::InteractionModelEngine::kMinSupportedPathsPerReadRequest); + + // The oversized read handler should be evicted -> We should have one active read handler. + NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 1); + + backgroundReadCallback.ClearCounter(); + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), + [&]() { return backgroundReadCallback.reportsReceived > 0; }); + + // We don't check the readCallbackForOversizedRead, since it cannot prove anything -- it can be 0 even when the + // oversized read request is alive. We ensure this by checking (1) we have only one active read handler, (2) the one + // active read handler is the non-oversized one. + + // The non-oversized read handler should not be evicted. + NL_TEST_ASSERT(apSuite, backgroundReadCallback.reportsReceived > 0); + }); + + // Case 3: one oversized read and one non-oversized read, the remaining path in PathPool is suffcient but the ReadHandler pool + // is full, and one incoming (non-oversized) read request from __the same__ fabric => Reply Status::Busy without evicting any + // read handlers. + // Note: If the read handler pool is not full => We have enough resource for handling this request => Case 1.3.2 + TestCase( + Params{ + .ReadHandlerCapacity = 2, + .PathPoolCapacity = 2 * app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + .MaxFabrics = 2, + }, + [&]() { + TestReadCallback readCallback; + TestPerpetualListReadCallback readCallbackForOversizedRead; + TestPerpetualListReadCallback backgroundReadCallback; + std::vector> readClients; + + EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, 1, + app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), + app::ReadClient::InteractionType::Read, &backgroundReadCallback, readClients); + EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, + app::InteractionModelEngine::kMinSupportedPathsPerReadRequest + 1, + app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), + app::ReadClient::InteractionType::Read, &readCallbackForOversizedRead, readClients); + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { + return backgroundReadCallback.reportsReceived > 0 && readCallbackForOversizedRead.reportsReceived > 0; + }); + + NL_TEST_ASSERT(apSuite, readCallbackForOversizedRead.reportsReceived > 0 && backgroundReadCallback.reportsReceived > 0); + + EstablishReadOrSubscriptions( + apSuite, ctx.GetSessionBobToAlice(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + app::AttributePathParams(kTestEndpointId, TestCluster::Id, TestCluster::Attributes::Int16u::Id), + app::ReadClient::InteractionType::Read, &readCallback, readClients); + + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); + + // The new read request should be rejected. + NL_TEST_ASSERT(apSuite, readCallback.mOnError != 0); + NL_TEST_ASSERT(apSuite, readCallback.mLastError == CHIP_IM_GLOBAL_STATUS(Busy)); + + // Ensure the two read transactions are not evicted. + backgroundReadCallback.ClearCounter(); + readCallbackForOversizedRead.ClearCounter(); + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { + return readCallbackForOversizedRead.reportsReceived > 0 && backgroundReadCallback.reportsReceived > 0; + }); + NL_TEST_ASSERT(apSuite, readCallbackForOversizedRead.reportsReceived > 0 && backgroundReadCallback.reportsReceived > 0); + }); + + // Case 4.1: 1 fabric is oversized, and one incoming read request from __another__ fabric => accept by evicting one read request + // from the oversized fabric. + // Note: When there are more than one candidate, we will evict the larger one first (case 2), and the younger one (this case). + TestCase( + Params{ + .ReadHandlerCapacity = 2, + .PathPoolCapacity = 2 * app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + .MaxFabrics = 2, + }, + [&]() { + TestReadCallback readCallback; + TestPerpetualListReadCallback backgroundReadCallback1; + TestPerpetualListReadCallback backgroundReadCallback2; + std::vector> readClients; + + EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, 1, + app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), + app::ReadClient::InteractionType::Read, &backgroundReadCallback1, readClients); + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), + [&]() { return backgroundReadCallback1.reportsReceived > 0; }); + + EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, 1, + app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), + app::ReadClient::InteractionType::Read, &backgroundReadCallback2, readClients); + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), + [&]() { return backgroundReadCallback2.reportsReceived > 0; }); + NL_TEST_ASSERT(apSuite, backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); + + backgroundReadCallback1.ClearCounter(); + backgroundReadCallback2.ClearCounter(); + + EstablishReadOrSubscriptions( + apSuite, ctx.GetSessionAliceToBob(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + app::AttributePathParams(kTestEndpointId, TestCluster::Id, TestCluster::Attributes::Int16u::Id), + app::ReadClient::InteractionType::Read, &readCallback, readClients); + + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); + + // The new read request should be rejected. + NL_TEST_ASSERT(apSuite, readCallback.mOnError == 0); + NL_TEST_ASSERT(apSuite, readCallback.mOnDone == 1); + NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == app::InteractionModelEngine::kMinSupportedPathsPerReadRequest); + + // One of the read requests from Bob to Alice should be evicted. + // We should have only one 1 active read handler, since the transaction from Alice to Bob has finished already, and one + // of two Bob to Alice transactions has been evicted. + NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 1); + + // Note: Younger read handler will be evicted. + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), + [&]() { return backgroundReadCallback1.reportsReceived > 0; }); + NL_TEST_ASSERT(apSuite, backgroundReadCallback1.reportsReceived > 0); + }); + + // Case 4.2: Like case 4.1, but now the over sized fabric contains one (older) oversized read request and one (younger) + // non-oversized read request. We will evict the oversized one instead of the younger one. + TestCase( + Params{ + .ReadHandlerCapacity = 2, + .PathPoolCapacity = 2 * app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + .MaxFabrics = 2, + }, + [&]() { + TestReadCallback readCallback; + TestPerpetualListReadCallback backgroundReadCallback1; + TestPerpetualListReadCallback backgroundReadCallback2; + std::vector> readClients; + + EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, + app::InteractionModelEngine::kMinSupportedPathsPerReadRequest + 1, + app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), + app::ReadClient::InteractionType::Read, &backgroundReadCallback1, readClients); + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), + [&]() { return backgroundReadCallback1.reportsReceived > 0; }); + + EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, 1, + app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), + app::ReadClient::InteractionType::Read, &backgroundReadCallback2, readClients); + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), + [&]() { return backgroundReadCallback2.reportsReceived > 0; }); + NL_TEST_ASSERT(apSuite, backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); + + backgroundReadCallback1.ClearCounter(); + backgroundReadCallback2.ClearCounter(); + + EstablishReadOrSubscriptions( + apSuite, ctx.GetSessionAliceToBob(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + app::AttributePathParams(kTestEndpointId, TestCluster::Id, TestCluster::Attributes::Int16u::Id), + app::ReadClient::InteractionType::Read, &readCallback, readClients); + + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); + + // The new read request should be rejected. + NL_TEST_ASSERT(apSuite, readCallback.mOnError == 0); + NL_TEST_ASSERT(apSuite, readCallback.mOnDone == 1); + NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == app::InteractionModelEngine::kMinSupportedPathsPerReadRequest); + + // One of the read requests from Bob to Alice should be evicted. + // We should have only one 1 active read handler, since the transaction from Alice to Bob has finished already, and one + // of two Bob to Alice transactions has been evicted. + NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 1); + + // Note: Larger read handler will be evicted before evicting the younger one. + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), + [&]() { return backgroundReadCallback2.reportsReceived > 0; }); + NL_TEST_ASSERT(apSuite, backgroundReadCallback2.reportsReceived > 0); + }); + + // The following tests are the cases of read transactions on PASE sessions. + + // Case 5.1: The device's fabric table is not full, PASE sessions are counted as a "valid" fabric and can evict existing read + // transactions. (In the same algorithm as in Test Case 2) + TestCase( + Params{ + .ReadHandlerCapacity = 3, + .PathPoolCapacity = 3 * app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + .MaxFabrics = 3, + }, + [&]() { + TestReadCallback readCallback; + TestPerpetualListReadCallback backgroundReadCallback1; + TestPerpetualListReadCallback backgroundReadCallback2; + TestPerpetualListReadCallback backgroundReadCallback3; + std::vector> readClients; + + EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, 1, + app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), + app::ReadClient::InteractionType::Read, &backgroundReadCallback1, readClients); + EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, 1, + app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), + app::ReadClient::InteractionType::Read, &backgroundReadCallback2, readClients); + EstablishReadOrSubscriptions(apSuite, ctx.GetSessionAliceToBob(), 1, 1, + app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), + app::ReadClient::InteractionType::Read, &backgroundReadCallback3, readClients); + + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { + return backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0 && + backgroundReadCallback3.reportsReceived > 0; + }); + NL_TEST_ASSERT(apSuite, + backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0 && + backgroundReadCallback3.reportsReceived > 0); + + EstablishReadOrSubscriptions( + apSuite, ctx.GetSessionCharlieToDavid(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + app::AttributePathParams(kTestEndpointId, TestCluster::Id, TestCluster::Attributes::Int16u::Id), + app::ReadClient::InteractionType::Read, &readCallback, readClients); + + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); + + // The new read request should be accepted. + NL_TEST_ASSERT(apSuite, readCallback.mOnError == 0); + NL_TEST_ASSERT(apSuite, readCallback.mOnDone == 1); + NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == app::InteractionModelEngine::kMinSupportedPathsPerReadRequest); + // Should evict one read request from Bob fabric for enough resources. + NL_TEST_ASSERT(apSuite, + app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers( + app::ReadHandler::InteractionType::Read, ctx.GetAliceFabricIndex()) == 1); + NL_TEST_ASSERT(apSuite, + app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers( + app::ReadHandler::InteractionType::Read, ctx.GetBobFabricIndex()) == 1); + }); + + // Case 5.2: The device's fabric table is not full, PASE sessions are counted as a "valid" fabric and can evict existing read + // transactions. (In the same algorithm as in Test Case 2) + // Note: The difference between 5.1 and 5.2 is which fabric is oversized, 5.1 and 5.2 also ensures that we will only evict the + // read handlers from oversized fabric. + TestCase( + Params{ + .ReadHandlerCapacity = 3, + .PathPoolCapacity = 3 * app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + .MaxFabrics = 3, + }, + [&]() { + TestReadCallback readCallback; + TestPerpetualListReadCallback backgroundReadCallback1; + TestPerpetualListReadCallback backgroundReadCallback2; + TestPerpetualListReadCallback backgroundReadCallback3; + std::vector> readClients; + + EstablishReadOrSubscriptions(apSuite, ctx.GetSessionAliceToBob(), 1, 1, + app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), + app::ReadClient::InteractionType::Read, &backgroundReadCallback1, readClients); + EstablishReadOrSubscriptions(apSuite, ctx.GetSessionAliceToBob(), 1, 1, + app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), + app::ReadClient::InteractionType::Read, &backgroundReadCallback2, readClients); + EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, 1, + app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), + app::ReadClient::InteractionType::Read, &backgroundReadCallback3, readClients); + + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { + return backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0 && + backgroundReadCallback3.reportsReceived > 0; + }); + NL_TEST_ASSERT(apSuite, + backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0 && + backgroundReadCallback3.reportsReceived > 0); + + EstablishReadOrSubscriptions( + apSuite, ctx.GetSessionCharlieToDavid(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + app::AttributePathParams(kTestEndpointId, TestCluster::Id, TestCluster::Attributes::Int16u::Id), + app::ReadClient::InteractionType::Read, &readCallback, readClients); + + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); + + // The new read request should be accepted. + NL_TEST_ASSERT(apSuite, readCallback.mOnError == 0); + NL_TEST_ASSERT(apSuite, readCallback.mOnDone == 1); + NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == app::InteractionModelEngine::kMinSupportedPathsPerReadRequest); + // Should evict one read request from Bob fabric for enough resources. + NL_TEST_ASSERT(apSuite, + app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers( + app::ReadHandler::InteractionType::Read, ctx.GetAliceFabricIndex()) == 1); + NL_TEST_ASSERT(apSuite, + app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers( + app::ReadHandler::InteractionType::Read, ctx.GetBobFabricIndex()) == 1); + }); + + // Case 6: The device's fabric table is full, PASE sessions won't be counted as a valid fabric and cannot evict existing read + // transactions. It will be rejected with Busy status code. + TestCase( + Params{ + .ReadHandlerCapacity = 3, + .PathPoolCapacity = 3 * app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + .MaxFabrics = 2, + }, + [&]() { + TestReadCallback readCallback; + TestPerpetualListReadCallback backgroundReadCallback1; + TestPerpetualListReadCallback backgroundReadCallback2; + TestPerpetualListReadCallback backgroundReadCallback3; + std::vector> readClients; + + EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, + app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), + app::ReadClient::InteractionType::Read, &backgroundReadCallback1, readClients); + EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, + app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), + app::ReadClient::InteractionType::Read, &backgroundReadCallback2, readClients); + EstablishReadOrSubscriptions(apSuite, ctx.GetSessionAliceToBob(), 1, + app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), + app::ReadClient::InteractionType::Read, &backgroundReadCallback3, readClients); + + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { + return backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0 && + backgroundReadCallback3.reportsReceived > 0; + }); + NL_TEST_ASSERT(apSuite, + backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0 && + backgroundReadCallback3.reportsReceived > 0); + + EstablishReadOrSubscriptions( + apSuite, ctx.GetSessionCharlieToDavid(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + app::AttributePathParams(kTestEndpointId, TestCluster::Id, TestCluster::Attributes::Int16u::Id), + app::ReadClient::InteractionType::Read, &readCallback, readClients); + + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); + + // The new read request should be rejected. + NL_TEST_ASSERT(apSuite, readCallback.mOnError == 1); + NL_TEST_ASSERT(apSuite, readCallback.mLastError == CHIP_IM_GLOBAL_STATUS(Busy)); + // Should evict one read request from Bob fabric for enough resources. + NL_TEST_ASSERT(apSuite, + app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers( + app::ReadHandler::InteractionType::Read, ctx.GetAliceFabricIndex()) == 2); + NL_TEST_ASSERT(apSuite, + app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers( + app::ReadHandler::InteractionType::Read, ctx.GetBobFabricIndex()) == 1); + }); + + // Case 7: We will accept read transactions on PASE session when the fabric table is full but we have enough resources for it. + // Note: The actual size is not important, since this read handler is accepted by the first if-clause in EnsureResourceForRead. + TestCase( + Params{ + .ReadHandlerCapacity = 3, + .PathPoolCapacity = 3 * app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + .MaxFabrics = 2, + }, + [&]() { + TestReadCallback readCallback; + TestPerpetualListReadCallback backgroundReadCallback1; + TestPerpetualListReadCallback backgroundReadCallback2; + std::vector> readClients; + + EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, + app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), + app::ReadClient::InteractionType::Read, &backgroundReadCallback1, readClients); + EstablishReadOrSubscriptions(apSuite, ctx.GetSessionAliceToBob(), 1, + app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), + app::ReadClient::InteractionType::Read, &backgroundReadCallback2, readClients); + + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { + return backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0; + }); + NL_TEST_ASSERT(apSuite, backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); + + EstablishReadOrSubscriptions( + apSuite, ctx.GetSessionCharlieToDavid(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + app::AttributePathParams(kTestEndpointId, TestCluster::Id, TestCluster::Attributes::Int16u::Id), + app::ReadClient::InteractionType::Read, &readCallback, readClients); + + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); + + // The new read request should be accepted. + NL_TEST_ASSERT(apSuite, readCallback.mOnError == 0); + NL_TEST_ASSERT(apSuite, readCallback.mOnDone == 1); + NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == app::InteractionModelEngine::kMinSupportedPathsPerReadRequest); + // No read transactions should be evicted. + NL_TEST_ASSERT(apSuite, + app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers( + app::ReadHandler::InteractionType::Read, ctx.GetAliceFabricIndex()) == 1); + NL_TEST_ASSERT(apSuite, + app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers( + app::ReadHandler::InteractionType::Read, ctx.GetBobFabricIndex()) == 1); + }); + + // Case 8.1: If the fabric table on the device is full, read transactions on PASE session will always be evicted when another + // read comeing in on one of the existing fabrics. + TestCase( + Params{ + .ReadHandlerCapacity = 2, + .PathPoolCapacity = 2 * app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + .MaxFabrics = 2, + }, + [&]() { + TestReadCallback readCallback; + TestPerpetualListReadCallback backgroundReadCallback1; + TestPerpetualListReadCallback backgroundReadCallback2; + std::vector> readClients; + + EstablishReadOrSubscriptions(apSuite, ctx.GetSessionCharlieToDavid(), 1, 1, + app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), + app::ReadClient::InteractionType::Read, &backgroundReadCallback1, readClients); + EstablishReadOrSubscriptions(apSuite, ctx.GetSessionAliceToBob(), 1, 1, + app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), + app::ReadClient::InteractionType::Read, &backgroundReadCallback2, readClients); + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { + return backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0; + }); + NL_TEST_ASSERT(apSuite, backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); + + EstablishReadOrSubscriptions( + apSuite, ctx.GetSessionBobToAlice(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + app::AttributePathParams(kTestEndpointId, TestCluster::Id, TestCluster::Attributes::Int16u::Id), + app::ReadClient::InteractionType::Read, &readCallback, readClients); + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); + + // The new read request should be accepted. + NL_TEST_ASSERT(apSuite, readCallback.mOnError == 0); + NL_TEST_ASSERT(apSuite, readCallback.mOnDone == 1); + NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == app::InteractionModelEngine::kMinSupportedPathsPerReadRequest); + // Should evict the read request on PASE session for enough resources. + NL_TEST_ASSERT( + apSuite, + app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Read) == 1); + NL_TEST_ASSERT(apSuite, + app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers( + app::ReadHandler::InteractionType::Read, kUndefinedFabricIndex) == 0); + }); + + // Case 8.2: If the fabric table on the device is full, read transactions on PASE session will always be evicted when another + // read comeing in on one of the existing fabrics. + // Note: The difference between 8.1 and 8.2 is the whether the existing fabric is oversized. + TestCase( + Params{ + .ReadHandlerCapacity = 2, + .PathPoolCapacity = 2 * app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + .MaxFabrics = 2, + }, + [&]() { + TestReadCallback readCallback; + TestPerpetualListReadCallback backgroundReadCallback1; + TestPerpetualListReadCallback backgroundReadCallback2; + std::vector> readClients; + + EstablishReadOrSubscriptions(apSuite, ctx.GetSessionCharlieToDavid(), 1, 1, + app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), + app::ReadClient::InteractionType::Read, &backgroundReadCallback1, readClients); + EstablishReadOrSubscriptions(apSuite, ctx.GetSessionAliceToBob(), 1, + app::InteractionModelEngine::kMinSupportedPathsPerReadRequest + 1, + app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), + app::ReadClient::InteractionType::Read, &backgroundReadCallback2, readClients); + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { + return backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0; + }); + NL_TEST_ASSERT(apSuite, backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); + + EstablishReadOrSubscriptions( + apSuite, ctx.GetSessionBobToAlice(), 1, 1, + app::AttributePathParams(kTestEndpointId, TestCluster::Id, TestCluster::Attributes::Int16u::Id), + app::ReadClient::InteractionType::Read, &readCallback, readClients); + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); + + // The new read request should be accepted. + NL_TEST_ASSERT(apSuite, readCallback.mOnError == 0); + NL_TEST_ASSERT(apSuite, readCallback.mOnDone == 1); + NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == 1); + // Should evict the read request on PASE session for enough resources. + NL_TEST_ASSERT( + apSuite, + app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Read) == 1); + NL_TEST_ASSERT(apSuite, + app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers( + app::ReadHandler::InteractionType::Read, kUndefinedFabricIndex) == 0); + }); + + // Case 9.1: If the fabric table on the device is not full, read transactions on PASE session will NOT be evicted when the + // resources used by all PASE sessions ARE NOT exceeding the resources guaranteed to a normal fabric. + TestCase( + Params{ + .ReadHandlerCapacity = 3, + .PathPoolCapacity = 3 * app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + .MaxFabrics = 3, + }, + [&]() { + TestReadCallback readCallback; + TestPerpetualListReadCallback backgroundReadCallbackForPASESession; + TestPerpetualListReadCallback backgroundReadCallback1; + TestPerpetualListReadCallback backgroundReadCallback2; + std::vector> readClients; + + EstablishReadOrSubscriptions( + apSuite, ctx.GetSessionCharlieToDavid(), 1, 1, app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), + app::ReadClient::InteractionType::Read, &backgroundReadCallbackForPASESession, readClients); + EstablishReadOrSubscriptions(apSuite, ctx.GetSessionAliceToBob(), 1, 1, + app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), + app::ReadClient::InteractionType::Read, &backgroundReadCallback1, readClients); + EstablishReadOrSubscriptions(apSuite, ctx.GetSessionAliceToBob(), 1, 1, + app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), + app::ReadClient::InteractionType::Read, &backgroundReadCallback2, readClients); + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { + return backgroundReadCallbackForPASESession.reportsReceived > 0 && backgroundReadCallback1.reportsReceived > 0 && + backgroundReadCallback2.reportsReceived > 0; + }); + NL_TEST_ASSERT(apSuite, + backgroundReadCallbackForPASESession.reportsReceived > 0 && + backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0); + + EstablishReadOrSubscriptions( + apSuite, ctx.GetSessionBobToAlice(), 1, 1, + app::AttributePathParams(kTestEndpointId, TestCluster::Id, TestCluster::Attributes::Int16u::Id), + app::ReadClient::InteractionType::Read, &readCallback, readClients); + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); + + // The new read request should be accepted. + NL_TEST_ASSERT(apSuite, readCallback.mOnError == 0); + NL_TEST_ASSERT(apSuite, readCallback.mOnDone == 1); + NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == 1); + + // The read handler on PASE session should not be evicted since the resources used by all PASE sessions are not + // exceeding the resources guaranteed to a normal fabric. + NL_TEST_ASSERT( + apSuite, + app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Read) == 2); + NL_TEST_ASSERT(apSuite, + app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers( + app::ReadHandler::InteractionType::Read, kUndefinedFabricIndex) == 1); + }); + + // Case 9.2: If the fabric table on the device is not full, the read handlers from normal fabrics MAY be evicted before all read + // transactions from PASE sessions are evicted. + // Note: With this setup, the interaction model engine guarantees 2 read transactions and 2 * 9 = 18 paths on each fabric. + TestCase( + Params{ + .ReadHandlerCapacity = 6, + .PathPoolCapacity = 6 * app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + .MaxFabrics = 3, + }, + [&]() { + TestReadCallback readCallback; + TestPerpetualListReadCallback backgroundReadCallbackForPASESession; + TestPerpetualListReadCallback backgroundReadCallback1; + TestPerpetualListReadCallback backgroundReadCallback2; + std::vector> readClients; + + EstablishReadOrSubscriptions( + apSuite, ctx.GetSessionCharlieToDavid(), 3, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest - 1, + app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, + &backgroundReadCallbackForPASESession, readClients); + EstablishReadOrSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 3, + app::InteractionModelEngine::kMinSupportedPathsPerReadRequest + 1, + app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), + app::ReadClient::InteractionType::Read, &backgroundReadCallback1, readClients); + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { + return app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers( + app::ReadHandler::InteractionType::Read) == 6; + }); + NL_TEST_ASSERT(apSuite, + app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers( + app::ReadHandler::InteractionType::Read, kUndefinedFabricIndex) == 3); + + // We have to evict one read transaction on PASE session and one read transaction on Alice's fabric. + EstablishReadOrSubscriptions( + apSuite, ctx.GetSessionAliceToBob(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + app::AttributePathParams(kTestEndpointId, TestCluster::Id, TestCluster::Attributes::Int16u::Id), + app::ReadClient::InteractionType::Read, &readCallback, readClients); + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); + + // The new read request should be accepted. + NL_TEST_ASSERT(apSuite, readCallback.mOnError == 0); + NL_TEST_ASSERT(apSuite, readCallback.mOnDone == 1); + NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == app::InteractionModelEngine::kMinSupportedPathsPerReadRequest); + + // No more than one read handler on PASE session should be evicted exceeding the resources guaranteed to a normal + // fabric. Note: We are using ">=" here since it is also acceptable if we choose to evict one read transaction from + // Alice fabric. + NL_TEST_ASSERT( + apSuite, + app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Read) >= 4); + NL_TEST_ASSERT(apSuite, + app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers( + app::ReadHandler::InteractionType::Read, kUndefinedFabricIndex) >= 2); + }); + + // Case 10: If the fabric table on the device is full, we won't evict read requests from normal fabrics before we have evicted + // ALL read requests from PASE sessions. + TestCase( + Params{ + .ReadHandlerCapacity = 4, + .PathPoolCapacity = 4 * app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + .MaxFabrics = 2, + }, + [&]() { + TestReadCallback readCallback; + TestPerpetualListReadCallback backgroundReadCallbackForPASESession; + TestPerpetualListReadCallback backgroundReadCallback1; + TestPerpetualListReadCallback backgroundReadCallback2; + std::vector> readClients; + + EstablishReadOrSubscriptions( + apSuite, ctx.GetSessionCharlieToDavid(), 2, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest - 1, + app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), app::ReadClient::InteractionType::Read, + &backgroundReadCallbackForPASESession, readClients); + EstablishReadOrSubscriptions(apSuite, ctx.GetSessionAliceToBob(), 1, + app::InteractionModelEngine::kMinSupportedPathsPerReadRequest + 1, + app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), + app::ReadClient::InteractionType::Read, &backgroundReadCallback1, readClients); + EstablishReadOrSubscriptions(apSuite, ctx.GetSessionAliceToBob(), 1, + app::InteractionModelEngine::kMinSupportedPathsPerReadRequest + 1, + app::AttributePathParams(kTestEndpointId, kPerpetualClusterId, 1), + app::ReadClient::InteractionType::Read, &backgroundReadCallback2, readClients); + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { + return backgroundReadCallbackForPASESession.reportsReceived > 0 && backgroundReadCallback1.reportsReceived > 0 && + backgroundReadCallback2.reportsReceived > 0 && + app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Read, + kUndefinedFabricIndex) == 2; + }); + NL_TEST_ASSERT(apSuite, + backgroundReadCallbackForPASESession.reportsReceived > 0 && + backgroundReadCallback1.reportsReceived > 0 && backgroundReadCallback2.reportsReceived > 0 && + app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers( + app::ReadHandler::InteractionType::Read, kUndefinedFabricIndex) == 2); + + // To handle this read request, we must evict both read transactions from the PASE session. + EstablishReadOrSubscriptions( + apSuite, ctx.GetSessionBobToAlice(), 1, app::InteractionModelEngine::kMinSupportedPathsPerReadRequest, + app::AttributePathParams(kTestEndpointId, TestCluster::Id, TestCluster::Attributes::Int16u::Id), + app::ReadClient::InteractionType::Read, &readCallback, readClients); + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.mOnDone != 0; }); + + // The new read request should be accepted. + NL_TEST_ASSERT(apSuite, readCallback.mOnError == 0); + NL_TEST_ASSERT(apSuite, readCallback.mOnDone == 1); + NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == app::InteractionModelEngine::kMinSupportedPathsPerReadRequest); + + // The read handler on PASE session should be evicted, and the read transactions on a normal fabric should be untouched + // although it is oversized. + NL_TEST_ASSERT( + apSuite, + app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers(app::ReadHandler::InteractionType::Read) == 2); + NL_TEST_ASSERT(apSuite, + app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers( + app::ReadHandler::InteractionType::Read, kUndefinedFabricIndex) == 0); + }); + + app::InteractionModelEngine::GetInstance()->ShutdownActiveReads(); + ctx.DrainAndServiceIO(); - NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadClients() == 0); NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); - engine->SetForceHandlerQuota(false); + app::InteractionModelEngine::GetInstance()->SetForceHandlerQuota(false); + app::InteractionModelEngine::GetInstance()->SetConfigMaxFabrics(-1); + app::InteractionModelEngine::GetInstance()->SetHandlerCapacityForReads(-1); + app::InteractionModelEngine::GetInstance()->SetPathPoolCapacityForReads(-1); } // Needs to be larger than our plausible path pool. @@ -3204,7 +4144,7 @@ const nlTest sTests[] = NL_TEST_DEF("TestReadSubscribeAttributeResponseWithCache", TestReadInteraction::TestReadSubscribeAttributeResponseWithCache), NL_TEST_DEF("TestReadHandler_KillOverQuotaSubscriptions", TestReadInteraction::TestReadHandler_KillOverQuotaSubscriptions), NL_TEST_DEF("TestReadHandler_KillOldestSubscriptions", TestReadInteraction::TestReadHandler_KillOldestSubscriptions), - NL_TEST_DEF("TestReadHandler_TwoParallelReads", TestReadInteraction::TestReadHandler_TwoParallelReads), + NL_TEST_DEF("TestReadHandler_ParallelReads", TestReadInteraction::TestReadHandler_ParallelReads), NL_TEST_DEF("TestReadHandler_TooManyPaths", TestReadInteraction::TestReadHandler_TooManyPaths), NL_TEST_DEF("TestReadHandler_TwoParallelReadsSecondTooManyPaths", TestReadInteraction::TestReadHandler_TwoParallelReadsSecondTooManyPaths), NL_TEST_SENTINEL() diff --git a/src/lib/core/CHIPConfig.h b/src/lib/core/CHIPConfig.h index 8994cf7bc0f5e9..5084ee22b3d832 100644 --- a/src/lib/core/CHIPConfig.h +++ b/src/lib/core/CHIPConfig.h @@ -761,7 +761,10 @@ extern const char CHIP_NON_PRODUCTION_MARKER[]; * The following definitions sets the maximum number of corresponding interaction model object pool size. * * * #CHIP_IM_MAX_NUM_COMMAND_HANDLER - * * #CHIP_IM_MAX_NUM_READ_HANDLER + * * #CHIP_IM_MAX_NUM_READS + * * #CHIP_IM_MAX_NUM_SUBSCRIPTIONS + * * #CHIP_IM_SERVER_MAX_NUM_PATH_GROUPS_FOR_SUBSCRIPTIONS + * * #CHIP_IM_SERVER_MAX_NUM_PATH_GROUPS_FOR_READS * * #CHIP_IM_MAX_REPORTS_IN_FLIGHT * * #CHIP_IM_SERVER_MAX_NUM_PATH_GROUPS * * #CHIP_IM_SERVER_MAX_NUM_DIRTY_SET @@ -782,17 +785,30 @@ extern const char CHIP_NON_PRODUCTION_MARKER[]; #endif /** - * @def CHIP_IM_MAX_NUM_READ_HANDLER + * @def CHIP_IM_MAX_NUM_SUBSCRIPTIONS * - * @brief Defines the maximum number of ReadHandler, limits the number of active read transactions on server. + * @brief Defines the maximum number of ReadHandler for subscriptions, limits the number of active subscription transactions on + * server. * - * The default value comes from 3sub per fabric * max number of fabrics, then reserve 1 read client for each fabric. + * The default value comes from 3sub per fabric * max number of fabrics. * - * TODO: (#17085) Should be changed to (CHIP_CONFIG_MAX_FABRICS * 4) after we can hold more read handlers on more concise + * TODO: (#17085) Should be changed to (CHIP_CONFIG_MAX_FABRICS * 3) after we can hold more read handlers on more concise * devices. */ -#ifndef CHIP_IM_MAX_NUM_READ_HANDLER -#define CHIP_IM_MAX_NUM_READ_HANDLER (CHIP_CONFIG_MAX_FABRICS * 3) +#ifndef CHIP_IM_MAX_NUM_SUBSCRIPTIONS +#define CHIP_IM_MAX_NUM_SUBSCRIPTIONS (CHIP_CONFIG_MAX_FABRICS * 2) +#endif + +/** + * @def CHIP_IM_MAX_NUM_READS + * + * @brief Defines the maximum number of ReadHandler for read transactions, limits the number of active read transactions on + * server. + * + * The default value is one per fabric * max number of fabrics. + */ +#ifndef CHIP_IM_MAX_NUM_READS +#define CHIP_IM_MAX_NUM_READS (CHIP_CONFIG_MAX_FABRICS) #endif /** @@ -805,17 +821,23 @@ extern const char CHIP_NON_PRODUCTION_MARKER[]; #endif /** - * @def CHIP_IM_SERVER_MAX_NUM_PATH_GROUPS + * @def CHIP_IM_SERVER_MAX_NUM_PATH_GROUPS_FOR_SUBSCRIPTIONS * - * @brief Defines the maximum number of path objects, limits the number of attributes being read or subscribed at the same time. + * @brief The maximum number of path objects for subscriptions, limits the number of attributes being subscribed at the same time. + */ +#ifndef CHIP_IM_SERVER_MAX_NUM_PATH_GROUPS_FOR_SUBSCRIPTIONS +// #define CHIP_IM_SERVER_MAX_NUM_PATH_GROUPS (CHIP_IM_MAX_NUM_SUBSCRIPTIONS * 3) +// TODO: (#17085) Should be (CHIP_IM_MAX_NUM_SUBSCRIPTIONS * 3) +#define CHIP_IM_SERVER_MAX_NUM_PATH_GROUPS_FOR_SUBSCRIPTIONS (CHIP_IM_MAX_NUM_SUBSCRIPTIONS * 2) +#endif + +/** + * @def CHIP_IM_SERVER_MAX_NUM_PATH_GROUPS_FOR_READS * - * The default value comes from 3path per subsctipion * 3sub per fabric * max number of fabrics, then reserve 1 read client with 9 - * paths for each fabric. + * @brief Defines the maximum number of path objects for read requests. */ -#ifndef CHIP_IM_SERVER_MAX_NUM_PATH_GROUPS -// #define CHIP_IM_SERVER_MAX_NUM_PATH_GROUPS (CHIP_CONFIG_MAX_FABRICS * 18) -// TODO: (#17085) Should be 3 sub * 3 path + 9 path (for read) = 18 -#define CHIP_IM_SERVER_MAX_NUM_PATH_GROUPS (CHIP_CONFIG_MAX_FABRICS * 13) +#ifndef CHIP_IM_SERVER_MAX_NUM_PATH_GROUPS_FOR_READS +#define CHIP_IM_SERVER_MAX_NUM_PATH_GROUPS_FOR_READS (CHIP_IM_MAX_NUM_READS * 9) #endif /** diff --git a/src/messaging/tests/MessagingContext.cpp b/src/messaging/tests/MessagingContext.cpp index 76a8ecef3bc8c4..dd6db9fea07c15 100644 --- a/src/messaging/tests/MessagingContext.cpp +++ b/src/messaging/tests/MessagingContext.cpp @@ -57,6 +57,9 @@ CHIP_ERROR MessagingContext::Init(TransportMgrBase * transport, IOContext * ioCo ReturnErrorOnFailure(CreateSessionBobToAlice()); ReturnErrorOnFailure(CreateSessionAliceToBob()); ReturnErrorOnFailure(CreateSessionBobToFriends()); + + ReturnErrorOnFailure(CreatePASESessionCharlieToDavid()); + ReturnErrorOnFailure(CreatePASESessionDavidToCharlie()); } return CHIP_NO_ERROR; @@ -99,6 +102,20 @@ CHIP_ERROR MessagingContext::CreateSessionAliceToBob() mAliceFabricIndex, mBobAddress, CryptoContext::SessionRole::kResponder); } +CHIP_ERROR MessagingContext::CreatePASESessionCharlieToDavid() +{ + return mSessionManager.InjectPaseSessionWithTestKey(mSessionCharlieToDavid, kCharlieKeyId, 0xdeadbeef, kDavidKeyId, + kUndefinedFabricIndex, mDavidAddress, + CryptoContext::SessionRole::kInitiator); +} + +CHIP_ERROR MessagingContext::CreatePASESessionDavidToCharlie() +{ + return mSessionManager.InjectPaseSessionWithTestKey(mSessionDavidToCharlie, kDavidKeyId, 0xcafe, kCharlieKeyId, + kUndefinedFabricIndex, mCharlieAddress, + CryptoContext::SessionRole::kResponder); +} + CHIP_ERROR MessagingContext::CreateSessionBobToFriends() { mSessionBobToFriends.Emplace(GetFriendsGroupId(), mBobFabricIndex); @@ -117,6 +134,18 @@ SessionHandle MessagingContext::GetSessionAliceToBob() return std::move(sessionHandle.Value()); } +SessionHandle MessagingContext::GetSessionCharlieToDavid() +{ + auto sessionHandle = mSessionCharlieToDavid.Get(); + return std::move(sessionHandle.Value()); +} + +SessionHandle MessagingContext::GetSessionDavidToCharlie() +{ + auto sessionHandle = mSessionDavidToCharlie.Get(); + return std::move(sessionHandle.Value()); +} + SessionHandle MessagingContext::GetSessionBobToFriends() { return SessionHandle(mSessionBobToFriends.Value()); diff --git a/src/messaging/tests/MessagingContext.h b/src/messaging/tests/MessagingContext.h index 7e39168726daf6..a759364de331e5 100644 --- a/src/messaging/tests/MessagingContext.h +++ b/src/messaging/tests/MessagingContext.h @@ -99,8 +99,10 @@ class MessagingContext : public PlatformMemoryUser return addr; } - static const uint16_t kBobKeyId = 1; - static const uint16_t kAliceKeyId = 2; + static const uint16_t kBobKeyId = 1; + static const uint16_t kAliceKeyId = 2; + static const uint16_t kCharlieKeyId = 3; + static const uint16_t kDavidKeyId = 4; NodeId GetBobNodeId() const; NodeId GetAliceNodeId() const; GroupId GetFriendsGroupId() const { return mFriendsGroupId; } @@ -118,6 +120,8 @@ class MessagingContext : public PlatformMemoryUser CHIP_ERROR CreateSessionBobToAlice(); CHIP_ERROR CreateSessionAliceToBob(); CHIP_ERROR CreateSessionBobToFriends(); + CHIP_ERROR CreatePASESessionCharlieToDavid(); + CHIP_ERROR CreatePASESessionDavidToCharlie(); void ExpireSessionBobToAlice(); void ExpireSessionAliceToBob(); @@ -125,6 +129,8 @@ class MessagingContext : public PlatformMemoryUser SessionHandle GetSessionBobToAlice(); SessionHandle GetSessionAliceToBob(); + SessionHandle GetSessionCharlieToDavid(); + SessionHandle GetSessionDavidToCharlie(); SessionHandle GetSessionBobToFriends(); const Transport::PeerAddress & GetAliceAddress() { return mAliceAddress; } @@ -154,8 +160,12 @@ class MessagingContext : public PlatformMemoryUser GroupId mFriendsGroupId = 0x0101; Transport::PeerAddress mAliceAddress; Transport::PeerAddress mBobAddress; + Transport::PeerAddress mCharlieAddress; + Transport::PeerAddress mDavidAddress; SessionHolder mSessionAliceToBob; SessionHolder mSessionBobToAlice; + SessionHolder mSessionCharlieToDavid; + SessionHolder mSessionDavidToCharlie; Optional mSessionBobToFriends; };