diff --git a/src/app/InteractionModelEngine.cpp b/src/app/InteractionModelEngine.cpp index e4f76d46d113b5..89ae89e5f434d0 100644 --- a/src/app/InteractionModelEngine.cpp +++ b/src/app/InteractionModelEngine.cpp @@ -34,6 +34,9 @@ extern bool emberAfContainsAttribute(chip::EndpointId endpoint, chip::ClusterId namespace chip { namespace app { + +using Protocols::InteractionModel::Status; + InteractionModelEngine sInteractionModelEngine; InteractionModelEngine::InteractionModelEngine() {} @@ -504,19 +507,18 @@ CHIP_ERROR InteractionModelEngine::OnTimedRequest(Messaging::ExchangeContext * a return handler->OnMessageReceived(apExchangeContext, aPayloadHeader, std::move(aPayload)); } -CHIP_ERROR InteractionModelEngine::OnUnsolicitedReportData(Messaging::ExchangeContext * apExchangeContext, - const PayloadHeader & aPayloadHeader, - System::PacketBufferHandle && aPayload) +Status InteractionModelEngine::OnUnsolicitedReportData(Messaging::ExchangeContext * apExchangeContext, + const PayloadHeader & aPayloadHeader, System::PacketBufferHandle && aPayload) { System::PacketBufferTLVReader reader; reader.Init(aPayload.Retain()); ReportDataMessage::Parser report; - ReturnErrorOnFailure(report.Init(reader)); + VerifyOrReturnError(report.Init(reader) == CHIP_NO_ERROR, Status::InvalidAction); SubscriptionId subscriptionId = 0; - ReturnErrorOnFailure(report.GetSubscriptionId(&subscriptionId)); - ReturnErrorOnFailure(report.ExitContainer()); + VerifyOrReturnError(report.GetSubscriptionId(&subscriptionId) == CHIP_NO_ERROR, Status::InvalidAction); + VerifyOrReturnError(report.ExitContainer() == CHIP_NO_ERROR, Status::InvalidAction); for (auto * readClient = mpActiveReadClientList; readClient != nullptr; readClient = readClient->GetNextClient()) { @@ -530,10 +532,12 @@ CHIP_ERROR InteractionModelEngine::OnUnsolicitedReportData(Messaging::ExchangeCo continue; } - return readClient->OnUnsolicitedReportData(apExchangeContext, std::move(aPayload)); + VerifyOrReturnError(readClient->OnUnsolicitedReportData(apExchangeContext, std::move(aPayload)) == CHIP_NO_ERROR, + Status::InvalidAction); + return Status::Success; } - return CHIP_NO_ERROR; + return Status::InvalidSubscription; } CHIP_ERROR InteractionModelEngine::OnUnsolicitedMessageReceived(const PayloadHeader & payloadHeader, @@ -580,8 +584,7 @@ CHIP_ERROR InteractionModelEngine::OnMessageReceived(Messaging::ExchangeContext } else if (aPayloadHeader.HasMessageType(Protocols::InteractionModel::MsgType::ReportData)) { - ReturnErrorOnFailure(OnUnsolicitedReportData(apExchangeContext, aPayloadHeader, std::move(aPayload))); - status = Status::Success; + status = OnUnsolicitedReportData(apExchangeContext, aPayloadHeader, std::move(aPayload)); } else if (aPayloadHeader.HasMessageType(MsgType::TimedRequest)) { @@ -590,6 +593,7 @@ CHIP_ERROR InteractionModelEngine::OnMessageReceived(Messaging::ExchangeContext else { ChipLogProgress(InteractionModel, "Msg type %d not supported", aPayloadHeader.GetMessageType()); + status = Status::InvalidAction; } if (status != Status::Success && !apExchangeContext->IsGroupExchangeContext()) diff --git a/src/app/InteractionModelEngine.h b/src/app/InteractionModelEngine.h index 208b88d29bfa60..28637499085f9a 100644 --- a/src/app/InteractionModelEngine.h +++ b/src/app/InteractionModelEngine.h @@ -397,8 +397,8 @@ class InteractionModelEngine : public Messaging::UnsolicitedMessageHandler, /**This function handles processing of un-solicited ReportData messages on the client, which can * only occur post subscription establishment */ - CHIP_ERROR OnUnsolicitedReportData(Messaging::ExchangeContext * apExchangeContext, const PayloadHeader & aPayloadHeader, - System::PacketBufferHandle && aPayload); + Status OnUnsolicitedReportData(Messaging::ExchangeContext * apExchangeContext, const PayloadHeader & aPayloadHeader, + System::PacketBufferHandle && aPayload); void DispatchCommand(CommandHandler & apCommandObj, const ConcreteCommandPath & aCommandPath, TLV::TLVReader & apPayload) override; diff --git a/src/app/ReadClient.cpp b/src/app/ReadClient.cpp index 9c6df9be9dcbb2..08a9a512083027 100644 --- a/src/app/ReadClient.cpp +++ b/src/app/ReadClient.cpp @@ -34,6 +34,8 @@ namespace chip { namespace app { +using Status = Protocols::InteractionModel::Status; + ReadClient::ReadClient(InteractionModelEngine * apImEngine, Messaging::ExchangeManager * apExchangeMgr, Callback & apCallback, InteractionType aInteractionType) : mExchange(*this), @@ -435,6 +437,13 @@ CHIP_ERROR ReadClient::OnMessageReceived(Messaging::ExchangeContext * apExchange } exit: + if (err != CHIP_NO_ERROR) + { + // TODO: if we get here with a ReportData that has an incorrect subscription id, we need to send status with + // InvalidSubscription + StatusResponse::Send(Status::InvalidAction, apExchangeContext, false /*aExpectResponse*/); + } + if ((!IsSubscriptionType() && !mPendingMoreChunks) || err != CHIP_NO_ERROR) { Close(err); @@ -461,13 +470,11 @@ CHIP_ERROR ReadClient::ProcessReportData(System::PacketBufferHandle && aPayload) { CHIP_ERROR err = CHIP_NO_ERROR; ReportDataMessage::Parser report; - bool suppressResponse = true; SubscriptionId subscriptionId = 0; EventReportIBs::Parser eventReportIBs; AttributeReportIBs::Parser attributeReportIBs; System::PacketBufferTLVReader reader; - reader.Init(std::move(aPayload)); err = report.Init(reader); SuccessOrExit(err); @@ -570,12 +577,10 @@ CHIP_ERROR ReadClient::ProcessReportData(System::PacketBufferHandle && aPayload) } } - if (!suppressResponse) + if (!suppressResponse && err == CHIP_NO_ERROR) { bool noResponseExpected = IsSubscriptionActive() && !mPendingMoreChunks; - err = StatusResponse::Send(err == CHIP_NO_ERROR ? Protocols::InteractionModel::Status::Success - : Protocols::InteractionModel::Status::InvalidSubscription, - mExchange.Get(), !noResponseExpected); + err = StatusResponse::Send(Status::Success, mExchange.Get(), !noResponseExpected); } mIsPrimingReports = false; diff --git a/src/app/tests/TestReadInteraction.cpp b/src/app/tests/TestReadInteraction.cpp index bc349cb6d9135a..47cdac5c074e65 100644 --- a/src/app/tests/TestReadInteraction.cpp +++ b/src/app/tests/TestReadInteraction.cpp @@ -319,6 +319,11 @@ class TestReadInteraction static void TestSubscribeRoundtripChunkStatusReportTimeout(nlTestSuite * apSuite, void * apContext); static void TestPostSubscribeRoundtripChunkStatusReportTimeout(nlTestSuite * apSuite, void * apContext); static void TestPostSubscribeRoundtripChunkReportTimeout(nlTestSuite * apSuite, void * apContext); + static void TestReadClientReceiveInvalidMessage(nlTestSuite * apSuite, void * apContext); + static void TestSubscribeClientReceiveInvalidStatusResponse(nlTestSuite * apSuite, void * apContext); + static void TestSubscribeClientReceiveWellFormedStatusResponse(nlTestSuite * apSuite, void * apContext); + static void TestSubscribeClientReceiveInvalidReportMessage(nlTestSuite * apSuite, void * apContext); + static void TestSubscribeClientReceiveUnsolicitedInvalidReportMessage(nlTestSuite * apSuite, void * apContext); private: static void GenerateReportData(nlTestSuite * apSuite, void * apContext, System::PacketBufferHandle & aPayload, @@ -2640,6 +2645,429 @@ void TestReadInteraction::TestPostSubscribeRoundtripChunkReportTimeout(nlTestSui ctx.CreateSessionBobToAlice(); } + +// Read Client sends the read request, Read Handler drops the response, then test injects unknown status reponse message for Read +// Client. +void TestReadInteraction::TestReadClientReceiveInvalidMessage(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); + + MockInteractionModelApp delegate; + auto * engine = chip::app::InteractionModelEngine::GetInstance(); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); + + chip::app::AttributePathParams attributePathParams[2]; + readPrepareParams.mpAttributePathParamsList = attributePathParams; + readPrepareParams.mpAttributePathParamsList[0].mEndpointId = kTestEndpointId; + readPrepareParams.mpAttributePathParamsList[0].mClusterId = kTestClusterId; + readPrepareParams.mpAttributePathParamsList[0].mAttributeId = 1; + + readPrepareParams.mpAttributePathParamsList[1].mEndpointId = kTestEndpointId; + readPrepareParams.mpAttributePathParamsList[1].mClusterId = kTestClusterId; + readPrepareParams.mpAttributePathParamsList[1].mAttributeId = 2; + + readPrepareParams.mAttributePathParamsListSize = 2; + + { + app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), delegate, + chip::app::ReadClient::InteractionType::Read); + + ctx.GetLoopback().mSentMessageCount = 0; + ctx.GetLoopback().mNumMessagesToDrop = 1; + ctx.GetLoopback().mNumMessagesToAllowBeforeDropping = 1; + ctx.GetLoopback().mDroppedMessageCount = 0; + err = readClient.SendRequest(readPrepareParams); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + ctx.DrainAndServiceIO(); + + System::PacketBufferHandle msgBuf = System::PacketBufferHandle::New(kMaxSecureSduLengthBytes); + NL_TEST_ASSERT(apSuite, !msgBuf.IsNull()); + System::PacketBufferTLVWriter writer; + writer.Init(std::move(msgBuf)); + StatusResponseMessage::Builder response; + response.Init(&writer); + response.Status(Protocols::InteractionModel::Status::Busy); + NL_TEST_ASSERT(apSuite, writer.Finalize(&msgBuf) == CHIP_NO_ERROR); + PayloadHeader payloadHeader; + payloadHeader.SetExchangeID(0); + payloadHeader.SetMessageType(chip::Protocols::InteractionModel::MsgType::StatusResponse); + + rm->ClearRetransTable(readClient.mExchange.Get()); + NL_TEST_ASSERT(apSuite, ctx.GetLoopback().mSentMessageCount == 2); + NL_TEST_ASSERT(apSuite, ctx.GetLoopback().mDroppedMessageCount == 1); + ctx.GetLoopback().mSentMessageCount = 0; + ctx.GetLoopback().mNumMessagesToDrop = 0; + ctx.GetLoopback().mNumMessagesToAllowBeforeDropping = 0; + ctx.GetLoopback().mDroppedMessageCount = 0; + readClient.OnMessageReceived(readClient.mExchange.Get(), payloadHeader, std::move(msgBuf)); + ctx.DrainAndServiceIO(); + + // TODO: Need to validate what status is being sent to the ReadHandler + // The ReadHandler closed its exchange when it sent the Report Data (which we dropped). + // Since we synthesized the StatusResponse to the ReadClient, instead of sending it from the ReadHandler, + // the only messages here are the ReadClient's StatusResponse to the unexpected message and an MRP ack. + NL_TEST_ASSERT(apSuite, ctx.GetLoopback().mSentMessageCount == 2); + NL_TEST_ASSERT(apSuite, delegate.mError == CHIP_IM_GLOBAL_STATUS(Busy)); + } + + engine->Shutdown(); + ctx.ExpireSessionAliceToBob(); + ctx.ExpireSessionBobToAlice(); + ctx.CreateSessionAliceToBob(); + ctx.CreateSessionBobToAlice(); +} + +// Read Client sends the subscribe request, Read Handler drops the response, then test injects unknown status response message for +// Read Client. +void TestReadInteraction::TestSubscribeClientReceiveInvalidStatusResponse(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); + + MockInteractionModelApp delegate; + auto * engine = chip::app::InteractionModelEngine::GetInstance(); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); + + chip::app::AttributePathParams attributePathParams[2]; + readPrepareParams.mpAttributePathParamsList = attributePathParams; + readPrepareParams.mpAttributePathParamsList[0].mEndpointId = kTestEndpointId; + readPrepareParams.mpAttributePathParamsList[0].mClusterId = kTestClusterId; + readPrepareParams.mpAttributePathParamsList[0].mAttributeId = 1; + + readPrepareParams.mpAttributePathParamsList[1].mEndpointId = kTestEndpointId; + readPrepareParams.mpAttributePathParamsList[1].mClusterId = kTestClusterId; + readPrepareParams.mpAttributePathParamsList[1].mAttributeId = 2; + + readPrepareParams.mAttributePathParamsListSize = 2; + + readPrepareParams.mMinIntervalFloorSeconds = 2; + readPrepareParams.mMaxIntervalCeilingSeconds = 5; + + { + app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), delegate, + chip::app::ReadClient::InteractionType::Subscribe); + + ctx.GetLoopback().mSentMessageCount = 0; + ctx.GetLoopback().mNumMessagesToDrop = 1; + ctx.GetLoopback().mNumMessagesToAllowBeforeDropping = 1; + ctx.GetLoopback().mDroppedMessageCount = 0; + err = readClient.SendRequest(readPrepareParams); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + ctx.DrainAndServiceIO(); + + System::PacketBufferHandle msgBuf = System::PacketBufferHandle::New(kMaxSecureSduLengthBytes); + NL_TEST_ASSERT(apSuite, !msgBuf.IsNull()); + System::PacketBufferTLVWriter writer; + writer.Init(std::move(msgBuf)); + StatusResponseMessage::Builder response; + response.Init(&writer); + response.Status(Protocols::InteractionModel::Status::Busy); + NL_TEST_ASSERT(apSuite, writer.Finalize(&msgBuf) == CHIP_NO_ERROR); + PayloadHeader payloadHeader; + payloadHeader.SetExchangeID(0); + payloadHeader.SetMessageType(chip::Protocols::InteractionModel::MsgType::StatusResponse); + + rm->ClearRetransTable(readClient.mExchange.Get()); + NL_TEST_ASSERT(apSuite, ctx.GetLoopback().mSentMessageCount == 2); + NL_TEST_ASSERT(apSuite, ctx.GetLoopback().mDroppedMessageCount == 1); + + NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadHandlers() == 1); + NL_TEST_ASSERT(apSuite, engine->ActiveHandlerAt(0) != nullptr); + rm->ClearRetransTable(engine->ActiveHandlerAt(0)->mExchangeCtx.Get()); + ctx.GetLoopback().mSentMessageCount = 0; + ctx.GetLoopback().mNumMessagesToDrop = 0; + ctx.GetLoopback().mNumMessagesToAllowBeforeDropping = 0; + ctx.GetLoopback().mDroppedMessageCount = 0; + + readClient.OnMessageReceived(readClient.mExchange.Get(), payloadHeader, std::move(msgBuf)); + ctx.DrainAndServiceIO(); + + // TODO: Need to validate what status is being sent to the ReadHandler + // The ReadHandler's exchange is still open when we synthesize the StatusResponse. + // Since we synthesized the StatusResponse to the ReadClient, instead of sending it from the ReadHandler, + // the only messages here are the ReadClient's StatusResponse to the unexpected message and an MRP ack. + // The ReadHandler should have sent a StatusResponse too, but it's buggy and does not do that. + NL_TEST_ASSERT(apSuite, ctx.GetLoopback().mSentMessageCount == 2); + + NL_TEST_ASSERT(apSuite, delegate.mError == CHIP_IM_GLOBAL_STATUS(Busy)); + NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadHandlers() == 0); + } + NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadClients() == 0); + engine->Shutdown(); + ctx.ExpireSessionAliceToBob(); + ctx.ExpireSessionBobToAlice(); + ctx.CreateSessionAliceToBob(); + ctx.CreateSessionBobToAlice(); +} + +// Read Client sends the subscribe request, Read Handler drops the response, then test injects well-formed status response message +// with Success for Read Client, we expect the error with CHIP_ERROR_INVALID_MESSAGE_TYPE +void TestReadInteraction::TestSubscribeClientReceiveWellFormedStatusResponse(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); + + MockInteractionModelApp delegate; + auto * engine = chip::app::InteractionModelEngine::GetInstance(); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); + + chip::app::AttributePathParams attributePathParams[2]; + readPrepareParams.mpAttributePathParamsList = attributePathParams; + readPrepareParams.mpAttributePathParamsList[0].mEndpointId = kTestEndpointId; + readPrepareParams.mpAttributePathParamsList[0].mClusterId = kTestClusterId; + readPrepareParams.mpAttributePathParamsList[0].mAttributeId = 1; + + readPrepareParams.mpAttributePathParamsList[1].mEndpointId = kTestEndpointId; + readPrepareParams.mpAttributePathParamsList[1].mClusterId = kTestClusterId; + readPrepareParams.mpAttributePathParamsList[1].mAttributeId = 2; + + readPrepareParams.mAttributePathParamsListSize = 2; + + readPrepareParams.mMinIntervalFloorSeconds = 2; + readPrepareParams.mMaxIntervalCeilingSeconds = 5; + + { + app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), delegate, + chip::app::ReadClient::InteractionType::Subscribe); + + ctx.GetLoopback().mSentMessageCount = 0; + ctx.GetLoopback().mNumMessagesToDrop = 1; + ctx.GetLoopback().mNumMessagesToAllowBeforeDropping = 1; + ctx.GetLoopback().mDroppedMessageCount = 0; + err = readClient.SendRequest(readPrepareParams); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + ctx.DrainAndServiceIO(); + + System::PacketBufferHandle msgBuf = System::PacketBufferHandle::New(kMaxSecureSduLengthBytes); + NL_TEST_ASSERT(apSuite, !msgBuf.IsNull()); + System::PacketBufferTLVWriter writer; + writer.Init(std::move(msgBuf)); + StatusResponseMessage::Builder response; + response.Init(&writer); + response.Status(Protocols::InteractionModel::Status::Success); + NL_TEST_ASSERT(apSuite, writer.Finalize(&msgBuf) == CHIP_NO_ERROR); + PayloadHeader payloadHeader; + payloadHeader.SetExchangeID(0); + payloadHeader.SetMessageType(chip::Protocols::InteractionModel::MsgType::StatusResponse); + + rm->ClearRetransTable(readClient.mExchange.Get()); + NL_TEST_ASSERT(apSuite, ctx.GetLoopback().mSentMessageCount == 2); + NL_TEST_ASSERT(apSuite, ctx.GetLoopback().mDroppedMessageCount == 1); + + NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadHandlers() == 1); + NL_TEST_ASSERT(apSuite, engine->ActiveHandlerAt(0) != nullptr); + rm->ClearRetransTable(engine->ActiveHandlerAt(0)->mExchangeCtx.Get()); + ctx.GetLoopback().mSentMessageCount = 0; + ctx.GetLoopback().mNumMessagesToDrop = 0; + ctx.GetLoopback().mNumMessagesToAllowBeforeDropping = 0; + ctx.GetLoopback().mDroppedMessageCount = 0; + + readClient.OnMessageReceived(readClient.mExchange.Get(), payloadHeader, std::move(msgBuf)); + ctx.DrainAndServiceIO(); + + // TODO: Need to validate what status is being sent to the ReadHandler + // The ReadHandler's exchange is still open when we synthesize the StatusResponse. + // Since we synthesized the StatusResponse to the ReadClient, instead of sending it from the ReadHandler, + // the only messages here are the ReadClient's StatusResponse to the unexpected message and an MRP ack. + // The ReadHandler should have sent a StatusResponse too, but it's buggy and does not do that. + NL_TEST_ASSERT(apSuite, ctx.GetLoopback().mSentMessageCount == 2); + + NL_TEST_ASSERT(apSuite, delegate.mError == CHIP_ERROR_INVALID_MESSAGE_TYPE); + NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadHandlers() == 0); + } + NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadClients() == 0); + engine->Shutdown(); + ctx.ExpireSessionAliceToBob(); + ctx.ExpireSessionBobToAlice(); + ctx.CreateSessionAliceToBob(); + ctx.CreateSessionBobToAlice(); +} + +// Read Client sends the subscribe request, Read Handler drops the response, then test injects invalid report message for Read +// Client. +void TestReadInteraction::TestSubscribeClientReceiveInvalidReportMessage(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); + + MockInteractionModelApp delegate; + auto * engine = chip::app::InteractionModelEngine::GetInstance(); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); + + chip::app::AttributePathParams attributePathParams[2]; + readPrepareParams.mpAttributePathParamsList = attributePathParams; + readPrepareParams.mpAttributePathParamsList[0].mEndpointId = kTestEndpointId; + readPrepareParams.mpAttributePathParamsList[0].mClusterId = kTestClusterId; + readPrepareParams.mpAttributePathParamsList[0].mAttributeId = 1; + + readPrepareParams.mpAttributePathParamsList[1].mEndpointId = kTestEndpointId; + readPrepareParams.mpAttributePathParamsList[1].mClusterId = kTestClusterId; + readPrepareParams.mpAttributePathParamsList[1].mAttributeId = 2; + + readPrepareParams.mAttributePathParamsListSize = 2; + + readPrepareParams.mMinIntervalFloorSeconds = 2; + readPrepareParams.mMaxIntervalCeilingSeconds = 5; + + { + app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), delegate, + chip::app::ReadClient::InteractionType::Subscribe); + + ctx.GetLoopback().mSentMessageCount = 0; + ctx.GetLoopback().mNumMessagesToDrop = 1; + ctx.GetLoopback().mNumMessagesToAllowBeforeDropping = 1; + ctx.GetLoopback().mDroppedMessageCount = 0; + err = readClient.SendRequest(readPrepareParams); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + ctx.DrainAndServiceIO(); + + System::PacketBufferHandle msgBuf = System::PacketBufferHandle::New(kMaxSecureSduLengthBytes); + NL_TEST_ASSERT(apSuite, !msgBuf.IsNull()); + System::PacketBufferTLVWriter writer; + writer.Init(std::move(msgBuf)); + ReportDataMessage::Builder response; + response.Init(&writer); + NL_TEST_ASSERT(apSuite, writer.Finalize(&msgBuf) == CHIP_NO_ERROR); + PayloadHeader payloadHeader; + payloadHeader.SetExchangeID(0); + payloadHeader.SetMessageType(chip::Protocols::InteractionModel::MsgType::ReportData); + + rm->ClearRetransTable(readClient.mExchange.Get()); + NL_TEST_ASSERT(apSuite, ctx.GetLoopback().mSentMessageCount == 2); + NL_TEST_ASSERT(apSuite, ctx.GetLoopback().mDroppedMessageCount == 1); + NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadHandlers() == 1); + NL_TEST_ASSERT(apSuite, engine->ActiveHandlerAt(0) != nullptr); + rm->ClearRetransTable(engine->ActiveHandlerAt(0)->mExchangeCtx.Get()); + + ctx.GetLoopback().mSentMessageCount = 0; + ctx.GetLoopback().mNumMessagesToDrop = 0; + ctx.GetLoopback().mNumMessagesToAllowBeforeDropping = 0; + ctx.GetLoopback().mDroppedMessageCount = 0; + + readClient.OnMessageReceived(readClient.mExchange.Get(), payloadHeader, std::move(msgBuf)); + ctx.DrainAndServiceIO(); + + // TODO: Need to validate what status is being sent to the ReadHandler + // The ReadHandler's exchange is still open when we synthesize the ReportData. + // Since we synthesized the ReportData to the ReadClient, instead of sending it from the ReadHandler, + // the only messages here are the ReadClient's StatusResponse to the unexpected message and an MRP ack. + // The ReadHandler should have sent a StatusResponse too, but it's buggy and does not do that. + NL_TEST_ASSERT(apSuite, ctx.GetLoopback().mSentMessageCount == 2); + + NL_TEST_ASSERT(apSuite, delegate.mError == CHIP_ERROR_END_OF_TLV); + + NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadHandlers() == 0); + } + NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadClients() == 0); + engine->Shutdown(); + ctx.ExpireSessionAliceToBob(); + ctx.ExpireSessionBobToAlice(); + ctx.CreateSessionAliceToBob(); + ctx.CreateSessionBobToAlice(); +} + +// Read Client create the subscription, handler sends unsolicited malformed report to client, +// InteractionModelEngine::OnUnsolicitedReportData would process this malformed report and sends out status report +void TestReadInteraction::TestSubscribeClientReceiveUnsolicitedInvalidReportMessage(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); + + MockInteractionModelApp delegate; + auto * engine = chip::app::InteractionModelEngine::GetInstance(); + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice()); + + chip::app::AttributePathParams attributePathParams[2]; + readPrepareParams.mpAttributePathParamsList = attributePathParams; + readPrepareParams.mpAttributePathParamsList[0].mEndpointId = kTestEndpointId; + readPrepareParams.mpAttributePathParamsList[0].mClusterId = kTestClusterId; + readPrepareParams.mpAttributePathParamsList[0].mAttributeId = 1; + + readPrepareParams.mpAttributePathParamsList[1].mEndpointId = kTestEndpointId; + readPrepareParams.mpAttributePathParamsList[1].mClusterId = kTestClusterId; + readPrepareParams.mpAttributePathParamsList[1].mAttributeId = 2; + + readPrepareParams.mAttributePathParamsListSize = 2; + + readPrepareParams.mMinIntervalFloorSeconds = 2; + readPrepareParams.mMaxIntervalCeilingSeconds = 5; + + { + ctx.GetLoopback().mSentMessageCount = 0; + app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), delegate, + chip::app::ReadClient::InteractionType::Subscribe); + + err = readClient.SendRequest(readPrepareParams); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + ctx.DrainAndServiceIO(); + NL_TEST_ASSERT(apSuite, ctx.GetLoopback().mSentMessageCount == 5); + NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadHandlers() == 1); + NL_TEST_ASSERT(apSuite, engine->ActiveHandlerAt(0) != nullptr); + delegate.mpReadHandler = engine->ActiveHandlerAt(0); + + System::PacketBufferHandle msgBuf = System::PacketBufferHandle::New(kMaxSecureSduLengthBytes); + NL_TEST_ASSERT(apSuite, !msgBuf.IsNull()); + System::PacketBufferTLVWriter writer; + writer.Init(std::move(msgBuf)); + ReportDataMessage::Builder response; + response.Init(&writer); + NL_TEST_ASSERT(apSuite, writer.Finalize(&msgBuf) == CHIP_NO_ERROR); + + ctx.GetLoopback().mSentMessageCount = 0; + auto exchange = InteractionModelEngine::GetInstance()->GetExchangeManager()->NewContext( + delegate.mpReadHandler->mSessionHandle.Get().Value(), delegate.mpReadHandler); + delegate.mpReadHandler->mExchangeCtx.Grab(exchange); + err = delegate.mpReadHandler->mExchangeCtx->SendMessage(Protocols::InteractionModel::MsgType::ReportData, std::move(msgBuf), + Messaging::SendMessageFlags::kExpectResponse); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + ctx.DrainAndServiceIO(); + + // The server sends a data report. + // The client receives the data report data and sends out status report with invalid action. + // The server should respond with a status report of its own, leading to 4 messages (because + // the client would ack the server's status report), but it's buggy and just sends an ack to the status report it got. + NL_TEST_ASSERT(apSuite, ctx.GetLoopback().mSentMessageCount == 3); + } + NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadClients() == 0); + engine->Shutdown(); +} + } // namespace app } // namespace chip @@ -2670,7 +3098,11 @@ const nlTest sTests[] = NL_TEST_DEF("TestReadHandlerInvalidAttributePath", chip::app::TestReadInteraction::TestReadHandlerInvalidAttributePath), NL_TEST_DEF("TestProcessSubscribeRequest", chip::app::TestReadInteraction::TestProcessSubscribeRequest), NL_TEST_DEF("TestSubscribeRoundtrip", chip::app::TestReadInteraction::TestSubscribeRoundtrip), - + NL_TEST_DEF("TestReadClientReceiveInvalidMessage", chip::app::TestReadInteraction::TestReadClientReceiveInvalidMessage), + NL_TEST_DEF("TestSubscribeClientReceiveInvalidStatusResponse", chip::app::TestReadInteraction::TestSubscribeClientReceiveInvalidStatusResponse), + NL_TEST_DEF("TestSubscribeClientReceiveWellFormedStatusResponse", chip::app::TestReadInteraction::TestSubscribeClientReceiveWellFormedStatusResponse), + NL_TEST_DEF("TestSubscribeClientReceiveInvalidReportMessage", chip::app::TestReadInteraction::TestSubscribeClientReceiveInvalidReportMessage), + NL_TEST_DEF("TestSubscribeClientReceiveUnsolicitedInvalidReportMessage", chip::app::TestReadInteraction::TestSubscribeClientReceiveUnsolicitedInvalidReportMessage), NL_TEST_DEF("TestSubscribeUrgentWildcardEvent", chip::app::TestReadInteraction::TestSubscribeUrgentWildcardEvent), NL_TEST_DEF("TestSubscribeWildcard", chip::app::TestReadInteraction::TestSubscribeWildcard), NL_TEST_DEF("TestSubscribePartialOverlap", chip::app::TestReadInteraction::TestSubscribePartialOverlap),