diff --git a/src/app/ClusterInfo.h b/src/app/ClusterInfo.h index c07d3491623b9f..084c2fa49635a8 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 IsPathIncluded(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 c57c3417331ec4..15105f28053d73 100644 --- a/src/app/ReadClient.cpp +++ b/src/app/ReadClient.cpp @@ -215,17 +215,20 @@ CHIP_ERROR ReadClient::SendStatusResponse(CHIP_ERROR aError) { if (IsAwaitingInitialReport()) { - MoveToState(ClientState::AwaitingSubscribeResponse); + if (!mPendingMoreChunks) + { + MoveToState(ClientState::AwaitingSubscribeResponse); + } } else { RefreshLivenessCheckTimer(); } } - ReturnLogErrorOnFailure( - mpExchangeCtx->SendMessage(Protocols::InteractionModel::MsgType::StatusResponse, std::move(msgBuf), - Messaging::SendFlags(IsAwaitingSubscribeResponse() ? Messaging::SendMessageFlags::kExpectResponse - : Messaging::SendMessageFlags::kNone))); + ReturnLogErrorOnFailure(mpExchangeCtx->SendMessage(Protocols::InteractionModel::MsgType::StatusResponse, std::move(msgBuf), + Messaging::SendFlags((IsAwaitingSubscribeResponse() || mPendingMoreChunks) + ? Messaging::SendMessageFlags::kExpectResponse + : Messaging::SendMessageFlags::kNone))); return CHIP_NO_ERROR; } @@ -293,7 +296,7 @@ CHIP_ERROR ReadClient::OnMessageReceived(Messaging::ExchangeContext * apExchange } exit: - if (!IsSubscriptionType() || err != CHIP_NO_ERROR) + if ((!IsSubscriptionType() && !mPendingMoreChunks) || err != CHIP_NO_ERROR) { ShutdownInternal(err); } @@ -332,7 +335,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; @@ -381,10 +383,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); @@ -410,7 +413,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); @@ -426,7 +429,7 @@ CHIP_ERROR ReadClient::ProcessReportData(System::PacketBufferHandle && aPayload) exit: SendStatusResponse(err); - if (!mInitialReport) + if (!mInitialReport && !mPendingMoreChunks) { mpExchangeCtx = nullptr; } diff --git a/src/app/ReadClient.h b/src/app/ReadClient.h index 3e678746b0df30..5a9c216b7dc89b 100644 --- a/src/app/ReadClient.h +++ b/src/app/ReadClient.h @@ -266,6 +266,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 506f6a8e28eeb1..bd60f2df5182ce 100644 --- a/src/app/ReadHandler.cpp +++ b/src/app/ReadHandler.cpp @@ -155,6 +155,15 @@ CHIP_ERROR ReadHandler::OnStatusResponse(Messaging::ExchangeContext * apExchange VerifyOrExit((statusCode == Protocols::InteractionModel::Status::Success), err = CHIP_ERROR_INVALID_ARGUMENT); switch (mState) { + case HandlerState::AwaitingChunkingResponse: + InteractionModelEngine::GetInstance()->GetReportingEngine().OnReportConfirm(); + MoveToState(HandlerState::GeneratingReports); + if (mpExchangeCtx) + { + mpExchangeCtx->WillSendMessage(); + } + SuccessOrExit(err = InteractionModelEngine::GetInstance()->GetReportingEngine().ScheduleRun()); + break; case HandlerState::AwaitingReportResponse: if (IsSubscriptionType()) { @@ -192,7 +201,7 @@ 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()) @@ -206,7 +215,7 @@ CHIP_ERROR ReadHandler::SendReportData(System::PacketBufferHandle && aPayload) mpExchangeCtx->SetResponseTimeout(kImMessageTimeout); } VerifyOrReturnLogError(mpExchangeCtx != nullptr, CHIP_ERROR_INCORRECT_STATE); - MoveToState(HandlerState::AwaitingReportResponse); + MoveToState(aMoreChunks ? HandlerState::AwaitingChunkingResponse : HandlerState::AwaitingReportResponse); CHIP_ERROR err = mpExchangeCtx->SendMessage(Protocols::InteractionModel::MsgType::ReportData, std::move(aPayload), Messaging::SendFlags(Messaging::SendMessageFlags::kExpectResponse)); if (err == CHIP_NO_ERROR) @@ -216,7 +225,10 @@ CHIP_ERROR ReadHandler::SendReportData(System::PacketBufferHandle && aPayload) err = RefreshSubscribeSyncTimer(); } } - ClearDirty(); + if (!aMoreChunks) + { + ClearDirty(); + } return err; } @@ -380,7 +392,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: @@ -441,6 +454,9 @@ const char * ReadHandler::GetStateStr() const case HandlerState::GeneratingReports: return "GeneratingReports"; + case HandlerState::AwaitingChunkingResponse: + return "AwaitingChunkingResponse"; + case HandlerState::AwaitingReportResponse: return "AwaitingReportResponse"; } diff --git a/src/app/ReadHandler.h b/src/app/ReadHandler.h index 6fb333d4663cd4..ea0615c4da2c32 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 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; } @@ -130,7 +132,12 @@ class ReadHandler : public Messaging::ExchangeDelegate 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; + mAttributePathExpandIterator = AttributePathExpandIterator(mpAttributeClusterInfoList); + } void ClearDirty() { mDirty = false; } bool IsDirty() { return mDirty; } NodeId GetInitiatorNodeId() const { return mInitiatorNodeId; } @@ -140,11 +147,12 @@ class ReadHandler : public Messaging::ExchangeDelegate friend class TestReadInteraction; enum class HandlerState { - Uninitialized = 0, ///< The handler has not been initialized - Initialized, ///< The handler has been initialized and is ready - GeneratingReports, ///< The handler has received either a Read or Subscribe request and is the process of generating a - ///< report. - AwaitingReportResponse, ///< The handler has sent the report to the client and is awaiting a status response. + Uninitialized = 0, ///< The handler has not been initialized + Initialized, ///< The handler has been initialized and is ready + GeneratingReports, ///< The handler has received either a Read or Subscribe request and is the process of generating a + ///< report. + AwaitingChunkingResponse, ///< The handler just sent a report chunk and is waiting a status response. + AwaitingReportResponse, ///< The handler has sent the last report chunk to the client and is awaiting a status response. }; static void OnRefreshSubscribeTimerSyncCallback(System::Layer * apSystemLayer, void * apAppState); @@ -189,11 +197,12 @@ class ReadHandler : public Messaging::ExchangeDelegate uint16_t mMinIntervalFloorSeconds = 0; uint16_t mMaxIntervalCeilingSeconds = 0; Optional mSessionHandle; - bool mHoldReport = false; - bool mDirty = false; - bool mActiveSubscription = false; - NodeId mInitiatorNodeId = kUndefinedNodeId; - FabricIndex mFabricIndex = 0; + bool mHoldReport = false; + bool mDirty = false; + bool mActiveSubscription = 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..c0a6c22e1a2666 100644 --- a/src/app/reporting/Engine.cpp +++ b/src/app/reporting/Engine.cpp @@ -64,19 +64,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,7 +84,7 @@ 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; @@ -94,59 +93,69 @@ Engine::RetrieveClusterData(FabricIndex aAccessingFabricIndex, AttributeReportIB CHIP_ERROR Engine::BuildSingleReportDataAttributeReportIBs(ReportDataMessage::Builder & aReportDataBuilder, ReadHandler * apReadHandler) { - CHIP_ERROR err = CHIP_NO_ERROR; - bool attributeClean = true; + CHIP_ERROR err = CHIP_NO_ERROR; + bool attributeDataWritten = false; 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 path; + mMoreChunkedMessages = true; + for (; apReadHandler->GetAttributePathExpandIterator()->Get(path); apReadHandler->GetAttributePathExpandIterator()->Next()) { - for (auto path = mpGlobalDirtySet; path != nullptr; path = path->mpNext) + if (!apReadHandler->IsInitialReport()) { - if (clusterInfo->IsAttributePathSupersetOf(*path)) + bool concretePathDirty = false; + // TODO: Optimize this implementation by making the iterator only emit intersected paths. + for (auto dirtyPath = mpGlobalDirtySet; dirtyPath != nullptr; dirtyPath = dirtyPath->mpNext) { - // 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); + if (dirtyPath->IsPathIncluded(path)) + { + concretePathDirty = true; + break; + } } - else if (path->IsAttributePathSupersetOf(*clusterInfo)) - { - err = RetrieveClusterData(apReadHandler->GetFabricIndex(), AttributeReportIBs, *clusterInfo); - } - 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; } + + TLV::TLVWriter attributeBackup; + attributeReportIBs.Checkpoint(attributeBackup); + // Retrieve data for this cluster instance and clear its dirty flag. + err = RetrieveClusterData(apReadHandler->GetFabricIndex(), attributeReportIBs, path); + if (err != CHIP_NO_ERROR) + { + attributeReportIBs.Rollback(attributeBackup); + } + SuccessOrExit(err); + attributeDataWritten = true; } + mMoreChunkedMessages = 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); } + return err; } @@ -263,6 +272,7 @@ CHIP_ERROR Engine::BuildAndSendSingleReportData(ReadHandler * apReadHandler) VerifyOrExit(!bufHandle.IsNull(), err = CHIP_ERROR_NO_MEMORY); reportDataWriter.Init(std::move(bufHandle)); + reportDataWriter.ReserveBuffer(128); // Magic number, just avoid using up MAC stuff. // Create a report data. err = reportDataBuilder.Init(&reportDataWriter); @@ -275,6 +285,8 @@ CHIP_ERROR Engine::BuildAndSendSingleReportData(ReadHandler * apReadHandler) reportDataBuilder.SubscriptionId(subscriptionId); } + SuccessOrExit(err = reportDataWriter.ReserveBuffer(Engine::kReservedSizeForMoreChunksFlag)); + err = BuildSingleReportDataAttributeReportIBs(reportDataBuilder, apReadHandler); SuccessOrExit(err); @@ -282,7 +294,7 @@ CHIP_ERROR Engine::BuildAndSendSingleReportData(ReadHandler * apReadHandler) 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. + SuccessOrExit(err = reportDataWriter.UnreserveBuffer(Engine::kReservedSizeForMoreChunksFlag)); if (mMoreChunkedMessages) { reportDataBuilder.MoreChunkedMessages(mMoreChunkedMessages); @@ -443,7 +455,7 @@ CHIP_ERROR Engine::SendReport(ReadHandler * apReadHandler, System::PacketBufferH // 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), mMoreChunkedMessages); return err; } diff --git a/src/app/reporting/Engine.h b/src/app/reporting/Engine.h index 72b9c6329f22fa..2bdf508c82ea8a 100644 --- a/src/app/reporting/Engine.h +++ b/src/app/reporting/Engine.h @@ -95,7 +95,7 @@ class Engine CHIP_ERROR BuildSingleReportDataAttributeReportIBs(ReportDataMessage::Builder & reportDataBuilder, ReadHandler * apReadHandler); CHIP_ERROR BuildSingleReportDataEventReports(ReportDataMessage::Builder & reportDataBuilder, ReadHandler * apReadHandler); CHIP_ERROR RetrieveClusterData(FabricIndex aAccessingFabricIndex, AttributeReportIBs::Builder & aAttributeReportIBs, - ClusterInfo & aClusterInfo); + const ConcreteAttributePath & aClusterInfo); EventNumber CountEvents(ReadHandler * apReadHandler, EventNumber * apInitialEvents); /** @@ -149,6 +149,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 b5079798c3d6c6..a2cfbe9c494aef 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,6 +279,9 @@ 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 TestReadChunkingRoundtrip(nlTestSuite * apSuite, void * apContext); static void TestSubscribeRoundtrip(nlTestSuite * apSuite, void * apContext); static void TestSubscribeEarlyShutdown(nlTestSuite * apSuite, void * apContext); static void TestSubscribeInvalidAttributePathRoundtrip(nlTestSuite * apSuite, void * apContext); @@ -398,7 +408,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)); @@ -527,7 +537,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)); @@ -718,6 +728,91 @@ 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(); +} + +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::TestReadInvalidAttributePathRoundtrip(nlTestSuite * apSuite, void * apContext) { TestContext & ctx = *static_cast(apContext); @@ -1124,6 +1219,8 @@ 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("CheckReadClient", chip::app::TestReadInteraction::TestReadClient), NL_TEST_DEF("CheckReadHandler", chip::app::TestReadInteraction::TestReadHandler), NL_TEST_DEF("TestReadClientGenerateAttributePathList", chip::app::TestReadInteraction::TestReadClientGenerateAttributePathList), 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 7912f6c724422c..90cd5da5c9cdbe 100644 --- a/src/controller/tests/data_model/BUILD.gn +++ b/src/controller/tests/data_model/BUILD.gn @@ -32,6 +32,7 @@ chip_test_suite("interaction-tests") { "${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..4b901854c5c20d 100644 --- a/src/lib/core/CHIPTLV.h +++ b/src/lib/core/CHIPTLV.h @@ -1090,6 +1090,37 @@ 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. + * @retval other Other CHIP or platform-specific errors returned by the configured + */ + 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 reserved required 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 +2151,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);