From 10728560072e53b499894667945af95de6bdcd5b Mon Sep 17 00:00:00 2001 From: Song GUO Date: Wed, 17 Nov 2021 23:39:11 +0800 Subject: [PATCH] [IM] Wildcard read and chunking (#11304) * [IM] Support chunking in report engine * Address comments * Support wildcard path in read / subscribe interaction --- src/app/ClusterInfo.h | 10 + src/app/ReadClient.cpp | 14 +- src/app/ReadClient.h | 1 + src/app/ReadHandler.cpp | 38 +++- src/app/ReadHandler.h | 22 +- src/app/reporting/Engine.cpp | 170 +++++++++------ src/app/reporting/Engine.h | 19 +- src/app/tests/TestReadInteraction.cpp | 255 ++++++++++++++++++++++- src/app/tests/integration/BUILD.gn | 2 + src/app/util/mock/Constants.h | 10 +- src/app/util/mock/Functions.h | 36 ++++ src/app/util/mock/attribute-storage.cpp | 89 ++++++++ src/controller/tests/data_model/BUILD.gn | 1 + src/lib/core/CHIPTLV.h | 29 +++ src/lib/core/CHIPTLVTags.h | 2 + src/lib/core/CHIPTLVWriter.cpp | 2 + 16 files changed, 603 insertions(+), 97 deletions(-) create mode 100644 src/app/util/mock/Functions.h diff --git a/src/app/ClusterInfo.h b/src/app/ClusterInfo.h index c07d3491623b9f..afefd092fe420b 100644 --- a/src/app/ClusterInfo.h +++ b/src/app/ClusterInfo.h @@ -18,6 +18,7 @@ #pragma once +#include #include #include #include @@ -57,6 +58,15 @@ struct ClusterInfo return true; } + bool IsAttributePathSupersetOf(const ConcreteAttributePath & other) const + { + VerifyOrReturnError(HasWildcardEndpointId() || mEndpointId == other.mEndpointId, false); + VerifyOrReturnError(HasWildcardClusterId() || mClusterId == other.mClusterId, false); + VerifyOrReturnError(HasWildcardAttributeId() || mAttributeId == other.mAttributeId, false); + + return true; + } + bool HasWildcard() const { return HasWildcardEndpointId() || HasWildcardClusterId() || HasWildcardAttributeId(); } /** diff --git a/src/app/ReadClient.cpp b/src/app/ReadClient.cpp index e6321a05cbb582..46e90dcee8c554 100644 --- a/src/app/ReadClient.cpp +++ b/src/app/ReadClient.cpp @@ -261,7 +261,7 @@ CHIP_ERROR ReadClient::OnMessageReceived(Messaging::ExchangeContext * apExchange } exit: - if (!IsSubscriptionType() || err != CHIP_NO_ERROR) + if ((!IsSubscriptionType() && !mPendingMoreChunks) || err != CHIP_NO_ERROR) { ShutdownInternal(err); } @@ -300,7 +300,6 @@ CHIP_ERROR ReadClient::ProcessReportData(System::PacketBufferHandle && aPayload) bool isEventReportsPresent = false; bool isAttributeReportIBsPresent = false; bool suppressResponse = false; - bool moreChunkedMessages = false; uint64_t subscriptionId = 0; EventReports::Parser EventReports; AttributeReportIBs::Parser attributeReportIBs; @@ -349,10 +348,11 @@ CHIP_ERROR ReadClient::ProcessReportData(System::PacketBufferHandle && aPayload) } SuccessOrExit(err); - err = report.GetMoreChunkedMessages(&moreChunkedMessages); + err = report.GetMoreChunkedMessages(&mPendingMoreChunks); if (CHIP_END_OF_TLV == err) { - err = CHIP_NO_ERROR; + mPendingMoreChunks = false; + err = CHIP_NO_ERROR; } SuccessOrExit(err); @@ -378,7 +378,7 @@ CHIP_ERROR ReadClient::ProcessReportData(System::PacketBufferHandle && aPayload) err = CHIP_NO_ERROR; } SuccessOrExit(err); - if (isAttributeReportIBsPresent && nullptr != mpCallback && !moreChunkedMessages) + if (isAttributeReportIBsPresent && nullptr != mpCallback) { TLV::TLVReader attributeReportIBsReader; attributeReportIBs.GetReader(&attributeReportIBsReader); @@ -407,9 +407,9 @@ CHIP_ERROR ReadClient::ProcessReportData(System::PacketBufferHandle && aPayload) StatusResponse::SendStatusResponse(err == CHIP_NO_ERROR ? Protocols::InteractionModel::Status::Success : Protocols::InteractionModel::Status::InvalidSubscription, - mpExchangeCtx, IsAwaitingSubscribeResponse()); + mpExchangeCtx, IsAwaitingSubscribeResponse() || mPendingMoreChunks); - if (!mInitialReport) + if (!mInitialReport && !mPendingMoreChunks) { mpExchangeCtx = nullptr; } diff --git a/src/app/ReadClient.h b/src/app/ReadClient.h index d3c98371a21ee8..9fa7e3d755adb1 100644 --- a/src/app/ReadClient.h +++ b/src/app/ReadClient.h @@ -265,6 +265,7 @@ class ReadClient : public Messaging::ExchangeDelegate Callback * mpCallback = nullptr; ClientState mState = ClientState::Uninitialized; bool mInitialReport = true; + bool mPendingMoreChunks = false; uint16_t mMinIntervalFloorSeconds = 0; uint16_t mMaxIntervalCeilingSeconds = 0; uint64_t mSubscriptionId = 0; diff --git a/src/app/ReadHandler.cpp b/src/app/ReadHandler.cpp index b5f8dbba50c25e..48f6c1c80ccbe5 100644 --- a/src/app/ReadHandler.cpp +++ b/src/app/ReadHandler.cpp @@ -54,6 +54,7 @@ CHIP_ERROR ReadHandler::Init(Messaging::ExchangeManager * apExchangeMgr, Interac mHoldReport = false; mDirty = false; mActiveSubscription = false; + mIsChunkedReport = false; mInteractionType = aInteractionType; mInitiatorNodeId = apExchangeContext->GetSessionHandle().GetPeerNodeId(); mFabricIndex = apExchangeContext->GetSessionHandle().GetFabricIndex(); @@ -107,6 +108,7 @@ void ReadHandler::Shutdown(ShutdownOptions aOptions) mHoldReport = false; mDirty = false; mActiveSubscription = false; + mIsChunkedReport = false; mInitiatorNodeId = kUndefinedNodeId; } @@ -140,7 +142,18 @@ CHIP_ERROR ReadHandler::OnStatusResponse(Messaging::ExchangeContext * apExchange switch (mState) { case HandlerState::AwaitingReportResponse: - if (IsSubscriptionType()) + if (IsChunkedReport()) + { + InteractionModelEngine::GetInstance()->GetReportingEngine().OnReportConfirm(); + MoveToState(HandlerState::GeneratingReports); + if (mpExchangeCtx) + { + mpExchangeCtx->WillSendMessage(); + } + // Trigger ReportingEngine run for sending next chunk of data. + SuccessOrExit(err = InteractionModelEngine::GetInstance()->GetReportingEngine().ScheduleRun()); + } + else if (IsSubscriptionType()) { InteractionModelEngine::GetInstance()->GetReportingEngine().OnReportConfirm(); if (IsInitialReport()) @@ -176,10 +189,10 @@ CHIP_ERROR ReadHandler::OnStatusResponse(Messaging::ExchangeContext * apExchange return err; } -CHIP_ERROR ReadHandler::SendReportData(System::PacketBufferHandle && aPayload) +CHIP_ERROR ReadHandler::SendReportData(System::PacketBufferHandle && aPayload, bool aMoreChunks) { VerifyOrReturnLogError(IsReportable(), CHIP_ERROR_INCORRECT_STATE); - if (IsInitialReport()) + if (IsInitialReport() || IsChunkedReport()) { mSessionHandle.SetValue(mpExchangeCtx->GetSessionHandle()); } @@ -190,6 +203,7 @@ CHIP_ERROR ReadHandler::SendReportData(System::PacketBufferHandle && aPayload) mpExchangeCtx->SetResponseTimeout(kImMessageTimeout); } VerifyOrReturnLogError(mpExchangeCtx != nullptr, CHIP_ERROR_INCORRECT_STATE); + mIsChunkedReport = aMoreChunks; MoveToState(HandlerState::AwaitingReportResponse); CHIP_ERROR err = mpExchangeCtx->SendMessage(Protocols::InteractionModel::MsgType::ReportData, std::move(aPayload), Messaging::SendFlags(Messaging::SendMessageFlags::kExpectResponse)); @@ -200,7 +214,10 @@ CHIP_ERROR ReadHandler::SendReportData(System::PacketBufferHandle && aPayload) err = RefreshSubscribeSyncTimer(); } } - ClearDirty(); + if (!aMoreChunks) + { + ClearDirty(); + } return err; } @@ -319,20 +336,28 @@ CHIP_ERROR ReadHandler::ProcessAttributePathList(AttributePathIBs::Parser & aAtt AttributePathIB::Parser path; err = path.Init(reader); SuccessOrExit(err); - // TODO: Support wildcard paths here // TODO: MEIs (ClusterId and AttributeId) have a invalid pattern instead of a single invalid value, need to add separate // functions for checking if we have received valid values. + // TODO: Wildcard cluster id with non-global attributes or wildcard attribute paths should be rejected. err = path.GetEndpoint(&(clusterInfo.mEndpointId)); if (err == CHIP_NO_ERROR) { VerifyOrExit(!clusterInfo.HasWildcardEndpointId(), err = CHIP_ERROR_IM_MALFORMED_ATTRIBUTE_PATH); } + else if (err == CHIP_END_OF_TLV) + { + err = CHIP_NO_ERROR; + } SuccessOrExit(err); err = path.GetCluster(&(clusterInfo.mClusterId)); if (err == CHIP_NO_ERROR) { VerifyOrExit(!clusterInfo.HasWildcardClusterId(), err = CHIP_ERROR_IM_MALFORMED_ATTRIBUTE_PATH); } + else if (err == CHIP_END_OF_TLV) + { + err = CHIP_NO_ERROR; + } SuccessOrExit(err); err = path.GetAttribute(&(clusterInfo.mAttributeId)); @@ -364,7 +389,8 @@ CHIP_ERROR ReadHandler::ProcessAttributePathList(AttributePathIBs::Parser & aAtt // if we have exhausted this container if (CHIP_END_OF_TLV == err) { - err = CHIP_NO_ERROR; + mAttributePathExpandIterator = AttributePathExpandIterator(mpAttributeClusterInfoList); + err = CHIP_NO_ERROR; } exit: diff --git a/src/app/ReadHandler.h b/src/app/ReadHandler.h index 6fb333d4663cd4..7f1e644832b0dc 100644 --- a/src/app/ReadHandler.h +++ b/src/app/ReadHandler.h @@ -24,6 +24,7 @@ #pragma once +#include #include #include #include @@ -97,12 +98,13 @@ class ReadHandler : public Messaging::ExchangeDelegate * Send ReportData to initiator * * @param[in] aPayload A payload that has read request data + * @param[in] aMoreChunks A flags indicating there will be more chunks expected to be sent for this read request * * @retval #Others If fails to send report data * @retval #CHIP_NO_ERROR On success. * */ - CHIP_ERROR SendReportData(System::PacketBufferHandle && aPayload); + CHIP_ERROR SendReportData(System::PacketBufferHandle && aPayload, bool mMoreChunks); bool IsFree() const { return mState == HandlerState::Uninitialized; } bool IsReportable() const { return mState == HandlerState::GeneratingReports && !mHoldReport; } @@ -126,11 +128,19 @@ class ReadHandler : public Messaging::ExchangeDelegate bool IsReadType() { return mInteractionType == InteractionType::Read; } bool IsSubscriptionType() { return mInteractionType == InteractionType::Subscribe; } + bool IsChunkedReport() { return mIsChunkedReport; } bool IsInitialReport() { return mInitialReport; } bool IsActiveSubscription() const { return mActiveSubscription; } CHIP_ERROR OnSubscribeRequest(Messaging::ExchangeContext * apExchangeContext, System::PacketBufferHandle && aPayload); void GetSubscriptionId(uint64_t & aSubscriptionId) { aSubscriptionId = mSubscriptionId; } - void SetDirty() { mDirty = true; } + AttributePathExpandIterator * GetAttributePathExpandIterator() { return &mAttributePathExpandIterator; } + void SetDirty() + { + mDirty = true; + // If the contents of the global dirty set have changed, we need to reset the iterator since the paths + // we've sent up till now are no longer valid and need to be invalidated. + mAttributePathExpandIterator = AttributePathExpandIterator(mpAttributeClusterInfoList); + } void ClearDirty() { mDirty = false; } bool IsDirty() { return mDirty; } NodeId GetInitiatorNodeId() const { return mInitiatorNodeId; } @@ -192,8 +202,12 @@ class ReadHandler : public Messaging::ExchangeDelegate bool mHoldReport = false; bool mDirty = false; bool mActiveSubscription = false; - NodeId mInitiatorNodeId = kUndefinedNodeId; - FabricIndex mFabricIndex = 0; + // The flag indicating we are in the middle of a series of chunked report messages, this flag will be cleared during sending + // last chunked message. + bool mIsChunkedReport = false; + NodeId mInitiatorNodeId = kUndefinedNodeId; + FabricIndex mFabricIndex = 0; + AttributePathExpandIterator mAttributePathExpandIterator = AttributePathExpandIterator(nullptr); }; } // namespace app } // namespace chip diff --git a/src/app/reporting/Engine.cpp b/src/app/reporting/Engine.cpp index 3ea8d2c22bb11d..21b4fa2986b5f3 100644 --- a/src/app/reporting/Engine.cpp +++ b/src/app/reporting/Engine.cpp @@ -33,17 +33,15 @@ namespace app { namespace reporting { CHIP_ERROR Engine::Init() { - mMoreChunkedMessages = false; - mNumReportsInFlight = 0; - mCurReadHandlerIdx = 0; + mNumReportsInFlight = 0; + mCurReadHandlerIdx = 0; return CHIP_NO_ERROR; } void Engine::Shutdown() { - mMoreChunkedMessages = false; - mNumReportsInFlight = 0; - mCurReadHandlerIdx = 0; + mNumReportsInFlight = 0; + mCurReadHandlerIdx = 0; InteractionModelEngine::GetInstance()->ReleaseClusterInfoList(mpGlobalDirtySet); mpGlobalDirtySet = nullptr; } @@ -64,19 +62,18 @@ EventNumber Engine::CountEvents(ReadHandler * apReadHandler, EventNumber * apIni CHIP_ERROR Engine::RetrieveClusterData(FabricIndex aAccessingFabricIndex, AttributeReportIBs::Builder & aAttributeReportIBs, - ClusterInfo & aClusterInfo) + const ConcreteAttributePath & aPath) { CHIP_ERROR err = CHIP_NO_ERROR; - ConcreteAttributePath path(aClusterInfo.mEndpointId, aClusterInfo.mClusterId, aClusterInfo.mAttributeId); AttributeReportIB::Builder attributeReport = aAttributeReportIBs.CreateAttributeReport(); - ChipLogDetail(DataManagement, " Cluster %" PRIx32 ", Attribute %" PRIx32 " is dirty", aClusterInfo.mClusterId, - aClusterInfo.mAttributeId); + ChipLogDetail(DataManagement, " Cluster %" PRIx32 ", Attribute %" PRIx32 " is dirty", aPath.mClusterId, + aPath.mAttributeId); - MatterPreAttributeReadCallback(path); - err = ReadSingleClusterData(aAccessingFabricIndex, path, attributeReport); - MatterPostAttributeReadCallback(path); + MatterPreAttributeReadCallback(aPath); + err = ReadSingleClusterData(aAccessingFabricIndex, aPath, attributeReport); + MatterPostAttributeReadCallback(aPath); SuccessOrExit(err); attributeReport.EndOfAttributeReportIB(); err = attributeReport.GetError(); @@ -85,72 +82,94 @@ Engine::RetrieveClusterData(FabricIndex aAccessingFabricIndex, AttributeReportIB if (err != CHIP_NO_ERROR) { ChipLogError(DataManagement, "Error retrieving data from clusterId: " ChipLogFormatMEI ", err = %" CHIP_ERROR_FORMAT, - ChipLogValueMEI(aClusterInfo.mClusterId), err.Format()); + ChipLogValueMEI(aPath.mClusterId), err.Format()); } return err; } CHIP_ERROR Engine::BuildSingleReportDataAttributeReportIBs(ReportDataMessage::Builder & aReportDataBuilder, - ReadHandler * apReadHandler) + ReadHandler * apReadHandler, bool * apHasMoreChunks) { - CHIP_ERROR err = CHIP_NO_ERROR; - bool attributeClean = true; + CHIP_ERROR err = CHIP_NO_ERROR; + bool attributeDataWritten = false; + bool hasMoreChunks = true; TLV::TLVWriter backup; aReportDataBuilder.Checkpoint(backup); - AttributeReportIBs::Builder AttributeReportIBs = aReportDataBuilder.CreateAttributeReportIBs(); + auto attributeReportIBs = aReportDataBuilder.CreateAttributeReportIBs(); SuccessOrExit(err = aReportDataBuilder.GetError()); - // TODO: Need to handle multiple chunk of message - for (auto clusterInfo = apReadHandler->GetAttributeClusterInfolist(); clusterInfo != nullptr; clusterInfo = clusterInfo->mpNext) + { - if (apReadHandler->IsInitialReport()) - { - // Retrieve data for this cluster instance and clear its dirty flag. - err = RetrieveClusterData(apReadHandler->GetFabricIndex(), AttributeReportIBs, *clusterInfo); - VerifyOrExit(err == CHIP_NO_ERROR, - ChipLogError(DataManagement, " Error retrieving data from cluster, aborting")); - attributeClean = false; - } - else + ConcreteAttributePath readPath; + + // For each path included in the interested path of the read handler... + for (; apReadHandler->GetAttributePathExpandIterator()->Get(readPath); + apReadHandler->GetAttributePathExpandIterator()->Next()) { - for (auto path = mpGlobalDirtySet; path != nullptr; path = path->mpNext) + if (!apReadHandler->IsInitialReport()) { - if (clusterInfo->IsAttributePathSupersetOf(*path)) - { - // SetDirty injects path into GlobalDirtySet path that don't have the particular nodeId, - // need to inject nodeId from subscribed path here. - ClusterInfo dirtyPath = *path; - dirtyPath.mNodeId = clusterInfo->mNodeId; - err = RetrieveClusterData(apReadHandler->GetFabricIndex(), AttributeReportIBs, dirtyPath); - } - else if (path->IsAttributePathSupersetOf(*clusterInfo)) + bool concretePathDirty = false; + // TODO: Optimize this implementation by making the iterator only emit intersected paths. + for (auto dirtyPath = mpGlobalDirtySet; dirtyPath != nullptr; dirtyPath = dirtyPath->mpNext) { - err = RetrieveClusterData(apReadHandler->GetFabricIndex(), AttributeReportIBs, *clusterInfo); + if (dirtyPath->IsAttributePathSupersetOf(readPath)) + { + concretePathDirty = true; + break; + } } - else + + if (!concretePathDirty) { - // partial overlap is not possible, hence the 'continue' here: clusterInfo and path have nothing in - // common. + // This attribute is not dirty, we just skip this one. continue; } - VerifyOrExit(err == CHIP_NO_ERROR, - ChipLogError(DataManagement, " Error retrieving data from cluster, aborting")); - attributeClean = false; } + // If we are processing a read request, or the initial report of a subscription, just regard all paths as dirty paths. + + TLV::TLVWriter attributeBackup; + attributeReportIBs.Checkpoint(attributeBackup); + err = RetrieveClusterData(apReadHandler->GetFabricIndex(), attributeReportIBs, readPath); + if (err != CHIP_NO_ERROR) + { + // We met a error during writing reports, one common case is we are running out of buffer, rollback the + // attributeReportIB to avoid any partial data. + attributeReportIBs.Rollback(attributeBackup); + } + SuccessOrExit(err); + attributeDataWritten = true; } + // We just visited all paths interested by this read handler and did not abort in the middle of iteration, there are no more + // chunks for this report. + hasMoreChunks = false; } - AttributeReportIBs.EndOfAttributeReportIBs(); - err = AttributeReportIBs.GetError(); - exit: - if (attributeClean || err != CHIP_NO_ERROR) + if ((err == CHIP_ERROR_BUFFER_TOO_SMALL) || (err == CHIP_ERROR_NO_MEMORY)) + { + ChipLogDetail(DataManagement, " We cannot put more chunks into this report. Enable chunking."); + err = CHIP_NO_ERROR; + } + + if (err == CHIP_NO_ERROR) + { + attributeReportIBs.EndOfAttributeReportIBs(); + err = attributeReportIBs.GetError(); + } + + if (!attributeDataWritten || err != CHIP_NO_ERROR) { aReportDataBuilder.Rollback(backup); } + else if (apHasMoreChunks != nullptr) + { + *apHasMoreChunks = hasMoreChunks; + } + return err; } -CHIP_ERROR Engine::BuildSingleReportDataEventReports(ReportDataMessage::Builder & aReportDataBuilder, ReadHandler * apReadHandler) +CHIP_ERROR Engine::BuildSingleReportDataEventReports(ReportDataMessage::Builder & aReportDataBuilder, ReadHandler * apReadHandler, + bool * apHasMoreChunks) { CHIP_ERROR err = CHIP_NO_ERROR; size_t eventCount = 0; @@ -161,6 +180,7 @@ CHIP_ERROR Engine::BuildSingleReportDataEventReports(ReportDataMessage::Builder EventNumber * eventNumberList = apReadHandler->GetVendedEventNumberList(); EventManagement & eventManager = EventManagement::GetInstance(); EventReports::Builder EventReports; + bool hasMoreChunks = false; aReportDataBuilder.Checkpoint(backup); @@ -205,7 +225,7 @@ CHIP_ERROR Engine::BuildSingleReportDataEventReports(ReportDataMessage::Builder // priority level. err = CHIP_NO_ERROR; apReadHandler->MoveToNextScheduledDirtyPriority(); - mMoreChunkedMessages = false; + hasMoreChunks = false; } else if ((err == CHIP_ERROR_BUFFER_TOO_SMALL) || (err == CHIP_ERROR_NO_MEMORY)) { @@ -229,7 +249,7 @@ CHIP_ERROR Engine::BuildSingleReportDataEventReports(ReportDataMessage::Builder err = CHIP_NO_ERROR; break; } - mMoreChunkedMessages = true; + hasMoreChunks = true; } else { @@ -250,6 +270,10 @@ CHIP_ERROR Engine::BuildSingleReportDataEventReports(ReportDataMessage::Builder { aReportDataBuilder.Rollback(backup); } + else if (apHasMoreChunks != nullptr) + { + *apHasMoreChunks = hasMoreChunks; + } return err; } @@ -259,11 +283,23 @@ CHIP_ERROR Engine::BuildAndSendSingleReportData(ReadHandler * apReadHandler) chip::System::PacketBufferTLVWriter reportDataWriter; ReportDataMessage::Builder reportDataBuilder; chip::System::PacketBufferHandle bufHandle = System::PacketBufferHandle::New(chip::app::kMaxSecureSduLengthBytes); + uint16_t reservedSize = 0; + bool hasMoreChunks = false; VerifyOrExit(!bufHandle.IsNull(), err = CHIP_ERROR_NO_MEMORY); + if (bufHandle->AvailableDataLength() > kMaxSecureSduLengthBytes) + { + reservedSize = static_cast(bufHandle->AvailableDataLength() - kMaxSecureSduLengthBytes); + } + reportDataWriter.Init(std::move(bufHandle)); + // Always limit the size of the generated packet to fit within kMaxSecureSduLengthBytes regardless of the available buffer + // capacity. + // Also, we need to reserve some extra space for the MIC field. + reportDataWriter.ReserveBuffer(static_cast(reservedSize + chip::Crypto::CHIP_CRYPTO_AEAD_MIC_LENGTH_BYTES)); + // Create a report data. err = reportDataBuilder.Init(&reportDataWriter); SuccessOrExit(err); @@ -275,17 +311,18 @@ CHIP_ERROR Engine::BuildAndSendSingleReportData(ReadHandler * apReadHandler) reportDataBuilder.SubscriptionId(subscriptionId); } - err = BuildSingleReportDataAttributeReportIBs(reportDataBuilder, apReadHandler); + SuccessOrExit(err = reportDataWriter.ReserveBuffer(Engine::kReservedSizeForMoreChunksFlag)); + + err = BuildSingleReportDataAttributeReportIBs(reportDataBuilder, apReadHandler, &hasMoreChunks); SuccessOrExit(err); - err = BuildSingleReportDataEventReports(reportDataBuilder, apReadHandler); + err = BuildSingleReportDataEventReports(reportDataBuilder, apReadHandler, &hasMoreChunks); SuccessOrExit(err); - // TODO: Add mechanism to set mSuppressResponse to handle status reports for multiple reports - // TODO: Add more chunk message support, currently mMoreChunkedMessages is always false. - if (mMoreChunkedMessages) + SuccessOrExit(err = reportDataWriter.UnreserveBuffer(Engine::kReservedSizeForMoreChunksFlag)); + if (hasMoreChunks) { - reportDataBuilder.MoreChunkedMessages(mMoreChunkedMessages); + reportDataBuilder.MoreChunkedMessages(hasMoreChunks); } reportDataBuilder.EndOfReportDataMessage(); @@ -311,13 +348,13 @@ CHIP_ERROR Engine::BuildAndSendSingleReportData(ReadHandler * apReadHandler) } #endif // CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK - ChipLogDetail(DataManagement, " Sending report..."); - err = SendReport(apReadHandler, std::move(bufHandle)); + ChipLogDetail(DataManagement, " Sending report (payload has %" PRIu32 " bytes)...", reportDataWriter.GetLengthWritten()); + err = SendReport(apReadHandler, std::move(bufHandle), hasMoreChunks); VerifyOrExit(err == CHIP_NO_ERROR, ChipLogError(DataManagement, " Error sending out report data with %" CHIP_ERROR_FORMAT "!", err.Format())); ChipLogDetail(DataManagement, " ReportsInFlight = %" PRIu32 " with readHandler %" PRIu32 ", RE has %s", mNumReportsInFlight, - mCurReadHandlerIdx, mMoreChunkedMessages ? "more messages" : "no more messages"); + mCurReadHandlerIdx, hasMoreChunks ? "more messages" : "no more messages"); exit: if (err != CHIP_NO_ERROR) @@ -397,7 +434,10 @@ CHIP_ERROR Engine::SetDirty(ClusterInfo & aClusterInfo) { for (auto & handler : InteractionModelEngine::GetInstance()->mReadHandlers) { - if (handler.IsSubscriptionType() && (handler.IsGeneratingReports() || handler.IsAwaitingReportResponse())) + // We call SetDirty for both read interactions and subscribe interactions, since we may sent inconsistent attribute data + // between two chunks. SetDirty will be ignored automatically by read handlers which is waiting for response to last message + // chunk for read interactions. + if (handler.IsGeneratingReports() || handler.IsAwaitingReportResponse()) { handler.SetDirty(); } @@ -437,13 +477,13 @@ void Engine::UpdateReadHandlerDirty(ReadHandler & aReadHandler) } } -CHIP_ERROR Engine::SendReport(ReadHandler * apReadHandler, System::PacketBufferHandle && aPayload) +CHIP_ERROR Engine::SendReport(ReadHandler * apReadHandler, System::PacketBufferHandle && aPayload, bool aHasMoreChunks) { CHIP_ERROR err = CHIP_NO_ERROR; // We can only have 1 report in flight for any given read - increment and break out. mNumReportsInFlight++; - err = apReadHandler->SendReportData(std::move(aPayload)); + err = apReadHandler->SendReportData(std::move(aPayload), aHasMoreChunks); return err; } diff --git a/src/app/reporting/Engine.h b/src/app/reporting/Engine.h index 72b9c6329f22fa..38aa29bd61d996 100644 --- a/src/app/reporting/Engine.h +++ b/src/app/reporting/Engine.h @@ -92,10 +92,12 @@ class Engine */ CHIP_ERROR BuildAndSendSingleReportData(ReadHandler * apReadHandler); - CHIP_ERROR BuildSingleReportDataAttributeReportIBs(ReportDataMessage::Builder & reportDataBuilder, ReadHandler * apReadHandler); - CHIP_ERROR BuildSingleReportDataEventReports(ReportDataMessage::Builder & reportDataBuilder, ReadHandler * apReadHandler); + CHIP_ERROR BuildSingleReportDataAttributeReportIBs(ReportDataMessage::Builder & reportDataBuilder, ReadHandler * apReadHandler, + bool * apHasMoreChunks); + CHIP_ERROR BuildSingleReportDataEventReports(ReportDataMessage::Builder & reportDataBuilder, ReadHandler * apReadHandler, + bool * apHasMoreChunks); CHIP_ERROR RetrieveClusterData(FabricIndex aAccessingFabricIndex, AttributeReportIBs::Builder & aAttributeReportIBs, - ClusterInfo & aClusterInfo); + const ConcreteAttributePath & aClusterInfo); EventNumber CountEvents(ReadHandler * apReadHandler, EventNumber * apInitialEvents); /** @@ -108,7 +110,7 @@ class Engine * Send Report via ReadHandler * */ - CHIP_ERROR SendReport(ReadHandler * apReadHandler, System::PacketBufferHandle && aPayload); + CHIP_ERROR SendReport(ReadHandler * apReadHandler, System::PacketBufferHandle && aPayload, bool aHasMoreChunks); /** * Generate and send the report data request when there exists subscription or read request @@ -116,12 +118,6 @@ class Engine */ static void Run(System::Layer * aSystemLayer, void * apAppState); - /** - * Boolean to show if more chunk message on the way - * - */ - bool mMoreChunkedMessages = false; - /** * Boolean to indicate if ScheduleRun is pending. This flag is used to prevent calling ScheduleRun multiple times * within the same execution context to avoid applying too much pressure on platforms that use small, fixed size event queues. @@ -149,6 +145,9 @@ class Engine * */ ClusterInfo * mpGlobalDirtySet = nullptr; + + constexpr static uint32_t kReservedSizeForMoreChunksFlag = + 2; // According to TLV encoding, the TAG length is 1 byte and its type is 1 byte. }; }; // namespace reporting diff --git a/src/app/tests/TestReadInteraction.cpp b/src/app/tests/TestReadInteraction.cpp index e0bc4a0eb1c795..2e9fc4b4b2aa78 100644 --- a/src/app/tests/TestReadInteraction.cpp +++ b/src/app/tests/TestReadInteraction.cpp @@ -27,6 +27,8 @@ #include #include #include +#include +#include #include #include #include @@ -234,6 +236,11 @@ CHIP_ERROR ReadSingleClusterData(FabricIndex aAccessingFabricIndex, const Concre AttributePathIB::Builder attributePath; ChipLogDetail(DataManagement, "TEST Cluster %" PRIx32 ", Field %" PRIx32 " is dirty", aPath.mClusterId, aPath.mAttributeId); + if (aPath.mClusterId >= Test::kMockEndpointMin) + { + return Test::ReadSingleMockClusterData(aAccessingFabricIndex, aPath, aAttributeReport); + } + if (!(aPath.mClusterId == kTestClusterId && aPath.mEndpointId == kTestEndpointId)) { AttributeStatusIB::Builder attributeStatus; @@ -272,7 +279,11 @@ class TestReadInteraction static void TestProcessSubscribeResponse(nlTestSuite * apSuite, void * apContext); static void TestProcessSubscribeRequest(nlTestSuite * apSuite, void * apContext); static void TestReadRoundtrip(nlTestSuite * apSuite, void * apContext); + static void TestReadWildcard(nlTestSuite * apSuite, void * apContext); + static void TestReadChunking(nlTestSuite * apSuite, void * apContext); + static void TestSetDirtyBetweenChunks(nlTestSuite * apSuite, void * apContext); static void TestSubscribeRoundtrip(nlTestSuite * apSuite, void * apContext); + static void TestSubscribeWildcard(nlTestSuite * apSuite, void * apContext); static void TestSubscribeEarlyShutdown(nlTestSuite * apSuite, void * apContext); static void TestSubscribeInvalidAttributePathRoundtrip(nlTestSuite * apSuite, void * apContext); static void TestReadInvalidAttributePathRoundtrip(nlTestSuite * apSuite, void * apContext); @@ -399,7 +410,7 @@ void TestReadInteraction::TestReadHandler(nlTestSuite * apSuite, void * apContex readHandler.Init(&ctx.GetExchangeManager(), nullptr, exchangeCtx, chip::app::ReadHandler::InteractionType::Read); GenerateReportData(apSuite, apContext, reportDatabuf); - err = readHandler.SendReportData(std::move(reportDatabuf)); + err = readHandler.SendReportData(std::move(reportDatabuf), false); NL_TEST_ASSERT(apSuite, err == CHIP_ERROR_INCORRECT_STATE); writer.Init(std::move(readRequestbuf)); @@ -528,7 +539,7 @@ void TestReadInteraction::TestReadHandlerInvalidAttributePath(nlTestSuite * apSu readHandler.Init(&ctx.GetExchangeManager(), nullptr, exchangeCtx, chip::app::ReadHandler::InteractionType::Read); GenerateReportData(apSuite, apContext, reportDatabuf); - err = readHandler.SendReportData(std::move(reportDatabuf)); + err = readHandler.SendReportData(std::move(reportDatabuf), false); NL_TEST_ASSERT(apSuite, err == CHIP_ERROR_INCORRECT_STATE); writer.Init(std::move(readRequestbuf)); @@ -719,6 +730,147 @@ void TestReadInteraction::TestReadRoundtrip(nlTestSuite * apSuite, void * apCont engine->Shutdown(); } +void TestReadInteraction::TestReadWildcard(nlTestSuite * apSuite, void * apContext) +{ + TestContext & ctx = *static_cast(apContext); + CHIP_ERROR err = CHIP_NO_ERROR; + + Messaging::ReliableMessageMgr * rm = ctx.GetExchangeManager().GetReliableMessageMgr(); + // Shouldn't have anything in the retransmit table when starting the test. + NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); + + GenerateEvents(apSuite, apContext); + + MockInteractionModelApp delegate; + auto * engine = chip::app::InteractionModelEngine::GetInstance(); + err = engine->Init(&ctx.GetExchangeManager(), &delegate); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); + + chip::app::AttributePathParams attributePathParams[1]; + attributePathParams[0].mEndpointId = Test::kMockEndpoint2; + attributePathParams[0].mClusterId = Test::MockClusterId(3); + + ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); + readPrepareParams.mpEventPathParamsList = nullptr; + readPrepareParams.mEventPathParamsListSize = 0; + readPrepareParams.mpAttributePathParamsList = attributePathParams; + readPrepareParams.mAttributePathParamsListSize = 1; + err = chip::app::InteractionModelEngine::GetInstance()->SendReadRequest(readPrepareParams, &delegate); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + InteractionModelEngine::GetInstance()->GetReportingEngine().Run(); + NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 5); + NL_TEST_ASSERT(apSuite, delegate.mGotReport); + NL_TEST_ASSERT(apSuite, !delegate.mReadError); + // By now we should have closed all exchanges and sent all pending acks, so + // there should be no queued-up things in the retransmit table. + NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); + + engine->Shutdown(); +} + +// TestReadChunking will try to read a few large attributes, the report won't fit into the MTU and result in chunking. +void TestReadInteraction::TestReadChunking(nlTestSuite * apSuite, void * apContext) +{ + TestContext & ctx = *static_cast(apContext); + CHIP_ERROR err = CHIP_NO_ERROR; + + Messaging::ReliableMessageMgr * rm = ctx.GetExchangeManager().GetReliableMessageMgr(); + // Shouldn't have anything in the retransmit table when starting the test. + NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); + + GenerateEvents(apSuite, apContext); + + MockInteractionModelApp delegate; + auto * engine = chip::app::InteractionModelEngine::GetInstance(); + err = engine->Init(&ctx.GetExchangeManager(), &delegate); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); + + chip::app::AttributePathParams attributePathParams[6]; + for (int i = 0; i < 6; i++) + { + attributePathParams[i].mEndpointId = Test::kMockEndpoint3; + attributePathParams[i].mClusterId = Test::MockClusterId(2); + attributePathParams[i].mAttributeId = Test::MockAttributeId(4); + } + + ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); + readPrepareParams.mpEventPathParamsList = nullptr; + readPrepareParams.mEventPathParamsListSize = 0; + readPrepareParams.mpAttributePathParamsList = attributePathParams; + readPrepareParams.mAttributePathParamsListSize = 6; + err = chip::app::InteractionModelEngine::GetInstance()->SendReadRequest(readPrepareParams, &delegate); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + InteractionModelEngine::GetInstance()->GetReportingEngine().Run(); + InteractionModelEngine::GetInstance()->GetReportingEngine().Run(); + + NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 6); + NL_TEST_ASSERT(apSuite, delegate.mGotReport); + NL_TEST_ASSERT(apSuite, !delegate.mReadError); + // By now we should have closed all exchanges and sent all pending acks, so + // there should be no queued-up things in the retransmit table. + NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); + + engine->Shutdown(); +} + +void TestReadInteraction::TestSetDirtyBetweenChunks(nlTestSuite * apSuite, void * apContext) +{ + TestContext & ctx = *static_cast(apContext); + CHIP_ERROR err = CHIP_NO_ERROR; + + Messaging::ReliableMessageMgr * rm = ctx.GetExchangeManager().GetReliableMessageMgr(); + // Shouldn't have anything in the retransmit table when starting the test. + NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); + + GenerateEvents(apSuite, apContext); + + MockInteractionModelApp delegate; + auto * engine = chip::app::InteractionModelEngine::GetInstance(); + err = engine->Init(&ctx.GetExchangeManager(), &delegate); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); + + chip::app::AttributePathParams attributePathParams[6]; + for (int i = 0; i < 6; i++) + { + attributePathParams[i].mEndpointId = Test::kMockEndpoint3; + attributePathParams[i].mClusterId = Test::MockClusterId(2); + attributePathParams[i].mAttributeId = Test::MockAttributeId(4); + } + + ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); + readPrepareParams.mpEventPathParamsList = nullptr; + readPrepareParams.mEventPathParamsListSize = 0; + readPrepareParams.mpAttributePathParamsList = attributePathParams; + readPrepareParams.mAttributePathParamsListSize = 6; + err = chip::app::InteractionModelEngine::GetInstance()->SendReadRequest(readPrepareParams, &delegate); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + ClusterInfo dirtyPath; + dirtyPath.mEndpointId = Test::kMockEndpoint3; + dirtyPath.mClusterId = Test::MockClusterId(2); + dirtyPath.mAttributeId = Test::MockAttributeId(4); + + InteractionModelEngine::GetInstance()->GetReportingEngine().Run(); + InteractionModelEngine::GetInstance()->GetReportingEngine().SetDirty(dirtyPath); + InteractionModelEngine::GetInstance()->GetReportingEngine().Run(); + InteractionModelEngine::GetInstance()->GetReportingEngine().Run(); + + // We should receive more than 6 attribute reports since the underlying path iterator should be reset. + NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse > 6); + NL_TEST_ASSERT(apSuite, delegate.mGotReport); + NL_TEST_ASSERT(apSuite, !delegate.mReadError); + // By now we should have closed all exchanges and sent all pending acks, so + // there should be no queued-up things in the retransmit table. + NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); + + engine->Shutdown(); +} + void TestReadInteraction::TestReadInvalidAttributePathRoundtrip(nlTestSuite * apSuite, void * apContext) { TestContext & ctx = *static_cast(apContext); @@ -1091,6 +1243,101 @@ void TestReadInteraction::TestSubscribeRoundtrip(nlTestSuite * apSuite, void * a engine->Shutdown(); } +void TestReadInteraction::TestSubscribeWildcard(nlTestSuite * apSuite, void * apContext) +{ + TestContext & ctx = *static_cast(apContext); + CHIP_ERROR err = CHIP_NO_ERROR; + + Messaging::ReliableMessageMgr * rm = ctx.GetExchangeManager().GetReliableMessageMgr(); + // Shouldn't have anything in the retransmit table when starting the test. + NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0); + + GenerateEvents(apSuite, apContext); + + MockInteractionModelApp delegate; + auto * engine = chip::app::InteractionModelEngine::GetInstance(); + err = engine->Init(&ctx.GetExchangeManager(), &delegate); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse); + + ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); + readPrepareParams.mEventPathParamsListSize = 0; + + chip::app::AttributePathParams attributePathParams[2]; + // Subscribe to full wildcard paths, repeat twice to ensure chunking. + readPrepareParams.mpAttributePathParamsList = attributePathParams; + readPrepareParams.mAttributePathParamsListSize = 2; + + readPrepareParams.mMinIntervalFloorSeconds = 2; + readPrepareParams.mMaxIntervalCeilingSeconds = 5; + printf("\nSend subscribe request message to Node: %" PRIu64 "\n", chip::kTestDeviceNodeId); + + err = engine->SendSubscribeRequest(readPrepareParams, &delegate); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + delegate.mNumAttributeResponse = 0; + readPrepareParams.mKeepSubscriptions = false; + err = engine->SendSubscribeRequest(readPrepareParams, &delegate); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + delegate.mGotReport = false; + + for (int i = 0; i < 10 && delegate.mNumSubscriptions == 0; i++) + { + // 10 is a magic number, we assume the initial reports will take no more than 10 chunks. + engine->GetReportingEngine().Run(); + } + NL_TEST_ASSERT(apSuite, delegate.mGotReport); + + // We have 29 attributes in our mock attribute storage. And we subscribed twice. + NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 58); + NL_TEST_ASSERT(apSuite, delegate.mNumSubscriptions == 1); + + // Set a concrete path dirty + { + delegate.mpReadHandler->mHoldReport = false; + delegate.mGotReport = false; + delegate.mNumAttributeResponse = 0; + + ClusterInfo dirtyPath; + dirtyPath.mEndpointId = Test::kMockEndpoint2; + dirtyPath.mClusterId = Test::MockClusterId(3); + dirtyPath.mAttributeId = Test::MockAttributeId(1); + + err = engine->GetReportingEngine().SetDirty(dirtyPath); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + engine->GetReportingEngine().Run(); + NL_TEST_ASSERT(apSuite, delegate.mGotReport); + // We subscribed wildcard path twice, so we will receive two reports here. + NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 2); + } + + // Set a endpoint dirty + { + delegate.mpReadHandler->mHoldReport = false; + delegate.mGotReport = false; + delegate.mNumAttributeResponse = 0; + + ClusterInfo dirtyPath; + dirtyPath.mEndpointId = Test::kMockEndpoint3; + + err = engine->GetReportingEngine().SetDirty(dirtyPath); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + for (int i = 0; i < 10 && delegate.mNumAttributeResponse < 26; i++) + { + delegate.mpReadHandler->mHoldReport = false; + // 10 is a magic number, we assume the report will use no more than 10 chunks. + engine->GetReportingEngine().Run(); + } + + NL_TEST_ASSERT(apSuite, delegate.mGotReport); + // Mock endpoint3 has 13 attributes in total, and we subscribed twice. + NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 26); + } + + engine->Shutdown(); +} + // Verify that subscription can be shut down just after receiving SUBSCRIBE RESPONSE, // before receiving any subsequent REPORT DATA. void TestReadInteraction::TestSubscribeEarlyShutdown(nlTestSuite * apSuite, void * apContext) @@ -1229,6 +1476,9 @@ namespace { const nlTest sTests[] = { NL_TEST_DEF("TestReadRoundtrip", chip::app::TestReadInteraction::TestReadRoundtrip), + NL_TEST_DEF("TestReadWildcard", chip::app::TestReadInteraction::TestReadWildcard), + NL_TEST_DEF("TestReadChunking", chip::app::TestReadInteraction::TestReadChunking), + NL_TEST_DEF("TestSetDirtyBetweenChunks", chip::app::TestReadInteraction::TestSetDirtyBetweenChunks), NL_TEST_DEF("CheckReadClient", chip::app::TestReadInteraction::TestReadClient), NL_TEST_DEF("CheckReadHandler", chip::app::TestReadInteraction::TestReadHandler), NL_TEST_DEF("TestReadClientGenerateAttributePathList", chip::app::TestReadInteraction::TestReadClientGenerateAttributePathList), @@ -1240,6 +1490,7 @@ const nlTest sTests[] = NL_TEST_DEF("TestProcessSubscribeResponse", chip::app::TestReadInteraction::TestProcessSubscribeResponse), NL_TEST_DEF("TestProcessSubscribeRequest", chip::app::TestReadInteraction::TestProcessSubscribeRequest), NL_TEST_DEF("TestSubscribeRoundtrip", chip::app::TestReadInteraction::TestSubscribeRoundtrip), + NL_TEST_DEF("TestSubscribeWildcard", chip::app::TestReadInteraction::TestSubscribeWildcard), NL_TEST_DEF("TestSubscribeEarlyShutdown", chip::app::TestReadInteraction::TestSubscribeEarlyShutdown), NL_TEST_DEF("TestSubscribeInvalidAttributePathRoundtrip", chip::app::TestReadInteraction::TestSubscribeInvalidAttributePathRoundtrip), NL_TEST_DEF("TestReadInvalidAttributePathRoundtrip", chip::app::TestReadInteraction::TestReadInvalidAttributePathRoundtrip), diff --git a/src/app/tests/integration/BUILD.gn b/src/app/tests/integration/BUILD.gn index 5c0088f8470645..77eb99bcaa3f27 100644 --- a/src/app/tests/integration/BUILD.gn +++ b/src/app/tests/integration/BUILD.gn @@ -27,6 +27,7 @@ executable("chip-im-initiator") { deps = [ "${chip_root}/src/app", + "${chip_root}/src/app/util/mock:mock_ember", "${chip_root}/src/lib/core", "${chip_root}/src/lib/support", "${chip_root}/src/platform", @@ -46,6 +47,7 @@ executable("chip-im-responder") { deps = [ "${chip_root}/src/app", + "${chip_root}/src/app/util/mock:mock_ember", "${chip_root}/src/lib/core", "${chip_root}/src/lib/support", "${chip_root}/src/platform", diff --git a/src/app/util/mock/Constants.h b/src/app/util/mock/Constants.h index 2c515c8ebf1e95..2b3e71d795222a 100644 --- a/src/app/util/mock/Constants.h +++ b/src/app/util/mock/Constants.h @@ -23,13 +23,17 @@ #pragma once +#include +#include +#include #include namespace chip { namespace Test { -constexpr EndpointId kMockEndpoint1 = 0xFFFE; -constexpr EndpointId kMockEndpoint2 = 0xFFFD; -constexpr EndpointId kMockEndpoint3 = 0xFFFC; +constexpr EndpointId kMockEndpoint1 = 0xFFFE; +constexpr EndpointId kMockEndpoint2 = 0xFFFD; +constexpr EndpointId kMockEndpoint3 = 0xFFFC; +constexpr EndpointId kMockEndpointMin = 0xFFF1; constexpr AttributeId MockAttributeId(const uint16_t & id) { diff --git a/src/app/util/mock/Functions.h b/src/app/util/mock/Functions.h new file mode 100644 index 00000000000000..7a6f2b2f7ce31f --- /dev/null +++ b/src/app/util/mock/Functions.h @@ -0,0 +1,36 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * This file contains functions for the mocked attribute-storage.cpp + */ + +#pragma once + +#include +#include +#include +#include + +namespace chip { +namespace Test { +CHIP_ERROR ReadSingleMockClusterData(FabricIndex aAccessingFabricIndex, const app::ConcreteAttributePath & aPath, + app::AttributeReportIB::Builder & aAttributeReport); +} // namespace Test +} // namespace chip diff --git a/src/app/util/mock/attribute-storage.cpp b/src/app/util/mock/attribute-storage.cpp index f9364ea9a37115..57c3bad233b2c5 100644 --- a/src/app/util/mock/attribute-storage.cpp +++ b/src/app/util/mock/attribute-storage.cpp @@ -30,8 +30,12 @@ #include #include +#include +#include +#include #include +#include #include #include #include @@ -72,6 +76,22 @@ AttributeId attributes[] = { // clang-format on }; +uint16_t mockClusterRevision = 1; +uint32_t mockFeatureMap = 0x1234; +bool mockAttribute1 = true; +int16_t mockAttribute2 = 42; +uint64_t mockAttribute3 = 0xdeadbeef0000cafe; +uint8_t mockAttribute4[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, + 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, + 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, +}; + } // namespace uint16_t emberAfEndpointCount(void) @@ -188,3 +208,72 @@ uint8_t emberAfClusterIndex(chip::EndpointId endpoint, chip::ClusterId cluster, } return UINT8_MAX; } + +namespace chip { +namespace Test { + +CHIP_ERROR ReadSingleMockClusterData(FabricIndex aAccessingFabricIndex, const ConcreteAttributePath & aPath, + AttributeReportIB::Builder & aAttributeReport) +{ + bool dataExists = + (emberAfGetServerAttributeIndexByAttributeId(aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId) != UINT16_MAX); + + ChipLogDetail(DataManagement, "Reading Mock Cluster %" PRIx32 ", Field %" PRIx32 " is dirty", aPath.mClusterId, + aPath.mAttributeId); + + AttributeDataIB::Builder attributeData; + AttributePathIB::Builder attributePath; + + if (!dataExists) + { + AttributeStatusIB::Builder attributeStatus; + attributeStatus = aAttributeReport.CreateAttributeStatus(); + attributePath = attributeStatus.CreatePath(); + attributePath.Endpoint(aPath.mEndpointId).Cluster(aPath.mClusterId).Attribute(aPath.mAttributeId).EndOfAttributePathIB(); + ReturnErrorOnFailure(attributePath.GetError()); + StatusIB::Builder errorStatus = attributeStatus.CreateErrorStatus(); + errorStatus.EncodeStatusIB(StatusIB(Protocols::InteractionModel::Status::UnsupportedAttribute)); + attributeStatus.EndOfAttributeStatusIB(); + ReturnErrorOnFailure(attributeStatus.GetError()); + return CHIP_NO_ERROR; + } + + attributeData = aAttributeReport.CreateAttributeData(); + attributePath = attributeData.CreatePath(); + attributePath.Endpoint(aPath.mEndpointId).Cluster(aPath.mClusterId).Attribute(aPath.mAttributeId).EndOfAttributePathIB(); + ReturnErrorOnFailure(attributePath.GetError()); + + TLV::TLVWriter * writer = attributeData.GetWriter(); + + switch (aPath.mAttributeId) + { + case Clusters::Globals::Attributes::ClusterRevision::Id: + ReturnErrorOnFailure(writer->Put(TLV::ContextTag(to_underlying(AttributeDataIB::Tag::kData)), mockClusterRevision)); + break; + case Clusters::Globals::Attributes::FeatureMap::Id: + ReturnErrorOnFailure(writer->Put(TLV::ContextTag(to_underlying(AttributeDataIB::Tag::kData)), mockFeatureMap)); + break; + case MockAttributeId(1): + ReturnErrorOnFailure(writer->Put(TLV::ContextTag(to_underlying(AttributeDataIB::Tag::kData)), mockAttribute1)); + break; + case MockAttributeId(2): + ReturnErrorOnFailure(writer->Put(TLV::ContextTag(to_underlying(AttributeDataIB::Tag::kData)), mockAttribute2)); + break; + case MockAttributeId(3): + ReturnErrorOnFailure(writer->Put(TLV::ContextTag(to_underlying(AttributeDataIB::Tag::kData)), mockAttribute3)); + break; + case MockAttributeId(4): + ReturnErrorOnFailure(writer->Put(TLV::ContextTag(to_underlying(AttributeDataIB::Tag::kData)), + chip::ByteSpan(mockAttribute4, sizeof(mockAttribute4)))); + break; + default: + // The key should found since we have checked above. + return CHIP_ERROR_KEY_NOT_FOUND; + } + + attributeData.DataVersion(0).EndOfAttributeDataIB(); + return attributeData.GetError(); +} + +} // namespace Test +} // namespace chip diff --git a/src/controller/tests/data_model/BUILD.gn b/src/controller/tests/data_model/BUILD.gn index ca5c751390f0f9..8293d9f1b827e5 100644 --- a/src/controller/tests/data_model/BUILD.gn +++ b/src/controller/tests/data_model/BUILD.gn @@ -32,6 +32,7 @@ chip_test_suite("data_model") { "${chip_root}/src/app", "${chip_root}/src/app/common:cluster-objects", "${chip_root}/src/app/tests:helpers", + "${chip_root}/src/app/util/mock:mock_ember", "${chip_root}/src/messaging/tests:helpers", "${chip_root}/src/transport/raw/tests:helpers", "${nlunit_test_root}:nlunit-test", diff --git a/src/lib/core/CHIPTLV.h b/src/lib/core/CHIPTLV.h index 9bfcc89ea1f004..b2774bdb66c7a6 100644 --- a/src/lib/core/CHIPTLV.h +++ b/src/lib/core/CHIPTLV.h @@ -1090,6 +1090,34 @@ class DLL_EXPORT TLVWriter */ CHIP_ERROR Finalize(); + /** + * Reserve some buffer for encoding future fields. + * + * @retval #CHIP_NO_ERROR Successfully reserved required buffer size. + * @retval #CHIP_ERROR_NO_MEMORY The reserved buffer size cannot fits into the remaining buffer size. + */ + CHIP_ERROR ReserveBuffer(uint32_t aBufferSize) + { + VerifyOrReturnError(mRemainingLen >= aBufferSize, CHIP_ERROR_NO_MEMORY); + mReservedSize += aBufferSize; + mRemainingLen -= aBufferSize; + return CHIP_NO_ERROR; + } + + /** + * Release previously reserved buffer. + * + * @retval #CHIP_NO_ERROR Successfully released reserved buffer size. + * @retval #CHIP_ERROR_NO_MEMORY The released buffer is larger than previously reserved buffer size. + */ + CHIP_ERROR UnreserveBuffer(uint32_t aBufferSize) + { + VerifyOrReturnError(mReservedSize >= aBufferSize, CHIP_ERROR_NO_MEMORY); + mReservedSize -= aBufferSize; + mRemainingLen += aBufferSize; + return CHIP_NO_ERROR; + } + /** * Encodes a TLV signed integer value. * @@ -2120,6 +2148,7 @@ class DLL_EXPORT TLVWriter uint32_t mRemainingLen; uint32_t mLenWritten; uint32_t mMaxLen; + uint32_t mReservedSize; TLVType mContainerType; private: diff --git a/src/lib/core/CHIPTLVTags.h b/src/lib/core/CHIPTLVTags.h index 7b49cea44ca4ea..e563aa86986da6 100644 --- a/src/lib/core/CHIPTLVTags.h +++ b/src/lib/core/CHIPTLVTags.h @@ -219,5 +219,7 @@ inline bool IsSpecialTag(Tag tag) return (tag & kProfileIdMask) == kSpecialTagMarker; } +constexpr uint8_t kMaxTLVTagLength = 8; + } // namespace TLV } // namespace chip diff --git a/src/lib/core/CHIPTLVWriter.cpp b/src/lib/core/CHIPTLVWriter.cpp index cb19570455fc25..919f4a5199d5f2 100644 --- a/src/lib/core/CHIPTLVWriter.cpp +++ b/src/lib/core/CHIPTLVWriter.cpp @@ -58,6 +58,7 @@ NO_INLINE void TLVWriter::Init(uint8_t * buf, size_t maxLen) mLenWritten = 0; mMaxLen = actualMaxLen; mContainerType = kTLVType_NotSpecified; + mReservedSize = 0; SetContainerOpen(false); SetCloseContainerReserved(true); @@ -77,6 +78,7 @@ CHIP_ERROR TLVWriter::Init(TLVBackingStore & backingStore, uint32_t maxLen) mLenWritten = 0; mMaxLen = maxLen; mContainerType = kTLVType_NotSpecified; + mReservedSize = 0; SetContainerOpen(false); SetCloseContainerReserved(true);